Merge remote-tracking branch 'origin/master' into net9-rollup

This commit is contained in:
github-actions[bot] 2024-11-28 17:19:34 +00:00
commit 73072d78ba
40 changed files with 537 additions and 1448 deletions

View file

@ -25,8 +25,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Lumina" Version="5.4.0" />
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
<PackageReference Include="Lumina" Version="5.6.0" />
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
<PrivateAssets>all</PrivateAssets>

View file

@ -57,7 +57,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved;
/// <summary>
/// Gets or sets a list of muted works.
/// Gets or sets a list of muted words.
/// </summary>
public List<string>? BadWords { get; set; }

View file

@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup Label="Feature">
<DalamudVersion>11.0.0.0</DalamudVersion>
<DalamudVersion>11.0.1.0</DalamudVersion>
<Description>XIV Launcher addon framework</Description>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>
@ -67,8 +67,8 @@
<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="5.4.0" />
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
<PackageReference Include="Lumina" Version="5.6.0" />
<PackageReference Include="Lumina.Excel" Version="7.1.3" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
<PrivateAssets>all</PrivateAssets>

View file

@ -137,7 +137,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
public bool IsPvP { get; private set; }
/// <inheritdoc/>
public bool IsPvPExcludingDen { get; private set; }
public bool IsPvPExcludingDen => this.IsPvP && this.TerritoryType != 250;
/// <inheritdoc />
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)
{

View file

@ -11,6 +11,7 @@ using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@ -20,10 +21,13 @@ using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType;
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType;
using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan;
using Lumina.Text;
using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
using SeString = Dalamud.Game.Text.SeStringHandling.SeString;
using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
namespace Dalamud.Game.Gui;
@ -107,6 +111,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
this.handleLinkClickHook.Dispose();
}
#region DalamudSeString
/// <inheritdoc/>
public void Print(XivChatEntry chat)
{
@ -137,45 +143,74 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
}
#endregion
#region LuminaSeString
/// <inheritdoc/>
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
}
/// <inheritdoc/>
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
}
#endregion
/// <summary>
/// Process a chat queue.
/// </summary>
public void UpdateQueue()
{
while (this.chatQueue.Count > 0)
{
var chat = this.chatQueue.Dequeue();
var replacedMessage = new SeStringBuilder();
if (this.chatQueue.Count == 0)
return;
// Normalize Unicode NBSP to the built-in one, as the former won't renderl
foreach (var payload in chat.Message.Payloads)
var sb = LSeStringBuilder.SharedPool.Get();
Span<byte> namebuf = stackalloc byte[256];
using var sender = new Utf8String();
using var message = new Utf8String();
while (this.chatQueue.TryDequeue(out var chat))
{
if (payload is TextPayload { Text: not null } textPayload)
sb.Clear();
foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString))
{
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]));
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);
}
if (chat.NameBytes.Length + 1 < namebuf.Length)
{
chat.NameBytes.AsSpan().CopyTo(namebuf);
namebuf[chat.NameBytes.Length] = 0;
sender.SetString(namebuf);
}
else
{
replacedMessage.Add(payload);
}
sender.SetString(chat.NameBytes.NullTerminate());
}
var sender = Utf8String.FromSequence(chat.Name.Encode());
var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode());
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);
}
/// <summary>
@ -228,29 +263,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 +286,33 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
});
}
private void PrintTagged(ReadOnlySpan<byte> message, XivChatType channel, string? tag, ushort? color)
{
var sb = LSeStringBuilder.SharedPool.Get();
if (!tag.IsNullOrEmpty())
{
if (color is not null)
{
sb.PushColorType(color.Value);
sb.Append($"[{tag}] ");
sb.PopColorType();
}
else
{
sb.Append($"[{tag}] ");
}
}
this.Print(new XivChatEntry
{
MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(),
Type = channel,
});
LSeStringBuilder.SharedPool.Return(sb);
}
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
{
this.inventoryItemCopyHook.Original(thisPtr, otherPtr);
@ -373,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;
@ -381,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);
@ -392,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)
@ -421,7 +460,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
}
finally
{
LuminaSeStringBuilder.SharedPool.Return(sb);
LSeStringBuilder.SharedPool.Return(sb);
}
}
}
@ -505,6 +544,14 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.PrintError(message, messageTag, tagColor);
/// <inheritdoc/>
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.Print(message, messageTag, tagColor);
/// <inheritdoc/>
public void PrintError(ReadOnlySpan<byte> 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);

