Skip to content

Commit

Permalink
Update Lumina and use MacroStringParser
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Soreepeong committed Sep 14, 2024
1 parent 4b4227d commit bfa7c53
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 123 deletions.
2 changes: 1 addition & 1 deletion Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Lumina" Version="4.1.1" />
<PackageReference Include="Lumina" Version="4.2.1" />
<PackageReference Include="Lumina.Excel" Version="7.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
Expand Down
2 changes: 1 addition & 1 deletion Dalamud/Dalamud.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageReference Include="Lumina" Version="4.1.1" />
<PackageReference Include="Lumina" Version="4.2.1" />
<PackageReference Include="Lumina.Excel" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
Expand Down
24 changes: 13 additions & 11 deletions Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ public override string ToString() =>
/// <inheritdoc/>
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;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Buffers;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
Expand All @@ -16,18 +15,16 @@

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>
Expand All @@ -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;

Expand All @@ -90,14 +69,6 @@ private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
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>
Expand All @@ -106,72 +77,16 @@ private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
/// <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.
Expand Down
38 changes: 19 additions & 19 deletions Dalamud/Utility/SeStringExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,44 +24,44 @@ public static class SeStringExtensions
/// <param name="ssb">Target SeString builder.</param>
/// <param name="macroString">Macro string in UTF-8 to compile and append to <paramref name="ssb"/>.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Must be called from the main thread.</remarks>
public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan<byte> macroString)
{
ThreadSafety.AssertMainThread();
return ssb.Append(Service<SeStringRenderer>.Get().Compile(macroString));
}
[Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")]
[Api11ToDo("Remove")]
public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan<byte> macroString) =>
ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError });

/// <summary>Compiles and appends a macro string.</summary>
/// <param name="ssb">Target SeString builder.</param>
/// <param name="macroString">Macro string in UTF-16 to compile and append to <paramref name="ssb"/>.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Must be called from the main thread.</remarks>
public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan<char> macroString)
{
ThreadSafety.AssertMainThread();
return ssb.Append(Service<SeStringRenderer>.Get().Compile(macroString));
}
[Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")]
[Api11ToDo("Remove")]
public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan<char> macroString) =>
ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError });

/// <summary>Compiles and appends a macro string.</summary>
/// <param name="ssb">Target SeString builder.</param>
/// <param name="macroString">Macro string in UTF-8 to compile and append to <paramref name="ssb"/>.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Must be called from the main thread.</remarks>
public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan<byte> macroString)
{
ThreadSafety.AssertMainThread();
return ssb.Append(DSeString.Parse(Service<SeStringRenderer>.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;
}

/// <summary>Compiles and appends a macro string.</summary>
/// <param name="ssb">Target SeString builder.</param>
/// <param name="macroString">Macro string in UTF-16 to compile and append to <paramref name="ssb"/>.</param>
/// <returns><c>this</c> for method chaining.</returns>
/// <remarks>Must be called from the main thread.</remarks>
public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan<char> macroString)
{
ThreadSafety.AssertMainThread();
return ssb.Append(DSeString.Parse(Service<SeStringRenderer>.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;
}

/// <summary>
Expand Down

0 comments on commit bfa7c53

Please sign in to comment.