From 3d67aecc175f6387a69ca25a6918e2a55b0592cf Mon Sep 17 00:00:00 2001 From: meli <57847713+ff-meli@users.noreply.github.com> Date: Thu, 13 Feb 2020 14:52:41 -0800 Subject: [PATCH] Update SeString Payload handling to make explicit payloads for all chunk types, including unknown ones. Also update ItemPayload to properly handle encoding the item name if present. These changes allow an incoming string to be parsed into payloads, and then fully reconstructed into a valid string, with or without changes. --- Dalamud/Game/Chat/SeStringHandling/Payload.cs | 8 ++- .../Game/Chat/SeStringHandling/PayloadType.cs | 6 ++- .../SeStringHandling/Payloads/ItemPayload.cs | 49 +++++++++++++++++- .../SeStringHandling/Payloads/RawPayload.cs | 51 +++++++++++++++++++ 4 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 Dalamud/Game/Chat/SeStringHandling/Payloads/RawPayload.cs diff --git a/Dalamud/Game/Chat/SeStringHandling/Payload.cs b/Dalamud/Game/Chat/SeStringHandling/Payload.cs index f5ff7aed0..fbbaf2ab8 100644 --- a/Dalamud/Game/Chat/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Chat/SeStringHandling/Payload.cs @@ -62,17 +62,21 @@ namespace Dalamud.Game.Chat.SeStringHandling case EmbeddedInfoType.Status: payload = new StatusPayload(); break; + case EmbeddedInfoType.LinkTerminator: - // Does not need to be handled - break; + // this has no custom handling and so needs to fallthrough to ensure it is captured default: Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType); + // rewind so we capture the Interactable byte in the raw data + reader.BaseStream.Seek(-1, SeekOrigin.Current); + payload = new RawPayload((byte)chunkType); break; } } break; default: Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); + payload = new RawPayload((byte)chunkType); break; } diff --git a/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs b/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs index 87a6a0461..6576e0524 100644 --- a/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs +++ b/Dalamud/Game/Chat/SeStringHandling/PayloadType.cs @@ -26,6 +26,10 @@ namespace Dalamud.Game.Chat.SeStringHandling /// /// An SeString payload representing raw, typed text. /// - RawText + RawText, + /// + /// An SeString payload representing any data we don't handle. + /// + Unknown } } diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs index d8bad2c0b..1a1dc0bb7 100644 --- a/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/ItemPayload.cs @@ -36,10 +36,21 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads { var actualItemId = IsHQ ? ItemId + 1000000 : ItemId; var idBytes = MakeInteger(actualItemId); + bool hasName = !string.IsNullOrEmpty(ItemName); var itemIdFlag = IsHQ ? IntegerType.Int16Plus1Million : IntegerType.Int16; var chunkLen = idBytes.Length + 5; + if (hasName) + { + // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself + chunkLen += (1 + 1 + ItemName.Length); + if (IsHQ) + { + chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space + } + } + var bytes = new List() { START_BYTE, @@ -48,7 +59,32 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads }; bytes.AddRange(idBytes); // unk - bytes.AddRange(new byte[] { 0x02, 0x01, END_BYTE }); + bytes.AddRange(new byte[] { 0x02, 0x01 }); + + // Links don't have to include the name, but if they do, it requires additional work + if (hasName) + { + var nameLen = ItemName.Length + 1; + if (IsHQ) + { + nameLen += 4; // space plus 3 bytes for HQ symbol + } + + bytes.AddRange(new byte[] + { + 0xFF, // unk + (byte)nameLen + }); + bytes.AddRange(Encoding.UTF8.GetBytes(ItemName)); + + if (IsHQ) + { + // space and HQ symbol + bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC }); + } + } + + bytes.Add(END_BYTE); return bytes.ToArray(); } @@ -74,7 +110,16 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads reader.ReadBytes(3); var itemNameLen = GetInteger(reader); - ItemName = Encoding.UTF8.GetString(reader.ReadBytes(itemNameLen)); + var itemNameBytes = reader.ReadBytes(itemNameLen); + + // HQ items have the HQ symbol as part of the name, but since we already recorded + // the HQ flag, we want just the bare name + if (IsHQ) + { + itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray(); + } + + ItemName = Encoding.UTF8.GetString(itemNameBytes); } } } diff --git a/Dalamud/Game/Chat/SeStringHandling/Payloads/RawPayload.cs b/Dalamud/Game/Chat/SeStringHandling/Payloads/RawPayload.cs new file mode 100644 index 000000000..77480291b --- /dev/null +++ b/Dalamud/Game/Chat/SeStringHandling/Payloads/RawPayload.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Dalamud.Game.Chat.SeStringHandling.Payloads +{ + public class RawPayload : Payload + { + public override PayloadType Type => PayloadType.Unknown; + + public byte ChunkType { get; private set; } + public byte[] Data { get; private set; } + + public RawPayload(byte chunkType) + { + ChunkType = chunkType; + } + + public override void Resolve() + { + // nothing to do + } + + public override byte[] Encode() + { + var chunkLen = Data.Length + 1; + + var bytes = new List() + { + START_BYTE, + ChunkType, + (byte)chunkLen + }; + bytes.AddRange(Data); + + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + public override string ToString() + { + return $"{Type} - Chunk type: {ChunkType:X}, Data: {BitConverter.ToString(Data).Replace("-", " ")}"; + } + + protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream) + { + Data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); + } + } +}