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; namespace MinecraftClient.Protocol.PacketPipeline { /// /// Wrapper for handling unencrypted & encrypted socket /// class SocketWrapper { private TcpClient tcpClient; private AesStream? AesStream; private PacketStream? packetStream = null; private Stream ReadStream, WriteStream; private bool Encrypted = false; public int CompressionThreshold { get; set; } = 0; private SemaphoreSlim SendSemaphore = new SemaphoreSlim(1, 1); private Task LastSendTask = Task.CompletedTask; /// /// Initialize a new SocketWrapper /// /// TcpClient connected to the server public SocketWrapper(TcpClient client) { tcpClient = client; ReadStream = WriteStream = client.GetStream(); } /// /// Check if the socket is still connected /// /// TRUE if still connected /// Silently dropped connection can only be detected by attempting to read/write data public bool IsConnected() { return tcpClient.Client != null && tcpClient.Connected; } /// /// Check if the socket has data available to read /// /// TRUE if data is available to read public bool HasDataAvailable() { return tcpClient.Client.Available > 0; } /// /// Switch network reading/writing to an encrypted stream /// /// AES secret key public void SwitchToEncrypted(byte[] secretKey) { if (Encrypted) throw new InvalidOperationException("Stream is already encrypted!?"); Encrypted = true; ReadStream = WriteStream = AesStream = new AesStream(tcpClient.Client, secretKey); } /// /// Send raw data to the server. /// /// data to send public async Task SendAsync(Memory buffer, CancellationToken cancellationToken = default) { await SendSemaphore.WaitAsync(); await LastSendTask; LastSendTask = WriteStream.WriteAsync(buffer, cancellationToken).AsTask(); SendSemaphore.Release(); } public async Task> GetNextPacket(bool handleCompress, CancellationToken cancellationToken = default) { // ConsoleIO.WriteLine("GetNextPacket"); if (packetStream != null) { await packetStream.DisposeAsync(); packetStream = null; } int readed = 0; (int packetSize, _) = await ReceiveVarIntRaw(ReadStream, cancellationToken); int packetID; if (handleCompress && CompressionThreshold > 0) { (int sizeUncompressed, readed) = await ReceiveVarIntRaw(ReadStream, cancellationToken); if (sizeUncompressed != 0) { ZlibBaseStream zlibBaseStream = new(AesStream ?? ReadStream, packetSize: packetSize - readed); ZLibStream zlibStream = new(zlibBaseStream, CompressionMode.Decompress, leaveOpen: false); zlibBaseStream.BufferSize = 16; (packetID, readed) = await ReceiveVarIntRaw(zlibStream, cancellationToken); zlibBaseStream.BufferSize = 512; // ConsoleIO.WriteLine("packetID = " + packetID + ", readed = " + zlibBaseStream.packetReaded + ", size = " + packetSize + " -> " + sizeUncompressed); packetStream = new(zlibStream, sizeUncompressed - readed, cancellationToken); return new(packetID, packetStream); } } (packetID, int readed2) = await ReceiveVarIntRaw(ReadStream, cancellationToken); packetStream = new(AesStream ?? ReadStream, packetSize - readed - readed2, cancellationToken); return new(packetID, packetStream); } private async Task> ReceiveVarIntRaw(Stream stream, CancellationToken cancellationToken = default) { int i = 0; int j = 0; byte[] b = new byte[1]; while (true) { await stream.ReadAsync(b); i |= (b[0] & 0x7F) << j++ * 7; if (j > 5) throw new OverflowException("VarInt too big"); if ((b[0] & 0x80) != 128) break; } return new(i, j); } /// /// Disconnect from the server /// public void Disconnect() { try { tcpClient.Close(); } catch (SocketException) { } catch (IOException) { } catch (NullReferenceException) { } catch (ObjectDisposedException) { } } } }