View file

@ -328,9 +328,9 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler
public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId;
/// <inheritdoc/>
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];
/// <inheritdoc/>
public IBattleChara? BattleChara => this.GameObject as IBattleChara;

View file

@ -102,12 +102,19 @@ 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"));
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);
}
}
/// <inheritdoc/>
public async Task UploadTax(MarketTaxRates taxRates)

View file

@ -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
/// <inheritdoc/>
protected override byte[] EncodeImpl()
{
var chunkLen = this.playerName.Length + 7;
var bytes = new List<byte>()
{
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;
}
/// <inheritdoc/>
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();
}
}

View file

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

View file

@ -20,12 +20,30 @@ public sealed class XivChatEntry
/// <summary>
/// Gets or sets the sender name.
/// </summary>
public SeString Name { get; set; } = string.Empty;
public SeString Name
{
get => SeString.Parse(this.NameBytes);
set => this.NameBytes = value.Encode();
}
/// <summary>
/// Gets or sets the message.
/// </summary>
public SeString Message { get; set; } = string.Empty;
public SeString Message
{
get => SeString.Parse(this.MessageBytes);
set => this.MessageBytes = value.Encode();
}
/// <summary>
/// Gets or Sets the name payloads
/// </summary>
public byte[] NameBytes { get; set; } = [];
/// <summary>
/// Gets or Sets the message payloads.
/// </summary>
public byte[] MessageBytes { get; set; } = [];
/// <summary>
/// Gets or sets a value indicating whether new message sounds should be silenced or not.

View file

@ -270,7 +270,7 @@ public static partial class ImGuiComponents
/// <param name="icon">Icon to use.</param>
/// <param name="text">Text to use.</param>
/// <returns>Width.</returns>
internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text)
public static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text)
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{

View file

@ -24,7 +24,7 @@ public static partial class ImGuiComponents
/// <param name="activeColor">The color of the actively-selected button.</param>
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
/// <returns>True if any button is clicked.</returns>
internal static bool IconButtonSelect<T>(string label, ref T val, IEnumerable<FontAwesomeIcon> optionIcons, IEnumerable<T> optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
public static bool IconButtonSelect<T>(string label, ref T val, IEnumerable<FontAwesomeIcon> optionIcons, IEnumerable<T> 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<FontAwesomeIcon, T>(icon, value));
return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor);
@ -43,7 +43,7 @@ public static partial class ImGuiComponents
/// <param name="activeColor">The color of the actively-selected button.</param>
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
/// <returns>True if any button is clicked.</returns>
internal static unsafe bool IconButtonSelect<T>(string label, ref T val, IEnumerable<KeyValuePair<FontAwesomeIcon, T>> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
public static unsafe bool IconButtonSelect<T>(string label, ref T val, IEnumerable<KeyValuePair<FontAwesomeIcon, T>> 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);

View file

@ -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<UIColor>() ?? throw new InvalidOperationException("Failed to access UIColor sheet."));
this.colorStackSet = new(dm.Excel.GetSheet<UIColor>());
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
}

View file

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

View file

