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)); + } + } +}