diff --git a/src/API/Extensions/TaskExtensions.cs b/src/API/Extensions/TaskExtensions.cs index b6fb6dd..b9d5c67 100644 --- a/src/API/Extensions/TaskExtensions.cs +++ b/src/API/Extensions/TaskExtensions.cs @@ -1,4 +1,6 @@ -namespace Void.Proxy.API.Extensions; +using Serilog; + +namespace Void.Proxy.API.Extensions; public static class TaskExtensions { @@ -7,7 +9,7 @@ public static Task CatchExceptions(this Task task) return task.ContinueWith(completedTask => { if (completedTask.Exception != null) - Console.WriteLine("Unhandled task Exception:\n" + completedTask.Exception.InnerException); + Log.Logger.Fatal("Unhandled task Exception:\n{Exception}", completedTask.Exception.InnerException); }, TaskContinuationOptions.OnlyOnFaulted); } diff --git a/src/API/Links/ILink.cs b/src/API/Links/ILink.cs index 207e903..aac48cd 100644 --- a/src/API/Links/ILink.cs +++ b/src/API/Links/ILink.cs @@ -13,5 +13,4 @@ public interface ILink : IEventListener, IAsyncDisposable public IMinecraftChannel ServerChannel { get; } public bool IsAlive { get; } public bool IsRestarting { get; } - public ValueTask RestartAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/API/Mojang/Profiles/IdentifiedKey.cs b/src/API/Mojang/Profiles/IdentifiedKey.cs new file mode 100644 index 0000000..d3726f4 --- /dev/null +++ b/src/API/Mojang/Profiles/IdentifiedKey.cs @@ -0,0 +1,99 @@ +using System.Security.Cryptography; +using System.Text; +using Void.Proxy.API.Network.IO.Buffers; +using Void.Proxy.API.Network.Protocol; + +namespace Void.Proxy.API.Mojang.Profiles; + +public record IdentifiedKey(IdentifiedKeyRevision Revision, long ExpiresAt, byte[] PublicKey, byte[] Signature) +{ + public static readonly byte[] YggdrasilSessionPublicKey = Convert.FromBase64String("MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAylB4B6m5lz7jwrcFz6Fd/fnfUhcvlxsTSn5kIK/2aGG1C3kMy4VjhwlxF6BFUSnfxhNswPjh3ZitkBxEAFY25uzkJFRwHwVA9mdwjashXILtR6OqdLXXFVyUPIURLOSWqGNBtb08EN5fMnG8iFLgEJIBMxs9BvF3s3/FhuHyPKiVTZmXY0WY4ZyYqvoKR+XjaTRPPvBsDa4WI2u1zxXMeHlodT3lnCzVvyOYBLXL6CJgByuOxccJ8hnXfF9yY4F0aeL080Jz/3+EBNG8RO4ByhtBf4Ny8NQ6stWsjfeUIvH7bU/4zCYcYOq4WrInXHqS8qruDmIl7P5XXGcabuzQstPf/h2CRAUpP/PlHXcMlvewjmGU6MfDK+lifScNYwjPxRo4nKTGFZf/0aqHCh/EAsQyLKrOIYRE0lDG3bzBh8ogIMLAugsAfBb6M3mqCqKaTMAf/VAjh5FFJnjS+7bE+bZEV0qwax1CEoPPJL1fIQjOS8zj086gjpGRCtSy9+bTPTfTR/SJ+VUB5G2IeCItkNHpJX2ygojFZ9n5Fnj7R9ZnOM+L8nyIjPu3aePvtcrXlyLhH/hvOfIOjPxOlqW+O5QwSFP4OEcyLAUgDdUgyW36Z5mB285uKW/ighzZsOTevVUG2QwDItObIV6i8RCxFbN2oDHyPaO5j1tTaBNyVt8CAwEAAQ=="); + + private bool? _isSignatureValid; + + public Uuid? ProfileUuid { get; set; } + + public bool? IsSignatureValid + { + get => _isSignatureValid ??= ValidateData(ProfileUuid ?? default); + set => _isSignatureValid = value; + } + + public bool VerifyDataSignature(byte[] signature, byte[] data) + { + try + { + using var rsa = RSA.Create(); + rsa.ImportSubjectPublicKeyInfo(PublicKey, out _); + + return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + catch + { + return false; + } + } + + public bool AddUuid(Uuid uuid) + { + var guid = uuid.AsGuid; + + if (guid == default) + return false; + + var profileGuid = ProfileUuid?.AsGuid; + + if (profileGuid != null) + return IsSignatureValid.HasValue && IsSignatureValid.Value && profileGuid.Equals(guid); + + if (!ValidateData(uuid)) + return false; + + IsSignatureValid = true; + ProfileUuid = uuid; + + return true; + } + + private bool ValidateData(Uuid uuid) + { + var guid = uuid.AsGuid; + + if (Revision == IdentifiedKeyRevision.GenericV1Revision) + { + var publicKeyText = $"-----BEGIN RSA PUBLIC KEY-----\n{Convert.ToBase64String(PublicKey, Base64FormattingOptions.InsertLineBreaks)}\n-----END RSA PUBLIC KEY-----\n"; + var verify = Encoding.ASCII.GetBytes(ExpiresAt + publicKeyText.Replace("\r", string.Empty)); + + using var rsa = RSA.Create(); + rsa.ImportSubjectPublicKeyInfo(YggdrasilSessionPublicKey, out _); + + return rsa.VerifyData(verify, Signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } + else + { + if (guid == default) + return false; + + var verify = new byte[PublicKey.Length + 24]; + var buffer = new MinecraftBuffer(verify); + buffer.WriteUuid(uuid); + buffer.WriteLong(ExpiresAt); + buffer.Write(PublicKey); + + using var rsa = RSA.Create(); + rsa.ImportSubjectPublicKeyInfo(YggdrasilSessionPublicKey, out _); + + return rsa.VerifyData(verify.AsSpan(0, buffer.Position), Signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); + } + } +} + +public class IdentifiedKeyRevision(IEnumerable backwardsCompatibleTo, List applicableTo) +{ + public static readonly IdentifiedKeyRevision GenericV1Revision = new([], [ProtocolVersion.MINECRAFT_1_19]); + + public static readonly IdentifiedKeyRevision LinkedV2Revision = new([], [ProtocolVersion.MINECRAFT_1_19_1]); + + public IEnumerable BackwardsCompatibleTo { get; } = backwardsCompatibleTo; + public List ApplicableTo { get; } = applicableTo; +} \ No newline at end of file diff --git a/src/API/Mojang/Uuid.cs b/src/API/Mojang/Uuid.cs index 0c4e5cc..bc1a95a 100644 --- a/src/API/Mojang/Uuid.cs +++ b/src/API/Mojang/Uuid.cs @@ -7,6 +7,8 @@ namespace Void.Proxy.API.Mojang; public struct Uuid(Guid guid) { + public static Uuid Empty { get; } = new(Guid.Empty); + public Guid AsGuid => guid; public override string ToString() diff --git a/src/API/Network/IO/Buffers/ReadOnly/ReadOnlySequenceBackingBuffer.cs b/src/API/Network/IO/Buffers/ReadOnly/ReadOnlySequenceBackingBuffer.cs index fc050f9..cddcecf 100644 --- a/src/API/Network/IO/Buffers/ReadOnly/ReadOnlySequenceBackingBuffer.cs +++ b/src/API/Network/IO/Buffers/ReadOnly/ReadOnlySequenceBackingBuffer.cs @@ -149,7 +149,7 @@ public ReadOnlySpan Slice(int length) // Should be safe in most cases, but prefer throw new NotSupportedException("That implementation would allocate memory, not supported yet") if (_currentBlock.Length < _blockPosition + length) - throw new IndexOutOfRangeException($"Current block length is {_currentBlock.Length} and position is {_blockPosition}, attempted to slice {length} bytes, reading from next blocks not implemented."); + throw new IndexOutOfRangeException($"Current block length is {_currentBlock.Length} and position is {_blockPosition}, attempted to slice {length} bytes, reading from next blocks not implemented. Sequence length is {_sequence.Length}."); var span = _currentBlock.Slice(_blockPosition, length); diff --git a/src/API/Network/IO/Channels/IMinecraftChannel.cs b/src/API/Network/IO/Channels/IMinecraftChannel.cs index 9a872ba..5be38a3 100644 --- a/src/API/Network/IO/Channels/IMinecraftChannel.cs +++ b/src/API/Network/IO/Channels/IMinecraftChannel.cs @@ -9,7 +9,9 @@ public interface IMinecraftChannel : IDisposable, IAsyncDisposable public bool CanWrite { get; } public IMinecraftStreamBase Head { get; } + public bool IsConfigured { get; } + public bool IsPaused { get; } public bool IsRedirectionSupported { get; } public void Add() where T : IMinecraftStream, new(); @@ -20,7 +22,8 @@ public interface IMinecraftChannel : IDisposable, IAsyncDisposable public void PrependBuffer(Memory memory); public ValueTask ReadMessageAsync(CancellationToken cancellationToken = default); public ValueTask WriteMessageAsync(IMinecraftMessage message, CancellationToken cancellationToken = default); - public void Flush(); + public void Pause(); + public void Resume(); public ValueTask FlushAsync(CancellationToken cancellationToken = default); public void Close(); } \ No newline at end of file diff --git a/src/API/Network/IO/Streams/Recyclable/MinecraftRecyclableStream.cs b/src/API/Network/IO/Streams/Recyclable/MinecraftRecyclableStream.cs index 8f6afcc..905de45 100644 --- a/src/API/Network/IO/Streams/Recyclable/MinecraftRecyclableStream.cs +++ b/src/API/Network/IO/Streams/Recyclable/MinecraftRecyclableStream.cs @@ -6,7 +6,8 @@ public abstract class MinecraftRecyclableStream { public static readonly RecyclableMemoryStreamManager RecyclableMemoryStreamManager = new(new RecyclableMemoryStreamManager.Options { - BlockSize = 1024, + // TODO: replace BlockSize to 1024, but that will cause some packets to be unable to read + BlockSize = 2048, LargeBufferMultiple = 1024 * 1024, MaximumBufferSize = 16 * 1024 * 1024, GenerateCallStacks = false, diff --git a/src/Common/Network/IO/Channels/Services/SimpleChannelBuilderService.cs b/src/Common/Network/IO/Channels/Services/SimpleChannelBuilderService.cs index d823640..ac8cdb2 100644 --- a/src/Common/Network/IO/Channels/Services/SimpleChannelBuilderService.cs +++ b/src/Common/Network/IO/Channels/Services/SimpleChannelBuilderService.cs @@ -61,7 +61,11 @@ public async ValueTask BuildPlayerChannelAsync(IPlayer player public async ValueTask BuildServerChannelAsync(IServer server, CancellationToken cancellationToken = default) { logger.LogTrace("Building channel for a {Server} server", server); - var channel = await BuildChannelAsync(Direction.Clientbound, server.CreateTcpClient().GetStream(), cancellationToken); + + var client = server.CreateTcpClient(); + client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + + var channel = await BuildChannelAsync(Direction.Clientbound, client.GetStream(), cancellationToken); logger.LogTrace("Server {Name} is using {ChannelTypeName} channel implementation", server.Name, channel.GetType().Name); return channel; diff --git a/src/Common/Network/IO/Channels/SimpleChannel.cs b/src/Common/Network/IO/Channels/SimpleChannel.cs index dff983c..e649fa2 100644 --- a/src/Common/Network/IO/Channels/SimpleChannel.cs +++ b/src/Common/Network/IO/Channels/SimpleChannel.cs @@ -11,11 +11,15 @@ namespace Void.Proxy.Common.Network.IO.Channels; public class SimpleChannel(IMinecraftStreamBase head) : IMinecraftChannel { + private TaskCompletionSource? _pause; + public bool CanRead => true; public bool CanWrite => true; public IMinecraftStreamBase Head => head; + public bool IsConfigured => head is IMinecraftStream; + public bool IsPaused => _pause is { Task.IsCompleted: false }; public bool IsRedirectionSupported => false; public void Add() where T : IMinecraftStream, new() @@ -56,6 +60,9 @@ public void PrependBuffer(Memory memory) public async ValueTask ReadMessageAsync(CancellationToken cancellationToken = default) { + if (_pause is not null) + await _pause.Task; + return head switch { IMinecraftPacketMessageStream stream => await stream.ReadPacketAsync(cancellationToken), @@ -83,9 +90,20 @@ public async ValueTask WriteMessageAsync(IMinecraftMessage message, Cancellation } } - public void Flush() + public void Pause() { - head.Flush(); + if (_pause is { Task.IsCompleted: false }) + throw new InvalidOperationException($"{nameof(IMinecraftChannel)} is already paused"); + + _pause = new TaskCompletionSource(); + } + + public void Resume() + { + if (_pause is null or { Task.IsCompleted: true }) + throw new InvalidOperationException($"{nameof(IMinecraftChannel)} is not paused"); + + _pause.SetResult(); } public async ValueTask FlushAsync(CancellationToken cancellationToken = default) @@ -108,6 +126,11 @@ public async ValueTask DisposeAsync() await head.DisposeAsync(); } + public void Flush() + { + head.Flush(); + } + private T Get(IMinecraftStreamBase? baseStream) where T : IMinecraftStreamBase { var current = baseStream ?? head; diff --git a/src/Common/Network/IO/Streams/Packet/MinecraftPacketMessageStream.cs b/src/Common/Network/IO/Streams/Packet/MinecraftPacketMessageStream.cs index c827680..2b22a1f 100644 --- a/src/Common/Network/IO/Streams/Packet/MinecraftPacketMessageStream.cs +++ b/src/Common/Network/IO/Streams/Packet/MinecraftPacketMessageStream.cs @@ -177,7 +177,7 @@ public IMinecraftPacket DecodePacket(RecyclableMemoryStream stream) stream.Dispose(); if (buffer.HasData) - throw new IndexOutOfRangeException($"The packet was not fully read. Bytes read: {buffer.Position}, Total length: {buffer.Length}."); + throw new IndexOutOfRangeException($"{packet} packet was not fully read. Bytes read: {buffer.Position}, Total length: {buffer.Length}."); return packet; } diff --git a/src/Platform/Links/Link.cs b/src/Platform/Links/Link.cs index 002576a..d2ee784 100644 --- a/src/Platform/Links/Link.cs +++ b/src/Platform/Links/Link.cs @@ -13,15 +13,15 @@ namespace Void.Proxy.Links; public class Link : ILink { + private readonly CancellationTokenSource _ctsPlayerToServer; + private readonly CancellationTokenSource _ctsPlayerToServerForce; + private readonly CancellationTokenSource _ctsServerToPlayer; + private readonly CancellationTokenSource _ctsServerToPlayerForce; private readonly IEventService _events; private readonly AsyncLock _lock; private readonly ILogger _logger; - private CancellationTokenSource _ctsPlayerToServer; - private CancellationTokenSource _ctsPlayerToServerForce; - private CancellationTokenSource _ctsServerToPlayer; - private CancellationTokenSource _ctsServerToPlayerForce; - private Task _playerToServerTask; - private Task _serverToPlayerTask; + private readonly Task _playerToServerTask; + private readonly Task _serverToPlayerTask; public Link(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMinecraftChannel serverChannel, ILogger logger, IEventService events) { @@ -50,53 +50,7 @@ public Link(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMi public IMinecraftChannel ServerChannel { get; init; } public bool IsAlive => _playerToServerTask.Status == TaskStatus.Running && _serverToPlayerTask.Status == TaskStatus.Running; - public bool IsRestarting { get; private set; } - - public async ValueTask RestartAsync(CancellationToken cancellationToken = default) - { - if (IsRestarting) - { - _logger.LogWarning("Link {Link} is already restarting", this); - return; - } - - _logger.LogTrace("Link {Link} is restarting", this); - - IsRestarting = true; - - await _ctsServerToPlayer.CancelAsync(); - _ctsServerToPlayer = new CancellationTokenSource(); - - if (await WaitWithTimeout(_serverToPlayerTask)) - { - _logger.LogTrace("Timed out waiting Server {Server} disconnection from Player {Player} manually, closing forcefully (Restart)", Server, Player); - await _ctsServerToPlayerForce.CancelAsync(); - _ctsServerToPlayerForce = new CancellationTokenSource(); - - if (await WaitWithTimeout(_serverToPlayerTask)) - throw new Exception($"Cannot dispose Link {this} (player=>server) (Restart)"); - } - - await _ctsPlayerToServer.CancelAsync(); - _ctsPlayerToServer = new CancellationTokenSource(); - - if (await WaitWithTimeout(_playerToServerTask)) - { - _logger.LogTrace("Timed out waiting Player {Player} disconnection from Server {Server} manually, closing forcefully (Restart)", Player, Server); - await _ctsPlayerToServerForce.CancelAsync(); - _ctsPlayerToServerForce = new CancellationTokenSource(); - - if (await WaitWithTimeout(_playerToServerTask)) - throw new Exception($"Cannot dispose Link {this} (server=>player) (Restart)"); - } - - IsRestarting = false; - - _playerToServerTask = ExecuteAsync(PlayerChannel, ServerChannel, Direction.Serverbound, _ctsPlayerToServer.Token, _ctsPlayerToServerForce.Token); - _serverToPlayerTask = ExecuteAsync(ServerChannel, PlayerChannel, Direction.Clientbound, _ctsServerToPlayer.Token, _ctsServerToPlayerForce.Token); - - _logger.LogTrace("Link {Link} successfully restarted", this); - } + public bool IsRestarting { get; } public async ValueTask DisposeAsync() { diff --git a/src/Platform/Platform.cs b/src/Platform/Platform.cs index 9add893..c52b718 100644 --- a/src/Platform/Platform.cs +++ b/src/Platform/Platform.cs @@ -51,6 +51,7 @@ public async Task StartAsync(CancellationToken cancellationToken) logger.LogInformation("Starting connection listener"); _listener = new TcpListener(settings.Address, settings.Port); + _listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); _listener.Start(); logger.LogInformation("Connection listener started on port {Port}", settings.Port); diff --git a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/EncryptionResponsePacket.cs b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/EncryptionResponsePacket.cs index a60e1d1..a058e11 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/EncryptionResponsePacket.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/EncryptionResponsePacket.cs @@ -7,27 +7,76 @@ namespace Void.Proxy.Plugins.ProtocolSupport.Java.v1_13_to_1_20_1.Packets.Server public class EncryptionResponsePacket : IMinecraftPacket { public required byte[] SharedSecret { get; set; } - public required byte[] VerifyToken { get; set; } + public required byte[]? VerifyToken { get; set; } + + // 1.19 - 1.19.2 + public required long? Salt { get; set; } + public required byte[]? MessageSignature { get; set; } public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) { buffer.WriteVarInt(SharedSecret.Length); buffer.Write(SharedSecret); - buffer.WriteVarInt(VerifyToken.Length); - buffer.Write(VerifyToken); + + var hasVerifyToken = VerifyToken is not null; + + if (protocolVersion >= ProtocolVersion.MINECRAFT_1_19 && protocolVersion < ProtocolVersion.MINECRAFT_1_19_3 && !hasVerifyToken) + { + buffer.WriteBoolean(hasVerifyToken); + + if (Salt is null) + throw new ArgumentNullException(nameof(Salt)); + + if (MessageSignature is null) + throw new ArgumentNullException(nameof(MessageSignature)); + + buffer.WriteLong(Salt.Value); + buffer.WriteVarInt(MessageSignature.Length); + buffer.Write(MessageSignature); + } + + if (hasVerifyToken) + { + buffer.WriteVarInt(VerifyToken!.Length); + buffer.Write(VerifyToken); + } } public static EncryptionResponsePacket Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) { var sharedSecretLength = buffer.ReadVarInt(); - var sharedSecret = buffer.Read(sharedSecretLength); - var verifyTokenLength = buffer.ReadVarInt(); - var verifyToken = buffer.Read(verifyTokenLength); + var sharedSecret = buffer.Read(sharedSecretLength).ToArray(); + + var hasVerifyToken = true; + byte[]? verifyToken = null; + long? salt = null; + byte[]? messageSignature = null; + + if (protocolVersion >= ProtocolVersion.MINECRAFT_1_19 && protocolVersion < ProtocolVersion.MINECRAFT_1_19_3) + { + hasVerifyToken = buffer.ReadBoolean(); + + if (!hasVerifyToken) + { + salt = buffer.ReadLong(); + + var messageSignatureLength = buffer.ReadVarInt(); + messageSignature = buffer.Read(messageSignatureLength).ToArray(); + } + } + + if (hasVerifyToken) + { + var verifyTokenLength = buffer.ReadVarInt(); + verifyToken = buffer.Read(verifyTokenLength).ToArray(); + } return new EncryptionResponsePacket { - SharedSecret = sharedSecret.ToArray(), - VerifyToken = verifyToken.ToArray() + SharedSecret = sharedSecret, + VerifyToken = verifyToken, + Salt = salt, + MessageSignature = messageSignature }; } diff --git a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/LoginStartPacket.cs b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/LoginStartPacket.cs new file mode 100644 index 0000000..da87d7d --- /dev/null +++ b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Packets/Serverbound/LoginStartPacket.cs @@ -0,0 +1,100 @@ +using Void.Proxy.API.Mojang; +using Void.Proxy.API.Mojang.Profiles; +using Void.Proxy.API.Network.IO.Buffers; +using Void.Proxy.API.Network.Protocol; +using Void.Proxy.Common.Network.IO.Messages; + +namespace Void.Proxy.Plugins.ProtocolSupport.Java.v1_13_to_1_20_1.Packets.Serverbound; + +public class LoginStartPacket : IMinecraftPacket +{ + public required GameProfile Profile { get; set; } + public required IdentifiedKey? Key { get; set; } + + public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + buffer.WriteString(Profile.Username); + + if (protocolVersion >= ProtocolVersion.MINECRAFT_1_19) + { + if (protocolVersion < ProtocolVersion.MINECRAFT_1_19_3) + { + var hasSignatureData = Key is not null; + buffer.WriteBoolean(hasSignatureData); + + if (hasSignatureData) + { + buffer.WriteLong(Key!.ExpiresAt); + + buffer.WriteVarInt(Key.PublicKey.Length); + buffer.Write(Key.PublicKey); + + buffer.WriteVarInt(Key.Signature.Length); + buffer.Write(Key.Signature); + } + } + + if (Key is { ProfileUuid: not null }) + { + buffer.WriteBoolean(true); + buffer.WriteUuid(Key.ProfileUuid.Value); + } + else if (Profile.Id.AsGuid != default) + { + buffer.WriteBoolean(true); + buffer.WriteUuid(Profile.Id); + } + else + { + buffer.WriteBoolean(false); + } + } + } + + public static LoginStartPacket Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + var username = buffer.ReadString(); + var uuid = Uuid.Empty; + IdentifiedKey? key = null; + + if (protocolVersion >= ProtocolVersion.MINECRAFT_1_19) + { + if (protocolVersion < ProtocolVersion.MINECRAFT_1_19_3) + { + var hasSignatureData = buffer.ReadBoolean(); + + if (hasSignatureData) + { + var revision = protocolVersion == ProtocolVersion.MINECRAFT_1_19 ? IdentifiedKeyRevision.GenericV1Revision : IdentifiedKeyRevision.LinkedV2Revision; + var expiresAt = buffer.ReadLong(); + + var publicKeyLength = buffer.ReadVarInt(); + var publicKey = buffer.Read(publicKeyLength).ToArray(); + + var signatureLength = buffer.ReadVarInt(); + var signature = buffer.Read(signatureLength).ToArray(); + + key = new IdentifiedKey(revision, expiresAt, publicKey, signature); + + if (key.ProfileUuid.HasValue) + uuid = key.ProfileUuid.Value; + } + } + + var hasUuid = buffer.ReadBoolean(); + + if (hasUuid) + uuid = buffer.ReadUuid(); + } + + return new LoginStartPacket + { + Profile = new GameProfile(uuid, username, []), + Key = key + }; + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Plugin.cs b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Plugin.cs index 8bcbc38..a8daec1 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Plugin.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Plugin.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Void.Proxy.API.Events; @@ -145,7 +144,7 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can return; } - logger.LogDebug("Received packet {Packet}", @event.Message); + logger.LogTrace("Received packet {Packet}", @event.Message); switch (@event.Message) { @@ -160,13 +159,10 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can break; case SetCompressionPacket setCompression: @event.Link.ServerChannel.AddBefore(); - logger.LogDebug("Link {Link} enabled compression in server channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + logger.LogTrace("Link {Link} enabled compression in server channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); var zlibStream = @event.Link.ServerChannel.Get(); zlibStream.CompressionThreshold = setCompression.Threshold; - - // cannot be awaited because default ILink implementation awaits for all event listeners one of which we are - var restartTask = @event.Link.RestartAsync(cancellationToken); break; case LoginSuccessPacket loginSuccess: holder = @event.Link.Player.Context.Services.GetRequiredService(); @@ -188,30 +184,40 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio return; } - logger.LogDebug("Sent packet {Packet}", @event.Message); + logger.LogTrace("Sent packet {Packet}", @event.Message); switch (@event.Message) { case HandshakePacket handshake: var holder = @event.Link.Player.Context.Services.GetRequiredService(); - holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundLoginMappings); + + if (handshake.NextState == 2) + holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundLoginMappings); + else + holder.Reset(); + break; + case LoginStartPacket loginStart: + @event.Link.PlayerChannel.Pause(); break; case SetCompressionPacket setCompression: @event.Link.PlayerChannel.AddBefore(); - // ILink restart is already scheduled here, should we enable compression with client separately? - var zlibStream = @event.Link.PlayerChannel.Get(); zlibStream.CompressionThreshold = setCompression.Threshold; - logger.LogDebug("Link {Link} enabled compression in player channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + logger.LogTrace("Link {Link} enabled compression in player channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + @event.Link.PlayerChannel.Resume(); break; case LoginSuccessPacket loginSuccess: holder = @event.Link.Player.Context.Services.GetRequiredService(); holder.ReplacePackets(Direction.Clientbound, Mappings.ClientboundPlayMappings); + + if (@event.Link.PlayerChannel.IsPaused) + @event.Link.PlayerChannel.Resume(); break; case EncryptionRequestPacket encryptionRequest: - logger.LogDebug(JsonSerializer.Serialize(encryptionRequest)); + @event.Link.PlayerChannel.Resume(); + @event.Link.ServerChannel.Pause(); break; case EncryptionResponsePacket encryptionResponse: var serverPrivateKey = Convert.FromHexString("30820275020100300d06092a864886f70d01010105000482025f3082025b020100028181008833058bd694928c004a7ffbaeca35f5bac94b070082080958c71bc8bc23a507c64432a8d38cb3d405928ef58f89f681c87a3909f3a1ec08cbdc5223efb612efcdeb639fd93562ac73e407c6bd8d68d7dc1a226e6c95c9f3e5a94edaec76a98c411caf6caec3f2618ea33169b72e42687faf4ac7478af722df6c234b5fcc82ef0203010001028180119dc27a8dd74b052a07237aa2e700b261e5d0a5d288ff0fc66dd51d7cf2d750f97204b8c01ebe26644f75323478a71658f486157ac5021f392456c8d323d25b0583aa927c9bf835017fe3af24da1c4dd6ab416c06bbedfef40ef4428adc60d4e2d5e613d3278ca87590fc91ff7d43ad359cf16215af0391e53eef1581882dc1024100a99aca2367811e10f8e15ee5472d50507756bb48c5be1cc5cb6251b43dcec7163f52913766722968d1843daf99687026010db8986ad1c58005c238eec2ca2bff024100cd940653037a7ff8e8e4aa3d1d6d543a12297538dac4c4def496fd96d030dd0c0daa32da80b14e1344a8571e140ce0d27e009cbe0a7e9124a0a90a2e0bac691102401ddc048e6b208e3c8ab492d266cf917e392469e08bffc66d043b910adc7ed50a13a7e3ad0f3a3614201eda055a4acac3c617b6520f2c534b10b87af17e15bddd023f3c3a21a03064b3193921c4be22e0e4cc1e8606d1a14604674d40ef0a3ff410ce773265b39e0053df513e0047cf97f645b4a4794733cbe0b9da57aba3d1c7b10241009905827e05808b57c0c836d29b1efe9852f502509b54ab6246539d359cbb036904283b6f6f5636659f7b25478949f0acc3a7cf4edf13cd2539376247845deff1"); @@ -221,8 +227,8 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio @event.Link.ServerChannel.AddBefore(new AesCfb8BufferedStream(secret)); @event.Link.PlayerChannel.AddBefore(new AesCfb8BufferedStream(secret)); - // cannot be awaited because default ILink implementation awaits for all event listeners one of which we are - var restartTask = @event.Link.RestartAsync(cancellationToken); + @event.Link.PlayerChannel.Pause(); + @event.Link.ServerChannel.Resume(); break; static byte[] Decrypt(byte[] key, byte[] data) diff --git a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Registries/Mappings.cs b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Registries/Mappings.cs index 030b99d..829086f 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Registries/Mappings.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_13_to_1_20_1/Registries/Mappings.cs @@ -22,6 +22,7 @@ public static class Mappings public static readonly IReadOnlyDictionary ServerboundLoginMappings = new Dictionary { + { [new PacketMapping(0x00, Plugin.SupportedVersions.First())], typeof(LoginStartPacket) }, { [new PacketMapping(0x01, Plugin.SupportedVersions.First())], typeof(EncryptionResponsePacket) } }; diff --git a/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Packets/Serverbound/LoginStartPacket.cs b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Packets/Serverbound/LoginStartPacket.cs new file mode 100644 index 0000000..9de4004 --- /dev/null +++ b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Packets/Serverbound/LoginStartPacket.cs @@ -0,0 +1,29 @@ +using Void.Proxy.API.Mojang.Profiles; +using Void.Proxy.API.Network.IO.Buffers; +using Void.Proxy.API.Network.Protocol; +using Void.Proxy.Common.Network.IO.Messages; + +namespace Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest.Packets.Serverbound; + +public class LoginStartPacket : IMinecraftPacket +{ + public required GameProfile Profile { get; set; } + + public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + buffer.WriteString(Profile.Username); + buffer.WriteUuid(Profile.Id); + } + + public static LoginStartPacket Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + var username = buffer.ReadString(); + var uuid = buffer.ReadUuid(); + + return new LoginStartPacket { Profile = new GameProfile(uuid, username, []) }; + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Packets/Serverbound/LoginSuccessPacket.cs b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Packets/Serverbound/LoginSuccessPacket.cs new file mode 100644 index 0000000..cc12629 --- /dev/null +++ b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Packets/Serverbound/LoginSuccessPacket.cs @@ -0,0 +1,48 @@ +using Void.Proxy.API.Mojang.Profiles; +using Void.Proxy.API.Network.IO.Buffers; +using Void.Proxy.API.Network.Protocol; +using Void.Proxy.Common.Network.IO.Messages; + +namespace Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest.Packets.Serverbound; + +public class LoginSuccessPacket : IMinecraftPacket +{ + public required GameProfile GameProfile { get; set; } + public required bool? StrictErrorHandling { get; set; } + + public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + buffer.WriteUuid(GameProfile.Id); + buffer.WriteString(GameProfile.Username); + buffer.WritePropertyArray(GameProfile.Properties); + + if (protocolVersion < ProtocolVersion.MINECRAFT_1_20_5) + return; + + if (!StrictErrorHandling.HasValue) + throw new ArgumentNullException(nameof(StrictErrorHandling)); + + buffer.WriteBoolean(StrictErrorHandling.Value); + } + + public static LoginSuccessPacket Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + var uuid = buffer.ReadUuid(); + var username = buffer.ReadString(); + var properties = buffer.ReadPropertyArray(); + bool? strictErrorHandling = null; + + if (protocolVersion >= ProtocolVersion.MINECRAFT_1_20_5) + strictErrorHandling = buffer.ReadBoolean(); + + return new LoginSuccessPacket + { + GameProfile = new GameProfile(uuid, username, properties), + StrictErrorHandling = strictErrorHandling + }; + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Plugin.cs b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Plugin.cs index 9a0d4d6..7b50751 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Plugin.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Plugin.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Void.Proxy.API.Events; @@ -145,7 +144,7 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can return; } - logger.LogDebug("Received packet {Packet}", @event.Message); + logger.LogTrace("Received packet {Packet}", @event.Message); switch (@event.Message) { @@ -160,13 +159,10 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can break; case SetCompressionPacket setCompression: @event.Link.ServerChannel.AddBefore(); - logger.LogDebug("Link {Link} enabled compression in server channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + logger.LogTrace("Link {Link} enabled compression in server channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); var zlibStream = @event.Link.ServerChannel.Get(); zlibStream.CompressionThreshold = setCompression.Threshold; - - // cannot be awaited because default ILink implementation awaits for all event listeners one of which we are - var restartTask = @event.Link.RestartAsync(cancellationToken); break; case LoginAcknowledgedPacket loginAcknowledged: holder = @event.Link.Player.Context.Services.GetRequiredService(); @@ -176,9 +172,6 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can holder = @event.Link.Player.Context.Services.GetRequiredService(); holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundPlayMappings); break; - case KeepAliveResponsePacket keepAliveResponse: - logger.LogDebug("Player {Link} sent keep alive response ({Id})", @event.Link.Player, keepAliveResponse.Id); - break; } } @@ -195,23 +188,33 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio return; } - logger.LogDebug("Sent packet {Packet}", @event.Message); + logger.LogTrace("Sent packet {Packet}", @event.Message); switch (@event.Message) { case HandshakePacket handshake: var holder = @event.Link.Player.Context.Services.GetRequiredService(); - holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundLoginMappings); + + if (handshake.NextState == 2) + holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundLoginMappings); + else + holder.Reset(); + break; + case LoginStartPacket loginStart: + @event.Link.PlayerChannel.Pause(); break; case SetCompressionPacket setCompression: @event.Link.PlayerChannel.AddBefore(); - // ILink restart is already scheduled here, should we enable compression with client separately? - var zlibStream = @event.Link.PlayerChannel.Get(); zlibStream.CompressionThreshold = setCompression.Threshold; - logger.LogDebug("Link {Link} enabled compression in player channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + logger.LogTrace("Link {Link} enabled compression in player channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + @event.Link.PlayerChannel.Resume(); + break; + case LoginSuccessPacket loginSuccess: + if (@event.Link.PlayerChannel.IsPaused) + @event.Link.PlayerChannel.Resume(); break; case LoginAcknowledgedPacket loginAcknowledged: holder = @event.Link.Player.Context.Services.GetRequiredService(); @@ -222,7 +225,8 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio holder.ReplacePackets(Direction.Clientbound, Mappings.ClientboundPlayMappings); break; case EncryptionRequestPacket encryptionRequest: - logger.LogDebug(JsonSerializer.Serialize(encryptionRequest)); + @event.Link.PlayerChannel.Resume(); + @event.Link.ServerChannel.Pause(); break; case EncryptionResponsePacket encryptionResponse: var serverPrivateKey = Convert.FromHexString("30820275020100300d06092a864886f70d01010105000482025f3082025b020100028181008833058bd694928c004a7ffbaeca35f5bac94b070082080958c71bc8bc23a507c64432a8d38cb3d405928ef58f89f681c87a3909f3a1ec08cbdc5223efb612efcdeb639fd93562ac73e407c6bd8d68d7dc1a226e6c95c9f3e5a94edaec76a98c411caf6caec3f2618ea33169b72e42687faf4ac7478af722df6c234b5fcc82ef0203010001028180119dc27a8dd74b052a07237aa2e700b261e5d0a5d288ff0fc66dd51d7cf2d750f97204b8c01ebe26644f75323478a71658f486157ac5021f392456c8d323d25b0583aa927c9bf835017fe3af24da1c4dd6ab416c06bbedfef40ef4428adc60d4e2d5e613d3278ca87590fc91ff7d43ad359cf16215af0391e53eef1581882dc1024100a99aca2367811e10f8e15ee5472d50507756bb48c5be1cc5cb6251b43dcec7163f52913766722968d1843daf99687026010db8986ad1c58005c238eec2ca2bff024100cd940653037a7ff8e8e4aa3d1d6d543a12297538dac4c4def496fd96d030dd0c0daa32da80b14e1344a8571e140ce0d27e009cbe0a7e9124a0a90a2e0bac691102401ddc048e6b208e3c8ab492d266cf917e392469e08bffc66d043b910adc7ed50a13a7e3ad0f3a3614201eda055a4acac3c617b6520f2c534b10b87af17e15bddd023f3c3a21a03064b3193921c4be22e0e4cc1e8606d1a14604674d40ef0a3ff410ce773265b39e0053df513e0047cf97f645b4a4794733cbe0b9da57aba3d1c7b10241009905827e05808b57c0c836d29b1efe9852f502509b54ab6246539d359cbb036904283b6f6f5636659f7b25478949f0acc3a7cf4edf13cd2539376247845deff1"); @@ -232,8 +236,8 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio @event.Link.ServerChannel.AddBefore(new AesCfb8BufferedStream(secret)); @event.Link.PlayerChannel.AddBefore(new AesCfb8BufferedStream(secret)); - // cannot be awaited because default ILink implementation awaits for all event listeners one of which we are - var restartTask = @event.Link.RestartAsync(cancellationToken); + @event.Link.PlayerChannel.Pause(); + @event.Link.ServerChannel.Resume(); break; static byte[] Decrypt(byte[] key, byte[] data) diff --git a/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Registries/Mappings.cs b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Registries/Mappings.cs index 746f67f..0bbedb9 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Registries/Mappings.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_20_2_to_latest/Registries/Mappings.cs @@ -17,11 +17,13 @@ public static class Mappings public static readonly IReadOnlyDictionary ClientboundLoginMappings = new Dictionary { { [new PacketMapping(0x01, Plugin.SupportedVersions.First())], typeof(EncryptionRequestPacket) }, + { [new PacketMapping(0x02, Plugin.SupportedVersions.First())], typeof(LoginSuccessPacket) }, { [new PacketMapping(0x03, Plugin.SupportedVersions.First())], typeof(SetCompressionPacket) } }; public static readonly IReadOnlyDictionary ServerboundLoginMappings = new Dictionary { + { [new PacketMapping(0x00, Plugin.SupportedVersions.First())], typeof(LoginStartPacket) }, { [new PacketMapping(0x01, Plugin.SupportedVersions.First())], typeof(EncryptionResponsePacket) }, { [new PacketMapping(0x03, Plugin.SupportedVersions.First())], typeof(LoginAcknowledgedPacket) } }; diff --git a/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Packets/Serverbound/LoginStartPacket.cs b/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Packets/Serverbound/LoginStartPacket.cs new file mode 100644 index 0000000..3990cf7 --- /dev/null +++ b/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Packets/Serverbound/LoginStartPacket.cs @@ -0,0 +1,26 @@ +using Void.Proxy.API.Mojang; +using Void.Proxy.API.Mojang.Profiles; +using Void.Proxy.API.Network.IO.Buffers; +using Void.Proxy.API.Network.Protocol; +using Void.Proxy.Common.Network.IO.Messages; + +namespace Void.Proxy.Plugins.ProtocolSupport.Java.v1_7_2_to_1_12_2.Packets.Serverbound; + +public class LoginStartPacket : IMinecraftPacket +{ + public required GameProfile Profile { get; set; } + + public void Encode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + buffer.WriteString(Profile.Username); + } + + public static LoginStartPacket Decode(ref MinecraftBuffer buffer, ProtocolVersion protocolVersion) + { + return new LoginStartPacket { Profile = new GameProfile(Uuid.Empty, buffer.ReadString(), []) }; + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Plugin.cs b/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Plugin.cs index af0aa25..e79ee47 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Plugin.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Plugin.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Void.Proxy.API.Events; @@ -145,7 +144,7 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can return; } - logger.LogDebug("Received packet {Packet}", @event.Message); + logger.LogTrace("Received packet {Packet}", @event.Message); switch (@event.Message) { @@ -160,13 +159,10 @@ public void OnMessageReceived(MessageReceivedEvent @event, CancellationToken can break; case SetCompressionPacket setCompression: @event.Link.ServerChannel.AddBefore(); - logger.LogDebug("Link {Link} enabled compression in server channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + logger.LogTrace("Link {Link} enabled compression in server channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); var zlibStream = @event.Link.ServerChannel.Get(); zlibStream.CompressionThreshold = setCompression.Threshold; - - // cannot be awaited because default ILink implementation awaits for all event listeners one of which we are - var restartTask = @event.Link.RestartAsync(cancellationToken); break; case LoginSuccessPacket loginSuccess: holder = @event.Link.Player.Context.Services.GetRequiredService(); @@ -183,35 +179,45 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio case BufferedBinaryMessage bufferedBinaryMessage: logger.LogTrace("Sent buffer length {Length} to {Direction} {PlayerOrServer}", bufferedBinaryMessage.Stream.Length, @event.To, @event.To == Side.Client ? @event.Link.Player : @event.Link.Server); return; + case LoginStartPacket loginStart: + @event.Link.PlayerChannel.Pause(); + break; case BinaryPacket binaryPacket: logger.LogTrace("Sent packet id {PacketId:X2}, length {Length} to {Direction} {PlayerOrServer}", binaryPacket.Id, binaryPacket.Stream.Length, @event.To, @event.To == Side.Client ? @event.Link.Player : @event.Link.Server); return; } - logger.LogDebug("Sent packet {Packet}", @event.Message); + logger.LogTrace("Sent packet {Packet}", @event.Message); switch (@event.Message) { case HandshakePacket handshake: var holder = @event.Link.Player.Context.Services.GetRequiredService(); - holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundLoginMappings); + + if (handshake.NextState == 2) + holder.ReplacePackets(Direction.Serverbound, Mappings.ServerboundLoginMappings); + else + holder.Reset(); break; case SetCompressionPacket setCompression: @event.Link.PlayerChannel.AddBefore(); - // ILink restart is already scheduled here, should we enable compression with client separately? - var zlibStream = @event.Link.PlayerChannel.Get(); zlibStream.CompressionThreshold = setCompression.Threshold; - logger.LogDebug("Link {Link} enabled compression in player channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + logger.LogTrace("Link {Link} enabled compression in player channel with threshold {CompressionThreshold}", @event.Link, setCompression.Threshold); + @event.Link.PlayerChannel.Resume(); break; case LoginSuccessPacket loginSuccess: holder = @event.Link.Player.Context.Services.GetRequiredService(); holder.ReplacePackets(Direction.Clientbound, Mappings.ClientboundPlayMappings); + + if (@event.Link.PlayerChannel.IsPaused) + @event.Link.PlayerChannel.Resume(); break; case EncryptionRequestPacket encryptionRequest: - logger.LogDebug(JsonSerializer.Serialize(encryptionRequest)); + @event.Link.PlayerChannel.Resume(); + @event.Link.ServerChannel.Pause(); break; case EncryptionResponsePacket encryptionResponse: var serverPrivateKey = Convert.FromHexString("30820275020100300d06092a864886f70d01010105000482025f3082025b020100028181008833058bd694928c004a7ffbaeca35f5bac94b070082080958c71bc8bc23a507c64432a8d38cb3d405928ef58f89f681c87a3909f3a1ec08cbdc5223efb612efcdeb639fd93562ac73e407c6bd8d68d7dc1a226e6c95c9f3e5a94edaec76a98c411caf6caec3f2618ea33169b72e42687faf4ac7478af722df6c234b5fcc82ef0203010001028180119dc27a8dd74b052a07237aa2e700b261e5d0a5d288ff0fc66dd51d7cf2d750f97204b8c01ebe26644f75323478a71658f486157ac5021f392456c8d323d25b0583aa927c9bf835017fe3af24da1c4dd6ab416c06bbedfef40ef4428adc60d4e2d5e613d3278ca87590fc91ff7d43ad359cf16215af0391e53eef1581882dc1024100a99aca2367811e10f8e15ee5472d50507756bb48c5be1cc5cb6251b43dcec7163f52913766722968d1843daf99687026010db8986ad1c58005c238eec2ca2bff024100cd940653037a7ff8e8e4aa3d1d6d543a12297538dac4c4def496fd96d030dd0c0daa32da80b14e1344a8571e140ce0d27e009cbe0a7e9124a0a90a2e0bac691102401ddc048e6b208e3c8ab492d266cf917e392469e08bffc66d043b910adc7ed50a13a7e3ad0f3a3614201eda055a4acac3c617b6520f2c534b10b87af17e15bddd023f3c3a21a03064b3193921c4be22e0e4cc1e8606d1a14604674d40ef0a3ff410ce773265b39e0053df513e0047cf97f645b4a4794733cbe0b9da57aba3d1c7b10241009905827e05808b57c0c836d29b1efe9852f502509b54ab6246539d359cbb036904283b6f6f5636659f7b25478949f0acc3a7cf4edf13cd2539376247845deff1"); @@ -221,8 +227,8 @@ public void OnMessageSent(MessageSentEvent @event, CancellationToken cancellatio @event.Link.ServerChannel.AddBefore(new AesCfb8BufferedStream(secret)); @event.Link.PlayerChannel.AddBefore(new AesCfb8BufferedStream(secret)); - // cannot be awaited because default ILink implementation awaits for all event listeners one of which we are - var restartTask = @event.Link.RestartAsync(cancellationToken); + @event.Link.PlayerChannel.Pause(); + @event.Link.ServerChannel.Resume(); break; static byte[] Decrypt(byte[] key, byte[] data) diff --git a/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Registries/Mappings.cs b/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Registries/Mappings.cs index 0d1d6cb..1b167e4 100644 --- a/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Registries/Mappings.cs +++ b/src/Plugins/ProtocolSupport/Java/v1_7_2_to_1_12_2/Registries/Mappings.cs @@ -23,6 +23,7 @@ public static class Mappings public static readonly IReadOnlyDictionary ServerboundLoginMappings = new Dictionary { + { [new PacketMapping(0x00, Plugin.SupportedVersions.First())], typeof(LoginStartPacket) }, { [new PacketMapping(0x01, Plugin.SupportedVersions.First())], typeof(EncryptionResponsePacket) } };