diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index a81ab54fc..5f91a6ac8 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 a0322aab1..7d4910760 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,7 +71,7 @@ - + diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index cbe416625..8b020b111 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -37,17 +37,19 @@ public class DalamudLinkPayload : Payload /// protected override byte[] EncodeImpl() { - return new Lumina.Text.SeStringBuilder() - .BeginMacro(MacroCode.Link) - .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) - .AppendUIntExpression(this.CommandId) - .AppendIntExpression(this.Extra1) - .AppendIntExpression(this.Extra2) - .BeginStringExpression() - .Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString })) - .EndExpression() - .EndMacro() - .ToArray(); + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + var res = ssb.BeginMacro(MacroCode.Link) + .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) + .AppendUIntExpression(this.CommandId) + .AppendIntExpression(this.Extra1) + .AppendIntExpression(this.Extra2) + .BeginStringExpression() + .Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString })) + .EndExpression() + .EndMacro() + .ToArray(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); + return res; } /// diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 83c777130..8afc1b473 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -1,4 +1,3 @@ -using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; @@ -16,18 +15,16 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Client.UI.Misc; using ImGuiNET; using Lumina.Excel.GeneratedSheets2; +using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon; -using SeStringBuilder = Lumina.Text.SeStringBuilder; - namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; /// Draws SeString. @@ -46,30 +43,12 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// of this placeholder. On its own, usually displayed like [OBJ]. private const int ObjectReplacementCharacter = '\uFFFC'; - /// SeString to return instead, if macro encoder has failed and could not provide us the reason. - private static readonly ReadOnlySeString MacroEncoderEncodeStringError = - new SeStringBuilder() - .BeginMacro(MacroCode.ColorType).AppendIntExpression(508).EndMacro() - .BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(509).EndMacro() - .Append( - ""u8) - .BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(0).EndMacro() - .BeginMacro(MacroCode.ColorType).AppendIntExpression(0).EndMacro() - .ToReadOnlySeString(); - [ServiceManager.ServiceDependency] private readonly GameConfig gameConfig = Service.Get(); /// Cache of compiled SeStrings from . private readonly ConcurrentLru cache = new(1024); - /// Sets the global invalid parameter handler. Used to suppress vsprintf_s from raising. - /// There exists a thread local version of this, but as the game-provided implementation is what - /// effectively is a screaming tool that the game has a bug, it should be safe to fail in any means. - private readonly delegate* unmanaged< - delegate* unmanaged, - delegate* unmanaged> setInvalidParameterHandler; - /// Parsed gfdata.gfd file, containing bitmap font icon lookup table. private readonly GfdFile gfd; @@ -90,14 +69,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService this.colorStackSet = new( dm.Excel.GetSheet() ?? throw new InvalidOperationException("Failed to access UIColor sheet.")); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; - - // SetUnhandledExceptionFilter(who cares); - // _set_purecall_handler(() => *(int*)0 = 0xff14); - // _set_invalid_parameter_handler(() => *(int*)0 = 0xff14); - var f = sigScanner.ScanText( - "ff 15 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ??") + 26; - fixed (void* p = &this.setInvalidParameterHandler) - *(nint*)p = *(int*)f + f + 4; } /// Finalizes an instance of the class. @@ -106,72 +77,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); - /// Compiles a SeString from a text macro representation. - /// SeString text macro representation. - /// Compiled SeString. - public ReadOnlySeString Compile(ReadOnlySpan text) - { - // MacroEncoder looks stateful; disallowing calls from off main threads for now. - ThreadSafety.AssertMainThread(); - - var prev = this.setInvalidParameterHandler(&MsvcrtInvalidParameterHandlerDetour); - try - { - using var tmp = new Utf8String(); - RaptureTextModule.Instance()->MacroEncoder.EncodeString(&tmp, text); - return new(tmp.AsSpan().ToArray()); - } - catch (Exception) - { - return MacroEncoderEncodeStringError; - } - finally - { - this.setInvalidParameterHandler(prev); - } - - [UnmanagedCallersOnly] - static void MsvcrtInvalidParameterHandlerDetour(char* a, char* b, char* c, int d, nuint e) => - throw new InvalidOperationException(); - } - - /// Compiles a SeString from a text macro representation. - /// SeString text macro representation. - /// Compiled SeString. - public ReadOnlySeString Compile(ReadOnlySpan text) - { - var len = Encoding.UTF8.GetByteCount(text); - if (len >= 1024) - { - var buf = ArrayPool.Shared.Rent(len + 1); - buf[Encoding.UTF8.GetBytes(text, buf)] = 0; - var res = this.Compile(buf); - ArrayPool.Shared.Return(buf); - return res; - } - else - { - Span buf = stackalloc byte[len + 1]; - buf[Encoding.UTF8.GetBytes(text, buf)] = 0; - return this.Compile(buf); - } - } - /// Compiles and caches a SeString from a text macro representation. /// SeString text macro representation. /// Newline characters will be normalized to newline payloads. /// Compiled SeString. - public ReadOnlySeString CompileAndCache(string text) - { - // MacroEncoder looks stateful; disallowing calls from off main threads for now. - // Note that this is replicated in context.Compile. Only access cache from the main thread. - ThreadSafety.AssertMainThread(); - - return this.cache.GetOrAdd( + public ReadOnlySeString CompileAndCache(string text) => + this.cache.GetOrAdd( text, - static (text, context) => context.Compile(text.ReplaceLineEndings("
")), - this); - } + static text => ReadOnlySeString.FromMacroString( + text.ReplaceLineEndings("
"), + new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError })); /// Compiles and caches a SeString from a text macro representation, and then draws it. /// SeString text macro representation. diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 545a7e8a8..f78cf09ce 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Lumina.Text.Parse; using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; using DSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; @@ -24,44 +24,44 @@ public static class SeStringExtensions /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. - public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) - { - ThreadSafety.AssertMainThread(); - return ssb.Append(Service.Get().Compile(macroString)); - } + [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")] + [Api11ToDo("Remove")] + public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => + ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-16 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. - public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) - { - ThreadSafety.AssertMainThread(); - return ssb.Append(Service.Get().Compile(macroString)); - } + [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")] + [Api11ToDo("Remove")] + public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => + ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) { - ThreadSafety.AssertMainThread(); - return ssb.Append(DSeString.Parse(Service.Get().Compile(macroString))); + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); + ssb.Append(DSeString.Parse(lssb.ToReadOnlySeString().Data.Span)); + LSeStringBuilder.SharedPool.Return(lssb); + return ssb; } /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-16 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) { - ThreadSafety.AssertMainThread(); - return ssb.Append(DSeString.Parse(Service.Get().Compile(macroString))); + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); + ssb.Append(DSeString.Parse(lssb.ToReadOnlySeString().Data.Span)); + LSeStringBuilder.SharedPool.Return(lssb); + return ssb; } ///