diff --git a/Projects/Server/Buffers/CircularBufferWriter.cs b/Projects/Server/Buffers/CircularBufferWriter.cs index 345a9d8e0..5ad814a52 100644 --- a/Projects/Server/Buffers/CircularBufferWriter.cs +++ b/Projects/Server/Buffers/CircularBufferWriter.cs @@ -479,22 +479,6 @@ namespace System.Buffers Write((byte)0); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - if (Position < _first.Length) - { - _first.Slice(Position).Clear(); - _second.Clear(); - } - else - { - _second.Slice(Position - _first.Length).Clear(); - } - - Position = Length; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear(int amount) { diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs index ae0e3ecd9..b932d3843 100644 --- a/Projects/Server/Buffers/SpanWriter.cs +++ b/Projects/Server/Buffers/SpanWriter.cs @@ -31,7 +31,7 @@ namespace System.Buffers private Span _buffer; private int _position; - public int Length { get; private set; } + public int BytesWritten { get; private set; } public int Position { @@ -40,9 +40,9 @@ namespace System.Buffers { _position = value; - if (value > Length) + if (value > BytesWritten) { - Length = value; + BytesWritten = value; } } } @@ -58,7 +58,7 @@ namespace System.Buffers _resize = resize; _buffer = initialBuffer; _position = 0; - Length = 0; + BytesWritten = 0; _arrayToReturnToPool = null; } @@ -68,16 +68,16 @@ namespace System.Buffers _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); _buffer = _arrayToReturnToPool; _position = 0; - Length = 0; + BytesWritten = 0; } [MethodImpl(MethodImplOptions.NoInlining)] private void Grow(int additionalCapacity) { - var newSize = Math.Max(Length + additionalCapacity, _buffer.Length * 2); + var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2); byte[] poolArray = ArrayPool.Shared.Rent(newSize); - _buffer.Slice(0, Length).CopyTo(poolArray); + _buffer.Slice(0, BytesWritten).CopyTo(poolArray); byte[]? toReturn = _arrayToReturnToPool; _buffer = _arrayToReturnToPool = poolArray; @@ -112,7 +112,7 @@ namespace System.Buffers throw new OutOfMemoryException(); } - Grow(capacity - Length); + Grow(capacity - BytesWritten); } } @@ -161,6 +161,14 @@ namespace System.Buffers Position += 4; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(int value) + { + GrowIfNeeded(4); + BinaryPrimitives.WriteInt32LittleEndian(_buffer.Slice(_position), value); + Position += 4; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(uint value) { @@ -169,6 +177,14 @@ namespace System.Buffers Position += 4; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(uint value) + { + GrowIfNeeded(4); + BinaryPrimitives.WriteUInt32LittleEndian(_buffer.Slice(_position), value); + Position += 4; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(long value) { @@ -317,7 +333,7 @@ namespace System.Buffers var newPosition = Math.Max(0, origin switch { SeekOrigin.Current => _position + offset, - SeekOrigin.End => Length + offset, + SeekOrigin.End => BytesWritten + offset, _ => offset // Begin }); diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index ab823521a..6b9eaa88f 100644 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -32,7 +32,8 @@ namespace Server.Network { public delegate void NetStateCreatedCallback(NetState ns); - public delegate void EncodePacket(ref CircularBuffer buffer, ref int length); + public delegate void DecodePacket(ref CircularBuffer buffer, ref int length); + public delegate void EncodePacket(ReadOnlySpan inputBuffer, ref CircularBuffer outputBuffer, out int length); public partial class NetState : IComparable, IDisposable { @@ -54,7 +55,7 @@ namespace Server.Network private byte[] _sendBuffer; private long m_NextCheckActivity; private volatile bool m_Running; - private volatile EncodePacket _packetDecoder; + private volatile DecodePacket _packetDecoder; private volatile EncodePacket _packetEncoder; internal int m_AuthID; @@ -115,7 +116,7 @@ namespace Server.Network public IPAddress Address { get; } - public EncodePacket PacketDecoder + public DecodePacket PacketDecoder { get => _packetDecoder; set => _packetDecoder = value; @@ -392,8 +393,15 @@ namespace Server.Network try { - buffer.CopyFrom(span); - _packetEncoder?.Invoke(ref buffer, ref length); + if (_packetEncoder != null) + { + _packetEncoder(span, ref buffer, out length); + } + else + { + buffer.CopyFrom(span); + } + SendPipe.Writer.Advance((uint)length); } catch (Exception ex) @@ -418,7 +426,12 @@ namespace Server.Network try { - _packetEncoder?.Invoke(ref buffer, ref length); + // _packetEncoder?.Invoke(ref buffer, ref length); + if (CompressionEnabled) + { + NetworkCompression.Compress(ref buffer, ref length); // This will be changed soon + } + SendPipe.Writer.Advance((uint)length); } catch (Exception ex) diff --git a/Projects/Server/Network/NetworkCompression.cs b/Projects/Server/Network/NetworkCompression.cs index 49cfe1646..f9c438c25 100644 --- a/Projects/Server/Network/NetworkCompression.cs +++ b/Projects/Server/Network/NetworkCompression.cs @@ -125,6 +125,70 @@ namespace Server.Network return outputIdx; } + public static void Compress(ReadOnlySpan input, ref CircularBuffer output, out int length) + { + length = Compress(input, ref output); + } + + public static int Compress(ReadOnlySpan input, ref CircularBuffer output) + { + if (input.Length > DefiniteOverflow) + { + return 0; + } + + int bitCount = 0; + int bitValue = 0; + + int inputIdx = 0; + int outputIdx = 0; + + while (inputIdx < input.Length) + { + int i = input[inputIdx++] << 1; + + bitCount += _huffmanTable[i]; + bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; + + while (bitCount >= 8) + { + bitCount -= 8; + + if (output.Length < outputIdx + 1) + { + return 0; + } + + output[outputIdx++] = (byte)(bitValue >> bitCount); + } + } + + // terminal code + bitCount += _huffmanTable[0x200]; + bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; + + // align on byte boundary + if ((bitCount & 7) != 0) + { + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); + } + + while (bitCount >= 8) + { + bitCount -= 8; + + if (output.Length < outputIdx + 1) + { + return 0; + } + + output[outputIdx++] = (byte)(bitValue >> bitCount); + } + + return outputIdx; + } + public static int Compress(ReadOnlySpan input, Span output) { if (input.Length > DefiniteOverflow) diff --git a/Projects/Server/Network/Packets/IncomingAccountPackets.cs b/Projects/Server/Network/Packets/IncomingAccountPackets.cs index 23d9d4dd6..50a44bb47 100644 --- a/Projects/Server/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/Server/Network/Packets/IncomingAccountPackets.cs @@ -417,6 +417,8 @@ namespace Server.Network if (e.Accepted) { state.CityInfo = e.CityInfo; + + // Comment out these lines to turn off huffman compression state.CompressionEnabled = true; state.PacketEncoder = NetworkCompression.Compress; diff --git a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs index 7ec723d7c..4093eba55 100644 --- a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs @@ -61,13 +61,13 @@ namespace Server.Network */ public static void SendChangeCharacter(this NetState ns, IAccount a) { - if (ns == null || a == null || !ns.GetSendBuffer(out var buffer)) + if (ns == null || a == null) { return; } var length = 5 + a.Length * 60; - var writer = new CircularBufferWriter(buffer); + var writer = new SpanWriter(stackalloc byte[length]); writer.Write((byte)0x81); // Packet ID writer.Write((ushort)length); @@ -98,7 +98,7 @@ namespace Server.Network writer.Write((byte)count); writer.Seek(position, SeekOrigin.Begin); - ns.Send(ref buffer, writer.Position); + ns.Send(writer.Span); } /** @@ -107,17 +107,7 @@ namespace Server.Network * * Sends a requests for the client version */ - public static void SendClientVersionRequest(this NetState ns) - { - if (ns != null && ns.GetSendBuffer(out var buffer)) - { - buffer[0] = 0xBD; // Packet ID - buffer[1] = 0x00; - buffer[2] = 0x03; // Length - - ns.Send(ref buffer, 3); - } - } + public static void SendClientVersionRequest(this NetState ns) => ns?.Send(stackalloc byte[] { 0xBD, 0x00, 0x03 }); /** * Packet: 0x85 @@ -125,16 +115,8 @@ namespace Server.Network * * Sends the result of a deletion request */ - public static void SendCharacterDeleteResult(this NetState ns, DeleteResultType res) - { - if (ns != null && ns.GetSendBuffer(out var buffer)) - { - buffer[0] = 0x85; // Packet ID - buffer[1] = (byte)res; - - ns.Send(ref buffer, 2); - } - } + public static void SendCharacterDeleteResult(this NetState ns, DeleteResultType res) => + ns?.Send(stackalloc byte[] { 0x85, (byte)res }); /** * Packet: 0x53 @@ -142,16 +124,8 @@ namespace Server.Network * * Sends a PopupMessage with a predetermined message */ - public static void SendPopupMessage(this NetState ns, PMMessage msg) - { - if (ns != null && ns.GetSendBuffer(out var buffer)) - { - buffer[0] = 0x53; // Packet ID - buffer[1] = (byte)msg; - - ns.Send(ref buffer, 2); - } - } + public static void SendPopupMessage(this NetState ns, PMMessage msg) => + ns?.Send(stackalloc byte[] { 0x53, (byte)msg }); /** * Packet: 0xB9 @@ -161,7 +135,7 @@ namespace Server.Network */ public static void SendSupportedFeature(this NetState ns) { - if (ns == null || !ns.GetSendBuffer(out var buffer)) + if (ns == null) { return; } @@ -183,7 +157,8 @@ namespace Server.Network } } - var writer = new CircularBufferWriter(buffer); + var length = ns.ExtendedSupportedFeatures ? 5 : 3; + var writer = new SpanWriter(stackalloc byte[length]); writer.Write((byte)0xB9); // Packet ID if (ns.ExtendedSupportedFeatures) @@ -195,7 +170,7 @@ namespace Server.Network writer.Write((ushort)flags); } - ns.Send(ref buffer, ns.ExtendedSupportedFeatures ? 5 : 3); + ns.Send(writer.Span); } /** @@ -206,12 +181,12 @@ namespace Server.Network */ public static void SendLoginConfirmation(this NetState ns, Mobile m) { - if (ns == null || !ns.GetSendBuffer(out var buffer)) + if (ns == null) { return; } - var writer = new CircularBufferWriter(buffer); + var writer = new SpanWriter(stackalloc byte[37]); writer.Write((byte)0x1B); // PacketID writer.Write(m.Serial); writer.Write(0); @@ -234,9 +209,9 @@ namespace Server.Network writer.Write((short)(map?.Width ?? Map.Felucca.Width)); writer.Write((short)(map?.Height ?? Map.Felucca.Height)); - writer.Clear(); + writer.Clear(writer.Capacity - writer.Position); // Remaining is zero - ns.Send(ref buffer, 37); + ns.Send(writer.Span); } /** @@ -247,12 +222,7 @@ namespace Server.Network */ public static void SendLoginComplete(this NetState ns) { - if (ns != null && ns.GetSendBuffer(out var buffer)) - { - buffer[0] = 0x55; // Packet ID - - ns.Send(ref buffer, 1); - } + ns?.Send(stackalloc byte[] { 0x55 }); } /** @@ -268,10 +238,6 @@ namespace Server.Network return; } - var writer = new CircularBufferWriter(buffer); - writer.Write((byte)0x86); // Packet ID - writer.Seek(2, SeekOrigin.Current); // Length - var highSlot = -1; for (var i = a.Length - 1; i >= 0; i--) @@ -284,6 +250,11 @@ namespace Server.Network } var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); + var length = 4 + count * 60; + var writer = new SpanWriter(stackalloc byte[length]); + writer.Write((byte)0x86); // Packet ID + writer.Write((ushort)length); + writer.Write((byte)count); for (int i = 0; i < count; i++) @@ -302,8 +273,7 @@ namespace Server.Network } } - writer.WritePacketLength(); - ns.Send(ref buffer, writer.Position); + ns.Send(writer.Span); } /** @@ -316,7 +286,7 @@ namespace Server.Network { var acct = ns?.Account; - if (acct == null || !ns.GetSendBuffer(out var buffer)) + if (acct == null) { return; } @@ -326,10 +296,6 @@ namespace Server.Network var cityInfo = ns.CityInfo; - var writer = new CircularBufferWriter(buffer); - writer.Write((byte)0xA9); // Packet ID - writer.Seek(2, SeekOrigin.Current); // Length - var highSlot = -1; for (var i = acct.Length - 1; i >= 0; i--) @@ -342,6 +308,12 @@ namespace Server.Network } var count = Math.Max(Math.Max(highSlot + 1, acct.Limit), 5); + var length = (client70130 ? + 11 + (textLength * 2 + 25) * cityInfo.Length : + 9 + (textLength * 2 + 1) * cityInfo.Length) + count * 60; + var writer = new SpanWriter(stackalloc byte[length]); + writer.Write((byte)0xA9); // Packet ID + writer.Write((ushort)length); writer.Write((byte)count); for (int i = 0; i < count; i++) @@ -403,8 +375,7 @@ namespace Server.Network writer.Write((short)-1); } - writer.WritePacketLength(); - ns.Send(ref buffer, writer.Position); + ns.Send(writer.Span); } /** @@ -413,37 +384,28 @@ namespace Server.Network * * Sends a reason for rejecting the login */ - public static void SendAccountLoginRejected(this NetState ns, ALRReason reason) - { - if (ns != null && ns.GetSendBuffer(out var buffer)) - { - buffer[0] = 0x82; // Packet ID - buffer[1] = (byte)reason; - - ns.Send(ref buffer, 2); - } - } + public static void SendAccountLoginRejected(this NetState ns, ALRReason reason) => + ns?.Send(stackalloc byte[] { 0x82, (byte)reason }); /** * Packet: 0xA8 - * Length: up to 240 bytes + * Length: 6 + 40 bytes per server listing * * Sends login acknowledge with server listing */ public static void SendAccountLoginAck(this NetState ns) { - if (ns == null || !ns.GetSendBuffer(out var buffer)) + if (ns == null) { return; } - var writer = new CircularBufferWriter(buffer); - writer.Write((byte)0xA8); // Packet ID - writer.Seek(2, SeekOrigin.Current); // Length - - writer.Write((byte)0x5D); - var info = ns.ServerInfo; + var length = 6 + 40 * info.Length; + var writer = new SpanWriter(stackalloc byte[length]); + writer.Write((byte)0xA8); // Packet ID + writer.Write((ushort)length); + writer.Write((byte)0x5D); writer.Write((ushort)info.Length); for (var i = 0; i < info.Length; ++i) @@ -458,8 +420,7 @@ namespace Server.Network writer.Write(si.RawAddress); } - writer.WritePacketLength(); - ns.Send(ref buffer, writer.Position); + ns.Send(writer.Span); } /** @@ -470,19 +431,19 @@ namespace Server.Network */ public static void SendPlayServerAck(this NetState ns, ServerInfo si, int authId) { - if (ns == null || !ns.GetSendBuffer(out var buffer)) + if (ns == null) { return; } - var writer = new CircularBufferWriter(buffer); + var writer = new SpanWriter(stackalloc byte[11]); writer.Write((byte)0x8C); // Packet ID writer.WriteLE(si.RawAddress); writer.Write((short)si.Address.Port); writer.Write(authId); - ns.Send(ref buffer, writer.Position); + ns.Send(writer.Span); } } } diff --git a/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs b/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs index bedb1bb5e..2d9a5f61a 100644 --- a/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs +++ b/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs @@ -28,12 +28,12 @@ namespace Server.Network public static void SendArrow(this NetState ns, byte command, int x, int y, Serial s) { - if (ns == null || !ns.GetSendBuffer(out var buffer)) + if (ns == null) { return; } - var writer = new CircularBufferWriter(buffer); + var writer = new SpanWriter(stackalloc byte[10]); writer.Write((byte)0xBA); // Packet ID writer.Write(command); @@ -55,7 +55,7 @@ namespace Server.Network writer.Write((short)-1); } - ns.Send(ref buffer, writer.Position); + ns.Send(writer.Span); } } }