@ -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;
/// <summary>Enumerates a UTF-N byte sequence by codepoint.</summary>
[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")]
internal ref struct UtfEnumerator
{
private readonly ReadOnlySpan<byte> data;
private readonly UtfEnumeratorFlags flags;
private readonly byte numBytesPerUnit;
private bool isBigEndian;
/// <summary>Initializes a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
/// <param name="data">UTF-N byte sequence.</param>
/// <param name="flags">Enumeration flags.</param>
public UtfEnumerator(ReadOnlySpan<byte> 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."),
};
}
/// <inheritdoc cref="IEnumerator.Current"/>
public Subsequence Current { get; private set; } = default;
/// <summary>Creates a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
/// <param name="data">UTF-N byte sequence.</param>
/// <param name="flags">Enumeration flags.</param>
/// <returns>A new enumerator.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UtfEnumerator From(ReadOnlySpan<byte> data, UtfEnumeratorFlags flags) => new(data, flags);
/// <summary>Creates a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
/// <param name="data">UTF-N byte sequence.</param>
/// <param name="flags">Enumeration flags.</param>
/// <returns>A new enumerator.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UtfEnumerator From(ReadOnlySpan<char> data, UtfEnumeratorFlags flags) =>
new(MemoryMarshal.Cast<char, byte>(data), flags);
/// <summary>Gets the representative <c>char</c> for a given SeString macro code.</summary>
/// <param name="macroCode">The macro code.</param>
/// <returns>Representative <c>char</c>, or <see cref="char.MaxValue"/> if none.</returns>
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,
};
/// <summary>Attempts to peek the next item.</summary>
/// <param name="nextSubsequence">Retrieved next item.</param>
/// <param name="isStillBigEndian">Whether it still should be parsed in big endian.</param>
/// <returns><c>true</c> if anything is retrieved.</returns>
/// <exception cref="EncoderFallbackException">The sequence is not a fully valid Unicode sequence, and
/// <see cref="UtfEnumeratorFlags.ThrowOnFirstError"/> is set.</exception>
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;
}
}
/// <inheritdoc cref="IEnumerator.MoveNext"/>
[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;
}
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UtfEnumerator GetEnumerator() => new(this.data, this.flags);
/// <summary>A part of a UTF-N sequence containing one codepoint.</summary>
[StructLayout(LayoutKind.Explicit, Size = 16)]
[DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")]
public readonly struct Subsequence : IEquatable<Subsequence>
{
/// <summary>The codepoint. Valid if <see cref="IsSeStringPayload"/> is <c>false</c>.</summary>
[FieldOffset(0)]
public readonly UtfValue Value;
/// <summary>The macro code. Valid if <see cref="IsSeStringPayload"/> is <c>true</c>.</summary>
[FieldOffset(0)]
public readonly MacroCode MacroCode;
/// <summary>The offset of this part of a UTF-8 sequence.</summary>
[FieldOffset(4)]
public readonly int ByteOffset;
/// <summary>The length of this part of a UTF-8 sequence.</summary>
/// <remarks>This may not match <see cref="UtfValue.Length8"/>, if <see cref="BrokenSequence"/> is <c>true</c>.
/// </remarks>
[FieldOffset(8)]
public readonly int ByteLength;
/// <summary>Whether this part of the UTF-8 sequence is broken.</summary>
[FieldOffset(12)]
public readonly bool BrokenSequence;
/// <summary>Whether this part of the SeString sequence is a payload.</summary>
[FieldOffset(13)]
public readonly bool IsSeStringPayload;
/// <summary>Storage at byte offset 0, for fast <see cref="Equals(Subsequence)"/> implementation.</summary>
[FieldOffset(0)]
private readonly ulong storage0;
/// <summary>Storage at byte offset 8, for fast <see cref="Equals(Subsequence)"/> implementation.</summary>
[FieldOffset(8)]
private readonly ulong storage1;
/// <summary>Initializes a new instance of the <see cref="Subsequence"/> struct.</summary>
/// <param name="value">The value.</param>
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
/// <param name="brokenSequence">Whether this part of the UTF-N sequence is broken.</param>
/// <param name="isSeStringPayload">Whether this part of the SeString sequence is a payload.</param>
[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;
}
/// <summary>Gets the effective <c>char</c> value, with invalid or non-representable codepoints replaced.
/// </summary>
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
public char EffectiveChar
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue;
}
/// <summary>Gets the effective <c>int</c> value, with invalid codepoints replaced.</summary>
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
public int EffectiveInt =>
this.IsSeStringPayload
? RepresentativeCharFor(this.MacroCode)
: this.BrokenSequence || !this.Value.TryGetRune(out var rune)
? 0xFFFD
: rune.Value;
/// <summary>Gets the effective <see cref="Rune"/> value, with invalid codepoints replaced.</summary>
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
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);
/// <summary>Creates a new instance of the <see cref="Subsequence"/> struct from a Unicode value.</summary>
/// <param name="codepoint">The codepoint.</param>
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
/// <param name="brokenSequence">Whether this part of the UTF-N sequence is broken.</param>
/// <returns>A new instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) =>
new(codepoint, byteOffset, byteLength, brokenSequence, false);
/// <summary>Creates a new instance of the <see cref="Subsequence"/> struct from a SeString payload.</summary>
/// <param name="macroCode">The macro code.</param>
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
/// <returns>A new instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) =>
new((uint)macroCode, byteOffset, byteLength, false, true);
/// <summary>Tests whether this subsequence contains a valid Unicode codepoint.</summary>
/// <returns><c>true</c> if this subsequence contains a valid Unicode codepoint.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1);
}
}

