diff --git a/docs/Packets/C4-C7-OpenLetterExtended_by-server.md b/docs/Packets/C4-C7-OpenLetterExtended_by-server.md
new file mode 100644
index 000000000..f4135ab72
--- /dev/null
+++ b/docs/Packets/C4-C7-OpenLetterExtended_by-server.md
@@ -0,0 +1,22 @@
+# C4 C7 - OpenLetterExtended (by server)
+
+## Is sent when
+
+After the player requested to read a letter.
+
+## Causes the following actions on the client side
+
+The letter is opened in a new dialog.
+
+## Structure
+
+| Index | Length | Data Type | Value | Description |
+|-------|--------|-----------|-------|-------------|
+| 0 | 1 | Byte | 0xC4 | [Packet type](PacketTypes.md) |
+| 1 | 2 | Short | | Packet header - length of the packet |
+| 3 | 1 | Byte | 0xC7 | Packet header - packet type identifier |
+| 4 | 2 | ShortLittleEndian | | LetterIndex |
+| 6 | 42 | Binary | | SenderAppearance |
+| 48 | 1 | Byte | | Rotation |
+| 49 | 1 | Byte | | Animation |
+| 50 | | String | | Message |
\ No newline at end of file
diff --git a/docs/Packets/ServerToClient.md b/docs/Packets/ServerToClient.md
index 6811d291a..c8d9facdc 100644
--- a/docs/Packets/ServerToClient.md
+++ b/docs/Packets/ServerToClient.md
@@ -168,6 +168,7 @@
* [C1 C5 - LetterSendResponse (by server)](C1-C5-LetterSendResponse_by-server.md)
* [C3 C6 - AddLetter (by server)](C3-C6-AddLetter_by-server.md)
* [C4 C7 - OpenLetter (by server)](C4-C7-OpenLetter_by-server.md)
+ * [C4 C7 - OpenLetterExtended (by server)](C4-C7-OpenLetterExtended_by-server.md)
* [C1 C8 - RemoveLetter (by server)](C1-C8-RemoveLetter_by-server.md)
* [C3 CA - ChatRoomConnectionInfo (by server)](C3-CA-ChatRoomConnectionInfo_by-server.md)
* [C3 CB - FriendInvitationResult (by server)](C3-CB-FriendInvitationResult_by-server.md)
diff --git a/src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs b/src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs
new file mode 100644
index 000000000..ef6a84b7f
--- /dev/null
+++ b/src/GameServer/RemoteView/Messenger/ShowLetterExtendedPlugIn.cs
@@ -0,0 +1,64 @@
+//
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace MUnique.OpenMU.GameServer.RemoteView.Messenger;
+
+using System.Runtime.InteropServices;
+using MUnique.OpenMU.DataModel.Entities;
+using MUnique.OpenMU.GameLogic.Views.Messenger;
+using MUnique.OpenMU.Network;
+using MUnique.OpenMU.Network.Packets.ServerToClient;
+using MUnique.OpenMU.Network.PlugIns;
+using MUnique.OpenMU.PlugIns;
+
+///
+/// The default implementation of the which is forwarding everything to the game client with specific data packets.
+///
+[PlugIn(nameof(ShowLetterExtendedPlugIn), "The extended implementation of the IShowLetterPlugIn which is forwarding everything to the game client with specific data packets.")]
+[Guid("9FC6D771-FCCE-4780-B68E-5FBFF3FE1EB0")]
+[MinimumClient(106, 3, ClientLanguage.Invariant)]
+public class ShowLetterExtendedPlugIn : IShowLetterPlugIn
+{
+ private readonly RemotePlayer _player;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The player.
+ public ShowLetterExtendedPlugIn(RemotePlayer player) => this._player = player;
+
+ ///
+ public async ValueTask ShowLetterAsync(LetterBody letter)
+ {
+ var connection = this._player.Connection;
+ if (connection is null || this._player.SelectedCharacter is null)
+ {
+ return;
+ }
+
+ var appearanceSerializer = this._player.AppearanceSerializer;
+ var letterIndex = this._player.SelectedCharacter.Letters.IndexOf(letter.Header!);
+ int Write()
+ {
+ var size = OpenLetterExtendedRef.GetRequiredSize(letter.Message);
+ var span = connection.Output.GetSpan(size)[..size];
+ var result = new OpenLetterExtendedRef(span)
+ {
+ LetterIndex = (ushort)letterIndex,
+ Animation = letter.Animation,
+ Rotation = letter.Rotation,
+ Message = letter.Message,
+ };
+
+ if (letter.SenderAppearance is not null)
+ {
+ appearanceSerializer.WriteAppearanceData(result.SenderAppearance, letter.SenderAppearance, false);
+ }
+
+ return size;
+ }
+
+ await connection.SendAsync(Write).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
index 63d7cc837..668e822af 100644
--- a/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
+++ b/src/Network/Packets/ServerToClient/ConnectionExtensions.cs
@@ -5546,6 +5546,42 @@ int WritePacket()
await connection.SendAsync(WritePacket).ConfigureAwait(false);
}
+ ///
+ /// Sends a to this connection.
+ ///
+ /// The connection.
+ /// The letter index.
+ /// The sender appearance.
+ /// The rotation.
+ /// The animation.
+ /// The message.
+ ///
+ /// Is sent by the server when: After the player requested to read a letter.
+ /// Causes reaction on client side: The letter is opened in a new dialog.
+ ///
+ public static async ValueTask SendOpenLetterExtendedAsync(this IConnection? connection, ushort @letterIndex, Memory @senderAppearance, byte @rotation, byte @animation, string @message)
+ {
+ if (connection is null)
+ {
+ return;
+ }
+
+ int WritePacket()
+ {
+ var length = OpenLetterExtendedRef.GetRequiredSize(message);
+ var packet = new OpenLetterExtendedRef(connection.Output.GetSpan(length)[..length]);
+ packet.LetterIndex = @letterIndex;
+ @senderAppearance.Span.CopyTo(packet.SenderAppearance);
+ packet.Rotation = @rotation;
+ packet.Animation = @animation;
+ packet.Message = @message;
+
+ return packet.Header.Length;
+ }
+
+ await connection.SendAsync(WritePacket).ConfigureAwait(false);
+ }
+
///
/// Sends a to this connection.
///
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
index d9eb82efd..f01611cf6 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
+++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.cs
@@ -26790,6 +26790,127 @@ public string Message
}
+///
+/// Is sent by the server when: After the player requested to read a letter.
+/// Causes reaction on client side: The letter is opened in a new dialog.
+///
+public readonly struct OpenLetterExtended
+{
+ private readonly Memory _data;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The underlying data.
+ public OpenLetterExtended(Memory data)
+ : this(data, true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The underlying data.
+ /// If set to true, the header data is automatically initialized and written to the underlying span.
+ private OpenLetterExtended(Memory data, bool initialize)
+ {
+ this._data = data;
+ if (initialize)
+ {
+ var header = this.Header;
+ header.Type = HeaderType;
+ header.Code = Code;
+ header.Length = (ushort)data.Length;
+ }
+ }
+
+ ///
+ /// Gets the header type of this data packet.
+ ///
+ public static byte HeaderType => 0xC4;
+
+ ///
+ /// Gets the operation code of this data packet.
+ ///
+ public static byte Code => 0xC7;
+
+ ///
+ /// Gets the header of this packet.
+ ///
+ public C4Header Header => new (this._data);
+
+ ///
+ /// Gets or sets the letter index.
+ ///
+ public ushort LetterIndex
+ {
+ get => ReadUInt16LittleEndian(this._data.Span[4..]);
+ set => WriteUInt16LittleEndian(this._data.Span[4..], value);
+ }
+
+ ///
+ /// Gets or sets the sender appearance.
+ ///
+ public Span SenderAppearance
+ {
+ get => this._data.Slice(6, 42).Span;
+ }
+
+ ///
+ /// Gets or sets the rotation.
+ ///
+ public byte Rotation
+ {
+ get => this._data.Span[48];
+ set => this._data.Span[48] = value;
+ }
+
+ ///
+ /// Gets or sets the animation.
+ ///
+ public byte Animation
+ {
+ get => this._data.Span[49];
+ set => this._data.Span[49] = value;
+ }
+
+ ///
+ /// Gets or sets the message.
+ ///
+ public string Message
+ {
+ get => this._data.Span.ExtractString(50, this._data.Length - 50, System.Text.Encoding.UTF8);
+ set => this._data.Slice(50).Span.WriteString(value, System.Text.Encoding.UTF8);
+ }
+
+ ///
+ /// Performs an implicit conversion from a Memory of bytes to a .
+ ///
+ /// The packet as span.
+ /// The packet as struct.
+ public static implicit operator OpenLetterExtended(Memory packet) => new (packet, false);
+
+ ///
+ /// Performs an implicit conversion from to a Memory of bytes.
+ ///
+ /// The packet as struct.
+ /// The packet as byte span.
+ public static implicit operator Memory(OpenLetterExtended packet) => packet._data;
+
+ ///
+ /// Calculates the size of the packet for the specified field content.
+ ///
+ /// The content of the variable 'Message' field from which the size will be calculated.
+ public static int GetRequiredSize(string content) => System.Text.Encoding.UTF8.GetByteCount(content) + 1 + 50;
+
+ ///
+ /// Calculates the size of the packet for the specified field content.
+ ///
+ /// The content length in bytes of the variable 'Message' field from which the size will be calculated.
+ public static int GetRequiredSize(int contentLength) => contentLength + 1 + 50;
+}
+
+
///
/// Is sent by the server when: After a letter has been deleted by the request of the player.
/// Causes reaction on client side: The letter is removed from the letter list.
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
index d8064082f..25b9e48b7 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
+++ b/src/Network/Packets/ServerToClient/ServerToClientPackets.xml
@@ -9596,6 +9596,42 @@
+
+ C4Header
+ C7
+ OpenLetterExtended
+ ServerToClient
+ After the player requested to read a letter.
+ The letter is opened in a new dialog.
+
+
+ 4
+ ShortLittleEndian
+ LetterIndex
+
+
+ 6
+ Binary
+ SenderAppearance
+ 42
+
+
+ 48
+ Byte
+ Rotation
+
+
+ 49
+ Byte
+ Animation
+
+
+ 50
+ String
+ Message
+
+
+
C1Header
C8
diff --git a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
index 3f73cb01b..dedc95a75 100644
--- a/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
+++ b/src/Network/Packets/ServerToClient/ServerToClientPacketsRef.cs
@@ -25510,6 +25510,127 @@ public string Message
}
+///
+/// Is sent by the server when: After the player requested to read a letter.
+/// Causes reaction on client side: The letter is opened in a new dialog.
+///
+public readonly ref struct OpenLetterExtendedRef
+{
+ private readonly Span _data;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The underlying data.
+ public OpenLetterExtendedRef(Span data)
+ : this(data, true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The underlying data.
+ /// If set to true, the header data is automatically initialized and written to the underlying span.
+ private OpenLetterExtendedRef(Span data, bool initialize)
+ {
+ this._data = data;
+ if (initialize)
+ {
+ var header = this.Header;
+ header.Type = HeaderType;
+ header.Code = Code;
+ header.Length = (ushort)data.Length;
+ }
+ }
+
+ ///
+ /// Gets the header type of this data packet.
+ ///
+ public static byte HeaderType => 0xC4;
+
+ ///
+ /// Gets the operation code of this data packet.
+ ///
+ public static byte Code => 0xC7;
+
+ ///
+ /// Gets the header of this packet.
+ ///
+ public C4HeaderRef Header => new (this._data);
+
+ ///
+ /// Gets or sets the letter index.
+ ///
+ public ushort LetterIndex
+ {
+ get => ReadUInt16LittleEndian(this._data[4..]);
+ set => WriteUInt16LittleEndian(this._data[4..], value);
+ }
+
+ ///
+ /// Gets or sets the sender appearance.
+ ///
+ public Span SenderAppearance
+ {
+ get => this._data.Slice(6, 42);
+ }
+
+ ///
+ /// Gets or sets the rotation.
+ ///
+ public byte Rotation
+ {
+ get => this._data[48];
+ set => this._data[48] = value;
+ }
+
+ ///
+ /// Gets or sets the animation.
+ ///
+ public byte Animation
+ {
+ get => this._data[49];
+ set => this._data[49] = value;
+ }
+
+ ///
+ /// Gets or sets the message.
+ ///
+ public string Message
+ {
+ get => this._data.ExtractString(50, this._data.Length - 50, System.Text.Encoding.UTF8);
+ set => this._data.Slice(50).WriteString(value, System.Text.Encoding.UTF8);
+ }
+
+ ///
+ /// Performs an implicit conversion from a Span of bytes to a .
+ ///
+ /// The packet as span.
+ /// The packet as struct.
+ public static implicit operator OpenLetterExtendedRef(Span packet) => new (packet, false);
+
+ ///
+ /// Performs an implicit conversion from to a Span of bytes.
+ ///
+ /// The packet as struct.
+ /// The packet as byte span.
+ public static implicit operator Span(OpenLetterExtendedRef packet) => packet._data;
+
+ ///
+ /// Calculates the size of the packet for the specified field content.
+ ///
+ /// The content of the variable 'Message' field from which the size will be calculated.
+ public static int GetRequiredSize(string content) => System.Text.Encoding.UTF8.GetByteCount(content) + 1 + 50;
+
+ ///
+ /// Calculates the size of the packet for the specified field content.
+ ///
+ /// The content length in bytes of the variable 'Message' field from which the size will be calculated.
+ public static int GetRequiredSize(int contentLength) => contentLength + 1 + 50;
+}
+
+
///
/// Is sent by the server when: After a letter has been deleted by the request of the player.
/// Causes reaction on client side: The letter is removed from the letter list.