From 7603ef2a346fc8e1b4717efa1ec5efbd17b7b524 Mon Sep 17 00:00:00 2001 From: squid Date: Fri, 9 May 2025 20:43:33 +0200 Subject: [PATCH 1/9] feat(Program.cs): add ISerialGeneratorService to service collection for generating serials feat(IEntity.cs, IHaveSerial.cs, IHued.cs): create new interfaces for entities with specific properties feat(ISerialGeneratorService.cs, SerialGeneratorService.cs): add service and implementation for generating serials for entities --- src/Prima.Server/Program.cs | 1 + .../Interfaces/Entities/IEntity.cs | 12 +++++++++ .../Interfaces/Entities/IHaveSerial.cs | 8 ++++++ src/Prima.UOData/Interfaces/Entities/IHued.cs | 6 +++++ .../Services/ISerialGeneratorService.cs | 11 ++++++++ .../Services/SerialGeneratorService.cs | 25 +++++++++++++++++++ 6 files changed, 63 insertions(+) create mode 100644 src/Prima.UOData/Interfaces/Entities/IEntity.cs create mode 100644 src/Prima.UOData/Interfaces/Entities/IHaveSerial.cs create mode 100644 src/Prima.UOData/Interfaces/Entities/IHued.cs create mode 100644 src/Prima.UOData/Interfaces/Services/ISerialGeneratorService.cs create mode 100644 src/Prima.UOData/Services/SerialGeneratorService.cs diff --git a/src/Prima.Server/Program.cs b/src/Prima.Server/Program.cs index fbcd00e..a633731 100644 --- a/src/Prima.Server/Program.cs +++ b/src/Prima.Server/Program.cs @@ -102,6 +102,7 @@ static async Task Main(string[] args) builder.Services .AddService() .AddService() + .AddService() .AddService() ; diff --git a/src/Prima.UOData/Interfaces/Entities/IEntity.cs b/src/Prima.UOData/Interfaces/Entities/IEntity.cs new file mode 100644 index 0000000..c9b42e3 --- /dev/null +++ b/src/Prima.UOData/Interfaces/Entities/IEntity.cs @@ -0,0 +1,12 @@ +using Prima.UOData.Data.Geometry; + +namespace Prima.UOData.Interfaces.Entities; + +public interface IEntity +{ + Point3D Location { get; set; } + + bool InRange(Point2D p, int range); + + bool InRange(Point3D p, int range); +} diff --git a/src/Prima.UOData/Interfaces/Entities/IHaveSerial.cs b/src/Prima.UOData/Interfaces/Entities/IHaveSerial.cs new file mode 100644 index 0000000..104148e --- /dev/null +++ b/src/Prima.UOData/Interfaces/Entities/IHaveSerial.cs @@ -0,0 +1,8 @@ +using Prima.UOData.Id; + +namespace Prima.UOData.Interfaces.Entities; + +public interface IHaveSerial +{ + Serial Serial { get; set; } +} diff --git a/src/Prima.UOData/Interfaces/Entities/IHued.cs b/src/Prima.UOData/Interfaces/Entities/IHued.cs new file mode 100644 index 0000000..92864ac --- /dev/null +++ b/src/Prima.UOData/Interfaces/Entities/IHued.cs @@ -0,0 +1,6 @@ +namespace Prima.UOData.Interfaces.Entities; + +public interface IHued +{ + int HuedItemID { get; } +} diff --git a/src/Prima.UOData/Interfaces/Services/ISerialGeneratorService.cs b/src/Prima.UOData/Interfaces/Services/ISerialGeneratorService.cs new file mode 100644 index 0000000..e76b13c --- /dev/null +++ b/src/Prima.UOData/Interfaces/Services/ISerialGeneratorService.cs @@ -0,0 +1,11 @@ +using Prima.UOData.Id; + +namespace Prima.UOData.Interfaces.Services; + +public interface ISerialGeneratorService +{ + Serial GenerateSerial(); + + void GenerateSerial(TEntity entity); + +} diff --git a/src/Prima.UOData/Services/SerialGeneratorService.cs b/src/Prima.UOData/Services/SerialGeneratorService.cs new file mode 100644 index 0000000..9e2d6bd --- /dev/null +++ b/src/Prima.UOData/Services/SerialGeneratorService.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Logging; +using Prima.UOData.Id; +using Prima.UOData.Interfaces.Services; + +namespace Prima.UOData.Services; + +public class SerialGeneratorService : ISerialGeneratorService +{ + private readonly ILogger _logger; + + public SerialGeneratorService(ILogger logger) + { + _logger = logger; + } + + public Serial GenerateSerial() + { + throw new NotImplementedException(); + } + + public void GenerateSerial(TEntity entity) + { + throw new NotImplementedException(); + } +} From 4f64012a875a85781ba9db6284e9e1846bba1dad Mon Sep 17 00:00:00 2001 From: squid Date: Fri, 9 May 2025 20:45:56 +0200 Subject: [PATCH 2/9] feat(SerialProgressionEntity.cs): create a new entity class SerialProgressionEntity feat(SerialGeneratorService.cs): update GenerateSerial method to return Serial.Zero instead of throwing NotImplementedException refactor(SerialGeneratorService.cs): remove unnecessary NotImplementedException in GenerateSerial method --- src/Prima.UOData/Entities/SerialProgressionEntity.cs | 10 ++++++++++ src/Prima.UOData/Services/SerialGeneratorService.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/Prima.UOData/Entities/SerialProgressionEntity.cs diff --git a/src/Prima.UOData/Entities/SerialProgressionEntity.cs b/src/Prima.UOData/Entities/SerialProgressionEntity.cs new file mode 100644 index 0000000..bba78e2 --- /dev/null +++ b/src/Prima.UOData/Entities/SerialProgressionEntity.cs @@ -0,0 +1,10 @@ +using Prima.Core.Server.Entities.Base; + +namespace Prima.UOData.Entities; + +public class SerialProgressionEntity : BaseDbEntity +{ + public string SerialType { get; set; } + + public int LastSerial { get; set; } +} diff --git a/src/Prima.UOData/Services/SerialGeneratorService.cs b/src/Prima.UOData/Services/SerialGeneratorService.cs index 9e2d6bd..463a71a 100644 --- a/src/Prima.UOData/Services/SerialGeneratorService.cs +++ b/src/Prima.UOData/Services/SerialGeneratorService.cs @@ -15,11 +15,11 @@ public SerialGeneratorService(ILogger logger) public Serial GenerateSerial() { - throw new NotImplementedException(); + return Serial.Zero; } public void GenerateSerial(TEntity entity) { - throw new NotImplementedException(); + } } From b86352f1f29a927abf28f86cb7bbeedcc13e328d Mon Sep 17 00:00:00 2001 From: squid Date: Sat, 10 May 2025 16:58:02 +0200 Subject: [PATCH 3/9] refactor(PacketReader.cs): Remove unused using directives and comments, clean up code formatting, and improve code readability by removing unnecessary comments and empty lines. refactor(PacketWriter.cs): Remove unused using directives and comments, clean up code formatting, and improve code readability by removing unnecessary comments and empty lines. --- src/Prima.Network/Serializers/PacketReader.cs | 385 --------------- src/Prima.Network/Serializers/PacketWriter.cs | 449 ------------------ 2 files changed, 834 deletions(-) delete mode 100644 src/Prima.Network/Serializers/PacketReader.cs delete mode 100644 src/Prima.Network/Serializers/PacketWriter.cs diff --git a/src/Prima.Network/Serializers/PacketReader.cs b/src/Prima.Network/Serializers/PacketReader.cs deleted file mode 100644 index e7ff6fc..0000000 --- a/src/Prima.Network/Serializers/PacketReader.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System.Text; - -namespace Prima.Network.Serializers; - -/// -/// A modern, optimized packet reader for processing network packets. -/// Provides methods to read various data types from a byte buffer with proper boundary checking. -/// -public class PacketReader : IDisposable -{ - private byte[] _buffer; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - /// The byte array containing packet data. - /// The size of the packet data. - /// Whether the packet has a fixed size header. - public PacketReader() - { - } - - - public void Initialize(byte[] data, int size, bool fixedSize) - { - _buffer = data ?? throw new ArgumentNullException(nameof(data)); - Size = Math.Min(size, data.Length); - Position = fixedSize ? 1 : 3; - } - - /// - /// Gets the underlying buffer. - /// - public ReadOnlySpan Buffer => _buffer; - - /// - /// Gets the size of the packet. - /// - public int Size { get; private set; } - - /// - /// Gets the current position in the buffer. - /// - public int Position { get; private set; } - - /// - /// Gets the number of bytes available to read. - /// - public int Available => Size - Position; - - /// - /// Disposes the resources used by the packet reader. - /// - public void Dispose() - { - if (!_disposed) - { - _disposed = true; - GC.SuppressFinalize(this); - } - } - - /// - /// Sets the read position to the specified position. - /// - /// The position offset. - /// The reference point for the offset. - /// The new position. - public int Seek(int offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - Position = offset; - break; - case SeekOrigin.Current: - Position += offset; - break; - case SeekOrigin.End: - Position = Size - offset; - break; - } - - // Ensure position stays within bounds - Position = Math.Clamp(Position, 0, Size); - return Position; - } - - /// - /// Reads a 32-bit signed integer in network byte order (big endian). - /// - /// The 32-bit signed integer value. - public int ReadInt32() - { - if (Position + 4 > Size) - { - return 0; - } - - var value = (_buffer[Position] << 24) | - (_buffer[Position + 1] << 16) | - (_buffer[Position + 2] << 8) | - _buffer[Position + 3]; - - Position += 4; - return value; - } - - /// - /// Reads a 16-bit signed integer in network byte order (big endian). - /// - /// The 16-bit signed integer value. - public short ReadInt16() - { - if (Position + 2 > Size) - { - return 0; - } - - var value = (short)((_buffer[Position] << 8) | _buffer[Position + 1]); - Position += 2; - return value; - } - - /// - /// Reads an 8-bit unsigned integer. - /// - /// The 8-bit unsigned integer value. - public byte ReadByte() - { - if (Position + 1 > Size) - { - return 0; - } - - return _buffer[Position++]; - } - - /// - /// Reads a 32-bit unsigned integer in network byte order (big endian). - /// - /// The 32-bit unsigned integer value. - public uint ReadUInt32() - { - if (Position + 4 > Size) - { - return 0; - } - - var value = (uint)((_buffer[Position] << 24) | - (_buffer[Position + 1] << 16) | - (_buffer[Position + 2] << 8) | - _buffer[Position + 3]); - - Position += 4; - return value; - } - - /// - /// Reads a 16-bit unsigned integer in network byte order (big endian). - /// - /// The 16-bit unsigned integer value. - public ushort ReadUInt16() - { - if (Position + 2 > Size) - { - return 0; - } - - var value = (ushort)((_buffer[Position] << 8) | _buffer[Position + 1]); - Position += 2; - return value; - } - - /// - /// Reads a 16-bit unsigned integer in big-endian format. - /// - /// The 16-bit unsigned integer value. - public ushort ReadUInt16BE() - { - return ReadUInt16(); - } - - /// - /// Reads a 32-bit unsigned integer in big-endian format. - /// - /// The 32-bit unsigned integer value. - public uint ReadUInt32BE() - { - return ReadUInt32(); - } - - /// - /// Reads an 8-bit signed integer. - /// - /// The 8-bit signed integer value. - public sbyte ReadSByte() - { - if (Position + 1 > Size) - { - return 0; - } - - return (sbyte)_buffer[Position++]; - } - - /// - /// Reads a Boolean value. - /// - /// The Boolean value. - public bool ReadBoolean() - { - if (Position + 1 > Size) - { - return false; - } - - return _buffer[Position++] != 0; - } - - /// - /// Reads a specified number of bytes from the buffer. - /// - /// The number of bytes to read. - /// The byte array containing the read bytes. - public byte[] ReadBytes(int length) - { - if (length <= 0) - { - return Array.Empty(); - } - - var available = Math.Min(length, Size - Position); - - if (available <= 0) - { - return Array.Empty(); - } - - var result = new byte[available]; - _buffer.AsSpan(Position, available).CopyTo(result); - Position += available; - - return result; - } - - /// - /// Reads a string in ASCII format of fixed length. - /// - /// The fixed length of the string. - /// The string read from the buffer. - public string ReadFixedString(int length) - { - if (length <= 0 || Position >= Size) - { - return string.Empty; - } - - var available = Math.Min(length, Size - Position); - if (available <= 0) - { - return string.Empty; - } - - // Find the actual string length (up to null terminator) - var actualLength = 0; - for (var i = 0; i < available; i++) - { - if (_buffer[Position + i] == 0) - { - break; - } - - actualLength++; - } - - // Read the string characters - var result = Encoding.ASCII.GetString(_buffer, Position, actualLength); - Position += length; // Always advance by the full length - - return result; - } - - /// - /// Reads a null-terminated string from the current position. - /// - /// The string read from the buffer. - public string ReadString() - { - if (Position >= Size) - { - return string.Empty; - } - - // Find the null terminator - var end = Position; - while (end < Size && _buffer[end] != 0) - { - end++; - } - - var length = end - Position; - if (length <= 0) - { - // Skip the null terminator - if (end < Size) - { - Position = end + 1; - } - - return string.Empty; - } - - var result = Encoding.ASCII.GetString(_buffer, Position, length); - Position = end + 1; // Skip the null terminator - - return result; - } - - /// - /// Reads an enumeration value. - /// - /// The enumeration type. - /// The enumeration value. - public T ReadEnum() where T : Enum - { - var value = ReadByte(); - return (T)Enum.ToObject(typeof(T), value); - } - - /// - /// Reads a null-terminated Unicode string in little-endian format. - /// - /// The Unicode string. - public string ReadUnicodeStringLE() - { - if (Position + 1 >= Size) - { - return string.Empty; - } - - StringBuilder sb = new(); - - while (Position + 1 < Size) - { - var c = _buffer[Position++] | (_buffer[Position++] << 8); - if (c == 0) - { - break; - } - - sb.Append((char)c); - } - - return sb.ToString(); - } - - /// - /// Reads a null-terminated Unicode string. - /// - /// The Unicode string. - public string ReadUnicodeString() - { - if (Position + 1 >= Size) - { - return string.Empty; - } - - StringBuilder sb = new(); - - while (Position + 1 < Size) - { - var c = (_buffer[Position++] << 8) | _buffer[Position++]; - if (c == 0) - { - break; - } - - sb.Append((char)c); - } - - return sb.ToString(); - } -} diff --git a/src/Prima.Network/Serializers/PacketWriter.cs b/src/Prima.Network/Serializers/PacketWriter.cs deleted file mode 100644 index 9a98b9c..0000000 --- a/src/Prima.Network/Serializers/PacketWriter.cs +++ /dev/null @@ -1,449 +0,0 @@ -using System.Buffers.Binary; -using System.Net; - -namespace Prima.Network.Serializers; - -using System; -using System.IO; -using System.Text; - -/// -/// Provides functionality for writing primitive binary data for network packets. -/// -public sealed class PacketWriter : IDisposable -{ - private readonly byte[] _buffer = new byte[4]; - private readonly int _capacity; - private bool _disposed; - - - /// - /// Instantiates a new PacketWriter instance with the default capacity of 32 bytes. - /// - public PacketWriter() : this(32) - { - } - - /// - /// Instantiates a new PacketWriter instance with a given capacity. - /// - /// Initial capacity for the internal stream. - public PacketWriter(int capacity) - { - UnderlyingStream = new MemoryStream(capacity); - _capacity = capacity; - UnderlyingStream.SetLength(0); - } - - /// - /// Gets the total stream length. - /// - public long Length => UnderlyingStream.Length; - - /// - /// Gets or sets the current stream position. - /// - public long Position - { - get => UnderlyingStream.Position; - set => UnderlyingStream.Position = value; - } - - /// - /// The internal stream used by this PacketWriter instance. - /// - public MemoryStream UnderlyingStream { get; } - - /// - /// Writes a 1-byte boolean value to the underlying stream. False is represented by 0, true by 1. - /// - public void Write(bool value) - { - UnderlyingStream.WriteByte((byte)(value ? 1 : 0)); - } - - /// - /// Writes a 1-byte unsigned integer value to the underlying stream. - /// - /// The byte value to write. - public void Write(byte value) - { - UnderlyingStream.WriteByte(value); - } - - /// - /// Writes a 1-byte unsigned integer value to the underlying stream. - /// - /// The byte value to write. - public void WriteByte(byte value) - { - Write(value); - } - - /// - /// Writes a 1-byte signed integer value to the underlying stream. - /// - /// The signed byte value to write. - public void Write(sbyte value) - { - UnderlyingStream.WriteByte((byte)value); - } - - /// - /// Writes a 2-byte signed integer value to the underlying stream in big-endian format. - /// - /// The signed short value to write. - public void Write(short value) - { - _buffer[0] = (byte)(value >> 8); - _buffer[1] = (byte)value; - - UnderlyingStream.Write(_buffer, 0, 2); - } - - /// - /// Writes a 2-byte unsigned integer value to the underlying stream in big-endian format. - /// - /// The unsigned short value to write. - public void Write(ushort value) - { - _buffer[0] = (byte)(value >> 8); - _buffer[1] = (byte)value; - - UnderlyingStream.Write(_buffer, 0, 2); - } - - - /// - /// Writes a 4-byte signed integer value to the underlying stream in big-endian format. - /// - /// The signed integer value to write. - public void Write(int value) - { - _buffer[0] = (byte)(value >> 24); - _buffer[1] = (byte)(value >> 16); - _buffer[2] = (byte)(value >> 8); - _buffer[3] = (byte)value; - - UnderlyingStream.Write(_buffer, 0, 4); - } - - /// - /// Writes a 4-byte unsigned integer value to the underlying stream in big-endian format. - /// - /// The unsigned integer value to write. - public void Write(uint value) - { - _buffer[0] = (byte)(value >> 24); - _buffer[1] = (byte)(value >> 16); - _buffer[2] = (byte)(value >> 8); - _buffer[3] = (byte)value; - - UnderlyingStream.Write(_buffer, 0, 4); - } - - - /// - /// Writes a sequence of bytes to the underlying stream - /// - public void Write(ReadOnlySpan buffer) - { - UnderlyingStream.Write(buffer); - } - - /// - /// Writes a sequence of bytes to the underlying stream. - /// - /// The byte array containing data to write. - /// The starting position in the buffer. - /// The number of bytes to write. - public void Write(byte[] buffer, int offset, int size) - { - UnderlyingStream.Write(buffer, offset, size); - } - - /// - /// Writes a byte array to the underlying stream. - /// - /// The byte array to write. - public void Write(byte[] buffer) - { - if (buffer == null || buffer.Length == 0) - return; - - UnderlyingStream.Write(buffer, 0, buffer.Length); - } - - public void WriteIpAddress(IPAddress ipAddress) - { - //var ipBytes = ipAddress.GetAddressBytes(); - // Array.Reverse(ipBytes); - // Write(ipBytes); - Span integer = stackalloc byte[4]; - ipAddress.MapToIPv4().TryWriteBytes(integer, out var bytesWritten); - if (bytesWritten != 4) - { - throw new InvalidOperationException("IP Address could not be serialized to an integer"); - } - - var ipBytes = BinaryPrimitives.ReadUInt32LittleEndian(integer); - - Write(ipBytes); - } - - /// - /// Writes a fixed-length ASCII-encoded string value to the underlying stream. To fit (size), the string content is either - /// truncated or padded with null characters. - /// - public void WriteAsciiFixed(string value, int size) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteAsciiFixed() with null value"); - value = string.Empty; - } - - var length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + size); - - if (length >= size) - { - UnderlyingStream.Position += - Encoding.ASCII.GetBytes(value, 0, size, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - } - else - { - Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += size; - } - } - - - /// - /// Writes a dynamic-length ASCII-encoded string value to the underlying stream, followed by a 1-byte null character. - /// - /// The string value to write. - public void WriteAsciiNull(string value) - { - if (value == null) - { - value = string.Empty; - } - - int length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + length + 1); - - Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += length + 1; - } - - /// - /// Writes a dynamic-length little-endian unicode string value to the underlying stream, followed by a 2-byte null character. - /// - /// The string value to write. - public void WriteLittleUniNull(string value) - { - if (value == null) - { - value = string.Empty; - } - - int length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + ((length + 1) * 2)); - - UnderlyingStream.Position += Encoding.Unicode.GetBytes( - value, - 0, - length, - UnderlyingStream.GetBuffer(), - (int)UnderlyingStream.Position - ); - UnderlyingStream.Position += 2; - } - - /// - /// Writes a fixed-length little-endian unicode string value to the underlying stream. - /// To fit (size), the string content is either truncated or padded with null characters. - /// - /// The string value to write. - /// The fixed size (in characters) to write. - public void WriteLittleUniFixed(string value, int size) - { - if (value == null) - { - value = string.Empty; - } - - size *= 2; - - int length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + size); - - if ((length * 2) >= size) - UnderlyingStream.Position += Encoding.Unicode.GetBytes( - value, - 0, - length, - UnderlyingStream.GetBuffer(), - (int)UnderlyingStream.Position - ); - else - { - Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += size; - } - } - - /// - /// Writes a dynamic-length big-endian unicode string value to the underlying stream, followed by a 2-byte null character. - /// - /// The string value to write. - public void WriteBigUniNull(string value) - { - if (value == null) - { - value = string.Empty; - } - - int length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + ((length + 1) * 2)); - - UnderlyingStream.Position += Encoding.BigEndianUnicode.GetBytes( - value, - 0, - length, - UnderlyingStream.GetBuffer(), - (int)UnderlyingStream.Position - ); - UnderlyingStream.Position += 2; - } - - /// - /// Writes a fixed-length big-endian unicode string value to the underlying stream. - /// To fit (size), the string content is either truncated or padded with null characters. - /// - /// The string value to write. - /// The fixed size (in characters) to write. - public void WriteBigUniFixed(string value, int size) - { - if (value == null) - { - value = string.Empty; - } - - size *= 2; - - int length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + size); - - if ((length * 2) >= size) - UnderlyingStream.Position += Encoding.BigEndianUnicode.GetBytes( - value, - 0, - length, - UnderlyingStream.GetBuffer(), - (int)UnderlyingStream.Position - ); - else - { - Encoding.BigEndianUnicode.GetBytes( - value, - 0, - length, - UnderlyingStream.GetBuffer(), - (int)UnderlyingStream.Position - ); - UnderlyingStream.Position += size; - } - } - - /// - /// Writes an enum value as a byte to the underlying stream. - /// - /// The enum type. - /// The enum value to write. - public void WriteEnum(T value) where T : Enum - { - Write(Convert.ToByte(value)); - } - - /// - /// Fills the stream from the current position up to (capacity) with 0x00's - /// - public void Fill() - { - Fill((int)(_capacity - UnderlyingStream.Length)); - } - - /// - /// Writes a number of 0x00 byte values to the underlying stream. - /// - /// The number of 0x00 bytes to write. - public void Fill(int length) - { - if (UnderlyingStream.Position == UnderlyingStream.Length) - { - UnderlyingStream.SetLength(UnderlyingStream.Length + length); - UnderlyingStream.Seek(0, SeekOrigin.End); - } - else - { - UnderlyingStream.Write(new byte[length], 0, length); - } - } - - - - /// - /// Offsets the current position from an origin. - /// - /// A byte offset relative to origin. - /// A value of type SeekOrigin indicating the reference point. - /// The new position within the stream. - public long Seek(long offset, SeekOrigin origin) - { - return UnderlyingStream.Seek(offset, origin); - } - - /// - /// Gets the entire stream content as a byte array. - /// - /// A byte array containing the stream data. - public byte[] ToArray() - { - return UnderlyingStream.ToArray(); - } - - /// - /// Disposes of resources used by the PacketWriter. - /// - public void Dispose() - { - if (!_disposed) - { - UnderlyingStream?.Dispose(); - _disposed = true; - } - - GC.SuppressFinalize(this); - } - - /// - /// Disposes of resources used by the PacketWriter. - /// - /// Whether the method is being called from Dispose() or the finalizer. - protected void Dispose(bool disposing) - { - if (disposing && !_disposed) - { - UnderlyingStream?.Dispose(); - _disposed = true; - } - } -} From da591afda5dd5af81b63e2fa80911c11d2b20d7a Mon Sep 17 00:00:00 2001 From: squid Date: Sat, 10 May 2025 16:58:24 +0200 Subject: [PATCH 4/9] refactor(PacketManager.cs): replace ObjectPool with ObjectPool for better memory management refactor(PacketManager.cs): update WritePacket method to use SpanWriter instead of PacketWriter for improved performance refactor(PacketManager.cs): modify Read method to use SpanReader instead of PacketReader for consistency refactor(CityInfo.cs): update ToArray method to use SpanWriter instead of PacketWriter for better memory handling refactor(CharacterCreation.cs): change Read method parameter type to SpanReader for consistency refactor(CharactersStartingLocations.cs): update Write method to use SpanWriter instead of PacketWriter for consistency refactor(CharacterEntry.cs): replace PacketWriter with SpanWriter in ToArray method for better memory management --- src/Prima.Network/Services/PacketManager.cs | 43 +++++++++++-------- src/Prima.Tcp.Test/Program.cs | 2 +- src/Prima.UOData/Data/Map/CityInfo.cs | 11 ++--- src/Prima.UOData/Packets/CharacterCreation.cs | 9 ++-- .../Packets/CharactersStartingLocations.cs | 9 ++-- .../Packets/Entries/CharacterEntry.cs | 14 +++--- 6 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/Prima.Network/Services/PacketManager.cs b/src/Prima.Network/Services/PacketManager.cs index d01334b..f956b80 100644 --- a/src/Prima.Network/Services/PacketManager.cs +++ b/src/Prima.Network/Services/PacketManager.cs @@ -1,9 +1,10 @@ using Microsoft.Extensions.Logging; using Orion.Foundations.Pool; +using Orion.Foundations.Spans; using Prima.Network.Interfaces.Packets; using Prima.Network.Interfaces.Services; using Prima.Network.Internal; -using Prima.Network.Serializers; + namespace Prima.Network.Services; @@ -24,15 +25,15 @@ public class PacketManager : IPacketManager private readonly Dictionary> _packets = new(); - /// - /// Object pool for reusing PacketReader instances. - /// - private readonly ObjectPool _readerPool = new(); - - /// - /// Object pool for reusing PacketWriter instances. - /// - private readonly ObjectPool _writerPool = new(); + // /// + // /// Object pool for reusing PacketReader instances. + // /// + // private readonly ObjectPool _readerPool = new(); + // + // /// + // /// Object pool for reusing PacketWriter instances. + // /// + // private readonly ObjectPool _writerPool = new(); /// /// Initializes a new instance of the PacketManager class. @@ -72,20 +73,24 @@ public PacketManager(ILogger logger) /// A byte array containing the serialized packet data. public byte[] WritePacket(T packet) where T : IUoNetworkPacket { - using var packetWriter = new PacketWriter(); + using var packetWriter = new SpanWriter(stackalloc byte[1024], true); // Write OpCode packetWriter.Write(packet.OpCode); // Create a separate writer for the packet data - using var dataWriter = new PacketWriter(); - packet.Write(dataWriter); - var packetData = dataWriter.ToArray(); + //using var dataWriter = new PacketWriter(); + packet.Write(packetWriter); + var packetData = packetWriter.Span; // Write the packet data packetWriter.Write(packetData); - return packetWriter.ToArray(); + var array = packetWriter.ToSpan(); + + + // Return the serialized packet data + return array.Span.ToArray(); } /// @@ -201,16 +206,18 @@ private bool TryParsePacket(IUoNetworkPacket packet, byte[] packetData, int pack { try { - var packetReader = _readerPool.Get(); + var packetReader = new SpanReader(packetData); + - packetReader.Initialize(packetData, packetLength, true); + // packetReader.Initialize(packetData, packetLength, true); // Read the packet content + packetReader.ReadByte(); packet.Read(packetReader); _logger.LogDebug("Successfully parsed packet: {PacketType}", packet.GetType().Name); // Return the packet to the pool - _readerPool.Return(packetReader); + //_readerPool.Return(packetReader); return true; } diff --git a/src/Prima.Tcp.Test/Program.cs b/src/Prima.Tcp.Test/Program.cs index 12b7607..ba3c8eb 100644 --- a/src/Prima.Tcp.Test/Program.cs +++ b/src/Prima.Tcp.Test/Program.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Prima.Network.Packets; using Prima.Network.Packets.Entries; -using Prima.Network.Serializers; + using Prima.Network.Services; using SuperSimpleTcp; diff --git a/src/Prima.UOData/Data/Map/CityInfo.cs b/src/Prima.UOData/Data/Map/CityInfo.cs index 3434693..d63d1ce 100644 --- a/src/Prima.UOData/Data/Map/CityInfo.cs +++ b/src/Prima.UOData/Data/Map/CityInfo.cs @@ -1,4 +1,5 @@ -using Prima.Network.Serializers; +using Orion.Foundations.Spans; + using Prima.UOData.Data.Geometry; using Server; @@ -74,11 +75,11 @@ public Point3D Location public byte[] ToArray(int index) { - using var packetWriter = new PacketWriter(); + using var packetWriter = new SpanWriter(stackalloc byte[Length], true); packetWriter.Write((byte)index); - packetWriter.WriteAsciiFixed(City, 32); - packetWriter.WriteAsciiFixed(Building, 32); + packetWriter.WriteAscii(City, 32); + packetWriter.WriteAscii(Building, 32); packetWriter.Write(_location.X); packetWriter.Write(_location.Y); packetWriter.Write(_location.Z); @@ -86,6 +87,6 @@ public byte[] ToArray(int index) packetWriter.Write(Description); packetWriter.Write(0); // 0x00 - return packetWriter.ToArray(); + return packetWriter.Span.ToArray(); } } diff --git a/src/Prima.UOData/Packets/CharacterCreation.cs b/src/Prima.UOData/Packets/CharacterCreation.cs index fc15abc..16a4607 100644 --- a/src/Prima.UOData/Packets/CharacterCreation.cs +++ b/src/Prima.UOData/Packets/CharacterCreation.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + using Prima.UOData.Data; using Prima.UOData.Types; @@ -45,13 +46,13 @@ public CharacterCreation() : base(0xF8, 106) { } - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { reader.ReadInt32(); // (0xedededed) reader.ReadInt32(); // (0xffffffff) reader.ReadByte(); //(0xffffffff) - Name = reader.ReadFixedString(30); + Name = reader.ReadAscii(30); reader.ReadByte(); reader.ReadByte(); @@ -63,7 +64,7 @@ public override void Read(PacketReader reader) LoginCount = reader.ReadInt32(); Profession = ProfessionInfo.Professions[reader.ReadByte()]; - reader.ReadBytes(15); + reader.Read(new byte[15]); Sex = (SexType)reader.ReadByte(); diff --git a/src/Prima.UOData/Packets/CharactersStartingLocations.cs b/src/Prima.UOData/Packets/CharactersStartingLocations.cs index eb3be38..1437584 100644 --- a/src/Prima.UOData/Packets/CharactersStartingLocations.cs +++ b/src/Prima.UOData/Packets/CharactersStartingLocations.cs @@ -1,7 +1,7 @@ using Orion.Foundations.Spans; using Prima.Core.Server.Types.Uo; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + using Prima.UOData.Context; using Prima.UOData.Data; using Prima.UOData.Data.Map; @@ -39,7 +39,7 @@ public void FillCharacters(List? characters = null, int size = 7 } } - public override void Write(PacketWriter packetWriter) + public override void Write(SpanWriter packetWriter) { var client70130 = ProtocolChanges.HasFlag(ProtocolChanges.NewCharacterList); var textLength = client70130 ? 32 : 31; @@ -121,7 +121,8 @@ public override void Write(PacketWriter packetWriter) { writer.Write((short)-1); } -// 169 4 208 - packetWriter.Write(writer.Span); + + // 169 4 208 + packetWriter.Write(writer.Span.ToArray()); } } diff --git a/src/Prima.UOData/Packets/Entries/CharacterEntry.cs b/src/Prima.UOData/Packets/Entries/CharacterEntry.cs index 954a557..879cd08 100644 --- a/src/Prima.UOData/Packets/Entries/CharacterEntry.cs +++ b/src/Prima.UOData/Packets/Entries/CharacterEntry.cs @@ -1,4 +1,4 @@ -using Prima.Network.Serializers; +using Orion.Foundations.Spans; namespace Prima.UOData.Packets.Entries; @@ -16,19 +16,15 @@ public CharacterEntry(string name = "", string password = "") } - public byte[] ToArray() { - using var pr = new PacketWriter(); - - pr.WriteAsciiFixed(Name, 30); - pr.WriteAsciiFixed(Password, 30); + using var pr = new SpanWriter(stackalloc byte[60], true); - var arr= pr.ToArray(); + pr.WriteAscii(Name, 30); + pr.WriteAscii(Password, 30); - return arr; + return pr.Span.ToArray(); } public static int Lenght => 60; - } From e0117967602c0af5c9ee65df4df31e21bd04b301 Mon Sep 17 00:00:00 2001 From: squid Date: Sat, 10 May 2025 16:58:37 +0200 Subject: [PATCH 5/9] feat(Prima.Core.Server.csproj): update Orion.Core.Server package version to 0.28.0 refactor(IUoNetworkPacket.cs, BaseUoNetworkPacket.cs, ClientVersionRequest.cs, ConnectToGameServer.cs, FeatureFlagsResponse.cs, GameServerList.cs, GameServerLogin.cs, LoginDenied.cs, LoginRequest.cs, PingRequest.cs, SelectServer.cs): replace PacketReader and PacketWriter with SpanReader and SpanWriter for improved performance and memory efficiency feat(Prima.Network.csproj): update Orion.Core.Server, Orion.Network.Core, and Orion.Network.Tcp package versions to 0.28.0 --- .../Prima.Core.Server.csproj | 2 +- .../Interfaces/Packets/IUoNetworkPacket.cs | 7 +++--- .../Packets/Base/BaseUoNetworkPacket.cs | 6 ++--- .../Packets/ClientVersionRequest.cs | 4 +-- .../Packets/ConnectToGameServer.cs | 10 +++++--- .../Packets/FeatureFlagsResponse.cs | 7 +++--- src/Prima.Network/Packets/GameServerList.cs | 25 ++++++++++--------- src/Prima.Network/Packets/GameServerLogin.cs | 9 ++++--- src/Prima.Network/Packets/LoginDenied.cs | 11 ++++---- src/Prima.Network/Packets/LoginRequest.cs | 15 +++++------ src/Prima.Network/Packets/PingRequest.cs | 9 ++++--- src/Prima.Network/Packets/SelectServer.cs | 9 ++++--- src/Prima.Network/Prima.Network.csproj | 6 ++--- 13 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/Prima.Core.Server/Prima.Core.Server.csproj b/src/Prima.Core.Server/Prima.Core.Server.csproj index 74ab2dc..8d96862 100644 --- a/src/Prima.Core.Server/Prima.Core.Server.csproj +++ b/src/Prima.Core.Server/Prima.Core.Server.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs b/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs index e916a0e..c3c5c25 100644 --- a/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs +++ b/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs @@ -1,4 +1,5 @@ -using Prima.Network.Serializers; +using Orion.Foundations.Spans; + namespace Prima.Network.Interfaces.Packets; @@ -24,11 +25,11 @@ public interface IUoNetworkPacket /// Reads the packet data from the provided packet reader. /// /// The packet reader containing the packet data. - void Read(PacketReader reader); + void Read(SpanReader reader); /// /// Writes the packet data to the provided packet writer. /// /// The packet writer to write packet data to. - void Write(PacketWriter writer); + void Write(SpanWriter writer); } diff --git a/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs b/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs index f479778..03be0a2 100644 --- a/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs +++ b/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs @@ -1,5 +1,5 @@ +using Orion.Foundations.Spans; using Prima.Network.Interfaces.Packets; -using Prima.Network.Serializers; namespace Prima.Network.Packets.Base; @@ -36,7 +36,7 @@ protected BaseUoNetworkPacket(byte opCode, int length) /// /// The packet reader containing the packet data. /// Thrown if this method is not overridden in derived classes. - public virtual void Read(PacketReader reader) + public virtual void Read(SpanReader reader) { } @@ -47,7 +47,7 @@ public virtual void Read(PacketReader reader) /// /// The packet writer to write packet data to. /// Thrown if this method is not overridden in derived classes. - public virtual void Write(PacketWriter writer) + public virtual void Write(SpanWriter writer) { } diff --git a/src/Prima.Network/Packets/ClientVersionRequest.cs b/src/Prima.Network/Packets/ClientVersionRequest.cs index 0ca73cf..b010daa 100644 --- a/src/Prima.Network/Packets/ClientVersionRequest.cs +++ b/src/Prima.Network/Packets/ClientVersionRequest.cs @@ -1,6 +1,6 @@ using System.Net; +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; namespace Prima.Network.Packets; @@ -31,7 +31,7 @@ public ClientVersionRequest(int majorVersion, int minorVersion, int revision, in Prototype = prototype; } - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { Seed = reader.ReadInt32(); ClientIP = new IPAddress(Seed); diff --git a/src/Prima.Network/Packets/ConnectToGameServer.cs b/src/Prima.Network/Packets/ConnectToGameServer.cs index d124677..b02ab60 100644 --- a/src/Prima.Network/Packets/ConnectToGameServer.cs +++ b/src/Prima.Network/Packets/ConnectToGameServer.cs @@ -1,6 +1,6 @@ using System.Net; +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; namespace Prima.Network.Packets; @@ -34,9 +34,11 @@ public class ConnectToGameServer() : BaseUoNetworkPacket(0x8c, 11) /// Reads the packet data from the provided packet reader. /// /// The packet reader to read data from. - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { - byte[] ipBytes = reader.ReadBytes(4); + byte[] ipBytes = new byte[4]; + reader.Read(ipBytes); + GameServerIP = new IPAddress(ipBytes); GameServerPort = reader.ReadUInt16(); SessionKey = reader.ReadInt32(); @@ -46,7 +48,7 @@ public override void Read(PacketReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { byte[] ipBytes = GameServerIP.GetAddressBytes(); writer.Write(ipBytes); diff --git a/src/Prima.Network/Packets/FeatureFlagsResponse.cs b/src/Prima.Network/Packets/FeatureFlagsResponse.cs index 2a15a45..867b48e 100644 --- a/src/Prima.Network/Packets/FeatureFlagsResponse.cs +++ b/src/Prima.Network/Packets/FeatureFlagsResponse.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + using Prima.Network.Types; namespace Prima.Network.Packets; @@ -60,7 +61,7 @@ public FeatureFlagsResponse(FeatureFlags flags) : this() /// Reads the packet data from the provided packet reader. /// /// The packet reader to read data from. - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { Flags = (FeatureFlags)reader.ReadUInt32(); } @@ -69,7 +70,7 @@ public override void Read(PacketReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { writer.Write((uint)Flags); } diff --git a/src/Prima.Network/Packets/GameServerList.cs b/src/Prima.Network/Packets/GameServerList.cs index e9e03d2..e2eae77 100644 --- a/src/Prima.Network/Packets/GameServerList.cs +++ b/src/Prima.Network/Packets/GameServerList.cs @@ -1,7 +1,7 @@ using System.Net; +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; using Prima.Network.Packets.Entries; -using Prima.Network.Serializers; namespace Prima.Network.Packets; @@ -55,7 +55,7 @@ public void AddServer(GameServerEntry server) /// BYTE[4] Server IP address (reversed for network order) /// /// The packet writer to write the data to. - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { var servers = GetServers(); @@ -72,7 +72,7 @@ public override void Write(PacketWriter writer) private byte[] GetServers() { - using var stream = new PacketWriter(); + using var stream = new SpanWriter(stackalloc byte[Servers.Count * 40 + 6], true); for (var i = 0; i < Servers.Count; i++) { var server = Servers[i]; @@ -81,7 +81,7 @@ private byte[] GetServers() stream.Write((ushort)i); // Write server name (fixed 32 bytes) - stream.WriteAsciiFixed(server.Name, 32); + stream.WriteAscii(server.Name, 32); // Write load percentage stream.Write(server.LoadPercent); @@ -91,19 +91,19 @@ private byte[] GetServers() // Write IP address in reverse order (as specified in the protocol) // For example, 192.168.0.1 is sent as 0100A8C0 - //var ipBytes = server.IP.GetAddressBytes(); - //Array.Reverse(ipBytes); - stream.WriteIpAddress(server.IP); + var ipBytes = server.IP.GetAddressBytes(); + Array.Reverse(ipBytes); + stream.Write(ipBytes); } - return stream.ToArray(); + return stream.Span.ToArray(); } /// /// Reads packet data from the provided packet reader. /// /// The packet reader to read the data from. - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { reader.ReadByte(); reader.ReadByte(); @@ -119,15 +119,16 @@ public override void Read(PacketReader reader) { var entry = new GameServerEntry { - Index = reader.ReadUInt16BE(), - Name = reader.ReadFixedString(32), + Index = reader.ReadUInt16(), + Name = reader.ReadAscii(32), LoadPercent = reader.ReadByte(), TimeZone = reader.ReadByte() }; // IP address bytes are reversed in the packet // For example, 0100A8C0 needs to be converted to 192.168.0.1 - byte[] ipBytes = reader.ReadBytes(4); + byte[] ipBytes = new byte[4]; + reader.Read(ipBytes); Array.Reverse(ipBytes); entry.IP = new IPAddress(ipBytes); diff --git a/src/Prima.Network/Packets/GameServerLogin.cs b/src/Prima.Network/Packets/GameServerLogin.cs index 3ed3e4c..462a110 100644 --- a/src/Prima.Network/Packets/GameServerLogin.cs +++ b/src/Prima.Network/Packets/GameServerLogin.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + namespace Prima.Network.Packets; @@ -15,10 +16,10 @@ public GameServerLogin() : base(0x91, 65) { } - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { SessionKey = reader.ReadInt32(); - AccountId = reader.ReadFixedString(30); - Password = reader.ReadFixedString(30); + AccountId = reader.ReadAscii(30); + Password = reader.ReadAscii(30); } } diff --git a/src/Prima.Network/Packets/LoginDenied.cs b/src/Prima.Network/Packets/LoginDenied.cs index a5d9ce6..5bd35ed 100644 --- a/src/Prima.Network/Packets/LoginDenied.cs +++ b/src/Prima.Network/Packets/LoginDenied.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + using Prima.Network.Types; namespace Prima.Network.Packets; @@ -30,17 +31,17 @@ public LoginDenied(LoginDeniedReasonType reason) : this() /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { - writer.WriteEnum(Reason); + writer.Write((byte)Reason); } /// /// Reads the packet data from the provided packet reader. /// /// The packet reader to read data from. - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { - Reason = reader.ReadEnum(); + Reason = (LoginDeniedReasonType)reader.ReadByte(); } } diff --git a/src/Prima.Network/Packets/LoginRequest.cs b/src/Prima.Network/Packets/LoginRequest.cs index e0e2767..2283a8e 100644 --- a/src/Prima.Network/Packets/LoginRequest.cs +++ b/src/Prima.Network/Packets/LoginRequest.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + namespace Prima.Network.Packets; @@ -33,10 +34,10 @@ public class LoginRequest() : BaseUoNetworkPacket(0x80, 62) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { - writer.WriteAsciiFixed(Username, 30); - writer.WriteAsciiFixed(Password, 30); + writer.WriteAscii(Username, 30); + writer.WriteAscii(Password, 30); writer.Write(NextLoginKey); } @@ -44,10 +45,10 @@ public override void Write(PacketWriter writer) /// Reads the packet data from the provided packet reader. /// /// The packet reader to read data from. - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { - Username = reader.ReadFixedString(30); - Password = reader.ReadFixedString(30); + Username = reader.ReadAscii(30); + Password = reader.ReadAscii(30); NextLoginKey = reader.ReadByte(); } diff --git a/src/Prima.Network/Packets/PingRequest.cs b/src/Prima.Network/Packets/PingRequest.cs index a05f7ff..a56f111 100644 --- a/src/Prima.Network/Packets/PingRequest.cs +++ b/src/Prima.Network/Packets/PingRequest.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + namespace Prima.Network.Packets; @@ -14,15 +15,15 @@ public PingRequest() : base(0x73, 2) } - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { Sequence = reader.ReadByte(); base.Read(reader); } - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { - writer.WriteByte((byte)Sequence); + writer.Write((byte)Sequence); base.Write(writer); } } diff --git a/src/Prima.Network/Packets/SelectServer.cs b/src/Prima.Network/Packets/SelectServer.cs index c49fc14..b1fc1d3 100644 --- a/src/Prima.Network/Packets/SelectServer.cs +++ b/src/Prima.Network/Packets/SelectServer.cs @@ -1,5 +1,6 @@ +using Orion.Foundations.Spans; using Prima.Network.Packets.Base; -using Prima.Network.Serializers; + namespace Prima.Network.Packets; @@ -22,16 +23,16 @@ public class SelectServer() : BaseUoNetworkPacket(0xA0, 3) /// Reads the packet data from the provided packet reader. /// /// The packet reader to read data from. - public override void Read(PacketReader reader) + public override void Read(SpanReader reader) { - ShardId = reader.ReadUInt16BE(); + ShardId = reader.ReadUInt16(); } /// /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(PacketWriter writer) + public override void Write(SpanWriter writer) { writer.Write(ShardId); } diff --git a/src/Prima.Network/Prima.Network.csproj b/src/Prima.Network/Prima.Network.csproj index 69394fe..fea3bda 100644 --- a/src/Prima.Network/Prima.Network.csproj +++ b/src/Prima.Network/Prima.Network.csproj @@ -9,9 +9,9 @@ - - - + + + From 13ab0259e1b7a1f9ec939088ee1604bb58c06ae0 Mon Sep 17 00:00:00 2001 From: squid Date: Sun, 11 May 2025 09:24:32 +0200 Subject: [PATCH 6/9] feat(Prima.Network): add IpAddressExtensions class to handle IP address conversions feat(Prima.Network): implement Write method in IUoNetworkPacket interface feat(Prima.Network): implement Write method in BaseUoNetworkPacket class feat(Prima.Network): implement Write method in ConnectToGameServer class feat(Prima.Network): implement Write method in FeatureFlagsResponse class feat(Prima.Network): implement Write method in GameServerList class feat(Prima.Network): implement Write method in LoginDenied class feat(Prima.Network): implement Write method in LoginRequest class feat(Prima.Network): implement Write method in PingRequest class feat(Prima.Network): implement Write method in SelectServer class feat(Prima.Network): implement WritePacket method in PacketManager class feat(Prima.UOData): implement Write method in CharactersStartingLocations class --- .../Extensions/IpAddressExtensions.cs | 32 +++++++++++++++++++ .../Interfaces/Packets/IUoNetworkPacket.cs | 2 +- .../Packets/Base/BaseUoNetworkPacket.cs | 4 +-- .../Packets/ConnectToGameServer.cs | 7 ++-- .../Packets/FeatureFlagsResponse.cs | 5 ++- src/Prima.Network/Packets/GameServerList.cs | 12 +++---- src/Prima.Network/Packets/LoginDenied.cs | 4 ++- src/Prima.Network/Packets/LoginRequest.cs | 4 ++- src/Prima.Network/Packets/PingRequest.cs | 7 ++-- src/Prima.Network/Packets/SelectServer.cs | 5 ++- src/Prima.Network/Services/PacketManager.cs | 6 ++-- .../Packets/CharactersStartingLocations.cs | 5 ++- 12 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 src/Prima.Network/Extensions/IpAddressExtensions.cs diff --git a/src/Prima.Network/Extensions/IpAddressExtensions.cs b/src/Prima.Network/Extensions/IpAddressExtensions.cs new file mode 100644 index 0000000..f3f1411 --- /dev/null +++ b/src/Prima.Network/Extensions/IpAddressExtensions.cs @@ -0,0 +1,32 @@ +using System.Buffers.Binary; +using System.Net; + +namespace Prima.Network.Extensions; + +public static class IpAddressExtensions +{ + public static uint ToRawAddress(this IPEndPoint endPoint) + { + Span integer = stackalloc byte[4]; + endPoint.Address.MapToIPv4().TryWriteBytes(integer, out var bytesWritten); + if (bytesWritten != 4) + { + throw new InvalidOperationException("IP Address could not be serialized to an integer"); + } + + return BinaryPrimitives.ReadUInt32LittleEndian(integer); + } + + public static uint ToRawAddress(this IPAddress ipAddress) + { + Span integer = stackalloc byte[4]; + ipAddress.MapToIPv4().TryWriteBytes(integer, out var bytesWritten); + if (bytesWritten != 4) + { + throw new InvalidOperationException("IP Address could not be serialized to an integer"); + } + + return BinaryPrimitives.ReadUInt32LittleEndian(integer); + } + +} diff --git a/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs b/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs index c3c5c25..6540629 100644 --- a/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs +++ b/src/Prima.Network/Interfaces/Packets/IUoNetworkPacket.cs @@ -31,5 +31,5 @@ public interface IUoNetworkPacket /// Writes the packet data to the provided packet writer. /// /// The packet writer to write packet data to. - void Write(SpanWriter writer); + Span Write(); } diff --git a/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs b/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs index 03be0a2..c58f745 100644 --- a/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs +++ b/src/Prima.Network/Packets/Base/BaseUoNetworkPacket.cs @@ -47,9 +47,9 @@ public virtual void Read(SpanReader reader) /// /// The packet writer to write packet data to. /// Thrown if this method is not overridden in derived classes. - public virtual void Write(SpanWriter writer) + public virtual Span Write() { - + throw new NotImplementedException("Write method not implemented for this packet type."); } /// diff --git a/src/Prima.Network/Packets/ConnectToGameServer.cs b/src/Prima.Network/Packets/ConnectToGameServer.cs index b02ab60..831071a 100644 --- a/src/Prima.Network/Packets/ConnectToGameServer.cs +++ b/src/Prima.Network/Packets/ConnectToGameServer.cs @@ -36,7 +36,7 @@ public class ConnectToGameServer() : BaseUoNetworkPacket(0x8c, 11) /// The packet reader to read data from. public override void Read(SpanReader reader) { - byte[] ipBytes = new byte[4]; + byte[] ipBytes = new byte[4]; reader.Read(ipBytes); GameServerIP = new IPAddress(ipBytes); @@ -48,11 +48,14 @@ public override void Read(SpanReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(SpanWriter writer) + public Span Write() { + using var writer = new SpanWriter(stackalloc byte[4]); byte[] ipBytes = GameServerIP.GetAddressBytes(); writer.Write(ipBytes); writer.Write((short)GameServerPort); writer.Write(SessionKey); + + return writer.ToSpan().Span; } } diff --git a/src/Prima.Network/Packets/FeatureFlagsResponse.cs b/src/Prima.Network/Packets/FeatureFlagsResponse.cs index 867b48e..8d3f7b2 100644 --- a/src/Prima.Network/Packets/FeatureFlagsResponse.cs +++ b/src/Prima.Network/Packets/FeatureFlagsResponse.cs @@ -70,9 +70,12 @@ public override void Read(SpanReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(SpanWriter writer) + public Span Write() { + using var writer = new SpanWriter(stackalloc byte[5]); writer.Write((uint)Flags); + + return writer.ToSpan().Span; } /// diff --git a/src/Prima.Network/Packets/GameServerList.cs b/src/Prima.Network/Packets/GameServerList.cs index e2eae77..0513010 100644 --- a/src/Prima.Network/Packets/GameServerList.cs +++ b/src/Prima.Network/Packets/GameServerList.cs @@ -1,5 +1,6 @@ using System.Net; using Orion.Foundations.Spans; +using Prima.Network.Extensions; using Prima.Network.Packets.Base; using Prima.Network.Packets.Entries; @@ -55,8 +56,9 @@ public void AddServer(GameServerEntry server) /// BYTE[4] Server IP address (reversed for network order) /// /// The packet writer to write the data to. - public override void Write(SpanWriter writer) + public override Span Write() { + using var writer = new SpanWriter(stackalloc byte[Servers.Count * 40 + 6], true); var servers = GetServers(); writer.Write((ushort)(servers.Length + 6)); @@ -68,6 +70,8 @@ public override void Write(SpanWriter writer) writer.Write((ushort)Servers.Count); writer.Write(servers); + + return writer.ToSpan().Span; } private byte[] GetServers() @@ -89,11 +93,7 @@ private byte[] GetServers() // Write timezone stream.Write(server.TimeZone); - // Write IP address in reverse order (as specified in the protocol) - // For example, 192.168.0.1 is sent as 0100A8C0 - var ipBytes = server.IP.GetAddressBytes(); - Array.Reverse(ipBytes); - stream.Write(ipBytes); + stream.Write(server.IP.ToRawAddress()); } return stream.Span.ToArray(); diff --git a/src/Prima.Network/Packets/LoginDenied.cs b/src/Prima.Network/Packets/LoginDenied.cs index 5bd35ed..e968b3f 100644 --- a/src/Prima.Network/Packets/LoginDenied.cs +++ b/src/Prima.Network/Packets/LoginDenied.cs @@ -31,9 +31,11 @@ public LoginDenied(LoginDeniedReasonType reason) : this() /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(SpanWriter writer) + public Span Write() { + using var writer = new SpanWriter(stackalloc byte[1]); writer.Write((byte)Reason); + return writer.ToSpan().Span; } /// diff --git a/src/Prima.Network/Packets/LoginRequest.cs b/src/Prima.Network/Packets/LoginRequest.cs index 2283a8e..fec29c7 100644 --- a/src/Prima.Network/Packets/LoginRequest.cs +++ b/src/Prima.Network/Packets/LoginRequest.cs @@ -34,11 +34,13 @@ public class LoginRequest() : BaseUoNetworkPacket(0x80, 62) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(SpanWriter writer) + public Span Write() { + using var writer = new SpanWriter(stackalloc byte[62]); writer.WriteAscii(Username, 30); writer.WriteAscii(Password, 30); writer.Write(NextLoginKey); + return writer.ToSpan().Span; } /// diff --git a/src/Prima.Network/Packets/PingRequest.cs b/src/Prima.Network/Packets/PingRequest.cs index a56f111..c10d3ad 100644 --- a/src/Prima.Network/Packets/PingRequest.cs +++ b/src/Prima.Network/Packets/PingRequest.cs @@ -21,9 +21,12 @@ public override void Read(SpanReader reader) base.Read(reader); } - public override void Write(SpanWriter writer) + public Span Write() { + using var writer = new SpanWriter(stackalloc byte[2]); writer.Write((byte)Sequence); - base.Write(writer); + + return writer.ToSpan().Span; + } } diff --git a/src/Prima.Network/Packets/SelectServer.cs b/src/Prima.Network/Packets/SelectServer.cs index b1fc1d3..140709d 100644 --- a/src/Prima.Network/Packets/SelectServer.cs +++ b/src/Prima.Network/Packets/SelectServer.cs @@ -32,8 +32,11 @@ public override void Read(SpanReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public override void Write(SpanWriter writer) + public Span Write() { + using var writer = new SpanWriter(stackalloc byte[3]); writer.Write(ShardId); + + return writer.ToSpan().Span; } } diff --git a/src/Prima.Network/Services/PacketManager.cs b/src/Prima.Network/Services/PacketManager.cs index f956b80..6114a6b 100644 --- a/src/Prima.Network/Services/PacketManager.cs +++ b/src/Prima.Network/Services/PacketManager.cs @@ -73,15 +73,15 @@ public PacketManager(ILogger logger) /// A byte array containing the serialized packet data. public byte[] WritePacket(T packet) where T : IUoNetworkPacket { - using var packetWriter = new SpanWriter(stackalloc byte[1024], true); + using var packetWriter = new SpanWriter(1, true); // Write OpCode packetWriter.Write(packet.OpCode); // Create a separate writer for the packet data //using var dataWriter = new PacketWriter(); - packet.Write(packetWriter); - var packetData = packetWriter.Span; + + var packetData = packet.Write(); // Write the packet data packetWriter.Write(packetData); diff --git a/src/Prima.UOData/Packets/CharactersStartingLocations.cs b/src/Prima.UOData/Packets/CharactersStartingLocations.cs index 1437584..232f6f0 100644 --- a/src/Prima.UOData/Packets/CharactersStartingLocations.cs +++ b/src/Prima.UOData/Packets/CharactersStartingLocations.cs @@ -39,8 +39,9 @@ public void FillCharacters(List? characters = null, int size = 7 } } - public override void Write(SpanWriter packetWriter) + public Span Write() { + using var packetWriter = new SpanWriter(stackalloc byte[0], true); var client70130 = ProtocolChanges.HasFlag(ProtocolChanges.NewCharacterList); var textLength = client70130 ? 32 : 31; @@ -124,5 +125,7 @@ public override void Write(SpanWriter packetWriter) // 169 4 208 packetWriter.Write(writer.Span.ToArray()); + + return packetWriter.ToSpan().Span; } } From 3be22b4c89339b2518dc0604bbe4f7ece6103873 Mon Sep 17 00:00:00 2001 From: squid Date: Sun, 11 May 2025 15:14:48 +0200 Subject: [PATCH 7/9] refactor(IpAddressExtensions.cs): change variable name from 'ip' to 'ipAddress' for clarity feat(ConnectToGameServer.cs): add using statement for IpAddressExtensions to use ToRawAddress method feat(ConnectToGameServer.cs): change Write method to override method to adhere to base class refactor(ConnectToGameServer.cs): change SpanWriter buffer size to 4 bytes and set clearBuffer flag to true refactor(FeatureFlagsResponse.cs): change Write method to override method to adhere to base class refactor(GameServerList.cs): change SpanWriter buffer size to 1 byte and set clearBuffer flag to true refactor(LoginDenied.cs): change Write method to override method to adhere to base class refactor(PingRequest.cs): change Write method to override method to adhere to base class refactor(SelectServer.cs): change Write method to override method to adhere to base class --- src/Prima.Network/Extensions/IpAddressExtensions.cs | 4 +++- src/Prima.Network/Packets/ConnectToGameServer.cs | 8 ++++---- src/Prima.Network/Packets/FeatureFlagsResponse.cs | 2 +- src/Prima.Network/Packets/GameServerList.cs | 2 +- src/Prima.Network/Packets/LoginDenied.cs | 2 +- src/Prima.Network/Packets/PingRequest.cs | 2 +- src/Prima.Network/Packets/SelectServer.cs | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Prima.Network/Extensions/IpAddressExtensions.cs b/src/Prima.Network/Extensions/IpAddressExtensions.cs index f3f1411..5d5b048 100644 --- a/src/Prima.Network/Extensions/IpAddressExtensions.cs +++ b/src/Prima.Network/Extensions/IpAddressExtensions.cs @@ -26,7 +26,9 @@ public static uint ToRawAddress(this IPAddress ipAddress) throw new InvalidOperationException("IP Address could not be serialized to an integer"); } - return BinaryPrimitives.ReadUInt32LittleEndian(integer); + var ip = BinaryPrimitives.ReadUInt32LittleEndian(integer); + + return ip; } } diff --git a/src/Prima.Network/Packets/ConnectToGameServer.cs b/src/Prima.Network/Packets/ConnectToGameServer.cs index 831071a..8f56ac7 100644 --- a/src/Prima.Network/Packets/ConnectToGameServer.cs +++ b/src/Prima.Network/Packets/ConnectToGameServer.cs @@ -1,5 +1,6 @@ using System.Net; using Orion.Foundations.Spans; +using Prima.Network.Extensions; using Prima.Network.Packets.Base; namespace Prima.Network.Packets; @@ -48,11 +49,10 @@ public override void Read(SpanReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public Span Write() + public override Span Write() { - using var writer = new SpanWriter(stackalloc byte[4]); - byte[] ipBytes = GameServerIP.GetAddressBytes(); - writer.Write(ipBytes); + using var writer = new SpanWriter(stackalloc byte[4], true); + writer.Write(GameServerIP.ToRawAddress()); writer.Write((short)GameServerPort); writer.Write(SessionKey); diff --git a/src/Prima.Network/Packets/FeatureFlagsResponse.cs b/src/Prima.Network/Packets/FeatureFlagsResponse.cs index 8d3f7b2..8fb911e 100644 --- a/src/Prima.Network/Packets/FeatureFlagsResponse.cs +++ b/src/Prima.Network/Packets/FeatureFlagsResponse.cs @@ -70,7 +70,7 @@ public override void Read(SpanReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public Span Write() + public override Span Write() { using var writer = new SpanWriter(stackalloc byte[5]); writer.Write((uint)Flags); diff --git a/src/Prima.Network/Packets/GameServerList.cs b/src/Prima.Network/Packets/GameServerList.cs index 0513010..742374d 100644 --- a/src/Prima.Network/Packets/GameServerList.cs +++ b/src/Prima.Network/Packets/GameServerList.cs @@ -58,7 +58,7 @@ public void AddServer(GameServerEntry server) /// The packet writer to write the data to. public override Span Write() { - using var writer = new SpanWriter(stackalloc byte[Servers.Count * 40 + 6], true); + using var writer = new SpanWriter(stackalloc byte[1], true); var servers = GetServers(); writer.Write((ushort)(servers.Length + 6)); diff --git a/src/Prima.Network/Packets/LoginDenied.cs b/src/Prima.Network/Packets/LoginDenied.cs index e968b3f..bb89ea4 100644 --- a/src/Prima.Network/Packets/LoginDenied.cs +++ b/src/Prima.Network/Packets/LoginDenied.cs @@ -31,7 +31,7 @@ public LoginDenied(LoginDeniedReasonType reason) : this() /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public Span Write() + public override Span Write() { using var writer = new SpanWriter(stackalloc byte[1]); writer.Write((byte)Reason); diff --git a/src/Prima.Network/Packets/PingRequest.cs b/src/Prima.Network/Packets/PingRequest.cs index c10d3ad..5124d84 100644 --- a/src/Prima.Network/Packets/PingRequest.cs +++ b/src/Prima.Network/Packets/PingRequest.cs @@ -21,7 +21,7 @@ public override void Read(SpanReader reader) base.Read(reader); } - public Span Write() + public override Span Write() { using var writer = new SpanWriter(stackalloc byte[2]); writer.Write((byte)Sequence); diff --git a/src/Prima.Network/Packets/SelectServer.cs b/src/Prima.Network/Packets/SelectServer.cs index 140709d..bb4a073 100644 --- a/src/Prima.Network/Packets/SelectServer.cs +++ b/src/Prima.Network/Packets/SelectServer.cs @@ -32,7 +32,7 @@ public override void Read(SpanReader reader) /// Writes the packet data to the provided packet writer. /// /// The packet writer to write data to. - public Span Write() + public override Span Write() { using var writer = new SpanWriter(stackalloc byte[3]); writer.Write(ShardId); From 202465b7ab91fccb83833e1aa57b80d7eda7147c Mon Sep 17 00:00:00 2001 From: squid Date: Sun, 11 May 2025 19:09:18 +0200 Subject: [PATCH 8/9] refactor(ConnectToGameServer.cs): change writer method from Write to WriteLE for little-endian writing refactor(GameServerList.cs): optimize packet writing by directly writing server info to SpanWriter refactor(CharactersStartingLocations.cs): change method signature to override base class method for consistency test(PacketsTests.cs): update test method to use SpanWriter and SpanReader for writing and reading data types --- .../Packets/ConnectToGameServer.cs | 2 +- src/Prima.Network/Packets/GameServerList.cs | 39 ++++++++++++++----- .../Packets/CharactersStartingLocations.cs | 2 +- tests/Prima.Tests/PacketsTests.cs | 26 +++++++------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/Prima.Network/Packets/ConnectToGameServer.cs b/src/Prima.Network/Packets/ConnectToGameServer.cs index 8f56ac7..5d57216 100644 --- a/src/Prima.Network/Packets/ConnectToGameServer.cs +++ b/src/Prima.Network/Packets/ConnectToGameServer.cs @@ -52,7 +52,7 @@ public override void Read(SpanReader reader) public override Span Write() { using var writer = new SpanWriter(stackalloc byte[4], true); - writer.Write(GameServerIP.ToRawAddress()); + writer.WriteLE(GameServerIP.ToRawAddress()); writer.Write((short)GameServerPort); writer.Write(SessionKey); diff --git a/src/Prima.Network/Packets/GameServerList.cs b/src/Prima.Network/Packets/GameServerList.cs index 742374d..502420a 100644 --- a/src/Prima.Network/Packets/GameServerList.cs +++ b/src/Prima.Network/Packets/GameServerList.cs @@ -58,20 +58,31 @@ public void AddServer(GameServerEntry server) /// The packet writer to write the data to. public override Span Write() { - using var writer = new SpanWriter(stackalloc byte[1], true); - var servers = GetServers(); - writer.Write((ushort)(servers.Length + 6)); - // Write the system info flag - writer.Write(SystemInfoFlag); - // Write the number of servers - writer.Write((ushort)Servers.Count); + var info = Servers.ToArray(); + var length = 6 + 40 * info.Length; + var writer = new SpanWriter(stackalloc byte[length]); + writer.Write((ushort)length); + writer.Write((byte)0x5D); + writer.Write((ushort)info.Length); + + for (var i = 0; i < info.Length; ++i) + { + var si = info[i]; + + writer.Write((ushort)i); + writer.WriteAscii(si.Name, 32); + writer.Write((byte)si.LoadPercent); + writer.Write((sbyte)si.TimeZone); + // UO only supports IPv4 + writer.Write(si.IP.ToRawAddress()); + } + + return writer.Span.ToArray(); - writer.Write(servers); - return writer.ToSpan().Span; } private byte[] GetServers() @@ -92,8 +103,16 @@ private byte[] GetServers() // Write timezone stream.Write(server.TimeZone); + var ipBytes = server.IP.GetAddressBytes(); + // Reverse the IP address bytes for network order + Array.Reverse(ipBytes); + // Write the reversed IP address + + var ipInt = BitConverter.ToUInt32(ipBytes, 0); + var rawAddress = server.IP.ToRawAddress(); + - stream.Write(server.IP.ToRawAddress()); + stream.Write(ipInt); } return stream.Span.ToArray(); diff --git a/src/Prima.UOData/Packets/CharactersStartingLocations.cs b/src/Prima.UOData/Packets/CharactersStartingLocations.cs index 232f6f0..8ef176e 100644 --- a/src/Prima.UOData/Packets/CharactersStartingLocations.cs +++ b/src/Prima.UOData/Packets/CharactersStartingLocations.cs @@ -39,7 +39,7 @@ public void FillCharacters(List? characters = null, int size = 7 } } - public Span Write() + public override Span Write() { using var packetWriter = new SpanWriter(stackalloc byte[0], true); var client70130 = ProtocolChanges.HasFlag(ProtocolChanges.NewCharacterList); diff --git a/tests/Prima.Tests/PacketsTests.cs b/tests/Prima.Tests/PacketsTests.cs index dc4f27c..0d323ad 100644 --- a/tests/Prima.Tests/PacketsTests.cs +++ b/tests/Prima.Tests/PacketsTests.cs @@ -1,10 +1,11 @@ using System.Net; using Microsoft.Extensions.Logging; using Moq; +using Orion.Foundations.Spans; using Prima.Network.Interfaces.Services; using Prima.Network.Packets; using Prima.Network.Packets.Entries; -using Prima.Network.Serializers; + using Prima.Network.Services; using Prima.Network.Types; @@ -276,31 +277,32 @@ public void GameListMessage_Parse() public void PacketReaderWriter_DataTypes_Success() { // Arrange - using var writer = new PacketWriter(); + using var writer = new SpanWriter(stackalloc byte[4], true); // Write various data types writer.Write((byte)0x00); writer.Write((byte)0x01); writer.Write((ushort)0x0203); writer.Write((uint)0x04050607); - writer.WriteAsciiFixed("Test", 10); + writer.WriteAscii("Test", 10); writer.WriteAsciiNull("Variable"); - writer.WriteEnum(LoginDeniedReasonType.AccountBlocked); + writer.Write((byte)LoginDeniedReasonType.AccountBlocked); - var dataArray = writer.ToArray(); + var dataArray = writer.Span.ToArray(); // Act - using var reader = new PacketReader(); + using var reader = new SpanReader(dataArray); + - reader.Initialize(dataArray, dataArray.Length, true); // Assert + + Assert.That(reader.ReadByte(), Is.EqualTo(0x00)); Assert.That(reader.ReadByte(), Is.EqualTo(0x01)); - Assert.That(reader.ReadUInt16BE(), Is.EqualTo(0x0203)); - Assert.That(reader.ReadUInt32BE(), Is.EqualTo(0x04050607u)); - Assert.That(reader.ReadFixedString(10), Is.EqualTo("Test")); - Assert.That(reader.ReadString(), Is.EqualTo("Variable")); - Assert.That(reader.ReadEnum(), Is.EqualTo(LoginDeniedReasonType.AccountBlocked)); + Assert.That(reader.ReadInt16(), Is.EqualTo(0x0203)); + Assert.That(reader.ReadInt32(), Is.EqualTo(0x04050607u)); + Assert.That(reader.ReadAscii(10), Is.EqualTo("Test")); + Assert.That(reader.ReadAscii(), Is.EqualTo("Variable")); } /// From c8002257da09f2c7be42a3c5cca03ec88a6f3791 Mon Sep 17 00:00:00 2001 From: squid Date: Mon, 12 May 2025 09:08:18 +0200 Subject: [PATCH 9/9] feat(UserLoginContext.cs): add UserLoginContext record for user login information feat(Prima.Core.Server.csproj): update Orion.Core.Server package version to 0.28.2 feat(Prima.Network.csproj): update Orion.Core.Server, Orion.Network.Core, and Orion.Network.Tcp package versions to 0.28.2 refactor(LoginHandler.cs): add IScriptEngineService and IProcessQueueService dependencies for script execution refactor(LoginHandler.cs): add TriggerLogin method to handle user login events feat(EventScriptModule.cs): add OnUserLogin method to register a callback for user login events refactor(NetworkService.cs): update logic to handle multiple login servers for scalability --- .../Contexts/UserLoginContext.cs | 3 + .../Prima.Core.Server.csproj | 2 +- src/Prima.Network/Prima.Network.csproj | 6 +- src/Prima.Server/Handlers/LoginHandler.cs | 20 +++++- .../Modules/Scripts/EventScriptModule.cs | 22 +++++++ src/Prima.Server/Services/NetworkService.cs | 63 +++++++++++++------ 6 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 src/Prima.Core.Server/Contexts/UserLoginContext.cs diff --git a/src/Prima.Core.Server/Contexts/UserLoginContext.cs b/src/Prima.Core.Server/Contexts/UserLoginContext.cs new file mode 100644 index 0000000..495dbff --- /dev/null +++ b/src/Prima.Core.Server/Contexts/UserLoginContext.cs @@ -0,0 +1,3 @@ +namespace Prima.Core.Server.Contexts; + +public record UserLoginContext(string UserId, string Username); diff --git a/src/Prima.Core.Server/Prima.Core.Server.csproj b/src/Prima.Core.Server/Prima.Core.Server.csproj index 8d96862..662ba74 100644 --- a/src/Prima.Core.Server/Prima.Core.Server.csproj +++ b/src/Prima.Core.Server/Prima.Core.Server.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Prima.Network/Prima.Network.csproj b/src/Prima.Network/Prima.Network.csproj index fea3bda..ec9e479 100644 --- a/src/Prima.Network/Prima.Network.csproj +++ b/src/Prima.Network/Prima.Network.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/src/Prima.Server/Handlers/LoginHandler.cs b/src/Prima.Server/Handlers/LoginHandler.cs index f67ba31..dac2bb8 100644 --- a/src/Prima.Server/Handlers/LoginHandler.cs +++ b/src/Prima.Server/Handlers/LoginHandler.cs @@ -1,15 +1,18 @@ using System.Net; using System.Security.Cryptography; +using Orion.Core.Server.Interfaces.Services.System; using Orion.Foundations.Extensions; using Prima.Core.Server.Data.Config; using Prima.Core.Server.Data.Session; using Prima.Core.Server.Data.Uo; +using Prima.Core.Server.Entities; using Prima.Core.Server.Handlers.Base; using Prima.Core.Server.Interfaces.Listeners; using Prima.Core.Server.Interfaces.Services; using Prima.Network.Packets; using Prima.Network.Packets.Entries; using Prima.Network.Types; +using Prima.Server.Modules.Scripts; using Prima.UOData.Context; using Prima.UOData.Interfaces.Services; using Prima.UOData.Packets; @@ -26,6 +29,8 @@ public class LoginHandler private readonly INetworkService _networkService; private readonly PrimaServerConfig _primaServerConfig; + private readonly IScriptEngineService _scriptEngineService; + private readonly IProcessQueueService _processQueueService; private readonly List _gameServerEntries = new(); private readonly IMapService _mapService; @@ -33,7 +38,8 @@ public class LoginHandler public LoginHandler( ILogger logger, INetworkService networkService, IServiceProvider serviceProvider, - IAccountManager accountManager, PrimaServerConfig primaServerConfig, IMapService mapService + IAccountManager accountManager, PrimaServerConfig primaServerConfig, IMapService mapService, + IScriptEngineService scriptEngineService, IProcessQueueService processQueueService ) : base(logger, networkService, serviceProvider) { @@ -41,6 +47,8 @@ public LoginHandler( _accountManager = accountManager; _primaServerConfig = primaServerConfig; _mapService = mapService; + _scriptEngineService = scriptEngineService; + _processQueueService = processQueueService; CreateGameServerList(); } @@ -95,6 +103,8 @@ public async Task OnPacketReceived(NetworkSession session, LoginRequest packet) } Logger.LogInformation("Login successful for user {Username}", login.Username); + TriggerLogin(login); + var gameServerList = new GameServerList(); gameServerList.Servers.AddRange(_gameServerEntries); @@ -106,6 +116,14 @@ public async Task OnPacketReceived(NetworkSession session, LoginRequest packet) await session.SendPacketAsync(gameServerList); } + private void TriggerLogin(AccountEntity account) + { + _processQueueService.Enqueue( + "events", + () => { _scriptEngineService.ExecuteCallback(nameof(EventScriptModule.OnUserLogin), account); } + ); + } + public async Task OnPacketReceived(NetworkSession session, SelectServer packet) { Logger.LogInformation( diff --git a/src/Prima.Server/Modules/Scripts/EventScriptModule.cs b/src/Prima.Server/Modules/Scripts/EventScriptModule.cs index c2c0d7f..b6a1957 100644 --- a/src/Prima.Server/Modules/Scripts/EventScriptModule.cs +++ b/src/Prima.Server/Modules/Scripts/EventScriptModule.cs @@ -1,5 +1,6 @@ using Orion.Core.Server.Attributes.Scripts; using Orion.Core.Server.Interfaces.Services.System; +using Prima.Core.Server.Contexts; namespace Prima.Server.Modules.Scripts; @@ -28,4 +29,25 @@ public void HookEvent(string eventName, Action eventHandler) { _eventDispatcherService.SubscribeToEvent(eventName, eventHandler.Invoke); } + + [ScriptFunction("Register a callback when the user logs in")] + public void OnUserLogin(Action action) + { + _scriptEngineService.AddCallback( + nameof(OnUserLogin), + context => + { + if (context == null) + { + throw new ArgumentNullException(nameof(context), "Context cannot be null"); + return; + } + + if (context[0] is UserLoginContext userLoginContext) + { + action(userLoginContext); + } + } + ); + } } diff --git a/src/Prima.Server/Services/NetworkService.cs b/src/Prima.Server/Services/NetworkService.cs index a154524..a2dda0c 100644 --- a/src/Prima.Server/Services/NetworkService.cs +++ b/src/Prima.Server/Services/NetworkService.cs @@ -122,7 +122,7 @@ private void NetworkTransportManagerOnClientDisconnected(string transportId, str session.OnDisconnect -= DisconnectSession; - if (transportId == _loginContext) + if (transportId.StartsWith(_loginContext)) { _logger.LogInformation("Client disconnected from login server: {SessionId} => {Endpoint}", sessionId, endpoint); @@ -135,7 +135,7 @@ private void NetworkTransportManagerOnClientDisconnected(string transportId, str return; } - if (transportId == _gameContext) + if (transportId.StartsWith(_gameContext)) { _logger.LogInformation("Client disconnected from game server: {SessionId} => {Endpoint}", sessionId, endpoint); return; @@ -146,11 +146,11 @@ private void NetworkTransportManagerOnClientDisconnected(string transportId, str private void NetworkTransportManagerOnClientConnected(string transportId, string sessionId, string endpoint) { - if (transportId == _loginContext) + if (transportId.StartsWith(_loginContext)) { _logger.LogInformation("Client connected to login server: {SessionId} => {Endpoint}", sessionId, endpoint); } - else if (transportId == _gameContext) + else if (transportId.StartsWith(_gameContext)) { _logger.LogInformation("Client connected to game server: {SessionId} => {Endpoint}", sessionId, endpoint); } @@ -323,24 +323,49 @@ private async Task HandleIncomingMessages(NetworkMessageData data) public async Task StartAsync(CancellationToken cancellationToken = default) { - // Adding login server - _networkTransportManager.AddTransport( - new NonSecureTcpServer( - _loginContext, - ServerNetworkType.Servers, - IPAddress.Any, - _serverConfig.TcpServer.LoginPort - ) - ); + var loginServers = + GetListeningAddresses(IPEndPoint.Parse(IPAddress.Any.ToString() + _serverConfig.TcpServer.LoginPort)).ToList(); + foreach (var loginServer in loginServers) + { + try + { + _logger.LogInformation("Login server listening on {Address}", loginServer); + // Adding login server + _networkTransportManager.AddTransport( + new NonSecureTcpServer( + _loginContext + "_" + loginServer.Address, + ServerNetworkType.Servers, + loginServer.Address, + _serverConfig.TcpServer.LoginPort + ) + ); - // Adding game server - _networkTransportManager.AddTransport( - new NonSecureTcpServer(_gameContext, ServerNetworkType.Clients, IPAddress.Any, _serverConfig.TcpServer.GamePort) - ); + _logger.LogInformation( + "Game server listening on {Address}", + new IPEndPoint(loginServer.Address, _serverConfig.TcpServer.GamePort) + ); - _logger.LogInformation("Login server started on port {Port}", _serverConfig.TcpServer.LoginPort); - _logger.LogInformation("Game server started on port {Port}", _serverConfig.TcpServer.GamePort); + _networkTransportManager.AddTransport( + new NonSecureTcpServer( + _gameContext + "_" + loginServer.Address, + ServerNetworkType.Clients, + loginServer.Address, + _serverConfig.TcpServer.GamePort + ) + ); + } + + catch (Exception ex) + { + _logger.LogError( + ex, + "Error while starting login server on {Address}:{Port}", + loginServer.Address, + _serverConfig.TcpServer.LoginPort + ); + } + } await _networkTransportManager.StartAsync(cancellationToken); }