View file

@ -1,58 +0,0 @@
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
/// <summary>Flags on enumerating a unicode sequence.</summary>
[Flags]
internal enum UtfEnumeratorFlags
{
/// <summary>Use the default configuration of <see cref="Utf8"/> and <see cref="ReplaceErrors"/>.</summary>
Default = default,
/// <summary>Enumerate as UTF-8 (the default.)</summary>
Utf8 = Default,
/// <summary>Enumerate as UTF-8 in a SeString.</summary>
Utf8SeString = 1 << 1,
/// <summary>Enumerate as UTF-16.</summary>
Utf16 = 1 << 2,
/// <summary>Enumerate as UTF-32.</summary>
Utf32 = 1 << 3,
/// <summary>Bitmask for specifying the encoding.</summary>
UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32,
/// <summary>On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.)</summary>
ReplaceErrors = Default,
/// <summary>On error, drop the invalid byte.</summary>
IgnoreErrors = 1 << 4,
/// <summary>On error, stop the handling.</summary>
TerminateOnFirstError = 1 << 5,
/// <summary>On error, throw an exception.</summary>
ThrowOnFirstError = 1 << 6,
/// <summary>Bitmask for specifying the error handling mode.</summary>
ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError,
/// <summary>Use the current system native endianness from <see cref="BitConverter.IsLittleEndian"/>
/// (the default.)</summary>
NativeEndian = Default,
/// <summary>Use little endianness.</summary>
LittleEndian = 1 << 7,
/// <summary>Use big endianness.</summary>
BigEndian = 1 << 8,
/// <summary>Bitmask for specifying endianness.</summary>
EndiannessMask = NativeEndian | LittleEndian | BigEndian,
/// <summary>Disrespect byte order mask.</summary>
DisrespectByteOrderMask = 1 << 9,
/// <summary>Yield byte order masks, if it shows up.</summary>
YieldByteOrderMask = 1 << 10,
}

View file

