Update Lumina and use MacroStringParser (#2033)

No longer requires hook and main thread requirements on compiling macro
strings.

Needs lookup table fixing on Lumina; using reflection to fix for the
time being.
This commit is contained in:
srkizer 2024-09-15 02:23:24 +09:00 committed by GitHub
parent 4b4227d127
commit 1f74293de4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 40 additions and 123 deletions

View file

@ -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;
/// <summary>Draws SeString.</summary>
@ -46,30 +43,12 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
/// of this placeholder. On its own, usually displayed like <c>[OBJ]</c>.</summary>
private const int ObjectReplacementCharacter = '\uFFFC';
/// <summary>SeString to return instead, if macro encoder has failed and could not provide us the reason.</summary>
private static readonly ReadOnlySeString MacroEncoderEncodeStringError =
new SeStringBuilder()
.BeginMacro(MacroCode.ColorType).AppendIntExpression(508).EndMacro()
.BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(509).EndMacro()
.Append(
"<encode failed, and error message generation failed because the part that caused the error was too long>"u8)
.BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(0).EndMacro()
.BeginMacro(MacroCode.ColorType).AppendIntExpression(0).EndMacro()
.ToReadOnlySeString();
[ServiceManager.ServiceDependency]
private readonly GameConfig gameConfig = Service<GameConfig>.Get();
/// <summary>Cache of compiled SeStrings from <see cref="CompileAndCache"/>.</summary>
private readonly ConcurrentLru<string, ReadOnlySeString> cache = new(1024);
/// <summary>Sets the global invalid parameter handler. Used to suppress <c>vsprintf_s</c> from raising.</summary>
/// <remarks>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.</remarks>
private readonly delegate* unmanaged<
delegate* unmanaged<char*, char*, char*, int, nuint, void>,
delegate* unmanaged<char*, char*, char*, int, nuint, void>> setInvalidParameterHandler;
/// <summary>Parsed <c>gfdata.gfd</c> file, containing bitmap font icon lookup table.</summary>
private readonly GfdFile gfd;
@ -90,14 +69,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
this.colorStackSet = new(
dm.Excel.GetSheet<UIColor>() ?? throw new InvalidOperationException("Failed to access UIColor sheet."));
this.gfd = dm.GetFile<GfdFile>("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;
}
/// <summary>Finalizes an instance of the <see cref="SeStringRenderer"/> class.</summary>
@ -106,72 +77,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
/// <inheritdoc/>
void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources();
/// <summary>Compiles a SeString from a text macro representation.</summary>
/// <param name="text">SeString text macro representation.</param>
/// <returns>Compiled SeString.</returns>
public ReadOnlySeString Compile(ReadOnlySpan<byte> 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();
}
/// <summary>Compiles a SeString from a text macro representation.</summary>
/// <param name="text">SeString text macro representation.</param>
/// <returns>Compiled SeString.</returns>
public ReadOnlySeString Compile(ReadOnlySpan<char> text)
{
var len = Encoding.UTF8.GetByteCount(text);
if (len >= 1024)
{
var buf = ArrayPool<byte>.Shared.Rent(len + 1);
buf[Encoding.UTF8.GetBytes(text, buf)] = 0;
var res = this.Compile(buf);
ArrayPool<byte>.Shared.Return(buf);
return res;
}
else
{
Span<byte> buf = stackalloc byte[len + 1];
buf[Encoding.UTF8.GetBytes(text, buf)] = 0;
return this.Compile(buf);
}
}
/// <summary>Compiles and caches a SeString from a text macro representation.</summary>
/// <param name="text">SeString text macro representation.
/// Newline characters will be normalized to newline payloads.</param>
/// <returns>Compiled SeString.</returns>
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("<br>")),
this);
}
static text => ReadOnlySeString.FromMacroString(
text.ReplaceLineEndings("<br>"),
new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }));
/// <summary>Compiles and caches a SeString from a text macro representation, and then draws it.</summary>
/// <param name="text">SeString text macro representation.