This commit is contained in:
Anon 2026-04-04 00:32:07 +02:00
parent 1ca023be36
commit 3a4d8951d5
20 changed files with 1511 additions and 389 deletions

View file

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Inventory;
using MinecraftClient.Inventory.ItemPalettes;
using MinecraftClient.Mapping;
@ -313,6 +315,27 @@ namespace MinecraftClient.Protocol.Handlers
return i;
}
/// <summary>
/// Read an integer from the network asynchronously.
/// </summary>
/// <returns>The integer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public async Task<int> ReadNextVarIntRAWAsync(SocketWrapper socket, CancellationToken cancellationToken)
{
int i = 0;
int j = 0;
byte b;
while (true)
{
b = (await socket.ReadDataRAWAsync(1, cancellationToken))[0];
i |= (b & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((b & 0x80) != 128) break;
}
return i;
}
/// <summary>
/// Read an integer from a cache of bytes and remove it from the cache
/// </summary>

View file

@ -7,6 +7,7 @@ using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Crypto;
using MinecraftClient.Inventory;
using MinecraftClient.Mapping;
@ -29,7 +30,9 @@ namespace MinecraftClient.Protocol.Handlers
readonly IMinecraftComHandler handler;
private bool encrypted = false;
private readonly int protocolversion;
private Tuple<Thread, CancellationTokenSource>? netRead = null;
private Task? netReadTask;
private CancellationTokenSource? netReadCancellationTokenSource;
private int netReadThreadId = -1;
Crypto.AesCfb8Stream? s;
readonly TcpClient c;
@ -69,15 +72,15 @@ namespace MinecraftClient.Protocol.Handlers
c = Client;
}
private void Updater(object? o)
private void Updater(CancellationToken cancelToken)
{
var cancelToken = (CancellationToken)o!;
if (cancelToken.IsCancellationRequested)
return;
try
{
netReadThreadId = Environment.CurrentManagedThreadId;
using IDisposable _ = MainThreadExecutionScope.Enter(handler);
Stopwatch stopWatch = Stopwatch.StartNew();
long nextUpdateDue = 0;
@ -104,6 +107,8 @@ namespace MinecraftClient.Protocol.Handlers
catch (SocketException) { }
catch (ObjectDisposedException) { }
catch (OperationCanceledException) { }
catch (Exception) { }
finally { netReadThreadId = -1; }
if (cancelToken.IsCancellationRequested)
return;
@ -240,9 +245,13 @@ namespace MinecraftClient.Protocol.Handlers
private void StartUpdating()
{
netRead = new(new Thread(new ParameterizedThreadStart(Updater)), new CancellationTokenSource());
netRead.Item1.Name = "ProtocolPacketHandler";
netRead.Item1.Start(netRead.Item2.Token);
CancellationTokenSource netReadCts = new();
netReadCancellationTokenSource = netReadCts;
netReadTask = Task.Factory.StartNew(
() => Updater(netReadCts.Token),
netReadCts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
/// <summary>
@ -251,7 +260,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <returns>Net read thread ID</returns>
public int GetNetMainThreadId()
{
return netRead is not null ? netRead.Item1.ManagedThreadId : -1;
return netReadThreadId;
}
public bool SendCookieResponse(string name, byte[]? data)
@ -268,9 +277,9 @@ namespace MinecraftClient.Protocol.Handlers
{
try
{
if (netRead is not null)
if (netReadCancellationTokenSource is not null)
{
netRead.Item2.Cancel();
netReadCancellationTokenSource.Cancel();
c.Close();
}
}
@ -519,7 +528,8 @@ namespace MinecraftClient.Protocol.Handlers
Receive(pid, 0, 1, SocketFlags.None);
while (pid[0] == 0xFA) //Skip some early plugin messages
{
ProcessPacket(pid[0]);
using (MainThreadExecutionScope.Enter(handler))
ProcessPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
}
if (pid[0] == 0xFD)
@ -559,8 +569,7 @@ namespace MinecraftClient.Protocol.Handlers
if (session.ServerPublicKey is not null && session.SessionPreCheckTask is not null
&& serverIDhash == session.ServerIDhash && Enumerable.SequenceEqual(serverPublicKey, session.ServerPublicKey))
{
session.SessionPreCheckTask.Wait();
if (session.SessionPreCheckTask.Result) // PreCheck Successed
if (session.SessionPreCheckTask.IsCompletedSuccessfully && session.SessionPreCheckTask.Result)
needCheckSession = false;
}
@ -633,7 +642,8 @@ namespace MinecraftClient.Protocol.Handlers
Receive(pid, 0, 1, SocketFlags.None);
while (pid[0] >= 0xC0 && pid[0] != 0xFF) //Skip some early packets or plugin messages
{
ProcessPacket(pid[0]);
using (MainThreadExecutionScope.Enter(handler))
ProcessPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
}
if (pid[0] == (byte)1)

View file

@ -9,6 +9,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Crypto;
using MinecraftClient.Inventory;
using MinecraftClient.Inventory.ItemPalettes;
@ -117,8 +118,11 @@ namespace MinecraftClient.Protocol.Handlers
readonly PacketTypePalette packetPalette;
readonly SocketWrapper socketWrapper;
readonly DataTypes dataTypes;
Tuple<Thread, CancellationTokenSource>? netMain = null; // main thread
Tuple<Thread, CancellationTokenSource>? netReader = null; // reader thread
private Task? netMainTask;
private CancellationTokenSource? netMainCancellationTokenSource;
private int netMainThreadId = -1;
private Task? netReaderTask;
private CancellationTokenSource? netReaderCancellationTokenSource;
readonly ILogger log;
readonly RandomNumberGenerator randomGen;
private bool legacyAchievementsInitialized;
@ -278,17 +282,17 @@ namespace MinecraftClient.Protocol.Handlers
}
/// <summary>
/// Separate thread. Network reading loop.
/// Serialized packet/tick loop.
/// </summary>
private void Updater(object? o)
private void Updater(CancellationToken cancelToken)
{
var cancelToken = (CancellationToken)o!;
if (cancelToken.IsCancellationRequested)
return;
try
{
netMainThreadId = Environment.CurrentManagedThreadId;
using IDisposable _ = MainThreadExecutionScope.Enter(handler);
Stopwatch stopWatch = Stopwatch.StartNew();
long nextUpdateDue = 0;
while (!packetQueue.IsAddingCompleted)
@ -330,6 +334,13 @@ namespace MinecraftClient.Protocol.Handlers
catch (System.IO.IOException)
{
}
catch (Exception)
{
}
finally
{
netMainThreadId = -1;
}
if (cancelToken.IsCancellationRequested)
return;
@ -340,20 +351,13 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Read and decompress packets.
/// </summary>
internal void PacketReader(object? o)
internal async Task PacketReaderAsync(CancellationToken cancelToken)
{
var cancelToken = (CancellationToken)o!;
while (socketWrapper.IsConnected() && !cancelToken.IsCancellationRequested)
while (!cancelToken.IsCancellationRequested)
{
try
{
while (socketWrapper.HasDataAvailable())
{
packetQueue.Add(ReadNextPacket(), cancelToken);
if (cancelToken.IsCancellationRequested)
break;
}
packetQueue.Add(await ReadNextPacketAsync(cancelToken), cancelToken);
}
catch (OperationCanceledException)
{
@ -375,11 +379,10 @@ namespace MinecraftClient.Protocol.Handlers
{
break;
}
if (cancelToken.IsCancellationRequested)
catch (Exception)
{
break;
Thread.Sleep(10);
}
}
packetQueue.CompleteAdding();
@ -415,6 +418,30 @@ namespace MinecraftClient.Protocol.Handlers
return new(packetId, packetData);
}
internal async Task<Tuple<int, Queue<byte>>> ReadNextPacketAsync(CancellationToken cancellationToken)
{
var size = await dataTypes.ReadNextVarIntRAWAsync(socketWrapper, cancellationToken); //Packet size
Queue<byte> packetData = new(await socketWrapper.ReadDataRAWAsync(size, cancellationToken)); //Packet contents
if (protocolVersion >= MC_1_8_Version
&& compression_treshold >= 0)
{
var sizeUncompressed = dataTypes.ReadNextVarInt(packetData);
if (sizeUncompressed != 0)
{
var toDecompress = packetData.ToArray();
var uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed);
packetData = new Queue<byte>(uncompressed);
}
}
var packetId = dataTypes.ReadNextVarInt(packetData);
if (handler.GetNetworkPacketCaptureEnabled())
handler.OnNetworkPacket(packetId, packetData.ToList(), currentState == CurrentState.Login, true);
return new(packetId, packetData);
}
/// <summary>
/// Handle the given packet
/// </summary>
@ -3844,19 +3871,17 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
private void StartUpdating()
{
Thread threadUpdater = new(new ParameterizedThreadStart(Updater))
{
Name = "ProtocolPacketHandler"
};
netMain = new Tuple<Thread, CancellationTokenSource>(threadUpdater, new CancellationTokenSource());
threadUpdater.Start(netMain.Item2.Token);
CancellationTokenSource netMainCts = new();
netMainCancellationTokenSource = netMainCts;
netMainTask = Task.Factory.StartNew(
() => Updater(netMainCts.Token),
netMainCts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
Thread threadReader = new(new ParameterizedThreadStart(PacketReader))
{
Name = "ProtocolPacketReader"
};
netReader = new Tuple<Thread, CancellationTokenSource>(threadReader, new CancellationTokenSource());
threadReader.Start(netReader.Item2.Token);
CancellationTokenSource netReaderCts = new();
netReaderCancellationTokenSource = netReaderCts;
netReaderTask = PacketReaderAsync(netReaderCts.Token);
}
/// <summary>
@ -3865,7 +3890,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <returns>Net read thread ID</returns>
public int GetNetMainThreadId()
{
return netMain is not null ? netMain.Item1.ManagedThreadId : -1;
return netMainThreadId;
}
/// <summary>
@ -3875,14 +3900,14 @@ namespace MinecraftClient.Protocol.Handlers
{
try
{
if (netMain is not null)
if (netMainCancellationTokenSource is not null)
{
netMain.Item2.Cancel();
netMainCancellationTokenSource.Cancel();
}
if (netReader is not null)
if (netReaderCancellationTokenSource is not null)
{
netReader.Item2.Cancel();
netReaderCancellationTokenSource.Cancel();
socketWrapper.Disconnect();
}
}
@ -4106,7 +4131,8 @@ namespace MinecraftClient.Protocol.Handlers
return true; //No need to check session or start encryption
}
default:
HandlePacket(packetId, packetData);
using (MainThreadExecutionScope.Enter(handler))
HandlePacket(packetId, packetData);
break;
}
}
@ -4133,8 +4159,7 @@ namespace MinecraftClient.Protocol.Handlers
&& serverIDhash == session.ServerIDhash &&
serverPublicKey.SequenceEqual(session.ServerPublicKey))
{
session.SessionPreCheckTask.Wait();
if (session.SessionPreCheckTask.Result) // PreCheck Success
if (session.SessionPreCheckTask.IsCompletedSuccessfully && session.SessionPreCheckTask.Result)
needCheckSession = false;
}
@ -4256,7 +4281,8 @@ namespace MinecraftClient.Protocol.Handlers
return true;
}
default:
HandlePacket(packetId, packetData);
using (MainThreadExecutionScope.Enter(handler))
HandlePacket(packetId, packetData);
break;
}
}

View file

@ -1,5 +1,8 @@
using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Crypto;
namespace MinecraftClient.Protocol.Handlers
@ -68,6 +71,22 @@ namespace MinecraftClient.Protocol.Handlers
}
}
private async Task ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken)
{
int read = 0;
while (read < buffer.Length)
{
int currentRead = encrypted
? await s!.ReadAsync(buffer[read..], cancellationToken)
: await c.GetStream().ReadAsync(buffer[read..], cancellationToken);
if (currentRead == 0)
throw new IOException("Connection closed.");
read += currentRead;
}
}
/// <summary>
/// Read some data from the server.
/// </summary>
@ -84,6 +103,18 @@ namespace MinecraftClient.Protocol.Handlers
return Array.Empty<byte>();
}
public async Task<byte[]> ReadDataRAWAsync(int length, CancellationToken cancellationToken)
{
if (length > 0)
{
byte[] cache = new byte[length];
await ReceiveAsync(cache, cancellationToken);
return cache;
}
return Array.Empty<byte>();
}
/// <summary>
/// Send raw data to the server.
/// </summary>
@ -99,6 +130,17 @@ namespace MinecraftClient.Protocol.Handlers
c.Client.Send(buffer);
}
public async Task SendDataRAWAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
{
if (!IsConnected())
throw new SocketException((int)SocketError.NotConnected);
if (encrypted)
await s!.WriteAsync(buffer, cancellationToken);
else
await c.GetStream().WriteAsync(buffer, cancellationToken);
}
/// <summary>
/// Disconnect from the server
/// </summary>