@ -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;
/// <summary>Represents a single value to be used in a UTF-N byte sequence.</summary>
[StructLayout(LayoutKind.Explicit, Size = 4)]
[DebuggerDisplay("0x{IntValue,h} ({CharValue})")]
internal readonly struct UtfValue : IEquatable<UtfValue>, IComparable<UtfValue>
{
/// <summary>The unicode codepoint in <c>int</c>, that may not be in a valid range.</summary>
[FieldOffset(0)]
public readonly int IntValue;
/// <summary>The unicode codepoint in <c>uint</c>, that may not be in a valid range.</summary>
[FieldOffset(0)]
public readonly uint UIntValue;
/// <summary>The high UInt16 value in <c>char</c>, that may have been cut off if outside BMP.</summary>
[FieldOffset(0)]
public readonly char CharValue;
/// <summary>Initializes a new instance of the <see cref="UtfValue"/> struct.</summary>
/// <param name="value">The raw codepoint value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UtfValue(uint value) => this.UIntValue = value;
/// <inheritdoc cref="UtfValue(uint)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UtfValue(int value) => this.IntValue = value;
/// <summary>Gets the length of this codepoint, encoded in UTF-8.</summary>
public int Length8
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => GetEncodedLength8(this);
}
/// <summary>Gets the length of this codepoint, encoded in UTF-16.</summary>
public int Length16
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => GetEncodedLength16(this);
}
/// <summary>Gets the short name, if supported.</summary>
/// <returns>The buffer containing the short name, or empty if unsupported.</returns>
public ReadOnlySpan<char> 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;
/// <summary>Gets the short name of the codepoint, for some select codepoints.</summary>
/// <param name="codepoint">The codepoint.</param>
/// <returns>The value.</returns>
public static ReadOnlySpan<char> 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,
};
/// <summary>Gets the length of the codepoint, when encoded in UTF-8.</summary>
/// <param name="codepoint">The codepoint to encode.</param>
/// <returns>The length.</returns>
[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,
};
/// <summary>Gets the length of the codepoint, when encoded in UTF-16.</summary>
/// <param name="codepoint">The codepoint to encode.</param>
/// <returns>The length.</returns>
[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,
};
/// <inheritdoc cref="TryDecode8(ReadOnlySpan{byte}, out UtfValue, out int)"/>
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryDecode8(ref ReadOnlySpan<byte> source, out UtfValue value, out int length)
{
var v = TryDecode8(source, out value, out length);
source = source[length..];
return v;
}
/// <summary>Attempts to decode a value from a UTF-8 byte sequence.</summary>
/// <param name="source">The span to decode from.</param>
/// <param name="value">The decoded value.</param>
/// <param name="length">The length of the consumed bytes. <c>1</c> if sequence is broken.</param>
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> 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.</remarks>
public static unsafe bool TryDecode8(ReadOnlySpan<byte> 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;
}
}
/// <inheritdoc cref="TryDecode16(ReadOnlySpan{byte}, bool, out UtfValue, out int)"/>
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryDecode16(ref ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
{
var v = TryDecode16(source, be, out value, out length);
source = source[length..];
return v;
}
/// <summary>Attempts to decode a value from a UTF-16 byte sequence.</summary>
/// <param name="source">The span to decode from.</param>
/// <param name="be">Whether to use big endian.</param>
/// <param name="value">The decoded value.</param>
/// <param name="length">The length of the consumed bytes. <c>1</c> if cut short.
/// <c>2</c> if sequence is broken.</param>
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> 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.</remarks>
public static unsafe bool TryDecode16(ReadOnlySpan<byte> 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;
}
}
/// <inheritdoc cref="TryDecode32(ReadOnlySpan{byte}, bool, out UtfValue, out int)"/>
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryDecode32(ref ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
{
var v = TryDecode32(source, be, out value, out length);
source = source[length..];
return v;
}
/// <summary>Attempts to decode a value from a UTF-32 byte sequence.</summary>
/// <param name="source">The span to decode from.</param>
/// <param name="be">Whether to use big endian.</param>
/// <param name="value">The decoded value.</param>
/// <param name="length">The length of the consumed bytes. <c>1 to 3</c> if cut short.
/// <c>4</c> if sequence is broken.</param>
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> 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.</remarks>
public static bool TryDecode32(ReadOnlySpan<byte> 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;
}
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
/// <param name="target">The target stream.</param>
/// <param name="codepoint">The codepoint to encode.</param>
/// <returns>The length of the encoded data.</returns>
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Encode8(Stream target, int codepoint)
{
Span<byte> buf = stackalloc byte[7];
Encode8(buf, codepoint, out var length);
target.Write(buf[..length]);
return length;
}
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
/// <param name="target">The target byte span.</param>
/// <param name="codepoint">The codepoint to encode.</param>
/// <returns>The length of the encoded data.</returns>
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Encode8(ref Span<byte> target, int codepoint)
{
target = Encode8(target, codepoint, out var length);
return length;
}
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
/// <param name="target">The optional target byte span.</param>
/// <param name="codepoint">The codepoint to encode.</param>
/// <param name="length">The length of the encoded data.</param>
/// <returns>The remaning region of <paramref name="target"/>.</returns>
public static Span<byte> Encode8(Span<byte> 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;
}
}
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
/// <param name="target">The target stream.</param>
/// <param name="codepoint">The codepoint to encode.</param>
/// <param name="be">Whether to use big endian.</param>
/// <returns>The length of the encoded data.</returns>
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Encode16(Stream target, int codepoint, bool be)
{
Span<byte> buf = stackalloc byte[8];
Encode16(buf, codepoint, be, out var length);
target.Write(buf[..length]);
return length;
}
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
/// <param name="target">The target byte span.</param>
/// <param name="codepoint">The codepoint to encode.</param>
/// <param name="be">Whether to use big endian.</param>
/// <returns>The length of the encoded data.</returns>
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Encode16(ref Span<byte> target, int codepoint, bool be)
{
target = Encode16(target, codepoint, be, out var length);
return length;
}
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
/// <param name="target">The optional target byte span.</param>
/// <param name="codepoint">The codepoint to encode.</param>
/// <param name="be">Whether to use big endian.</param>
/// <param name="length">The length of the encoded data.</param>
/// <returns>The remaning region of <paramref name="target"/>.</returns>
public static Span<byte> Encode16(Span<byte> 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;
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(UtfValue other) => this.IntValue == other.IntValue;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => this.IntValue;
/// <summary>Attempts to get the corresponding rune.</summary>
/// <param name="rune">The retrieved rune.</param>
/// <returns><c>true</c> if retrieved.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetRune(out Rune rune)
{
if (Rune.IsValid(this.IntValue))
{
rune = new(this.IntValue);
return true;
}
rune = default;
return false;
}
/// <summary>Encodes the codepoint to the target.</summary>
/// <param name="target">The target byte span.</param>
/// <returns>The remaning region of <paramref name="target"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> Encode8(Span<byte> target) => Encode8(target, this, out _);
}

