mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Faster AES, Packet Stream, more async changes
This commit is contained in:
parent
3b7c9c8510
commit
3a20f2e235
19 changed files with 1009 additions and 272 deletions
|
|
@ -306,7 +306,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
byte b;
|
||||
while (true)
|
||||
{
|
||||
b = socket.ReadDataRAW(1)[0];
|
||||
b = socket.ReadByteRAW();
|
||||
i |= (b & 0x7F) << j++ * 7;
|
||||
if (j > 5) throw new OverflowException("VarInt too big");
|
||||
if ((b & 0x80) != 128) break;
|
||||
|
|
@ -327,7 +327,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
byte b;
|
||||
while (true)
|
||||
{
|
||||
b = (await socket.ReadDataRAWAsync(1, cancellationToken))[0];
|
||||
b = await socket.ReadByteRAWAsync(cancellationToken);
|
||||
i |= (b & 0x7F) << j++ * 7;
|
||||
if (j > 5) throw new OverflowException("VarInt too big");
|
||||
if ((b & 0x80) != 128) break;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
c = Client;
|
||||
}
|
||||
|
||||
private void Updater(CancellationToken cancelToken)
|
||||
private async Task UpdaterAsync(CancellationToken cancelToken)
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
return;
|
||||
|
|
@ -100,7 +100,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
long sleepLength = nextUpdateDue - stopWatch.ElapsedMilliseconds;
|
||||
if (sleepLength > 1)
|
||||
Thread.Sleep((int)Math.Min(sleepLength, ClientTickIntervalMilliseconds));
|
||||
await Task.Delay((int)Math.Min(sleepLength, ClientTickIntervalMilliseconds), cancelToken);
|
||||
}
|
||||
}
|
||||
catch (System.IO.IOException) { }
|
||||
|
|
@ -247,11 +247,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
CancellationTokenSource netReadCts = new();
|
||||
netReadCancellationTokenSource = netReadCts;
|
||||
netReadTask = Task.Factory.StartNew(
|
||||
() => Updater(netReadCts.Token),
|
||||
netReadCts.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
netReadTask = Task.Run(() => UpdaterAsync(netReadCts.Token), netReadCts.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <summary>
|
||||
/// Serialized packet/tick loop.
|
||||
/// </summary>
|
||||
private void Updater(CancellationToken cancelToken)
|
||||
private async Task UpdaterAsync(CancellationToken cancelToken)
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
return;
|
||||
|
|
@ -316,7 +316,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
long sleepLength = nextUpdateDue - stopWatch.ElapsedMilliseconds;
|
||||
if (sleepLength > 1)
|
||||
Thread.Sleep((int)Math.Min(sleepLength, ClientTickIntervalMilliseconds));
|
||||
await Task.Delay((int)Math.Min(sleepLength, ClientTickIntervalMilliseconds), cancelToken);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
|
|
@ -395,23 +395,9 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <param name="packetData">will contain raw packet Data</param>
|
||||
internal Tuple<int, Queue<byte>> ReadNextPacket()
|
||||
{
|
||||
var size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size
|
||||
Queue<byte> packetData = new(socketWrapper.ReadDataRAW(size)); //Packet contents
|
||||
|
||||
//Handle packet decompression
|
||||
if (protocolVersion >= MC_1_8_Version
|
||||
&& compression_treshold >= 0)
|
||||
{
|
||||
var sizeUncompressed = dataTypes.ReadNextVarInt(packetData);
|
||||
if (sizeUncompressed != 0) // != 0 means compressed, let's decompress
|
||||
{
|
||||
var toDecompress = packetData.ToArray();
|
||||
var uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed);
|
||||
packetData = new Queue<byte>(uncompressed);
|
||||
}
|
||||
}
|
||||
|
||||
var packetId = dataTypes.ReadNextVarInt(packetData); // Packet ID
|
||||
var (packetId, packetData) = socketWrapper.GetNextPacket(
|
||||
protocolVersion >= MC_1_8_Version ? compression_treshold : -1,
|
||||
dataTypes);
|
||||
if (handler.GetNetworkPacketCaptureEnabled())
|
||||
handler.OnNetworkPacket(packetId, packetData.ToList(), currentState == CurrentState.Login, true);
|
||||
|
||||
|
|
@ -420,22 +406,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
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);
|
||||
var (packetId, packetData) = await socketWrapper.GetNextPacketAsync(
|
||||
protocolVersion >= MC_1_8_Version ? compression_treshold : -1,
|
||||
dataTypes,
|
||||
cancellationToken);
|
||||
if (handler.GetNetworkPacketCaptureEnabled())
|
||||
handler.OnNetworkPacket(packetId, packetData.ToList(), currentState == CurrentState.Login, true);
|
||||
|
||||
|
|
@ -3867,21 +3841,17 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the updating thread. Should be called after login success.
|
||||
/// Start the serialized packet/tick tasks. Should be called after login success.
|
||||
/// </summary>
|
||||
private void StartUpdating()
|
||||
{
|
||||
CancellationTokenSource netMainCts = new();
|
||||
netMainCancellationTokenSource = netMainCts;
|
||||
netMainTask = Task.Factory.StartNew(
|
||||
() => Updater(netMainCts.Token),
|
||||
netMainCts.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
netMainTask = Task.Run(() => UpdaterAsync(netMainCts.Token), netMainCts.Token);
|
||||
|
||||
CancellationTokenSource netReaderCts = new();
|
||||
netReaderCancellationTokenSource = netReaderCts;
|
||||
netReaderTask = PacketReaderAsync(netReaderCts.Token);
|
||||
netReaderTask = Task.Run(() => PacketReaderAsync(netReaderCts.Token), netReaderCts.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -4381,14 +4351,8 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
var statusRequest = DataTypes.GetVarInt(0);
|
||||
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(statusRequest.Length), statusRequest));
|
||||
|
||||
// Read Response length
|
||||
var packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper);
|
||||
if (packetLength <= 0)
|
||||
return false;
|
||||
|
||||
// Read the Packet Id
|
||||
var packetData = new Queue<byte>(socketWrapper.ReadDataRAW(packetLength));
|
||||
if (dataTypes.ReadNextVarInt(packetData) != 0x00)
|
||||
var (statusPacketId, packetData) = socketWrapper.GetNextPacket(-1, dataTypes);
|
||||
if (statusPacketId != 0x00)
|
||||
return false;
|
||||
|
||||
// Get the Json data
|
||||
|
|
@ -4467,15 +4431,11 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
var pingRequest = dataTypes.ConcatBytes(DataTypes.GetVarInt(0x01), DataTypes.GetLong(pingPayload));
|
||||
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(pingRequest.Length), pingRequest));
|
||||
|
||||
packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper);
|
||||
if (packetLength > 0)
|
||||
var (pongPacketId, pongPacketData) = socketWrapper.GetNextPacket(-1, dataTypes);
|
||||
if (pongPacketId == 0x01)
|
||||
{
|
||||
packetData = new Queue<byte>(socketWrapper.ReadDataRAW(packetLength));
|
||||
if (dataTypes.ReadNextVarInt(packetData) == 0x01)
|
||||
{
|
||||
long pongPayload = dataTypes.ReadNextLong(packetData);
|
||||
pingMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - pingPayload;
|
||||
}
|
||||
long pongPayload = dataTypes.ReadNextLong(pongPacketData);
|
||||
pingMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - pingPayload;
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Crypto;
|
||||
using MinecraftClient.Protocol.PacketPipeline;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers
|
||||
{
|
||||
|
|
@ -12,9 +15,14 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// </summary>
|
||||
public class SocketWrapper
|
||||
{
|
||||
readonly TcpClient c;
|
||||
AesCfb8Stream? s;
|
||||
bool encrypted = false;
|
||||
private readonly TcpClient client;
|
||||
private readonly Stream networkStream;
|
||||
private readonly SemaphoreSlim sendSemaphore = new(1, 1);
|
||||
private readonly byte[] singleByteBuffer = new byte[1];
|
||||
private AesCfb8Stream? encryptedStream;
|
||||
private Stream readStream;
|
||||
private Stream writeStream;
|
||||
private bool encrypted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new SocketWrapper
|
||||
|
|
@ -22,7 +30,9 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <param name="client">TcpClient connected to the server</param>
|
||||
public SocketWrapper(TcpClient client)
|
||||
{
|
||||
c = client;
|
||||
this.client = client;
|
||||
networkStream = client.GetStream();
|
||||
readStream = writeStream = networkStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,7 +42,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <remarks>Silently dropped connection can only be detected by attempting to read/write data</remarks>
|
||||
public bool IsConnected()
|
||||
{
|
||||
return c.Client is not null && c.Connected;
|
||||
return client.Client is not null && client.Connected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -41,7 +51,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// <returns>TRUE if data is available to read</returns>
|
||||
public bool HasDataAvailable()
|
||||
{
|
||||
return c.Client.Available > 0;
|
||||
return client.Client.Available > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -52,39 +62,21 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
if (encrypted)
|
||||
throw new InvalidOperationException("Stream is already encrypted!?");
|
||||
s = new AesCfb8Stream(c.GetStream(), secretKey);
|
||||
encryptedStream = new AesCfb8Stream(networkStream, secretKey);
|
||||
readStream = writeStream = encryptedStream;
|
||||
encrypted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network reading method. Read bytes from the socket or encrypted socket.
|
||||
/// </summary>
|
||||
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
|
||||
public byte ReadByteRAW()
|
||||
{
|
||||
int read = 0;
|
||||
while (read < offset)
|
||||
{
|
||||
if (encrypted)
|
||||
read += s!.Read(buffer, start + read, offset - read);
|
||||
else
|
||||
read += c.Client.Receive(buffer, start + read, offset - read, f);
|
||||
}
|
||||
readStream.ReadExactly(singleByteBuffer);
|
||||
return singleByteBuffer[0];
|
||||
}
|
||||
|
||||
private async Task ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken)
|
||||
public async ValueTask<byte> ReadByteRAWAsync(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;
|
||||
}
|
||||
await readStream.ReadExactlyAsync(singleByteBuffer.AsMemory(0, 1), cancellationToken);
|
||||
return singleByteBuffer[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -96,8 +88,8 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
if (length > 0)
|
||||
{
|
||||
byte[] cache = new byte[length];
|
||||
Receive(cache, 0, length, SocketFlags.None);
|
||||
byte[] cache = GC.AllocateUninitializedArray<byte>(length);
|
||||
readStream.ReadExactly(cache);
|
||||
return cache;
|
||||
}
|
||||
return Array.Empty<byte>();
|
||||
|
|
@ -107,14 +99,34 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
if (length > 0)
|
||||
{
|
||||
byte[] cache = new byte[length];
|
||||
await ReceiveAsync(cache, cancellationToken);
|
||||
byte[] cache = GC.AllocateUninitializedArray<byte>(length);
|
||||
await readStream.ReadExactlyAsync(cache.AsMemory(0, length), cancellationToken);
|
||||
return cache;
|
||||
}
|
||||
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
internal Tuple<int, Queue<byte>> GetNextPacket(int compressionThreshold, DataTypes dataTypes)
|
||||
{
|
||||
int packetLength = ReadNextVarIntRaw();
|
||||
using PacketReadStream packetStream = new(readStream, packetLength);
|
||||
byte[] payload = ReadPacketPayload(packetStream, compressionThreshold);
|
||||
Queue<byte> packetData = new(payload);
|
||||
int packetId = dataTypes.ReadNextVarInt(packetData);
|
||||
return new(packetId, packetData);
|
||||
}
|
||||
|
||||
internal async Task<Tuple<int, Queue<byte>>> GetNextPacketAsync(int compressionThreshold, DataTypes dataTypes, CancellationToken cancellationToken)
|
||||
{
|
||||
int packetLength = await ReadNextVarIntRawAsync(cancellationToken);
|
||||
await using PacketReadStream packetStream = new(readStream, packetLength);
|
||||
byte[] payload = await ReadPacketPayloadAsync(packetStream, compressionThreshold, cancellationToken);
|
||||
Queue<byte> packetData = new(payload);
|
||||
int packetId = dataTypes.ReadNextVarInt(packetData);
|
||||
return new(packetId, packetData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send raw data to the server.
|
||||
/// </summary>
|
||||
|
|
@ -124,10 +136,16 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (!IsConnected())
|
||||
throw new SocketException((int)SocketError.NotConnected);
|
||||
|
||||
if (encrypted)
|
||||
s!.Write(buffer, 0, buffer.Length);
|
||||
else
|
||||
c.Client.Send(buffer);
|
||||
sendSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
writeStream.Write(buffer, 0, buffer.Length);
|
||||
writeStream.Flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
sendSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendDataRAWAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
|
||||
|
|
@ -135,10 +153,16 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (!IsConnected())
|
||||
throw new SocketException((int)SocketError.NotConnected);
|
||||
|
||||
if (encrypted)
|
||||
await s!.WriteAsync(buffer, cancellationToken);
|
||||
else
|
||||
await c.GetStream().WriteAsync(buffer, cancellationToken);
|
||||
await sendSemaphore.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await writeStream.WriteAsync(buffer, cancellationToken);
|
||||
await writeStream.FlushAsync(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
sendSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -148,12 +172,117 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
try
|
||||
{
|
||||
c.Close();
|
||||
encryptedStream?.Dispose();
|
||||
client.Close();
|
||||
}
|
||||
catch (SocketException) { }
|
||||
catch (System.IO.IOException) { }
|
||||
catch (NullReferenceException) { }
|
||||
catch (ObjectDisposedException) { }
|
||||
}
|
||||
|
||||
private int ReadNextVarIntRaw()
|
||||
{
|
||||
int value = 0;
|
||||
int position = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
byte current = ReadByteRAW();
|
||||
value |= (current & 0x7F) << position++ * 7;
|
||||
if (position > 5)
|
||||
throw new OverflowException("VarInt too big");
|
||||
if ((current & 0x80) != 0x80)
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> ReadNextVarIntRawAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
int value = 0;
|
||||
int position = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
byte current = await ReadByteRAWAsync(cancellationToken);
|
||||
value |= (current & 0x7F) << position++ * 7;
|
||||
if (position > 5)
|
||||
throw new OverflowException("VarInt too big");
|
||||
if ((current & 0x80) != 0x80)
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ReadPacketPayload(PacketReadStream packetStream, int compressionThreshold)
|
||||
{
|
||||
if (compressionThreshold >= 0)
|
||||
{
|
||||
int uncompressedLength = ReadNextVarIntRaw(packetStream);
|
||||
if (uncompressedLength > 0)
|
||||
{
|
||||
using ZLibStream zlibStream = new(packetStream, CompressionMode.Decompress, leaveOpen: true);
|
||||
byte[] payload = GC.AllocateUninitializedArray<byte>(uncompressedLength);
|
||||
zlibStream.ReadExactly(payload);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
return packetStream.ReadRemaining();
|
||||
}
|
||||
|
||||
private static async Task<byte[]> ReadPacketPayloadAsync(PacketReadStream packetStream, int compressionThreshold, CancellationToken cancellationToken)
|
||||
{
|
||||
if (compressionThreshold >= 0)
|
||||
{
|
||||
int uncompressedLength = await ReadNextVarIntRawAsync(packetStream, cancellationToken);
|
||||
if (uncompressedLength > 0)
|
||||
{
|
||||
await using ZLibStream zlibStream = new(packetStream, CompressionMode.Decompress, leaveOpen: true);
|
||||
byte[] payload = GC.AllocateUninitializedArray<byte>(uncompressedLength);
|
||||
await zlibStream.ReadExactlyAsync(payload.AsMemory(0, uncompressedLength), cancellationToken);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
return await packetStream.ReadRemainingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private static int ReadNextVarIntRaw(Stream stream)
|
||||
{
|
||||
int value = 0;
|
||||
int position = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int current = stream.ReadByte();
|
||||
if (current < 0)
|
||||
throw new IOException("Connection closed.");
|
||||
|
||||
value |= (current & 0x7F) << position++ * 7;
|
||||
if (position > 5)
|
||||
throw new OverflowException("VarInt too big");
|
||||
if ((current & 0x80) != 0x80)
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<int> ReadNextVarIntRawAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] buffer = new byte[1];
|
||||
int value = 0;
|
||||
int position = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
await stream.ReadExactlyAsync(buffer.AsMemory(0, 1), cancellationToken);
|
||||
byte current = buffer[0];
|
||||
|
||||
value |= (current & 0x7F) << position++ * 7;
|
||||
if (position > 5)
|
||||
throw new OverflowException("VarInt too big");
|
||||
if ((current & 0x80) != 0x80)
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue