From d856f657c2b241cd16e2dc7e5c8836643d08cc7f Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 17 Nov 2024 14:59:39 +0100 Subject: [PATCH 01/23] [master] Update ClientStructs (#2101) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 33a98af53..6ef1f01dc 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 33a98af530e52d5b54714ec9f7704c07bf9fdd91 +Subproject commit 6ef1f01dc12162e271b65705990b8175330806ca From d538ce6350ac464145b42cf90e1222ee133d38bf Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:46:17 +0100 Subject: [PATCH 02/23] Update ClientStructs (#2103) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6ef1f01dc..76b8b8982 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6ef1f01dc12162e271b65705990b8175330806ca +Subproject commit 76b8b8982b57cd85eed5a5f509feee0442ca1b6f From 2f502767386320d84192cc6acd2971d17931e31a Mon Sep 17 00:00:00 2001 From: Infi Date: Tue, 19 Nov 2024 02:29:53 +0100 Subject: [PATCH 03/23] Switch PlayerPayload Encode/Decode to using Lumina (#2107) * Switch PlayerPayload Encode/Decode to using Lumina - This also fixes a bug that PlayerPayload would encode worldIds as byte (they are ushort) * Remove comment --- .../Payloads/PlayerPayload.cs | 59 +++++++------------ 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index 07a13e5a3..55697782e 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -6,6 +6,9 @@ using Dalamud.Data; using Lumina.Excel; using Lumina.Excel.Sheets; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -84,50 +87,32 @@ public class PlayerPayload : Payload /// protected override byte[] EncodeImpl() { - var chunkLen = this.playerName.Length + 7; - var bytes = new List() - { - START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, - /* unk */ 0x01, - (byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually - /* unk */ 0x01, - /* unk */ 0xFF, // these sometimes vary but are frequently this - (byte)(this.playerName.Length + 1), - }; - - bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); - bytes.Add(END_BYTE); - - // TODO: should these really be here? additional payloads should come in separately already... - - // encoded names are followed by the name in plain text again - // use the payload parsing for consistency, as this is technically a new chunk - bytes.AddRange(new TextPayload(this.playerName).Encode()); - - // unsure about this entire packet, but it seems to always follow a name - bytes.AddRange(new byte[] - { - START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator, - 0x01, 0x01, 0x01, 0xFF, 0x01, - END_BYTE, - }); - - return bytes.ToArray(); + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + var res = ssb + .PushLinkCharacter(this.playerName, this.serverId) + .Append(this.playerName) + .PopLink() + .ToArray(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); + return res; } /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - // unk - reader.ReadByte(); + var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); + var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan()); - this.serverId = GetInteger(reader); + if (!rosps.TryGetExpression(out _, out var worldIdExpression, out _, out var characterNameExpression)) + return; - // unk - reader.ReadBytes(2); + if (!worldIdExpression.TryGetUInt(out var worldId)) + return; - var nameLen = (int)GetInteger(reader); - this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); + if (!characterNameExpression.TryGetString(out var characterName)) + return; + + this.serverId = worldId; + this.playerName = characterName.ExtractText(); } } From 192dc9c3c3b37b12faac4d2776f42c21365c98e0 Mon Sep 17 00:00:00 2001 From: ItsBexy <103910869+ItsBexy@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:30:12 -0700 Subject: [PATCH 04/23] Update Util.ShowStruct() (#2104) - Now prints field offsets, if/when they are defined. - Fixed a bug wherein Boolean fields were being printed with incorrect values (now tries reading the value as a byte, which seems to do the trick) --- Dalamud/Utility/Util.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 15327a66c..805532025 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -1203,6 +1203,8 @@ public static class Util .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); + if (fixedBuffer != null) { ImGui.Text($"fixed"); @@ -1212,6 +1214,11 @@ public static class Util } else { + if (offset != null) + { + ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.SameLine(); + } ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); } @@ -1224,6 +1231,8 @@ public static class Util { if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct + else if (f.FieldType == typeof(bool) && offset != null) + ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); else ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); } From bf7ef00ec0ba2733628defc6d7e2b4764ebbe3e2 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 19 Nov 2024 02:30:47 +0100 Subject: [PATCH 05/23] Update ClientStructs (#2105) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 76b8b8982..897f9996d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 76b8b8982b57cd85eed5a5f509feee0442ca1b6f +Subproject commit 897f9996dfa65046c8cf2848c4f783c9ce3b858e From 3a3d6b6e6ad33d135615f8f11673e233657922e5 Mon Sep 17 00:00:00 2001 From: Infi Date: Tue, 19 Nov 2024 03:05:28 +0100 Subject: [PATCH 06/23] Implement Print methods that work with Lumina SeString/ReadOnlySeString (#2106) * Implement Print methods that work with Lumina SeString/ReadOnlySeString * null terminate before passing to Utf8String * Rename XivChatEntryReadOnly to XivChatEntryRaw * Fix error from wrong conversion method * Follow Rider suggestion * Switch from AppendMacroString to BeginMacro for optimization * More optimization suggested by kizer * More kizer suggested optimizations * Fix small mistake * Use XivChatEntry and read/write to Byte fields accordingly --- Dalamud/Game/Gui/ChatGui.cs | 117 ++++++++++++++++++---------- Dalamud/Game/Text/XivChatEntry.cs | 22 +++++- Dalamud/Plugin/Services/IChatGui.cs | 24 +++++- 3 files changed, 118 insertions(+), 45 deletions(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index fefc82790..84622c5e8 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -8,9 +8,11 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Memory; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -20,6 +22,8 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text.Payloads; + using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType; using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType; @@ -107,6 +111,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.handleLinkClickHook.Dispose(); } + #region DalamudSeString + /// public void Print(XivChatEntry chat) { @@ -137,6 +143,24 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); } + #endregion + + #region LuminaSeString + + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor); + } + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); + } + + #endregion + /// /// Process a chat queue. /// @@ -145,30 +169,35 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui while (this.chatQueue.Count > 0) { var chat = this.chatQueue.Dequeue(); - var replacedMessage = new SeStringBuilder(); + var sb = LuminaSeStringBuilder.SharedPool.Get(); + var rosss = (ReadOnlySeStringSpan)chat.MessageBytes; - // Normalize Unicode NBSP to the built-in one, as the former won't renderl - foreach (var payload in chat.Message.Payloads) + foreach (var payload in rosss) { - if (payload is TextPayload { Text: not null } textPayload) + if (payload.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (payload.Type != ReadOnlySePayloadType.Text) { - var split = textPayload.Text.Split("\u202f"); // NARROW NO-BREAK SPACE - for (var i = 0; i < split.Length; i++) - { - replacedMessage.AddText(split[i]); - if (i + 1 < split.Length) - replacedMessage.Add(new RawPayload([0x02, (byte)Lumina.Text.Payloads.PayloadType.Indent, 0x01, 0x03])); - } + sb.Append(payload); + continue; } - else + + foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default)) { - replacedMessage.Add(payload); + if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); + else + sb.Append(c.EffectiveChar); } } - var sender = Utf8String.FromSequence(chat.Name.Encode()); - var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode()); - + var output = sb.ToArray(); + LuminaSeStringBuilder.SharedPool.Return(sb); + + var sender = Utf8String.FromSequence(chat.NameBytes.NullTerminate()); + var message = Utf8String.FromSequence(output.NullTerminate()); + var targetChannel = chat.Type ?? this.configuration.GeneralChatType; this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); @@ -228,29 +257,6 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } } - private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color) - { - var builder = new SeStringBuilder(); - - if (!tag.IsNullOrEmpty()) - { - if (color is not null) - { - builder.AddUiForeground($"[{tag}] ", color.Value); - } - else - { - builder.AddText($"[{tag}] "); - } - } - - this.Print(new XivChatEntry - { - Message = builder.AddText(message).Build(), - Type = channel, - }); - } - private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color) { var builder = new SeStringBuilder(); @@ -274,6 +280,31 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui }); } + private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) + { + var builder = new LuminaSeStringBuilder(); + + if (!tag.IsNullOrEmpty()) + { + if (color is not null) + { + builder.PushColorType(color.Value); + builder.Append($"[{tag}] "); + builder.PopColorType(); + } + else + { + builder.Append($"[{tag}] "); + } + } + + this.Print(new XivChatEntry + { + MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(), + Type = channel, + }); + } + private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) { this.inventoryItemCopyHook.Original(thisPtr, otherPtr); @@ -505,6 +536,14 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null) => this.chatGuiService.PrintError(message, messageTag, tagColor); + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.Print(message, messageTag, tagColor); + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.PrintError(message, messageTag, tagColor); + private void OnMessageForward(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) => this.ChatMessage?.Invoke(type, timestamp, ref sender, ref message, ref isHandled); diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index eb40d6636..7932ead72 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -20,12 +20,30 @@ public sealed class XivChatEntry /// /// Gets or sets the sender name. /// - public SeString Name { get; set; } = string.Empty; + public SeString Name + { + get => SeString.Parse(this.NameBytes); + set => this.NameBytes = value.Encode(); + } /// /// Gets or sets the message. /// - public SeString Message { get; set; } = string.Empty; + public SeString Message + { + get => SeString.Parse(this.MessageBytes); + set => this.MessageBytes = value.Encode(); + } + + /// + /// Gets or Sets the name payloads + /// + public byte[] NameBytes { get; set; } = []; + + /// + /// Gets or Sets the message payloads. + /// + public byte[] MessageBytes { get; set; } = []; /// /// Gets or sets a value indicating whether new message sounds should be silenced or not. diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 42bbd6b06..3f221b3bb 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -48,7 +48,7 @@ public interface IChatGui /// The sender name. /// The message sent. public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message); - + /// /// Event that will be fired when a chat message is sent to chat by the game. /// @@ -68,17 +68,17 @@ public interface IChatGui /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. /// public event OnMessageUnhandledDelegate ChatMessageUnhandled; - + /// /// Gets the ID of the last linked item. /// public uint LastLinkedItemId { get; } - + /// /// Gets the flags of the last linked item. /// public byte LastLinkedItemFlags { get; } - + /// /// Gets the dictionary of Dalamud Link Handlers. /// @@ -121,4 +121,20 @@ public interface IChatGui /// String to prepend message with "[messageTag] ". /// Color to display the message tag with. public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); } From c831af5a00b4cc69292aa598c92efb17e8542d6b Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:46:42 +0100 Subject: [PATCH 07/23] Update ClientStructs (#2109) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 897f9996d..6fcd967c6 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 897f9996dfa65046c8cf2848c4f783c9ce3b858e +Subproject commit 6fcd967c6258d02eba8a4ddd6b2fd5fb97b727ad From 6d664cc606ccfa0b7cbdb739c7950076e2bbf05d Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:36:17 +0100 Subject: [PATCH 08/23] Update ClientStructs (#2110) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6fcd967c6..c9d1f33c5 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6fcd967c6258d02eba8a4ddd6b2fd5fb97b727ad +Subproject commit c9d1f33c58f426b6480b2c00e5cdcb0668654973 From d19b7d70d5f1586154946c1db346e08352e71de2 Mon Sep 17 00:00:00 2001 From: srkizer Date: Thu, 21 Nov 2024 02:32:49 +0900 Subject: [PATCH 09/23] Move UtfEnumerator from Dalamud to Lumina (#2111) * Move UtfEnumerator from Dalamud to Lumina Comes with some trivial cleanups. * Update Lumina to 5.5.0 --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 2 +- Dalamud/Dalamud.csproj | 2 +- Dalamud/Game/Gui/ChatGui.cs | 96 +-- .../Game/Text/SeStringHandling/SeString.cs | 4 +- .../Internal/SeStringRenderer.cs | 4 +- .../TextProcessing/LineBreakEnumerator.cs | 2 + .../Internal/TextProcessing/UtfEnumerator.cs | 325 --------- .../TextProcessing/UtfEnumeratorFlags.cs | 58 -- .../Internal/TextProcessing/UtfValue.cs | 665 ------------------ .../Widgets/SeStringRendererTestWidget.cs | 8 +- 10 files changed, 63 insertions(+), 1103 deletions(-) delete mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs delete mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs delete mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 3f0ed53f4..754124ae1 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,7 +27,7 @@ - + diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 79f80ea8c..9399154c8 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,7 +71,7 @@ - + diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 84622c5e8..14346132a 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -8,7 +8,6 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; -using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -22,12 +21,13 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text; using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; -using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType; -using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; -using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType; -using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; +using SeString = Dalamud.Game.Text.SeStringHandling.SeString; +using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; namespace Dalamud.Game.Gui; @@ -166,45 +166,51 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui /// public void UpdateQueue() { - while (this.chatQueue.Count > 0) + if (this.chatQueue.Count == 0) + return; + + var sb = LSeStringBuilder.SharedPool.Get(); + Span namebuf = stackalloc byte[256]; + using var sender = new Utf8String(); + using var message = new Utf8String(); + while (this.chatQueue.TryDequeue(out var chat)) { - var chat = this.chatQueue.Dequeue(); - var sb = LuminaSeStringBuilder.SharedPool.Get(); - var rosss = (ReadOnlySeStringSpan)chat.MessageBytes; - - foreach (var payload in rosss) + sb.Clear(); + foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString)) { - if (payload.Type == ReadOnlySePayloadType.Invalid) - continue; - - if (payload.Type != ReadOnlySePayloadType.Text) - { - sb.Append(payload); - continue; - } - - foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default)) - { - if (c.Value.IntValue == 0x202F) - sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); - else - sb.Append(c.EffectiveChar); - } + if (c.IsSeStringPayload) + sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength)); + else if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); + else + sb.Append(c); } - var output = sb.ToArray(); - LuminaSeStringBuilder.SharedPool.Return(sb); + if (chat.NameBytes.Length + 1 < namebuf.Length) + { + chat.NameBytes.AsSpan().CopyTo(namebuf); + namebuf[chat.NameBytes.Length] = 0; + sender.SetString(namebuf); + } + else + { + sender.SetString(chat.NameBytes.NullTerminate()); + } - var sender = Utf8String.FromSequence(chat.NameBytes.NullTerminate()); - var message = Utf8String.FromSequence(output.NullTerminate()); + message.SetString(sb.GetViewAsSpan()); var targetChannel = chat.Type ?? this.configuration.GeneralChatType; - this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); - - sender->Dtor(true); - message->Dtor(true); + this.HandlePrintMessageDetour( + RaptureLogModule.Instance(), + targetChannel, + &sender, + &message, + chat.Timestamp, + (byte)(chat.Silent ? 1 : 0)); } + + LSeStringBuilder.SharedPool.Return(sb); } /// @@ -282,27 +288,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) { - var builder = new LuminaSeStringBuilder(); + var sb = LSeStringBuilder.SharedPool.Get(); if (!tag.IsNullOrEmpty()) { if (color is not null) { - builder.PushColorType(color.Value); - builder.Append($"[{tag}] "); - builder.PopColorType(); + sb.PushColorType(color.Value); + sb.Append($"[{tag}] "); + sb.PopColorType(); } else { - builder.Append($"[{tag}] "); + sb.Append($"[{tag}] "); } } this.Print(new XivChatEntry { - MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(), + MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(), Type = channel, }); + + LSeStringBuilder.SharedPool.Return(sb); } private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) @@ -412,7 +420,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); - var sb = LuminaSeStringBuilder.SharedPool.Get(); + var sb = LSeStringBuilder.SharedPool.Get(); try { var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload); @@ -423,7 +431,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui sb.Append(payload); if (payload.Type == ReadOnlySePayloadType.Macro && - payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link && + payload.MacroCode == MacroCode.Link && payload.TryGetExpression(out var expr1) && expr1.TryGetInt(out var expr1Val) && expr1Val == (int)LinkMacroPayloadType.Terminator) @@ -452,7 +460,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } finally { - LuminaSeStringBuilder.SharedPool.Return(sb); + LSeStringBuilder.SharedPool.Return(sb); } } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index baae181e3..7f1955da5 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -133,9 +133,7 @@ public class SeString { while (stream.Position < len) { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); + payloads.Add(Payload.Decode(reader)); } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 9d62de193..4937e4af0 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -19,6 +19,7 @@ using FFXIVClientStructs.FFXIV.Client.UI; using ImGuiNET; using Lumina.Excel.Sheets; +using Lumina.Text; using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -66,8 +67,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService [ServiceManager.ServiceConstructor] private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) { - this.colorStackSet = new( - dm.Excel.GetSheet() ?? throw new InvalidOperationException("Failed to access UIColor sheet.")); + this.colorStackSet = new(dm.Excel.GetSheet()); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs index fa994bcd2..8b5115369 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Lumina.Text; + using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass; using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory; using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass; diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs deleted file mode 100644 index b73bc85e4..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System.Collections; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -using Lumina.Text; -using Lumina.Text.Payloads; -using Lumina.Text.ReadOnly; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Enumerates a UTF-N byte sequence by codepoint. -[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")] -internal ref struct UtfEnumerator -{ - private readonly ReadOnlySpan data; - private readonly UtfEnumeratorFlags flags; - private readonly byte numBytesPerUnit; - private bool isBigEndian; - - /// Initializes a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - public UtfEnumerator(ReadOnlySpan data, UtfEnumeratorFlags flags) - { - this.data = data; - this.flags = flags; - this.numBytesPerUnit = (this.flags & UtfEnumeratorFlags.UtfMask) switch - { - UtfEnumeratorFlags.Utf8 or UtfEnumeratorFlags.Utf8SeString => 1, - UtfEnumeratorFlags.Utf16 => 2, - UtfEnumeratorFlags.Utf32 => 4, - _ => throw new ArgumentOutOfRangeException(nameof(this.flags), this.flags, "Multiple UTF flag specified."), - }; - this.isBigEndian = (flags & UtfEnumeratorFlags.EndiannessMask) switch - { - UtfEnumeratorFlags.NativeEndian => !BitConverter.IsLittleEndian, - UtfEnumeratorFlags.LittleEndian => false, - UtfEnumeratorFlags.BigEndian => true, - _ => throw new ArgumentOutOfRangeException(nameof(flags), flags, "Multiple endianness flag specified."), - }; - } - - /// - public Subsequence Current { get; private set; } = default; - - /// Creates a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - /// A new enumerator. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => new(data, flags); - - /// Creates a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - /// A new enumerator. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => - new(MemoryMarshal.Cast(data), flags); - - /// Gets the representative char for a given SeString macro code. - /// The macro code. - /// Representative char, or if none. - public static char RepresentativeCharFor(MacroCode macroCode) => macroCode switch - { - MacroCode.NewLine => '\u0085', - MacroCode.SoftHyphen => '\u00AD', - MacroCode.NonBreakingSpace => '\u00A0', - MacroCode.Hyphen => '-', - MacroCode.Icon or MacroCode.Icon2 => '\uFFFC', - _ => char.MaxValue, - }; - - /// Attempts to peek the next item. - /// Retrieved next item. - /// Whether it still should be parsed in big endian. - /// true if anything is retrieved. - /// The sequence is not a fully valid Unicode sequence, and - /// is set. - public readonly bool TryPeekNext(out Subsequence nextSubsequence, out bool isStillBigEndian) - { - var offset = this.Current.ByteOffset + this.Current.ByteLength; - isStillBigEndian = this.isBigEndian; - while (true) - { - var subspan = this.data[offset..]; - - if (subspan.IsEmpty) - { - nextSubsequence = default; - return false; - } - - UtfValue value; - int length; - var isBroken = - this.numBytesPerUnit switch - { - 1 => !UtfValue.TryDecode8(subspan, out value, out length), - 2 => !UtfValue.TryDecode16(subspan, isStillBigEndian, out value, out length), - 4 => !UtfValue.TryDecode32(subspan, isStillBigEndian, out value, out length), - _ => throw new InvalidOperationException(), - }; - if (!isBroken && value.IntValue == 0xFFFE) - { - if ((this.flags & UtfEnumeratorFlags.DisrespectByteOrderMask) == 0) - { - isStillBigEndian = !isStillBigEndian; - value = 0xFEFF; - } - - if ((this.flags & UtfEnumeratorFlags.YieldByteOrderMask) == 0) - { - offset += length; - continue; - } - } - - if (isBroken || !Rune.IsValid(value)) - { - switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) - { - case UtfEnumeratorFlags.ReplaceErrors: - break; - - case UtfEnumeratorFlags.IgnoreErrors: - offset = Math.Min(offset + this.numBytesPerUnit, this.data.Length); - continue; - - case UtfEnumeratorFlags.ThrowOnFirstError: - if (isBroken) - throw new EncoderFallbackException($"0x{subspan[0]:X02} is not a valid sequence."); - throw new EncoderFallbackException( - $"U+{value.UIntValue:X08} is not a valid unicode codepoint."); - - case UtfEnumeratorFlags.TerminateOnFirstError: - default: - nextSubsequence = default; - return false; - } - } - - if (isBroken) - value = subspan[0]; - - if (value == SeString.StartByte && (this.flags & UtfEnumeratorFlags.Utf8SeString) != 0) - { - var e = new ReadOnlySeStringSpan(subspan).GetEnumerator(); - e.MoveNext(); - switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) - { - case var _ when e.Current.Type is ReadOnlySePayloadType.Macro: - nextSubsequence = Subsequence.FromPayload( - e.Current.MacroCode, - offset, - e.Current.EnvelopeByteLength); - return true; - - case UtfEnumeratorFlags.ReplaceErrors: - value = '\uFFFE'; - length = e.Current.EnvelopeByteLength; - isBroken = true; - break; - - case UtfEnumeratorFlags.IgnoreErrors: - offset = Math.Min(offset + e.Current.EnvelopeByteLength, this.data.Length); - continue; - - case UtfEnumeratorFlags.ThrowOnFirstError: - throw new EncoderFallbackException("Invalid SeString payload."); - - case UtfEnumeratorFlags.TerminateOnFirstError: - default: - nextSubsequence = default; - return false; - } - } - - nextSubsequence = Subsequence.FromUnicode(value, offset, length, isBroken); - return true; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - if (!this.TryPeekNext(out var next, out var isStillBigEndian)) - return false; - - this.Current = next; - this.isBigEndian = isStillBigEndian; - return true; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfEnumerator GetEnumerator() => new(this.data, this.flags); - - /// A part of a UTF-N sequence containing one codepoint. - [StructLayout(LayoutKind.Explicit, Size = 16)] - [DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")] - public readonly struct Subsequence : IEquatable - { - /// The codepoint. Valid if is false. - [FieldOffset(0)] - public readonly UtfValue Value; - - /// The macro code. Valid if is true. - [FieldOffset(0)] - public readonly MacroCode MacroCode; - - /// The offset of this part of a UTF-8 sequence. - [FieldOffset(4)] - public readonly int ByteOffset; - - /// The length of this part of a UTF-8 sequence. - /// This may not match , if is true. - /// - [FieldOffset(8)] - public readonly int ByteLength; - - /// Whether this part of the UTF-8 sequence is broken. - [FieldOffset(12)] - public readonly bool BrokenSequence; - - /// Whether this part of the SeString sequence is a payload. - [FieldOffset(13)] - public readonly bool IsSeStringPayload; - - /// Storage at byte offset 0, for fast implementation. - [FieldOffset(0)] - private readonly ulong storage0; - - /// Storage at byte offset 8, for fast implementation. - [FieldOffset(8)] - private readonly ulong storage1; - - /// Initializes a new instance of the struct. - /// The value. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// Whether this part of the UTF-N sequence is broken. - /// Whether this part of the SeString sequence is a payload. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Subsequence(uint value, int byteOffset, int byteLength, bool brokenSequence, bool isSeStringPayload) - { - this.Value = new(value); - this.ByteOffset = byteOffset; - this.ByteLength = byteLength; - this.BrokenSequence = brokenSequence; - this.IsSeStringPayload = isSeStringPayload; - } - - /// Gets the effective char value, with invalid or non-representable codepoints replaced. - /// - /// if the character should not be displayed at all. - public char EffectiveChar - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue; - } - - /// Gets the effective int value, with invalid codepoints replaced. - /// if the character should not be displayed at all. - public int EffectiveInt => - this.IsSeStringPayload - ? RepresentativeCharFor(this.MacroCode) - : this.BrokenSequence || !this.Value.TryGetRune(out var rune) - ? 0xFFFD - : rune.Value; - - /// Gets the effective value, with invalid codepoints replaced. - /// if the character should not be displayed at all. - public Rune EffectiveRune - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.EffectiveInt); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Subsequence left, Subsequence right) => !left.Equals(right); - - /// Creates a new instance of the struct from a Unicode value. - /// The codepoint. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// Whether this part of the UTF-N sequence is broken. - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) => - new(codepoint, byteOffset, byteLength, brokenSequence, false); - - /// Creates a new instance of the struct from a SeString payload. - /// The macro code. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) => - new((uint)macroCode, byteOffset, byteLength, false, true); - - /// Tests whether this subsequence contains a valid Unicode codepoint. - /// true if this subsequence contains a valid Unicode codepoint. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1); - } -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs deleted file mode 100644 index 01380e40c..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Flags on enumerating a unicode sequence. -[Flags] -internal enum UtfEnumeratorFlags -{ - /// Use the default configuration of and . - Default = default, - - /// Enumerate as UTF-8 (the default.) - Utf8 = Default, - - /// Enumerate as UTF-8 in a SeString. - Utf8SeString = 1 << 1, - - /// Enumerate as UTF-16. - Utf16 = 1 << 2, - - /// Enumerate as UTF-32. - Utf32 = 1 << 3, - - /// Bitmask for specifying the encoding. - UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32, - - /// On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.) - ReplaceErrors = Default, - - /// On error, drop the invalid byte. - IgnoreErrors = 1 << 4, - - /// On error, stop the handling. - TerminateOnFirstError = 1 << 5, - - /// On error, throw an exception. - ThrowOnFirstError = 1 << 6, - - /// Bitmask for specifying the error handling mode. - ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError, - - /// Use the current system native endianness from - /// (the default.) - NativeEndian = Default, - - /// Use little endianness. - LittleEndian = 1 << 7, - - /// Use big endianness. - BigEndian = 1 << 8, - - /// Bitmask for specifying endianness. - EndiannessMask = NativeEndian | LittleEndian | BigEndian, - - /// Disrespect byte order mask. - DisrespectByteOrderMask = 1 << 9, - - /// Yield byte order masks, if it shows up. - YieldByteOrderMask = 1 << 10, -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs deleted file mode 100644 index 6930e6ba4..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs +++ /dev/null @@ -1,665 +0,0 @@ -using System.Buffers.Binary; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Represents a single value to be used in a UTF-N byte sequence. -[StructLayout(LayoutKind.Explicit, Size = 4)] -[DebuggerDisplay("0x{IntValue,h} ({CharValue})")] -internal readonly struct UtfValue : IEquatable, IComparable -{ - /// The unicode codepoint in int, that may not be in a valid range. - [FieldOffset(0)] - public readonly int IntValue; - - /// The unicode codepoint in uint, that may not be in a valid range. - [FieldOffset(0)] - public readonly uint UIntValue; - - /// The high UInt16 value in char, that may have been cut off if outside BMP. - [FieldOffset(0)] - public readonly char CharValue; - - /// Initializes a new instance of the struct. - /// The raw codepoint value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfValue(uint value) => this.UIntValue = value; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfValue(int value) => this.IntValue = value; - - /// Gets the length of this codepoint, encoded in UTF-8. - public int Length8 - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetEncodedLength8(this); - } - - /// Gets the length of this codepoint, encoded in UTF-16. - public int Length16 - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetEncodedLength16(this); - } - - /// Gets the short name, if supported. - /// The buffer containing the short name, or empty if unsupported. - public ReadOnlySpan ShortName - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetShortName(this); - } - - public static implicit operator uint(UtfValue c) => c.UIntValue; - - public static implicit operator int(UtfValue c) => c.IntValue; - - public static implicit operator UtfValue(byte c) => new(c); - - public static implicit operator UtfValue(sbyte c) => new(c); - - public static implicit operator UtfValue(ushort c) => new(c); - - public static implicit operator UtfValue(short c) => new(c); - - public static implicit operator UtfValue(uint c) => new(c); - - public static implicit operator UtfValue(int c) => new(c); - - public static implicit operator UtfValue(char c) => new(c); - - public static implicit operator UtfValue(Rune c) => new(c.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(UtfValue left, UtfValue right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(UtfValue left, UtfValue right) => !left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <(UtfValue left, UtfValue right) => left.CompareTo(right) < 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >(UtfValue left, UtfValue right) => left.CompareTo(right) > 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <=(UtfValue left, UtfValue right) => left.CompareTo(right) <= 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >=(UtfValue left, UtfValue right) => left.CompareTo(right) >= 0; - - /// Gets the short name of the codepoint, for some select codepoints. - /// The codepoint. - /// The value. - public static ReadOnlySpan GetShortName(int codepoint) => - codepoint switch - { - 0x00 => "NUL", - 0x01 => "SOH", - 0x02 => "STX", - 0x03 => "ETX", - 0x04 => "EOT", - 0x05 => "ENQ", - 0x06 => "ACK", - 0x07 => "BEL", - 0x08 => "BS", - 0x09 => "HT", - 0x0a => "LF", - 0x0b => "VT", - 0x0c => "FF", - 0x0d => "CR", - 0x0e => "SO", - 0x0f => "SI", - - 0x10 => "DLE", - 0x11 => "DC1", - 0x12 => "DC2", - 0x13 => "DC3", - 0x14 => "DC4", - 0x15 => "NAK", - 0x16 => "SYN", - 0x17 => "SOH", - 0x18 => "CAN", - 0x19 => "EOM", - 0x1a => "SUB", - 0x1b => "ESC", - 0x1c => "FS", - 0x1d => "GS", - 0x1e => "RS", - 0x1f => "US", - - 0x80 => "PAD", - 0x81 => "HOP", - 0x82 => "BPH", - 0x83 => "NBH", - 0x84 => "IND", - 0x85 => "NEL", - 0x86 => "SSA", - 0x87 => "ESA", - 0x88 => "HTS", - 0x89 => "HTJ", - 0x8a => "VTS", - 0x8b => "PLD", - 0x8c => "PLU", - 0x8d => "RI", - 0x8e => "SS2", - 0x8f => "SS3", - - 0x90 => "DCS", - 0x91 => "PU1", - 0x92 => "PU2", - 0x93 => "STS", - 0x94 => "CCH", - 0x95 => "MW", - 0x96 => "SPA", - 0x97 => "EPA", - 0x98 => "SOS", - 0x99 => "SGC", - 0x9a => "SCI", - 0x9b => "CSI", - 0x9c => "ST", - 0x9d => "OSC", - 0x9e => "PM", - 0x9f => "APC", - - 0xa0 => "NBSP", - 0xad => "SHY", - - _ => default, - }; - - /// Gets the length of the codepoint, when encoded in UTF-8. - /// The codepoint to encode. - /// The length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetEncodedLength8(int codepoint) => (uint)codepoint switch - { - < 1u << 7 => 1, - < 1u << 11 => 2, - < 1u << 16 => 3, - < 1u << 21 => 4, - // Not a valid Unicode codepoint anymore below. - < 1u << 26 => 5, - < 1u << 31 => 6, - _ => 7, - }; - - /// Gets the length of the codepoint, when encoded in UTF-16. - /// The codepoint to encode. - /// The length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetEncodedLength16(int codepoint) => (uint)codepoint switch - { - < 0x10000 => 2, - < 0x10000 + (1 << 20) => 4, - // Not a valid Unicode codepoint anymore below. - < 0x10000 + (1 << 30) => 6, - _ => 8, - }; - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode8(ref ReadOnlySpan source, out UtfValue value, out int length) - { - var v = TryDecode8(source, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-8 byte sequence. - /// The span to decode from. - /// The decoded value. - /// The length of the consumed bytes. 1 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static unsafe bool TryDecode8(ReadOnlySpan source, out UtfValue value, out int length) - { - if (source.IsEmpty) - { - value = default; - length = 0; - return false; - } - - fixed (byte* ptr = source) - { - if ((ptr[0] & 0x80) == 0) - { - length = 1; - value = ptr[0]; - } - else if ((ptr[0] & 0b11100000) == 0b11000000 && source.Length >= 2 - && ((uint)ptr[1] & 0b11000000) == 0b10000000) - { - length = 2; - value = (((uint)ptr[0] & 0x1F) << 6) | - (((uint)ptr[1] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11110000) == 0b11100000 && source.Length >= 3 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000) - { - length = 3; - value = (((uint)ptr[0] & 0x0F) << 12) | - (((uint)ptr[1] & 0x3F) << 6) | - (((uint)ptr[2] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111000) == 0b11110000 && source.Length >= 4 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000) - { - length = 4; - value = (((uint)ptr[0] & 0x07) << 18) | - (((uint)ptr[1] & 0x3F) << 12) | - (((uint)ptr[2] & 0x3F) << 6) | - (((uint)ptr[3] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111100) == 0b11111000 && source.Length >= 5 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000) - { - length = 5; - value = (((uint)ptr[0] & 0x03) << 24) | - (((uint)ptr[1] & 0x3F) << 18) | - (((uint)ptr[2] & 0x3F) << 12) | - (((uint)ptr[3] & 0x3F) << 6) | - (((uint)ptr[4] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111110) == 0b11111100 && source.Length >= 6 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000 - && ((uint)ptr[5] & 0b11000000) == 0b10000000) - { - length = 6; - value = (((uint)ptr[0] & 0x01) << 30) | - (((uint)ptr[1] & 0x3F) << 24) | - (((uint)ptr[2] & 0x3F) << 18) | - (((uint)ptr[3] & 0x3F) << 12) | - (((uint)ptr[4] & 0x3F) << 6) | - (((uint)ptr[5] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111111) == 0b11111110 && source.Length >= 7 - && ((uint)ptr[1] & 0b11111100) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000 - && ((uint)ptr[5] & 0b11000000) == 0b10000000 - && ((uint)ptr[6] & 0b11000000) == 0b10000000) - { - length = 7; - value = (((uint)ptr[1] & 0x03) << 30) | - (((uint)ptr[2] & 0x3F) << 24) | - (((uint)ptr[3] & 0x3F) << 18) | - (((uint)ptr[4] & 0x3F) << 12) | - (((uint)ptr[5] & 0x3F) << 6) | - (((uint)ptr[6] & 0x3F) << 0); - } - else - { - length = 1; - value = default; - return false; - } - - return true; - } - } - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode16(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - var v = TryDecode16(source, be, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-16 byte sequence. - /// The span to decode from. - /// Whether to use big endian. - /// The decoded value. - /// The length of the consumed bytes. 1 if cut short. - /// 2 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static unsafe bool TryDecode16(ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - if (source.Length < 2) - { - value = default; - length = source.Length; - return false; - } - - fixed (byte* ptr = source) - { - var p16 = (ushort*)ptr; - var val = be == BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(*p16) : *p16; - if (char.IsHighSurrogate((char)val)) - { - var lookahead1 = source.Length >= 4 ? p16[1] : 0; - var lookahead2 = source.Length >= 6 ? p16[2] : 0; - var lookahead3 = source.Length >= 8 ? p16[3] : 0; - if (char.IsLowSurrogate((char)lookahead1)) - { - // Not a valid Unicode codepoint anymore inside the block below. - if (char.IsLowSurrogate((char)lookahead2)) - { - if (char.IsLowSurrogate((char)lookahead3)) - { - value = 0x10000 - + (((val & 0x3) << 30) | - ((lookahead1 & 0x3FF) << 20) | - ((lookahead2 & 0x3FF) << 10) | - ((lookahead3 & 0x3FF) << 0)); - length = 8; - return true; - } - - value = 0x10000 - + (((val & 0x3FF) << 20) | - ((lookahead1 & 0x3FF) << 10) | - ((lookahead2 & 0x3FF) << 0)); - length = 6; - return true; - } - - value = 0x10000 + - (((val & 0x3FF) << 10) | - ((lookahead1 & 0x3FF) << 0)); - length = 4; - return true; - } - } - - // Calls are supposed to handle unpaired surrogates. - value = val; - length = 2; - return true; - } - } - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode32(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - var v = TryDecode32(source, be, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-32 byte sequence. - /// The span to decode from. - /// Whether to use big endian. - /// The decoded value. - /// The length of the consumed bytes. 1 to 3 if cut short. - /// 4 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static bool TryDecode32(ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - if (source.Length < 4) - { - value = default; - length = source.Length; - return false; - } - - length = 4; - if ((be && BinaryPrimitives.TryReadInt32BigEndian(source, out var i32)) - || (!be && BinaryPrimitives.TryReadInt32LittleEndian(source, out i32))) - { - value = i32; - return true; - } - - value = default; - return false; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The target stream. - /// The codepoint to encode. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode8(Stream target, int codepoint) - { - Span buf = stackalloc byte[7]; - Encode8(buf, codepoint, out var length); - target.Write(buf[..length]); - return length; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The target byte span. - /// The codepoint to encode. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode8(ref Span target, int codepoint) - { - target = Encode8(target, codepoint, out var length); - return length; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The optional target byte span. - /// The codepoint to encode. - /// The length of the encoded data. - /// The remaning region of . - public static Span Encode8(Span target, int codepoint, out int length) - { - var value = (uint)codepoint; - length = GetEncodedLength8(codepoint); - if (target.IsEmpty) - return target; - - switch (length) - { - case 1: - target[0] = (byte)value; - return target[1..]; - case 2: - target[0] = (byte)(0xC0 | ((value >> 6) & 0x1F)); - target[1] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[2..]; - case 3: - target[0] = (byte)(0xE0 | ((value >> 12) & 0x0F)); - target[1] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[3..]; - case 4: - target[0] = (byte)(0xF0 | ((value >> 18) & 0x07)); - target[1] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[4..]; - case 5: - target[0] = (byte)(0xF8 | ((value >> 24) & 0x03)); - target[1] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[5..]; - case 6: - target[0] = (byte)(0xFC | ((value >> 30) & 0x01)); - target[1] = (byte)(0x80 | ((value >> 24) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[5] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[6..]; - case 7: - target[0] = 0xFE; - target[1] = (byte)(0x80 | ((value >> 30) & 0x03)); - target[2] = (byte)(0x80 | ((value >> 24) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[5] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[6] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[7..]; - default: - Debug.Assert(false, $"{nameof(Length8)} property should have produced all possible cases."); - return target; - } - } - - /// Encodes the codepoint to the target in UTF-16. - /// The target stream. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode16(Stream target, int codepoint, bool be) - { - Span buf = stackalloc byte[8]; - Encode16(buf, codepoint, be, out var length); - target.Write(buf[..length]); - return length; - } - - /// Encodes the codepoint to the target in UTF-16. - /// The target byte span. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode16(ref Span target, int codepoint, bool be) - { - target = Encode16(target, codepoint, be, out var length); - return length; - } - - /// Encodes the codepoint to the target in UTF-16. - /// The optional target byte span. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// The remaning region of . - public static Span Encode16(Span target, int codepoint, bool be, out int length) - { - var value = (uint)codepoint; - length = GetEncodedLength16(codepoint); - if (target.IsEmpty) - return target; - - if (be) - { - switch (length) - { - case 2: - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)value); - return target[2..]; - case 4: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[4..]; - case 6: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[6..]; - case 8: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[8..]; - default: - Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); - return target; - } - } - - switch (length) - { - case 2: - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)value); - return target[2..]; - case 4: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[4..]; - case 6: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[6..]; - case 8: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[8..]; - default: - Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); - return target; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(UtfValue other) => this.IntValue == other.IntValue; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.IntValue; - - /// Attempts to get the corresponding rune. - /// The retrieved rune. - /// true if retrieved. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetRune(out Rune rune) - { - if (Rune.IsValid(this.IntValue)) - { - rune = new(this.IntValue); - return true; - } - - rune = default; - return false; - } - - /// Encodes the codepoint to the target. - /// The target byte span. - /// The remaning region of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Encode8(Span target) => Encode8(target, this, out _); -} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index f4b76942f..e026a6d2f 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Numerics; using System.Text; @@ -34,7 +33,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; private ImVectorWrapper testStringBuffer; private string testString = string.Empty; - private ExcelSheet addons; + private ExcelSheet addons = null!; private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; @@ -241,8 +240,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Button("Print to Chat Log")) { - fixed (byte* p = Service.Get().CompileAndCache(this.testString).Data.Span) - Service.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p)); + Service.Get().Print( + Game.Text.SeStringHandling.SeString.Parse( + Service.Get().CompileAndCache(this.testString).Data.Span)); } ImGuiHelpers.ScaledDummy(3); From dbbc2306dd6f3366afe618e048e64b6e9b8e1ac6 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 21 Nov 2024 00:54:05 +0100 Subject: [PATCH 10/23] NRE check LinkData in HandleLinkClickDetour (#2112) --- Dalamud/Game/Gui/ChatGui.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 14346132a..791cbb97a 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -412,7 +412,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData) { - if ((Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink) + if (linkData == null || linkData->Payload == null || (Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink) { this.handleLinkClickHook.Original(thisPtr, linkData); return; From 2fd8e49b34a62985505f8d2c55d0d55405ab5ddf Mon Sep 17 00:00:00 2001 From: Asriel Date: Sun, 24 Nov 2024 07:06:25 -0800 Subject: [PATCH 11/23] Update to Lumina 5.6.0 and Lumina.Excel 7.1.3 (#2118) --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 ++-- Dalamud/Dalamud.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 754124ae1..b85607f0f 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 9399154c8..338c231fe 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all From de999b78959c568d5ca053ab092c214969064022 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Sun, 24 Nov 2024 21:35:00 +0100 Subject: [PATCH 12/23] Fix IsPvPExcludingDen (#2120) --- Dalamud/Game/ClientState/ClientState.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 4ab69b391..c898e107a 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -137,7 +137,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public bool IsPvP { get; private set; } /// - public bool IsPvPExcludingDen { get; private set; } + public bool IsPvPExcludingDen => this.IsPvP && this.TerritoryType != 250; /// public bool IsGPosing => GameMain.IsInGPose(); @@ -195,7 +195,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState if (isPvP != this.IsPvP) { this.IsPvP = isPvP; - this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; if (this.IsPvP) { From c950b15a22d074a49e013fef94561b61509fa672 Mon Sep 17 00:00:00 2001 From: ItsBexy <103910869+ItsBexy@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:37:39 -0700 Subject: [PATCH 13/23] Minor interface adjustments (#2121) - Further ImRaii safety in UiDebug2 - Set some mistakenly internal methods in ImGuiComponents to public - Added SpanFullWidth flag to trees in Util.ShowStruct --- .../Components/ImGuiComponents.IconButton.cs | 2 +- .../ImGuiComponents.IconButtonSelect.cs | 4 +- .../UiDebug2/Browsing/AddonTree.AtkValues.cs | 10 +- .../Internal/UiDebug2/Browsing/Events.cs | 6 +- .../UiDebug2/Browsing/NodeTree.Editor.cs | 3 +- .../UiDebug2/Browsing/NodeTree.Image.cs | 5 +- .../Browsing/NodeTree.NineGrid.Offsets.cs | 69 ---- .../UiDebug2/Browsing/NodeTree.NineGrid.cs | 60 ++++ .../UiDebug2/Browsing/NodeTree.Res.cs | 8 +- .../UiDebug2/Browsing/NodeTree.Text.cs | 2 +- .../UiDebug2/Browsing/TimelineTree.cs | 11 +- .../Internal/UiDebug2/ElementSelector.cs | 10 +- .../Internal/UiDebug2/Popout.Addon.cs | 3 +- .../Internal/UiDebug2/Popout.Node.cs | 3 +- .../Internal/UiDebug2/UiDebug2.Sidebar.cs | 16 +- .../Interface/Internal/UiDebug2/UiDebug2.cs | 4 +- .../Internal/UiDebug2/Utility/Gui.cs | 12 +- Dalamud/Utility/Util.cs | 318 +++++++++--------- 18 files changed, 282 insertions(+), 264 deletions(-) delete mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index 5e64fe463..d2b1b4a36 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -270,7 +270,7 @@ public static partial class ImGuiComponents /// Icon to use. /// Text to use. /// Width. - internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) + public static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) { using (ImRaii.PushFont(UiBuilder.IconFont)) { diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs index 3f9c469bb..ad83c7201 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs @@ -24,7 +24,7 @@ public static partial class ImGuiComponents /// The color of the actively-selected button. /// The color of the buttons when hovered. /// True if any button is clicked. - internal static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) { var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair(icon, value)); return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor); @@ -43,7 +43,7 @@ public static partial class ImGuiComponents /// The color of the actively-selected button. /// The color of the buttons when hovered. /// True if any button is clicked. - internal static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) { defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button); activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs index 4b7a531c0..c3930821b 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs @@ -11,6 +11,7 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; namespace Dalamud.Interface.Internal.UiDebug2.Browsing; +/// public unsafe partial class AddonTree { /// @@ -23,12 +24,11 @@ public unsafe partial class AddonTree if (addon->AtkValuesCount > 0 && atkValue != null) { using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}"); - if (tree) + if (tree.Success) { - using (ImRaii.Table( - "atkUnitBase_atkValueTable", - 3, - ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg)) + using var tbl = ImRaii.Table("atkUnitBase_atkValueTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + + if (tbl.Success) { ImGui.TableSetupColumn("Index"); ImGui.TableSetupColumn("Type"); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index 45a2d90eb..c98cc933f 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -30,9 +30,11 @@ public static class Events using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); - if (tree) + if (tree.Success) { - using (ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg)) + using var tbl = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg); + + if (tbl.Success) { ImGui.TableSetupColumn("#", WidthFixed); ImGui.TableSetupColumn("Type", WidthFixed); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index 6b6522bb4..1f5abd0bf 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -27,7 +27,8 @@ internal unsafe partial class ResNodeTree /// private protected void DrawNodeEditorTable() { - using (ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX)) + using var tbl = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); + if (tbl.Success) { this.DrawEditorRows(); } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs index 4929fa1d9..8d93b3e76 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -65,7 +65,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth); - if (tree) + if (tree.Success) { PrintFieldValuePairs( ("Texture Type", $"{this.TexData.TexType}"), @@ -189,7 +189,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree private void PrintPartsTable() { - using (ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable)) + using var tbl = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); + if (tbl.Success) { ImGui.TableSetupColumn("Part ID", WidthFixed); ImGui.TableSetupColumn("Part Texture", WidthFixed); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs deleted file mode 100644 index 237303155..000000000 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Numerics; - -using FFXIVClientStructs.FFXIV.Component.GUI; - -using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; - -namespace Dalamud.Interface.Internal.UiDebug2.Browsing; - -/// -internal unsafe partial class NineGridNodeTree -{ - /// - /// A struct representing the four offsets of an . - /// - internal struct NineGridOffsets - { - /// Top offset. - internal int Top; - - /// Left offset. - internal int Left; - - /// Right offset. - internal int Right; - - /// Bottom offset. - internal int Bottom; - - /// - /// Initializes a new instance of the struct. - /// - /// The top offset. - /// The right offset. - /// The bottom offset. - /// The left offset. - internal NineGridOffsets(int top, int right, int bottom, int left) - { - this.Top = top; - this.Right = right; - this.Left = left; - this.Bottom = bottom; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The node using these offsets. - internal NineGridOffsets(AtkNineGridNode* ngNode) - : this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset) - { - } - - private NineGridOffsets(Vector4 v) - : this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W) - { - } - - public static implicit operator NineGridOffsets(Vector4 v) => new(v); - - public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left); - - public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a; - - public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a; - - /// Prints the offsets in ImGui. - internal readonly void Print() => PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}")); - } -} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs index 8f4e8c196..48825becb 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs @@ -1,3 +1,5 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -85,4 +87,62 @@ internal unsafe partial class NineGridNodeTree : ImageNodeTree this.DrawTextureAndParts(); } + + /// + /// A struct representing the four offsets of an . + /// + internal struct NineGridOffsets + { + /// Top offset. + internal int Top; + + /// Left offset. + internal int Left; + + /// Right offset. + internal int Right; + + /// Bottom offset. + internal int Bottom; + + /// + /// Initializes a new instance of the struct. + /// + /// The top offset. + /// The right offset. + /// The bottom offset. + /// The left offset. + internal NineGridOffsets(int top, int right, int bottom, int left) + { + this.Top = top; + this.Right = right; + this.Left = left; + this.Bottom = bottom; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The node using these offsets. + internal NineGridOffsets(AtkNineGridNode* ngNode) + : this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset) + { + } + + private NineGridOffsets(Vector4 v) + : this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W) + { + } + + public static implicit operator NineGridOffsets(Vector4 v) => new(v); + + public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left); + + public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a; + + public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a; + + /// Prints the offsets in ImGui. + internal readonly void Print() => Gui.PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}")); + } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index 2edf4e570..6c12d3b4c 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -128,11 +128,11 @@ internal unsafe partial class ResNodeTree : IDisposable return; } - using var c = ImRaii.PushColor(Text, color); + using var col = ImRaii.PushColor(Text, color); using var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth); - c.Pop(); + col.Pop(); - if (tree) + if (tree.Success) { var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); @@ -319,7 +319,7 @@ internal unsafe partial class ResNodeTree : IDisposable col.Pop(); - if (tree) + if (tree.Success) { var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); try diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index d9b4e7986..7c924f503 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -85,7 +85,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree { using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}"); - if (tree) + if (tree.Success) { var utf8String = this.NodeText; var seStringBytes = new byte[utf8String.BufUsed]; diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs index 8e21f4030..57e5eff99 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -58,7 +58,7 @@ public readonly unsafe partial struct TimelineTree { using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth); - if (tree) + if (tree.Success) { PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}"); @@ -312,7 +312,7 @@ public readonly unsafe partial struct TimelineTree { using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); - if (tree) + if (tree.Success) { PrintFieldValuePair("Animation", $"{address:X}"); @@ -320,10 +320,9 @@ public readonly unsafe partial struct TimelineTree if (columns.Count > 0) { - using (ImRaii.Table( - $"##{(nint)this.node}animTable{a}", - columns.Count, - Borders | SizingFixedFit | RowBg | NoHostExtendX)) + using var tbl = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); + + if (tbl.Success) { foreach (var c in columns) { diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index 6693c3fb0..65537e210 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -79,7 +79,9 @@ internal unsafe class ElementSelector : IDisposable /// internal void DrawInterface() { - using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true)) + using var ch = ImRaii.Child("###sidebar_elementSelector", new(250, -1), true); + + if (ch.Success) { using (ImRaii.PushFont(IconFont)) { @@ -153,9 +155,11 @@ internal unsafe class ElementSelector : IDisposable using (ImRaii.PushColor(WindowBg, new Vector4(0.5f))) { - using (ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse)) + using var ch = ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); + if (ch.Success) { - using (ImRaii.Group()) + using var gr = ImRaii.Group(); + if (gr.Success) { Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); ImGui.Spacing(); diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs index 91dfc8067..76112945e 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs @@ -39,7 +39,8 @@ internal class AddonPopoutWindow : Window, IDisposable /// public override void Draw() { - using (ImRaii.Child($"{this.WindowName}child", new(-1, -1), true)) + using var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true); + if (ch.Success) { this.addonTree.Draw(); } diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs index c07806823..fe8bc87ea 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs @@ -50,7 +50,8 @@ internal unsafe class NodePopoutWindow : Window, IDisposable { if (this.Node != null && this.AddonTree.ContainsNode(this.Node)) { - using (ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true)) + using var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); + if (ch.Success) { ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); this.firstDraw = false; diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs index 4253f13bc..50967453d 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -53,7 +53,8 @@ internal unsafe partial class UiDebug2 private void DrawSidebar() { - using (ImRaii.Group()) + using var gr = ImRaii.Group(); + if (gr.Success) { this.DrawNameSearch(); this.DrawAddonSelectionList(); @@ -63,7 +64,9 @@ internal unsafe partial class UiDebug2 private void DrawNameSearch() { - using (ImRaii.Child("###sidebar_nameSearch", new(250, 40), true)) + using var ch = ImRaii.Child("###sidebar_nameSearch", new(250, 40), true); + + if (ch.Success) { var atkUnitBaseSearch = this.addonNameSearch; @@ -90,7 +93,8 @@ internal unsafe partial class UiDebug2 private void DrawAddonSelectionList() { - using (ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar)) + using var ch = ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); + if (ch.Success) { var unitListBaseAddr = GetUnitListBaseAddr(); @@ -146,11 +150,11 @@ internal unsafe partial class UiDebug2 var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}"; - using var col1 = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + using var col = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); using var tree = ImRaii.TreeNode($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); - col1.Pop(); + col.Pop(); - if (tree) + if (tree.Success) { foreach (var option in options) { diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs index 17c64ddca..74727f2a5 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs @@ -83,7 +83,9 @@ internal partial class UiDebug2 : IDisposable { ImGui.SameLine(); - using (ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar)) + using var ch = ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); + + if (ch.Success) { if (this.elementSelector.Active) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index cc73b79c6..cc4f1b698 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -68,10 +68,10 @@ internal static class Gui /// Colors the text itself either white or black, depending on the luminosity of the background color. internal static void PrintColor(Vector4 color, string fmt) { - using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) - .Push(Button, color) - .Push(ButtonActive, color) - .Push(ButtonHovered, color)) + using (ImRaii.PushColor(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) + .Push(Button, color) + .Push(ButtonActive, color) + .Push(ButtonHovered, color)) { ImGui.SmallButton(fmt); } @@ -105,7 +105,9 @@ internal static class Gui var index = (int)Math.Floor(prog * tooltips.Length); - using (ImRaii.Tooltip()) + using var tt = ImRaii.Tooltip(); + + if (tt.Success) { ImGui.TextUnformatted(tooltips[index]); } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 805532025..c707081f4 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -28,6 +28,8 @@ using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.Memory; using Windows.Win32.System.Ole; +using Dalamud.Interface.Utility.Raii; + using static TerraFX.Interop.Windows.Windows; using Win32_PInvoke = Windows.Win32.PInvoke; @@ -1028,74 +1030,71 @@ public static class Util dm.Invoke(null, new[] { obj, path, addr }); } +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + private static unsafe void ShowSpanPrivate(ulong addr, IList path, int offset, bool isTop, in Span spanobj) { -#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type if (isTop) { fixed (void* p = spanobj) { - if (!ImGui.TreeNode( - $"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + - $"##print-obj-{addr:X}-{string.Join("-", path)}-head")) + using var tree = ImRaii.TreeNode($"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}-head", ImGuiTreeNodeFlags.SpanFullWidth); + if (tree.Success) { - return; + ShowSpanEntryPrivate(addr, path, offset, spanobj); } } } - - try + else { - const int batchSize = 20; - if (spanobj.Length > batchSize) - { - var skip = batchSize; - while ((spanobj.Length + skip - 1) / skip > batchSize) - skip *= batchSize; - for (var i = 0; i < spanobj.Length; i += skip) - { - var next = Math.Min(i + skip, spanobj.Length); - path.Add($"{offset + i:X}_{skip}"); - if (ImGui.TreeNode( - $"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + - $"##print-obj-{addr:X}-{string.Join("-", path)}")) - { - try - { - ShowSpanPrivate(addr, path, offset + i, false, spanobj[i..next]); - } - finally - { - ImGui.TreePop(); - } - } - - path.RemoveAt(path.Count - 1); - } - } - else - { - fixed (T* p = spanobj) - { - var pointerType = typeof(T*); - for (var i = 0; i < spanobj.Length; i++) - { - ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); - ImGui.SameLine(); - path.Add($"{offset + i}"); - ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); - } - } - } + ShowSpanEntryPrivate(addr, path, offset, spanobj); } - finally - { - if (isTop) - ImGui.TreePop(); - } -#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type } + private static unsafe void ShowSpanEntryPrivate(ulong addr, IList path, int offset, Span spanobj) { + const int batchSize = 20; + if (spanobj.Length > batchSize) + { + var skip = batchSize; + while ((spanobj.Length + skip - 1) / skip > batchSize) + { + skip *= batchSize; + } + + for (var i = 0; i < spanobj.Length; i += skip) + { + var next = Math.Min(i + skip, spanobj.Length); + path.Add($"{offset + i:X}_{skip}"); + + using (var tree = ImRaii.TreeNode($"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + if (tree.Success) + { + ShowSpanEntryPrivate(addr, path, offset + i, spanobj[i..next]); + } + } + + path.RemoveAt(path.Count - 1); + } + } + else + { + fixed (T* p = spanobj) + { + var pointerType = typeof(T*); + for (var i = 0; i < spanobj.Length; i++) + { + ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); + ImGui.SameLine(); + path.Add($"{offset + i}"); + ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); + } + } + } + } + +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + private static unsafe void ShowValue(ulong addr, IList path, Type type, object value, bool hideAddress) { if (type.IsPointer) @@ -1111,9 +1110,10 @@ public static class Util if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) { ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); - ImGui.PopStyleColor(); + using (ImRaii.PushColor(ImGuiCol.Text, 0xffcbc0ff)) + { + ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); + } } ImGui.SameLine(); @@ -1165,125 +1165,135 @@ public static class Util /// Do not print addresses. Use when displaying a copied value. private static void ShowStructInternal(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null, bool hideAddress = false) { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); - path ??= new List(); - var pathList = path is List ? (List)path : path.ToList(); - - if (moduleEndAddr == 0 && moduleStartAddr == 0) + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2))) { - try + path ??= new List(); + var pathList = path as List ?? path.ToList(); + + if (moduleEndAddr == 0 && moduleStartAddr == 0) { - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) + try { - moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); - moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); + moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; + } + else + { + moduleEndAddr = 1; + } } - else + catch { moduleEndAddr = 1; } } - catch + + if (autoExpand) { - moduleEndAddr = 1; + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); } - } - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - } + using var col = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF); + using var tree = ImRaii.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}", ImGuiTreeNodeFlags.SpanFullWidth); + col.Pop(); - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}")) - { - ImGui.PopStyleColor(); - foreach (var f in obj.GetType() - .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + if (tree.Success) { - var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); - var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); + ImGui.PopStyleColor(); + foreach (var f in obj.GetType() + .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + { + var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); - if (fixedBuffer != null) - { - ImGui.Text($"fixed"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), - $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); - } - else - { - if (offset != null) + if (fixedBuffer != null) { - ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.Text("fixed"); ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); } - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); - } - - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); - ImGui.SameLine(); - - pathList.Add(f.Name); - try - { - if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) - ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct - else if (f.FieldType == typeof(bool) && offset != null) - ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); else - ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); + { + if (offset != null) + { + ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.SameLine(); + } + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + } + + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); + ImGui.SameLine(); + + pathList.Add(f.Name); + try + { + if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) + { + ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct + } + else if (f.FieldType == typeof(bool) && offset != null) + { + ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); + } + else + { + ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); + } + } + catch (Exception ex) + { + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f))) + { + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + } + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } - catch (Exception ex) + + foreach (var p in obj.GetType().GetProperties().Where(static p => p.GetGetMethod()?.GetParameters().Length == 0)) { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); - ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); - ImGui.PopStyleColor(); - } - finally - { - pathList.RemoveAt(pathList.Count - 1); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); + ImGui.SameLine(); + + pathList.Add(p.Name); + try + { + if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) + { + ShowSpanProperty(addr, pathList, p, obj); + } + else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) + { + ImGui.Text("Cannot preview ref typed properties."); + } + else + { + ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); + } + } + catch (Exception ex) + { + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f))) + { + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + } + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } } - - foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) - { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); - ImGui.SameLine(); - - pathList.Add(p.Name); - try - { - if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) - ShowSpanProperty(addr, pathList, p, obj); - else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) - ImGui.Text("Cannot preview ref typed properties."); - else - ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); - } - catch (Exception ex) - { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); - ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); - ImGui.PopStyleColor(); - } - finally - { - pathList.RemoveAt(pathList.Count - 1); - } - } - - ImGui.TreePop(); } - else - { - ImGui.PopStyleColor(); - } - - ImGui.PopStyleVar(); } } From 0fdcffd3ad70c19030ee7098e93a8acb1a403ed7 Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 24 Nov 2024 23:00:50 +0100 Subject: [PATCH 14/23] Fix small typo (#2115) --- Dalamud/Configuration/Internal/DalamudConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 5b49f5c72..5c2378f68 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -57,7 +57,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved; /// - /// Gets or sets a list of muted works. + /// Gets or sets a list of muted words. /// public List? BadWords { get; set; } From 903a5ad5da412e529526dd2807495b9a32ef891c Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Sun, 24 Nov 2024 14:01:01 -0800 Subject: [PATCH 15/23] fix: Properly handle UTF-8 characters in ImGUI paths (#2122) - Fixes a bug where users with special characters in their filenames would not be able to save `dalamudUI.ini`. - Throw a special warning if `dalamudUI.ini` doesn't exist, as it's not an error case. --- Dalamud/Interface/Internal/InterfaceManager.cs | 9 ++++++++- lib/ImGuiScene | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index cb25bd9b5..f532b0412 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -604,10 +604,17 @@ internal partial class InterfaceManager : IInternalDisposableService if (iniFileInfo.Length > 1200000) { Log.Warning("dalamudUI.ini was over 1mb, deleting"); - iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName!, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); + iniFileInfo.CopyTo( + Path.Combine( + iniFileInfo.DirectoryName!, + $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); iniFileInfo.Delete(); } } + catch (FileNotFoundException) + { + Log.Warning("dalamudUI.ini did not exist, ImGUI will create a new one."); + } catch (Exception ex) { Log.Error(ex, "Could not delete dalamudUI.ini"); diff --git a/lib/ImGuiScene b/lib/ImGuiScene index 2f37349ff..d5dfde4b3 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit 2f37349ffd778561a1103a650683116c43edc86c +Subproject commit d5dfde4b39b032430deb46bc61084c18fb54b986 From 4c9b2a1577f8cd8c8b99e828d174b7122730e808 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Mon, 25 Nov 2024 11:21:07 -0800 Subject: [PATCH 16/23] Add universalis response logging for diagnostics (#2124) --- .../Universalis/UniversalisMarketBoardUploader.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index 9528a9b50..f326c5ce0 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -102,11 +102,18 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader var uploadPath = "/upload"; var uploadData = JsonConvert.SerializeObject(uploadObject); Log.Verbose("{ListingPath}: {ListingUpload}", uploadPath, uploadData); - await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json")); + var response = await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json")); - // ==================================================================================== - - Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId); + if (response.IsSuccessStatusCode) + { + Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId); + } + else + { + var body = await response.Content.ReadAsStringAsync(); + Log.Warning("Universalis data upload for item#{CatalogId} returned status code {StatusCode}.\n" + + " Response Body: {Body}", request.CatalogId, response.StatusCode, body); + } } /// From db4b6cea2cf89fda1ce5b3da23621cb0ac2a287f Mon Sep 17 00:00:00 2001 From: srkizer Date: Tue, 26 Nov 2024 10:09:43 +0900 Subject: [PATCH 17/23] Avoid re-creating title version string every frame (#2117) * Avoid re-creating title version string every frame * Marked title version string that it has been changed by Dalamud by appending a newline payload with a string expression embedded within, which the game will ignore (effectively ``.) * Added `PluginManager.LoadedPluginCount` which will count the number of loaded plugin without making a copy of the plugin list. * Made TitleScreenMenuWindow.OnVersionStringDraw` update the title version text addon only if number of loaded plugin changes or the text is missing the custom suffix from the first point. * Use OriginalTextPointer to determine if SetText(ROS) has been called --- .../Internal/Windows/TitleScreenMenuWindow.cs | 43 ++++++++++++------- Dalamud/Plugin/Internal/PluginManager.cs | 21 +++++++++ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index faf99aa2a..88f291954 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -10,7 +10,6 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState; using Dalamud.Game.Gui; using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; @@ -19,16 +18,18 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Internal; -using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using Dalamud.Storage.Assets; -using Dalamud.Support; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + namespace Dalamud.Interface.Internal.Windows; /// @@ -59,6 +60,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable private InOutCubic? fadeOutEasing; private State state = State.Hide; + + private int lastLoadedPluginCount = -1; /// /// Initializes a new instance of the class. @@ -441,29 +444,37 @@ internal class TitleScreenMenuWindow : Window, IDisposable textNode->TextFlags |= (byte)TextFlags.MultiLine; textNode->AlignmentType = AlignmentType.TopLeft; + var containsDalamudVersionString = textNode->OriginalTextPointer == textNode->NodeText.StringPtr; if (!this.configuration.ShowTsm || !this.showTsm.Value) { - textNode->NodeText.SetString(addon->AtkValues[1].String); + if (containsDalamudVersionString) + textNode->SetText(addon->AtkValues[1].String); + this.lastLoadedPluginCount = -1; return; } var pm = Service.GetNullable(); + var count = pm?.LoadedPluginCount ?? 0; - var pluginCount = pm?.InstalledPlugins.Count(c => c.State == PluginState.Loaded) ?? 0; + // Avoid rebuilding the string every frame. + if (containsDalamudVersionString && count == this.lastLoadedPluginCount) + return; + this.lastLoadedPluginCount = count; - var titleVersionText = new SeStringBuilder() - .AddText(addon->AtkValues[1].GetValueAsString()) - .AddText("\n\n") - .AddUiGlow(701) - .AddUiForeground(SeIconChar.BoxedLetterD.ToIconString(), 539) - .AddUiGlowOff() - .AddText($" Dalamud: {Util.GetScmVersion()}") - .AddText($" - {pluginCount} {(pluginCount != 1 ? "plugins" : "plugin")} loaded"); + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String)).Append("\n\n"); + lssb.PushEdgeColorType(701).PushColorType(539) + .Append(SeIconChar.BoxedLetterD.ToIconChar()) + .PopColorType().PopEdgeColorType(); + lssb.Append($" Dalamud: {Util.GetScmVersion()}"); - if (pm?.SafeMode ?? false) - titleVersionText.AddUiForeground(" [SAFE MODE]", 17); + lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded"); - textNode->NodeText.SetString(titleVersionText.Build().EncodeWithNullTerminator()); + if (pm?.SafeMode is true) + lssb.PushColorType(17).Append(" [SAFE MODE]").PopColorType(); + + textNode->SetText(lssb.GetViewAsSpan()); + LSeStringBuilder.SharedPool.Return(lssb); } private void TitleScreenMenuEntryListChange() => this.privateAtlas.BuildFontsAsync(); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index ddb59c027..9d71c60df 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -162,6 +162,27 @@ internal class PluginManager : IInternalDisposableService /// public static int DalamudApiLevel { get; private set; } + /// + /// Gets the number of loaded plugins. + /// + public int LoadedPluginCount + { + get + { + var res = 0; + lock (this.pluginListLock) + { + foreach (var p in this.installedPluginsList) + { + if (p.State == PluginState.Loaded) + res++; + } + } + + return res; + } + } + /// /// Gets a copy of the list of all loaded plugins. /// From d08966edf8f993a9a396015ba71d48f0284ac27b Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:41:45 +0100 Subject: [PATCH 18/23] Update ClientStructs (#2114) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index c9d1f33c5..696f71495 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit c9d1f33c58f426b6480b2c00e5cdcb0668654973 +Subproject commit 696f71495229e348109d1521a5ed9483c7ba3275 From c0464b80a581608a7afd1ebca7318f9d92a79c3a Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Tue, 26 Nov 2024 08:34:28 -0800 Subject: [PATCH 19/23] chore: ImGui PDB passthrough, null terminator fix (#2123) --- lib/ImGuiScene | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ImGuiScene b/lib/ImGuiScene index d5dfde4b3..b0d41471b 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit d5dfde4b39b032430deb46bc61084c18fb54b986 +Subproject commit b0d41471b7ef3d69daaf6d862eb74e7e00a25651 From 245d7be23705dc2a587fa38f0943954c35f8733a Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:53:25 +0100 Subject: [PATCH 20/23] Update ClientStructs (#2125) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 696f71495..d4778799d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 696f71495229e348109d1521a5ed9483c7ba3275 +Subproject commit d4778799d378fe66099fd337f55849acf1f16dcd From f3db18962f714cd39bd0b94dc9727c3ba19a32e1 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 26 Nov 2024 21:09:19 +0100 Subject: [PATCH 21/23] build: 11.0.1.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 338c231fe..61fb1efe4 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 11.0.0.0 + 11.0.1.0 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 7a52cb85db6db9084eae3b064c1c28facf405006 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Thu, 28 Nov 2024 05:30:24 +0900 Subject: [PATCH 22/23] Fix ShowStructInternal ImGui crash (#2127) --- Dalamud/Utility/Util.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index c707081f4..d5e14e212 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -1202,7 +1202,6 @@ public static class Util if (tree.Success) { - ImGui.PopStyleColor(); foreach (var f in obj.GetType() .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { From 1830af59fd98a884faf0474d7997cf748ff8f5a6 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Fri, 29 Nov 2024 02:19:11 +0900 Subject: [PATCH 23/23] Use object table index lookup for NamePlateGui GameObject resolution (#2129) --- Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 170fea687..94f185966 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -328,9 +328,9 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId; /// - public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable.CreateObjectReference( - (nint)this.context.Ui3DModule->NamePlateObjectInfoPointers[ - this.ArrayIndex].Value->GameObject); + public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable[ + this.context.Ui3DModule->NamePlateObjectInfoPointers[this.ArrayIndex] + .Value->GameObject->ObjectIndex]; /// public IBattleChara? BattleChara => this.GameObject as IBattleChara;