View file

@ -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");

View file

@ -11,6 +11,7 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
/// <inheritdoc cref="AddonTree"/>
public unsafe partial class AddonTree
{
/// <summary>
@ -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");

View file

@ -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);

View file

@ -27,7 +27,8 @@ internal unsafe partial class ResNodeTree
/// </summary>
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();
}

View file

@ -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);

View file

@ -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;
/// <inheritdoc cref="NineGridNodeTree"/>
internal unsafe partial class NineGridNodeTree
{
/// <summary>
/// A struct representing the four offsets of an <see cref="AtkNineGridNode"/>.
/// </summary>
internal struct NineGridOffsets
{
/// <summary>Top offset.</summary>
internal int Top;
/// <summary>Left offset.</summary>
internal int Left;
/// <summary>Right offset.</summary>
internal int Right;
/// <summary>Bottom offset.</summary>
internal int Bottom;
/// <summary>
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
/// </summary>
/// <param name="top">The top offset.</param>
/// <param name="right">The right offset.</param>
/// <param name="bottom">The bottom offset.</param>
/// <param name="left">The left offset.</param>
internal NineGridOffsets(int top, int right, int bottom, int left)
{
this.Top = top;
this.Right = right;
this.Left = left;
this.Bottom = bottom;
}
/// <summary>
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
/// </summary>
/// <param name="ngNode">The node using these offsets.</param>
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;
/// <summary>Prints the offsets in ImGui.</summary>
internal readonly void Print() => PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}"));
}
}

View file

@ -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();
}
/// <summary>
/// A struct representing the four offsets of an <see cref="AtkNineGridNode"/>.
/// </summary>
internal struct NineGridOffsets
{
/// <summary>Top offset.</summary>
internal int Top;
/// <summary>Left offset.</summary>
internal int Left;
/// <summary>Right offset.</summary>
internal int Right;
/// <summary>Bottom offset.</summary>
internal int Bottom;
/// <summary>
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
/// </summary>
/// <param name="top">The top offset.</param>
/// <param name="right">The right offset.</param>
/// <param name="bottom">The bottom offset.</param>
/// <param name="left">The left offset.</param>
internal NineGridOffsets(int top, int right, int bottom, int left)
{
this.Top = top;
this.Right = right;
this.Left = left;
this.Bottom = bottom;
}
/// <summary>
/// Initializes a new instance of the <see cref="NineGridOffsets"/> struct.
/// </summary>
/// <param name="ngNode">The node using these offsets.</param>
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;
/// <summary>Prints the offsets in ImGui.</summary>
internal readonly void Print() => Gui.PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}"));
}
}

View file

@ -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

View file

@ -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];

View file

@ -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)
{

View file

@ -79,7 +79,9 @@ internal unsafe class ElementSelector : IDisposable
/// </summary>
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();

View file

@ -39,7 +39,8 @@ internal class AddonPopoutWindow : Window, IDisposable
/// <inheritdoc/>
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();
}

View file

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

View file

@ -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)
{

View file

@ -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)
{

View file

@ -68,7 +68,7 @@ internal static class Gui
/// <remarks>Colors the text itself either white or black, depending on the luminosity of the background color.</remarks>
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))
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))
@ -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]);
}

View file

@ -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<byte> testStringBuffer;
private string testString = string.Empty;
private ExcelSheet<Addon> addons;
private ExcelSheet<Addon> 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<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span)
Service<ChatGui>.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p));
Service<ChatGui>.Get().Print(
Game.Text.SeStringHandling.SeString.Parse(
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
}
ImGuiHelpers.ScaledDummy(3);

View file

@ -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;
/// <summary>
@ -60,6 +61,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
private State state = State.Hide;
private int lastLoadedPluginCount = -1;
/// <summary>
/// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
/// </summary>
@ -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<PluginManager>.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();

View file

@ -162,6 +162,27 @@ internal class PluginManager : IInternalDisposableService
/// </summary>
public static int DalamudApiLevel { get; private set; }
/// <summary>
/// Gets the number of loaded plugins.
/// </summary>
public int LoadedPluginCount
{
get
{
var res = 0;
lock (this.pluginListLock)
{
foreach (var p in this.installedPluginsList)
{
if (p.State == PluginState.Loaded)
res++;
}
}
return res;
}
}
/// <summary>
/// Gets a copy of the list of all loaded plugins.
/// </summary>

View file

@ -121,4 +121,20 @@ public interface IChatGui
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
/// <param name="tagColor">Color to display the message tag with.</param>
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null);
/// <summary>
/// Queue a chat message. Dalamud will send queued messages on the next framework event.
/// </summary>
/// <param name="message">A message to send.</param>
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
/// <param name="tagColor">Color to display the message tag with.</param>
public void Print(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null);
/// <summary>
/// Queue a chat message. Dalamud will send queued messages on the next framework event.
/// </summary>
/// <param name="message">A message to send.</param>
/// <param name="messageTag">String to prepend message with "[messageTag] ".</param>
/// <param name="tagColor">Color to display the message tag with.</param>
public void PrintError(ReadOnlySpan<byte> message, string? messageTag = null, ushort? tagColor = null);
}

View file

@ -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,45 +1030,47 @@ 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<T>(ulong addr, IList<string> path, int offset, bool isTop, in Span<T> 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);
}
}
}
else
{
ShowSpanEntryPrivate(addr, path, offset, spanobj);
}
}
try
{
private static unsafe void ShowSpanEntryPrivate<T>(ulong addr, IList<string> path, int offset, Span<T> 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}");
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)}"))
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))
{
try
if (tree.Success)
{
ShowSpanPrivate(addr, path, offset + i, false, spanobj[i..next]);
}
finally
{
ImGui.TreePop();
ShowSpanEntryPrivate(addr, path, offset + i, spanobj[i..next]);
}
}
@ -1088,13 +1092,8 @@ public static class Util
}
}
}
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 ShowValue(ulong addr, IList<string> path, Type type, object value, bool hideAddress)
{
@ -1111,9 +1110,10 @@ public static class Util
if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr)
{
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff);
using (ImRaii.PushColor(ImGuiCol.Text, 0xffcbc0ff))
{
ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}");
ImGui.PopStyleColor();
}
}
ImGui.SameLine();
@ -1165,9 +1165,10 @@ public static class Util
/// <param name="hideAddress">Do not print addresses. Use when displaying a copied value.</param>
private static void ShowStructInternal(object obj, ulong addr, bool autoExpand = false, IEnumerable<string>? path = null, bool hideAddress = false)
{
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2));
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)))
{
path ??= new List<string>();
var pathList = path is List<string> ? (List<string>)path : path.ToList();
var pathList = path as List<string> ?? path.ToList();
if (moduleEndAddr == 0 && moduleStartAddr == 0)
{
@ -1190,28 +1191,36 @@ public static class Util
}
}
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF);
if (autoExpand)
{
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
}
if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}"))
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 (tree.Success)
{
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.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), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]");
}
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}");
}
@ -1223,15 +1232,24 @@ public static class Util
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)
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f));
using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)))
{
ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}");
ImGui.PopStyleColor();
}
}
finally
{
@ -1239,7 +1257,7 @@ public static class Util
}
}
foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0))
foreach (var p in obj.GetType().GetProperties().Where(static p => p.GetGetMethod()?.GetParameters().Length == 0))
{
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}");
ImGui.SameLine();
@ -1250,31 +1268,31 @@ public static class Util
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));
using (ImRaii.PushColor(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();
}
}
}
}

@ -1 +1 @@
Subproject commit 33a98af530e52d5b54714ec9f7704c07bf9fdd91
Subproject commit d4778799d378fe66099fd337f55849acf1f16dcd

@ -1 +1 @@
Subproject commit 2f37349ffd778561a1103a650683116c43edc86c
Subproject commit b0d41471b7ef3d69daaf6d862eb74e7e00a25651