From a7ab3b9deffbcb35e0afd08229edf7002931549a Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 28 Jul 2024 11:30:49 +0200 Subject: [PATCH 001/375] [master] Update ClientStructs (#1979) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 731e3ab00..f1df136b2 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 731e3ab0006ce56c4fe789aee148bc967965b914 +Subproject commit f1df136b28b1bf5319ddffc17c4563e575865de8 From c25f13261d7307b1192f751f33ed4303bcb4544f Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 28 Jul 2024 21:14:37 +0900 Subject: [PATCH 002/375] Make DtrBar more threadsafe (#1978) * Changed DtrBar to use ReaderWriterLockSlim so that there exists only one storage of entries, preventing possible desync. * DtrBarEntry will now hold a reference to the LocalPlugin that created the entry, so that DtrBarPluginScoped can defer plugin related handling to the main service. * Marked DtrBarEntry class itself to be turned internal in API 11. * Made IDtrBar.Entries return an immutable copy of underlying list of DtrBar entries, that will be freshly created whenever the list changes. --- .../Game/ClientState/Objects/ObjectTable.cs | 2 +- Dalamud/Game/Gui/Dtr/DtrBar.cs | 334 ++++++++++-------- Dalamud/Game/Gui/Dtr/DtrBarEntry.cs | 17 +- .../Windows/Data/Widgets/DtrBarWidget.cs | 139 +++++++- .../Windows/Settings/Tabs/SettingsTabDtr.cs | 19 + Dalamud/Plugin/Internal/PluginManager.cs | 6 - Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 6 - Dalamud/Plugin/Services/IDtrBar.cs | 16 +- Dalamud/Utility/Api10ToDoAttribute.cs | 29 -- Dalamud/Utility/Api11ToDoAttribute.cs | 24 ++ 10 files changed, 385 insertions(+), 207 deletions(-) delete mode 100644 Dalamud/Utility/Api10ToDoAttribute.cs create mode 100644 Dalamud/Utility/Api11ToDoAttribute.cs diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 50f4e81ce..c0b9c6e12 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -150,7 +150,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable }; } - [Api10ToDo("Use ThreadSafety.AssertMainThread() instead of this.")] + [Api11ToDo("Use ThreadSafety.AssertMainThread() instead of this.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool WarnMultithreadedUsage() { diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 55b2573f0..04e7fea07 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -1,6 +1,7 @@ -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Threading; using Dalamud.Configuration.Internal; using Dalamud.Game.Addon.Events; @@ -10,6 +11,7 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics; @@ -48,11 +50,13 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private readonly AddonLifecycleEventListener dtrPostRequestedUpdateListener; private readonly AddonLifecycleEventListener dtrPreFinalizeListener; - private readonly ConcurrentBag newEntries = new(); - private readonly List entries = new(); + private readonly ReaderWriterLockSlim entriesLock = new(); + private readonly List entries = []; private readonly Dictionary> eventHandles = new(); + private ImmutableList? entriesReadOnlyCopy; + private Utf8String* emptyString; private uint runningNodeIds = BaseNodeId; @@ -71,52 +75,108 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.framework.Update += this.Update; - this.configuration.DtrOrder ??= new List(); - this.configuration.DtrIgnore ??= new List(); + this.configuration.DtrOrder ??= []; + this.configuration.DtrIgnore ??= []; this.configuration.QueueSave(); } - /// - /// Event type fired each time a DtrEntry was removed. - /// - /// The title of the bar entry. - internal delegate void DtrEntryRemovedDelegate(string title); - - /// - /// Event fired each time a DtrEntry was removed. - /// - internal event DtrEntryRemovedDelegate? DtrEntryRemoved; - /// - public IReadOnlyList Entries => this.entries; - - /// - public IDtrBarEntry Get(string title, SeString? text = null) + public IReadOnlyList Entries { - if (this.entries.Any(x => x.Title == title) || this.newEntries.Any(x => x.Title == title)) - throw new ArgumentException("An entry with the same title already exists."); - - var entry = new DtrBarEntry(this.configuration, title, null); - entry.Text = text; - - // Add the entry to the end of the order list, if it's not there already. - if (!this.configuration.DtrOrder!.Contains(title)) - this.configuration.DtrOrder!.Add(title); - - this.newEntries.Add(entry); - - return entry; - } - - /// - public void Remove(string title) - { - if (this.entries.FirstOrDefault(entry => entry.Title == title) is { } dtrBarEntry) + get { - dtrBarEntry.Remove(); + var erc = this.entriesReadOnlyCopy; + if (erc is null) + { + this.entriesLock.EnterReadLock(); + this.entriesReadOnlyCopy = erc = [..this.entries]; + this.entriesLock.ExitReadLock(); + } + + return erc; } } + /// + /// Get a DTR bar entry. + /// This allows you to add your own text, and users to sort it. + /// + /// Plugin that owns the DTR bar, or null if owned by Dalamud. + /// A user-friendly name for sorting. + /// The text the entry shows. + /// The entry object used to update, hide and remove the entry. + /// Thrown when an entry with the specified title exists. + public IDtrBarEntry Get(LocalPlugin? plugin, string title, SeString? text = null) + { + this.entriesLock.EnterUpgradeableReadLock(); + + foreach (var existingEntry in this.entries) + { + if (existingEntry.Title == title) + { + existingEntry.ShouldBeRemoved = false; + this.entriesLock.ExitUpgradeableReadLock(); + if (plugin == existingEntry.OwnerPlugin) + return existingEntry; + throw new ArgumentException("An entry with the same title already exists."); + } + } + + this.entriesLock.EnterWriteLock(); + var entry = new DtrBarEntry(this.configuration, title, null) { Text = text, OwnerPlugin = plugin }; + this.entries.Add(entry); + + // Add the entry to the end of the order list, if it's not there already. + var dtrOrder = this.configuration.DtrOrder ??= []; + if (!dtrOrder.Contains(entry.Title)) + dtrOrder.Add(entry.Title); + this.ApplySortUnsafe(dtrOrder); + + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + + this.entriesLock.ExitUpgradeableReadLock(); + return entry; + } + + /// + public IDtrBarEntry Get(string title, SeString? text = null) => this.Get(null, title, text); + + /// + /// Removes a DTR bar entry from the system. + /// + /// Plugin that owns the DTR bar, or null if owned by Dalamud. + /// Title of the entry to remove, or null to remove all entries under the plugin. + /// Remove operation is not immediate. If you try to add right after removing, the operation may fail. + /// + public void Remove(LocalPlugin? plugin, string? title) + { + this.entriesLock.EnterUpgradeableReadLock(); + + foreach (var entry in this.entries) + { + if ((title is null || entry.Title == title) && (plugin is null || entry.OwnerPlugin == plugin)) + { + if (!entry.Added) + { + this.entriesLock.EnterWriteLock(); + this.RemoveEntry(entry); + this.entries.Remove(entry); + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + } + + entry.Remove(); + break; + } + } + + this.entriesLock.ExitUpgradeableReadLock(); + } + + /// + public void Remove(string title) => this.Remove(null, title); + /// void IInternalDisposableService.DisposeService() { @@ -124,10 +184,17 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener); this.addonLifecycle.UnregisterListener(this.dtrPreFinalizeListener); - foreach (var entry in this.entries) - this.RemoveEntry(entry); + this.framework.RunOnFrameworkThread( + () => + { + this.entriesLock.EnterWriteLock(); + foreach (var entry in this.entries) + this.RemoveEntry(entry); + this.entries.Clear(); + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + }).Wait(); - this.entries.Clear(); this.framework.Update -= this.Update; if (this.emptyString != null) @@ -137,23 +204,6 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar } } - /// - /// Remove nodes marked as "should be removed" from the bar. - /// - internal void HandleRemovedNodes() - { - foreach (var data in this.entries) - { - if (data.ShouldBeRemoved) - { - this.RemoveEntry(data); - this.DtrEntryRemoved?.Invoke(data.Title); - } - } - - this.entries.RemoveAll(d => d.ShouldBeRemoved); - } - /// /// Remove native resources for the specified entry. /// @@ -174,7 +224,17 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar /// /// The title to check for. /// Whether or not an entry with that title is registered. - internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title); + internal bool HasEntry(string title) + { + var found = false; + + this.entriesLock.EnterReadLock(); + for (var i = 0; i < this.entries.Count && !found; i++) + found = this.entries[i].Title == title; + this.entriesLock.ExitReadLock(); + + return found; + } /// /// Dirty the DTR bar entry with the specified title. @@ -183,24 +243,37 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar /// Whether the entry was found. internal bool MakeDirty(string title) { - var entry = this.entries.FirstOrDefault(x => x.Title == title); - if (entry == null) - return false; + var found = false; - entry.Dirty = true; - return true; + this.entriesLock.EnterReadLock(); + for (var i = 0; i < this.entries.Count && !found; i++) + { + found = this.entries[i].Title == title; + if (found) + this.entries[i].Dirty = true; + } + + this.entriesLock.ExitReadLock(); + + return found; } /// /// Reapply the DTR entry ordering from . /// internal void ApplySort() + { + this.entriesLock.EnterWriteLock(); + this.ApplySortUnsafe(this.configuration.DtrOrder ??= []); + this.entriesLock.ExitWriteLock(); + } + + private void ApplySortUnsafe(List dtrOrder) { // Sort the current entry list, based on the order in the configuration. - var positions = this.configuration - .DtrOrder! - .Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry))) - .ToDictionary(x => x.entry, x => x.index); + var positions = dtrOrder + .Select(entry => (entry, index: dtrOrder.IndexOf(entry))) + .ToDictionary(x => x.entry, x => x.index); this.entries.Sort((x, y) => { @@ -208,15 +281,13 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue; return xPos.CompareTo(yPos); }); + this.entriesReadOnlyCopy = null; } private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer(); private void Update(IFramework unused) { - this.HandleRemovedNodes(); - this.HandleAddedNodes(); - var dtr = this.GetDtr(); if (dtr == null || dtr->RootNode == null || dtr->RootNode->ChildNode == null) return; @@ -236,14 +307,27 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var runningXPos = this.entryStartPos; - foreach (var data in this.entries) + this.entriesLock.EnterUpgradeableReadLock(); + for (var i = 0; i < this.entries.Count; i++) { - if (!data.Added) + var data = this.entries[i]; + if (data.ShouldBeRemoved) { - data.Added = this.AddNode(data.TextNode); - data.Dirty = true; + this.entriesLock.EnterWriteLock(); + this.entries.RemoveAt(i); + this.RemoveEntry(data); + this.entriesReadOnlyCopy = null; + this.entriesLock.ExitWriteLock(); + i--; + continue; } + if (!data.Added) + data.Added = this.AddNode(data); + + if (!data.Added || data.TextNode is null) // TextNode check is unnecessary, but just in case. + continue; + var isHide = !data.Shown || data.UserHidden; var node = data.TextNode; var nodeHidden = !node->AtkResNode.IsVisible(); @@ -290,23 +374,10 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar data.Dirty = false; } + + this.entriesLock.ExitUpgradeableReadLock(); } - private void HandleAddedNodes() - { - if (!this.newEntries.IsEmpty) - { - foreach (var newEntry in this.newEntries) - { - newEntry.TextNode = this.MakeNode(++this.runningNodeIds); - this.entries.Add(newEntry); - } - - this.newEntries.Clear(); - this.ApplySort(); - } - } - private void FixCollision(AddonEvent eventType, AddonArgs addonInfo) { var addon = (AtkUnitBase*)addonInfo.Addon; @@ -316,7 +387,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var additionalWidth = 0; AtkResNode* collisionNode = null; - foreach (var index in Enumerable.Range(0, addon->UldManager.NodeListCount)) + for (var index = 0; index < addon->UldManager.NodeListCount; index++) { var node = addon->UldManager.NodeList[index]; if (node->IsVisible()) @@ -382,22 +453,20 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private void RecreateNodes() { this.runningNodeIds = BaseNodeId; - if (this.entries.Any()) - { - this.eventHandles.Clear(); - } + this.entriesLock.EnterReadLock(); + this.eventHandles.Clear(); foreach (var entry in this.entries) - { - entry.TextNode = this.MakeNode(++this.runningNodeIds); entry.Added = false; - } + this.entriesLock.ExitReadLock(); } - private bool AddNode(AtkTextNode* node) + private bool AddNode(DtrBarEntry data) { var dtr = this.GetDtr(); - if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false; + if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null) return false; + + var node = data.TextNode = this.MakeNode(++this.runningNodeIds); this.eventHandles.TryAdd(node->AtkResNode.NodeId, new List()); this.eventHandles[node->AtkResNode.NodeId].AddRange(new List @@ -420,6 +489,8 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar dtr->UldManager.UpdateDrawNodeList(); dtr->UpdateCollisionNodeList(false); Log.Debug("Updated node draw list"); + + data.Dirty = true; return true; } @@ -497,7 +568,15 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var addon = (AtkUnitBase*)atkUnitBase; var node = (AtkResNode*)atkResNode; - if (this.entries.FirstOrDefault(entry => entry.TextNode == node) is not { } dtrBarEntry) return; + DtrBarEntry? dtrBarEntry = null; + this.entriesLock.EnterReadLock(); + foreach (var entry in this.entries) + { + if (entry.TextNode == node) + dtrBarEntry = entry; + } + + this.entriesLock.ExitReadLock(); if (dtrBarEntry is { Tooltip: not null }) { @@ -541,58 +620,25 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar +internal sealed class DtrBarPluginScoped : IInternalDisposableService, IDtrBar { + private readonly LocalPlugin plugin; + [ServiceManager.ServiceDependency] private readonly DtrBar dtrBarService = Service.Get(); - private readonly Dictionary pluginEntries = new(); - - /// - /// Initializes a new instance of the class. - /// - internal DtrBarPluginScoped() - { - this.dtrBarService.DtrEntryRemoved += this.OnDtrEntryRemoved; - } + [ServiceManager.ServiceConstructor] + private DtrBarPluginScoped(LocalPlugin plugin) => this.plugin = plugin; /// public IReadOnlyList Entries => this.dtrBarService.Entries; /// - void IInternalDisposableService.DisposeService() - { - this.dtrBarService.DtrEntryRemoved -= this.OnDtrEntryRemoved; - - foreach (var entry in this.pluginEntries) - { - entry.Value.Remove(); - } - - this.pluginEntries.Clear(); - } + void IInternalDisposableService.DisposeService() => this.dtrBarService.Remove(this.plugin, null); /// - public IDtrBarEntry Get(string title, SeString? text = null) - { - // If we already have a known entry for this plugin, return it. - if (this.pluginEntries.TryGetValue(title, out var existingEntry)) return existingEntry; + public IDtrBarEntry Get(string title, SeString? text = null) => this.dtrBarService.Get(this.plugin, title, text); - return this.pluginEntries[title] = this.dtrBarService.Get(title, text); - } - /// - public void Remove(string title) - { - if (this.pluginEntries.TryGetValue(title, out var existingEntry)) - { - existingEntry.Remove(); - this.pluginEntries.Remove(title); - } - } - - private void OnDtrEntryRemoved(string title) - { - this.pluginEntries.Remove(title); - } + public void Remove(string title) => this.dtrBarService.Remove(this.plugin, title); } diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index fc5210fda..a2fa73ec6 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -1,7 +1,6 @@ -using System.Linq; - -using Dalamud.Configuration.Internal; +using Dalamud.Configuration.Internal; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; @@ -86,6 +85,7 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry /// /// Class representing an entry in the server info bar. /// +[Api11ToDo(Api11ToDoAttribute.MakeInternal)] public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry { private readonly DalamudConfiguration configuration; @@ -146,7 +146,7 @@ public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry } /// - [Api10ToDo("Maybe make this config scoped to internalname?")] + [Api11ToDo("Maybe make this config scoped to internalname?")] public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false; /// @@ -160,9 +160,9 @@ public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry internal Utf8String* Storage { get; set; } /// - /// Gets a value indicating whether this entry should be removed. + /// Gets or sets a value indicating whether this entry should be removed. /// - internal bool ShouldBeRemoved { get; private set; } + internal bool ShouldBeRemoved { get; set; } /// /// Gets or sets a value indicating whether this entry is dirty. @@ -174,6 +174,11 @@ public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry /// internal bool Added { get; set; } + /// + /// Gets or sets the plugin that owns this entry. + /// + internal LocalPlugin? OwnerPlugin { get; set; } + /// public bool TriggerClickAction() { diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs index 1f58541be..8b7a692d4 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/DtrBarWidget.cs @@ -1,4 +1,8 @@ -using Dalamud.Configuration.Internal; +using System.Linq; +using System.Threading; + +using Dalamud.Configuration.Internal; +using Dalamud.Game; using Dalamud.Game.Gui.Dtr; using ImGuiNET; @@ -8,17 +12,20 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying dtr test. /// -internal class DtrBarWidget : IDataWindowWidget +internal class DtrBarWidget : IDataWindowWidget, IDisposable { private IDtrBarEntry? dtrTest1; private IDtrBarEntry? dtrTest2; private IDtrBarEntry? dtrTest3; - + + private Thread? loadTestThread; + private CancellationTokenSource? loadTestThreadCt; + /// public string[]? CommandShortcuts { get; init; } = { "dtr", "dtrbar" }; - + /// - public string DisplayName { get; init; } = "DTR Bar"; + public string DisplayName { get; init; } = "DTR Bar"; /// public bool Ready { get; set; } @@ -26,31 +33,145 @@ internal class DtrBarWidget : IDataWindowWidget /// public void Load() { + this.ClearState(); this.Ready = true; } + /// + public void Dispose() => this.ClearState(); + /// public void Draw() { + if (this.loadTestThread?.IsAlive is not true) + { + if (ImGui.Button("Do multithreaded add/remove operation")) + { + var ct = this.loadTestThreadCt = new(); + var dbar = Service.Get(); + var fw = Service.Get(); + var rng = new Random(); + this.loadTestThread = new( + () => + { + var threads = Enumerable + .Range(0, Environment.ProcessorCount) + .Select( + i => new Thread( + (i % 4) switch + { + 0 => () => + { + try + { + while (true) + { + var n = $"DtrBarWidgetTest{rng.NextInt64(8)}"; + dbar.Get(n, n[^5..]); + fw.DelayTicks(1, ct.Token).Wait(ct.Token); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + 1 => () => + { + try + { + while (true) + { + dbar.Remove($"DtrBarWidgetTest{rng.NextInt64(8)}"); + fw.DelayTicks(1, ct.Token).Wait(ct.Token); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + 2 => () => + { + try + { + while (true) + { + var n = $"DtrBarWidgetTest{rng.NextInt64(8)}_"; + dbar.Get(n, n[^6..]); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + _ => () => + { + try + { + while (true) + { + dbar.Remove($"DtrBarWidgetTest{rng.NextInt64(8)}_"); + ct.Token.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + // ignore + } + }, + })) + .ToArray(); + foreach (var t in threads) t.Start(); + foreach (var t in threads) t.Join(); + for (var i = 0; i < 8; i++) dbar.Remove($"DtrBarWidgetTest{i % 8}"); + for (var i = 0; i < 8; i++) dbar.Remove($"DtrBarWidgetTest{i % 8}_"); + }); + this.loadTestThread.Start(); + } + } + else + { + if (ImGui.Button("Stop multithreaded add/remove operation")) + this.ClearState(); + } + + ImGui.Separator(); this.DrawDtrTestEntry(ref this.dtrTest1, "DTR Test #1"); + ImGui.Separator(); this.DrawDtrTestEntry(ref this.dtrTest2, "DTR Test #2"); + ImGui.Separator(); this.DrawDtrTestEntry(ref this.dtrTest3, "DTR Test #3"); + ImGui.Separator(); + ImGui.Text("IDtrBar.Entries:"); + foreach (var e in Service.Get().Entries) + ImGui.Text(e.Title); var configuration = Service.Get(); if (configuration.DtrOrder != null) { ImGui.Separator(); - + ImGui.Text("DtrOrder:"); foreach (var order in configuration.DtrOrder) - { ImGui.Text(order); - } } } - + + private void ClearState() + { + this.loadTestThreadCt?.Cancel(); + this.loadTestThread?.Join(); + this.loadTestThread = null; + this.loadTestThreadCt = null; + } + private void DrawDtrTestEntry(ref IDtrBarEntry? entry, string title) { var dtrBar = Service.Get(); diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs index 2559911cf..d1040b5b2 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabDtr.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using CheapLoc; using Dalamud.Configuration.Internal; @@ -45,6 +46,10 @@ public class SettingsTabDtr : SettingsTab } var isOrderChange = false; + Span upButtonCenters = stackalloc Vector2[order.Count]; + Span downButtonCenters = stackalloc Vector2[order.Count]; + scoped Span moveMouseTo = default; + var moveMouseToIndex = -1; for (var i = 0; i < order.Count; i++) { var title = order[i]; @@ -65,9 +70,13 @@ public class SettingsTabDtr : SettingsTab { (order[i], order[i - 1]) = (order[i - 1], order[i]); isOrderChange = true; + moveMouseToIndex = i - 1; + moveMouseTo = upButtonCenters; } } + upButtonCenters[i] = (ImGui.GetItemRectMin() + ImGui.GetItemRectMax()) / 2; + ImGui.SameLine(); var arrowDownText = $"{FontAwesomeIcon.ArrowDown.ToIconString()}##{title}"; @@ -81,9 +90,13 @@ public class SettingsTabDtr : SettingsTab { (order[i], order[i + 1]) = (order[i + 1], order[i]); isOrderChange = true; + moveMouseToIndex = i + 1; + moveMouseTo = downButtonCenters; } } + downButtonCenters[i] = (ImGui.GetItemRectMin() + ImGui.GetItemRectMax()) / 2; + ImGui.PopFont(); ImGui.SameLine(); @@ -107,6 +120,12 @@ public class SettingsTabDtr : SettingsTab // } } + if (moveMouseToIndex >= 0 && moveMouseToIndex < moveMouseTo.Length) + { + ImGui.GetIO().WantSetMousePos = true; + ImGui.GetIO().MousePos = moveMouseTo[moveMouseToIndex]; + } + configuration.DtrOrder = order.Concat(orderLeft).ToList(); configuration.DtrIgnore = ignore.Concat(ignoreLeft).ToList(); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 5d365d66c..910472f5f 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -15,13 +15,11 @@ using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.Gui; -using Dalamud.Game.Gui.Dtr; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Windows.PluginInstaller; using Dalamud.IoC; using Dalamud.Logging.Internal; using Dalamud.Networking.Http; @@ -1123,10 +1121,6 @@ internal class PluginManager : IInternalDisposableService return updateStatus; } - // We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates. - var dtr = Service.Get(); - dtr.HandleRemovedNodes(); - try { await this.InstallPluginInternalAsync(metadata.UpdateManifest, metadata.UseTesting, PluginLoadReason.Update, updateStream, workingPluginId); diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 00fa9d243..b94c40918 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -6,8 +6,6 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; -using Dalamud.Game.Gui; -using Dalamud.Game.Gui.Dtr; using Dalamud.Interface.Internal; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -15,7 +13,6 @@ using Dalamud.Plugin.Internal.Exceptions; using Dalamud.Plugin.Internal.Loader; using Dalamud.Plugin.Internal.Profiles; using Dalamud.Plugin.Internal.Types.Manifest; -using Dalamud.Utility; namespace Dalamud.Plugin.Internal.Types; @@ -540,9 +537,6 @@ internal class LocalPlugin : IDisposable } finally { - // We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates. - Service.GetNullable()?.HandleRemovedNodes(); - this.pluginLoadStateLock.Release(); } } diff --git a/Dalamud/Plugin/Services/IDtrBar.cs b/Dalamud/Plugin/Services/IDtrBar.cs index 733a6d7e1..8ab34c6f2 100644 --- a/Dalamud/Plugin/Services/IDtrBar.cs +++ b/Dalamud/Plugin/Services/IDtrBar.cs @@ -2,7 +2,6 @@ using Dalamud.Game.Gui.Dtr; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Utility; namespace Dalamud.Plugin.Services; @@ -12,10 +11,13 @@ namespace Dalamud.Plugin.Services; public interface IDtrBar { /// - /// Gets a read-only list of all DTR bar entries. + /// Gets a read-only copy of the list of all DTR bar entries. /// - public IReadOnlyList Entries { get; } - + /// If the list changes due to changes in order or insertion/removal, then this property will return a + /// completely new object on getter invocation. The returned object is safe to use from any thread, and will not + /// change. + IReadOnlyList Entries { get; } + /// /// Get a DTR bar entry. /// This allows you to add your own text, and users to sort it. @@ -24,11 +26,13 @@ public interface IDtrBar /// The text the entry shows. /// The entry object used to update, hide and remove the entry. /// Thrown when an entry with the specified title exists. - public IDtrBarEntry Get(string title, SeString? text = null); + IDtrBarEntry Get(string title, SeString? text = null); /// /// Removes a DTR bar entry from the system. /// /// Title of the entry to remove. - public void Remove(string title); + /// Remove operation is not guaranteed to be immediately effective. Calls to may result + /// in an entry marked to be remove being revived and used again. + void Remove(string title); } diff --git a/Dalamud/Utility/Api10ToDoAttribute.cs b/Dalamud/Utility/Api10ToDoAttribute.cs deleted file mode 100644 index a13aaead5..000000000 --- a/Dalamud/Utility/Api10ToDoAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Dalamud.Utility; - -/// -/// Utility class for marking something to be changed for API 10, for ease of lookup. -/// -[AttributeUsage(AttributeTargets.All, Inherited = false)] -internal sealed class Api10ToDoAttribute : Attribute -{ - /// - /// Marks that this exists purely for making API 9 plugins work. - /// - public const string DeleteCompatBehavior = "Delete. This is for making API 9 plugins work."; - - /// - /// Marks that this should be moved to an another namespace. - /// - public const string MoveNamespace = "Move to another namespace."; - - /// - /// Initializes a new instance of the class. - /// - /// The explanation. - /// The explanation 2. - public Api10ToDoAttribute(string what, string what2 = "") - { - _ = what; - _ = what2; - } -} diff --git a/Dalamud/Utility/Api11ToDoAttribute.cs b/Dalamud/Utility/Api11ToDoAttribute.cs new file mode 100644 index 000000000..0336120ba --- /dev/null +++ b/Dalamud/Utility/Api11ToDoAttribute.cs @@ -0,0 +1,24 @@ +namespace Dalamud.Utility; + +/// +/// Utility class for marking something to be changed for API 11, for ease of lookup. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false)] +internal sealed class Api11ToDoAttribute : Attribute +{ + /// + /// Marks that this should be made internal. + /// + public const string MakeInternal = "Make internal."; + + /// + /// Initializes a new instance of the class. + /// + /// The explanation. + /// The explanation 2. + public Api11ToDoAttribute(string what, string what2 = "") + { + _ = what; + _ = what2; + } +} From 1c0ad61335adc430fff958ce8f7cc396a140f793 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 28 Jul 2024 15:11:33 +0200 Subject: [PATCH 003/375] build: 10.0.0.8 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index c8cce8a8c..7e2e056e4 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.7 + 10.0.0.8 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 844b04faad95d7ad3e767d6779d1b07c9e596bd3 Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 28 Jul 2024 22:36:04 +0900 Subject: [PATCH 004/375] Implement SeString renderer (#1977) Does not have contextual evaluation. Only styling payloads are handled for (relative) simplicity. --- Dalamud/Dalamud.csproj | 7 + Dalamud/Game/Config/GameConfig.cs | 11 +- Dalamud/Game/Config/GameConfigSection.cs | 35 + Dalamud/Game/Config/PadButtonValue.cs | 85 + .../Internal/ImGuiSeStringRenderer/GfdFile.cs | 149 + .../ImGuiSeStringRenderer/SeStringRenderer.cs | 693 +++ .../TextProcessing/DerivedGeneralCategory.txt | 4233 +++++++++++++++++ .../TextProcessing/EastAsianWidth.txt | 2621 ++++++++++ .../TextProcessing/LineBreak.txt | 3608 ++++++++++++++ .../TextProcessing/LineBreakEnumerator.cs | 366 ++ .../TextProcessing/UnicodeData.cs | 118 + .../UnicodeEastAsianWidthClass.cs | 22 + .../TextProcessing/UnicodeEmojiProperty.cs | 23 + .../TextProcessing/UnicodeGeneralCategory.cs | 46 + .../TextProcessing/UnicodeLineBreakClass.cs | 169 + .../TextProcessing/UtfEnumerator.cs | 313 ++ .../TextProcessing/UtfEnumeratorFlags.cs | 58 + .../TextProcessing/UtfValue.cs | 665 +++ .../TextProcessing/emoji-data.txt | 1320 +++++ Dalamud/Interface/Internal/UiDebug.cs | 54 +- .../Internal/Windows/Data/DataWindow.cs | 1 + .../Widgets/SeStringRendererTestWidget.cs | 67 + Dalamud/Interface/Utility/ImGuiHelpers.cs | 12 + Dalamud/Plugin/Services/IGameConfig.cs | 10 +- 24 files changed, 14665 insertions(+), 21 deletions(-) create mode 100644 Dalamud/Game/Config/PadButtonValue.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/DerivedGeneralCategory.txt create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/EastAsianWidth.txt create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreak.txt create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs create mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/emoji-data.txt create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 7e2e056e4..5ad58e559 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -114,6 +114,13 @@ + + + + + + + diff --git a/Dalamud/Game/Config/GameConfig.cs b/Dalamud/Game/Config/GameConfig.cs index bfb58fd3c..9579d84bc 100644 --- a/Dalamud/Game/Config/GameConfig.cs +++ b/Dalamud/Game/Config/GameConfig.cs @@ -121,7 +121,10 @@ internal sealed class GameConfig : IInternalDisposableService, IGameConfig /// public bool TryGet(SystemConfigOption option, out StringConfigProperties? properties) => this.System.TryGetProperties(option.GetName(), out properties); - + + /// + public bool TryGet(SystemConfigOption option, out PadButtonValue value) => this.System.TryGetStringAsEnum(option.GetName(), out value); + /// public bool TryGet(UiConfigOption option, out bool value) => this.UiConfig.TryGet(option.GetName(), out value); @@ -346,7 +349,11 @@ internal class GameConfigPluginScoped : IInternalDisposableService, IGameConfig /// public bool TryGet(SystemConfigOption option, out StringConfigProperties? properties) => this.gameConfigService.TryGet(option, out properties); - + + /// + public bool TryGet(SystemConfigOption option, out PadButtonValue value) + => this.gameConfigService.TryGet(option, out value); + /// public bool TryGet(UiConfigOption option, out bool value) => this.gameConfigService.TryGet(option, out value); diff --git a/Dalamud/Game/Config/GameConfigSection.cs b/Dalamud/Game/Config/GameConfigSection.cs index 31e4a0b3f..9cd239d84 100644 --- a/Dalamud/Game/Config/GameConfigSection.cs +++ b/Dalamud/Game/Config/GameConfigSection.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Text; using Dalamud.Memory; using Dalamud.Utility; @@ -357,6 +358,40 @@ public class GameConfigSection return value; } + /// Attempts to get a string config value as an enum value. + /// Name of the config option. + /// The returned value of the config option. + /// Type of the enum. Name of each enum fields are compared against. + /// A value representing the success. + public unsafe bool TryGetStringAsEnum(string name, out T value) where T : struct, Enum + { + value = default; + if (!this.TryGetIndex(name, out var index)) + { + return false; + } + + if (!this.TryGetEntry(index, out var entry)) + { + return false; + } + + if (entry->Type != 4) + { + return false; + } + + if (entry->Value.String == null) + { + return false; + } + + var n8 = entry->Value.String->AsSpan(); + Span n16 = stackalloc char[Encoding.UTF8.GetCharCount(n8)]; + Encoding.UTF8.GetChars(n8, n16); + return Enum.TryParse(n16, out value); + } + /// /// Set a string config option. /// Note: Not all config options will be be immediately reflected in the game. diff --git a/Dalamud/Game/Config/PadButtonValue.cs b/Dalamud/Game/Config/PadButtonValue.cs new file mode 100644 index 000000000..bd6da48bb --- /dev/null +++ b/Dalamud/Game/Config/PadButtonValue.cs @@ -0,0 +1,85 @@ +namespace Dalamud.Game.Config; + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo + +/// Valid values for PadButton options under . +/// Names are the valid part. Enum values are exclusively for use with current Dalamud version. +public enum PadButtonValue +{ + /// Auto-run. + Autorun_Support, + + /// Change Hotbar Set. + Hotbar_Set_Change, + + /// Highlight Left Hotbar. + XHB_Left_Start, + + /// Highlight Right Hotbar. + XHB_Right_Start, + + /// Not directly referenced by Gamepad button customization window. + Cursor_Operation, + + /// Draw Weapon/Lock On. + Lockon_and_Sword, + + /// Sit/Lock On. + Lockon_and_Sit, + + /// Change Camera. + Camera_Modechange, + + /// Reset Camera Position. + Camera_Reset, + + /// Draw/Sheathe Weapon. + Drawn_Sword, + + /// Lock On. + Camera_Lockononly, + + /// Face Target. + FaceTarget, + + /// Assist Target. + AssistTarget, + + /// Face Camera. + LookCamera, + + /// Execute Macro #98 (Exclusive). + Macro98, + + /// Execute Macro #99 (Exclusive). + Macro99, + + /// Not Assigned. + Notset, + + /// Jump/Cancel Casting. + Jump, + + /// Select Target/Confirm. + Accept, + + /// Cancel. + Cancel, + + /// Open Map/Subcommands. + Map_Sub, + + /// Open Main Menu. + MainCommand, + + /// Select HUD. + HUD_Select, + + /// Move Character. + Move_Operation, + + /// Move Camera. + Camera_Operation, +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs new file mode 100644 index 000000000..2083d9cd9 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs @@ -0,0 +1,149 @@ +using System.IO; +using System.Numerics; +using System.Runtime.InteropServices; + +using Lumina.Data; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer; + +/// Reference member view of a .gfd file data. +internal sealed unsafe class GfdFile : FileResource +{ + /// Gets or sets the file header. + public GfdHeader Header { get; set; } + + /// Gets or sets the entries. + public GfdEntry[] Entries { get; set; } = []; + + /// + public override void LoadFile() + { + if (this.DataSpan.Length < sizeof(GfdHeader)) + throw new InvalidDataException($"Not enough space for a {nameof(GfdHeader)}"); + if (this.DataSpan.Length < sizeof(GfdHeader) + (this.Header.Count * sizeof(GfdEntry))) + throw new InvalidDataException($"Not enough space for all the {nameof(GfdEntry)}"); + + this.Header = MemoryMarshal.AsRef(this.DataSpan); + this.Entries = MemoryMarshal.Cast(this.DataSpan[sizeof(GfdHeader)..]).ToArray(); + } + + /// Attempts to get an entry. + /// The icon ID. + /// The entry. + /// Whether to follow redirects. + /// true if found. + public bool TryGetEntry(uint iconId, out GfdEntry entry, bool followRedirect = true) + { + if (iconId == 0) + { + entry = default; + return false; + } + + var entries = this.Entries; + if (iconId <= this.Entries.Length && entries[(int)(iconId - 1)].Id == iconId) + { + if (iconId <= entries.Length) + { + entry = entries[(int)(iconId - 1)]; + return !entry.IsEmpty; + } + + entry = default; + return false; + } + + var lo = 0; + var hi = entries.Length; + while (lo <= hi) + { + var i = lo + ((hi - lo) >> 1); + if (entries[i].Id == iconId) + { + if (followRedirect && entries[i].Redirect != 0) + { + iconId = entries[i].Redirect; + lo = 0; + hi = entries.Length; + continue; + } + + entry = entries[i]; + return !entry.IsEmpty; + } + + if (entries[i].Id < iconId) + lo = i + 1; + else + hi = i - 1; + } + + entry = default; + return false; + } + + /// Header of a .gfd file. + [StructLayout(LayoutKind.Sequential)] + public struct GfdHeader + { + /// Signature: "gftd0100". + public fixed byte Signature[8]; + + /// Number of entries. + public int Count; + + /// Unused/unknown. + public fixed byte Padding[4]; + } + + /// An entry of a .gfd file. + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + public struct GfdEntry + { + /// ID of the entry. + public ushort Id; + + /// The left offset of the entry. + public ushort Left; + + /// The top offset of the entry. + public ushort Top; + + /// The width of the entry. + public ushort Width; + + /// The height of the entry. + public ushort Height; + + /// Unknown/unused. + public ushort Unk0A; + + /// The redirected entry, maybe. + public ushort Redirect; + + /// Unknown/unused. + public ushort Unk0E; + + /// Gets a value indicating whether this entry is effectively empty. + public bool IsEmpty => this.Width == 0 || this.Height == 0; + + /// Gets or sets the size of this entry. + public Vector2 Size + { + get => new(this.Width, this.Height); + set => (this.Width, this.Height) = (checked((ushort)value.X), checked((ushort)value.Y)); + } + + /// Gets the UV0 of this entry. + public Vector2 Uv0 => new(this.Left / 512f, this.Top / 1024f); + + /// Gets the UV1 of this entry. + public Vector2 Uv1 => new((this.Left + this.Width) / 512f, (this.Top + this.Height) / 1024f); + + /// Gets the UV0 of the HQ version of this entry. + public Vector2 HqUv0 => new(this.Left / 256f, (this.Top + 170.5f) / 512f); + + /// Gets the UV1 of the HQ version of this entry. + public Vector2 HqUv1 => new((this.Left + this.Width) / 256f, (this.Top + this.Height + 170.5f) / 512f); + } +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs new file mode 100644 index 000000000..face85cfc --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs @@ -0,0 +1,693 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Text; + +using BitFaster.Caching.Lru; + +using Dalamud.Data; +using Dalamud.Game.Config; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +using Dalamud.Interface.Utility; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +using ImGuiNET; + +using Lumina.Excel.GeneratedSheets2; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer; + +/// Draws SeString. +[ServiceManager.EarlyLoadedService] +internal unsafe class SeStringRenderer : IInternalDisposableService +{ + private const int ChannelShadow = 0; + private const int ChannelEdge = 1; + private const int ChannelFore = 2; + private const int ChannelCount = 3; + + private const char SoftHyphen = '\u00AD'; + private const char ObjectReplacementCharacter = '\uFFFC'; + + [ServiceManager.ServiceDependency] + private readonly GameConfig gameConfig = Service.Get(); + + private readonly ConcurrentLru cache = new(1024); + + private readonly GfdFile gfd; + private readonly uint[] colorTypes; + private readonly uint[] edgeColorTypes; + + private readonly List words = []; + + private readonly List colorStack = []; + private readonly List edgeColorStack = []; + private readonly List shadowColorStack = []; + private bool bold; + private bool italic; + private Vector2 edge; + private Vector2 shadow; + + private ImDrawListSplitterPtr splitter = new(ImGuiNative.ImDrawListSplitter_ImDrawListSplitter()); + + [ServiceManager.ServiceConstructor] + private SeStringRenderer(DataManager dm) + { + var uiColor = dm.Excel.GetSheet()!; + var maxId = 0; + foreach (var row in uiColor) + maxId = (int)Math.Max(row.RowId, maxId); + + this.colorTypes = new uint[maxId + 1]; + this.edgeColorTypes = new uint[maxId + 1]; + foreach (var row in uiColor) + { + this.colorTypes[row.RowId] = BgraToRgba((row.UIForeground >> 8) | (row.UIForeground << 24)); + this.edgeColorTypes[row.RowId] = BgraToRgba((row.UIGlow >> 8) | (row.UIGlow << 24)); + } + + this.gfd = dm.GetFile("common/font/gfdata.gfd")!; + + return; + + static uint BgraToRgba(uint x) + { + var buf = (byte*)&x; + (buf[0], buf[2]) = (buf[2], buf[0]); + return x; + } + } + + /// Finalizes an instance of the class. + ~SeStringRenderer() => this.ReleaseUnmanagedResources(); + + /// + void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); + + /// Creates and caches a SeString from a text macro representation, and then draws it. + /// SeString text macro representation. + /// Wrapping width. If a non-positive number is provided, then the remainder of the width + /// will be used. + public void CompileAndDrawWrapped(string text, float wrapWidth = 0) + { + ThreadSafety.AssertMainThread(); + + this.DrawWrapped( + this.cache.GetOrAdd( + text, + static text => + { + var outstr = default(Utf8String); + outstr.Ctor(); + RaptureTextModule.Instance()->MacroEncoder.EncodeString(&outstr, text.ReplaceLineEndings("
")); + var res = new ReadOnlySeString(outstr.AsSpan().ToArray()); + outstr.Dtor(); + return res; + }).AsSpan(), + wrapWidth); + } + + /// + public void DrawWrapped(in Utf8String utf8String, float wrapWidth = 0) => + this.DrawWrapped(utf8String.AsSpan(), wrapWidth); + + /// Draws a SeString. + /// SeString to draw. + /// Wrapping width. If a non-positive number is provided, then the remainder of the width + /// will be used. + public void DrawWrapped(ReadOnlySeStringSpan sss, float wrapWidth = 0) + { + ThreadSafety.AssertMainThread(); + + if (wrapWidth <= 0) + wrapWidth = ImGui.GetContentRegionAvail().X; + + this.words.Clear(); + this.colorStack.Clear(); + this.edgeColorStack.Clear(); + this.shadowColorStack.Clear(); + + this.colorStack.Add(ImGui.GetColorU32(ImGuiCol.Text)); + this.edgeColorStack.Add(0); + this.shadowColorStack.Add(0); + this.bold = this.italic = false; + this.edge = Vector2.One; + this.shadow = Vector2.Zero; + + var state = new DrawState( + sss, + ImGui.GetWindowDrawList(), + this.splitter, + ImGui.GetFont(), + ImGui.GetFontSize(), + ImGui.GetCursorScreenPos()); + this.CreateTextFragments(ref state, wrapWidth); + + var size = Vector2.Zero; + for (var i = 0; i < this.words.Count; i++) + { + var word = this.words[i]; + this.DrawWord( + ref state, + word.Offset, + state.Raw.Data[word.From..word.To], + i == 0 + ? '\0' + : this.words[i - 1].IsSoftHyphenVisible + ? this.words[i - 1].LastRuneRepr + : this.words[i - 1].LastRuneRepr2); + + if (word.IsSoftHyphenVisible && i > 0) + { + this.DrawWord( + ref state, + word.Offset + new Vector2(word.AdvanceWidthWithoutLastRune, 0), + "-"u8, + this.words[i - 1].LastRuneRepr); + } + + size = Vector2.Max(size, word.Offset + new Vector2(word.VisibleWidth, state.FontSize)); + } + + state.Splitter.Merge(state.DrawList); + + ImGui.Dummy(size); + } + + /// Gets the printable char for the given char, or null(\0) if it should not be handled at all. + /// Character to determine. + /// Character to print, or null(\0) if none. + private static Rune? ToPrintableRune(int c) => c switch + { + char.MaxValue => null, + SoftHyphen => new('-'), + _ when UnicodeData.LineBreak[c] + is UnicodeLineBreakClass.BK + or UnicodeLineBreakClass.CR + or UnicodeLineBreakClass.LF + or UnicodeLineBreakClass.NL => new(0), + _ => new(c), + }; + + private void ReleaseUnmanagedResources() + { + if (this.splitter.NativePtr is not null) + this.splitter.Destroy(); + this.splitter = default; + } + + private void CreateTextFragments(ref DrawState state, float wrapWidth) + { + var prev = 0; + var runningOffset = Vector2.Zero; + var runningWidth = 0f; + foreach (var (curr, mandatory) in new LineBreakEnumerator(state.Raw, UtfEnumeratorFlags.Utf8SeString)) + { + var fragment = state.CreateFragment(this, prev, curr, mandatory, runningOffset); + var nextRunningWidth = Math.Max(runningWidth, runningOffset.X + fragment.VisibleWidth); + if (nextRunningWidth <= wrapWidth) + { + // New fragment fits in the current line. + if (this.words.Count > 0) + { + char lastFragmentEnd; + if (this.words[^1].EndsWithSoftHyphen) + { + runningOffset.X += this.words[^1].AdvanceWidthWithoutLastRune - this.words[^1].AdvanceWidth; + lastFragmentEnd = this.words[^1].LastRuneRepr; + } + else + { + lastFragmentEnd = this.words[^1].LastRuneRepr2; + } + + runningOffset.X += MathF.Round( + state.Font.GetDistanceAdjustmentForPair(lastFragmentEnd, fragment.FirstRuneRepr) * + state.FontSizeScale); + fragment = fragment with { Offset = runningOffset }; + } + + this.words.Add(fragment); + runningWidth = nextRunningWidth; + runningOffset.X += fragment.AdvanceWidth; + prev = curr; + } + else if (fragment.VisibleWidth <= wrapWidth) + { + // New fragment does not fit in the current line, but it will fit in the next line. + // Implicit conditions: runningWidth > 0, this.words.Count > 0 + runningWidth = fragment.VisibleWidth; + runningOffset.X = fragment.AdvanceWidth; + runningOffset.Y += state.FontSize; + prev = curr; + this.words[^1] = this.words[^1] with { MandatoryBreakAfter = true }; + this.words.Add(fragment with { Offset = runningOffset with { X = 0 } }); + } + else + { + // New fragment does not fit in the given width, and it needs to be broken down. + while (prev < curr) + { + if (runningOffset.X > 0) + { + runningOffset.X = 0; + runningOffset.Y += state.FontSize; + } + + fragment = state.CreateFragment(this, prev, curr, mandatory, runningOffset, wrapWidth); + runningWidth = fragment.VisibleWidth; + runningOffset.X = fragment.AdvanceWidth; + prev = fragment.To; + if (this.words.Count > 0) + this.words[^1] = this.words[^1] with { MandatoryBreakAfter = true }; + this.words.Add(fragment); + } + } + + if (fragment.MandatoryBreakAfter) + { + runningOffset.X = runningWidth = 0; + runningOffset.Y += state.FontSize; + } + } + } + + private void DrawWord(ref DrawState state, Vector2 offset, ReadOnlySpan span, char lastRuneRepr) + { + var gfdTextureSrv = + (nint)UIModule.Instance()->GetRaptureAtkModule()->AtkModule.AtkFontManager.Gfd->Texture-> + D3D11ShaderResourceView; + var x = 0f; + var width = 0f; + foreach (var c in UtfEnumerator.From(span, UtfEnumeratorFlags.Utf8SeString)) + { + if (c.IsSeStringPayload) + { + var enu = new ReadOnlySeStringSpan(span[c.ByteOffset..]).GetEnumerator(); + if (!enu.MoveNext()) + continue; + + var payload = enu.Current; + switch (payload.MacroCode) + { + case MacroCode.Color: + TouchColorStack(this.colorStack, payload); + continue; + case MacroCode.EdgeColor: + TouchColorStack(this.edgeColorStack, payload); + continue; + case MacroCode.ShadowColor: + TouchColorStack(this.shadowColorStack, payload); + continue; + case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.bold = u != 0; + continue; + case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.italic = u != 0; + continue; + case MacroCode.Edge when payload.TryGetExpression(out var e1, out var e2) && + e1.TryGetInt(out var v1) && e2.TryGetInt(out var v2): + this.edge = new(v1, v2); + continue; + case MacroCode.Shadow when payload.TryGetExpression(out var e1, out var e2) && + e1.TryGetInt(out var v1) && e2.TryGetInt(out var v2): + this.shadow = new(v1, v2); + continue; + case MacroCode.ColorType: + TouchColorTypeStack(this.colorStack, this.colorTypes, payload); + continue; + case MacroCode.EdgeColorType: + TouchColorTypeStack(this.edgeColorStack, this.edgeColorTypes, payload); + continue; + case MacroCode.Icon: + case MacroCode.Icon2: + { + if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is not (var icon and not None) || + !this.gfd.TryGetEntry((uint)icon, out var gfdEntry) || + gfdEntry.IsEmpty) + continue; + + var useHq = state.FontSize > 19; + var sizeScale = (state.FontSize + 1) / gfdEntry.Height; + state.SetCurrentChannel(ChannelFore); + state.Draw( + offset + new Vector2(x, 0), + gfdTextureSrv, + Vector2.Zero, + gfdEntry.Size * sizeScale, + Vector2.Zero, + useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, + useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1); + width = Math.Max(width, x + (gfdEntry.Width * sizeScale)); + x += MathF.Round(gfdEntry.Width * sizeScale); + lastRuneRepr = '\0'; + continue; + } + + default: + continue; + } + } + + if (ToPrintableRune(c.EffectiveChar) is not { } rune) + continue; + + var runeRepr = rune.Value is >= 0 and < char.MaxValue ? (char)rune.Value : '\uFFFE'; + if (runeRepr != 0) + { + var dist = state.Font.GetDistanceAdjustmentForPair(lastRuneRepr, runeRepr); + ref var g = ref *(ImGuiHelpers.ImFontGlyphReal*)state.Font.FindGlyph(runeRepr).NativePtr; + + var dyItalic = this.italic + ? new Vector2(state.Font.FontSize - g.Y0, state.Font.FontSize - g.Y1) / 6 + : Vector2.Zero; + + if (this.shadow != Vector2.Zero && this.shadowColorStack[^1] >= 0x1000000) + { + state.SetCurrentChannel(ChannelShadow); + state.Draw( + offset + this.shadow + new Vector2(x + dist, 0), + g, + dyItalic, + this.shadowColorStack[^1]); + } + + if (this.edge != Vector2.Zero && this.edgeColorStack[^1] >= 0x1000000) + { + state.SetCurrentChannel(ChannelEdge); + for (var dx = -this.edge.X; dx <= this.edge.X; dx++) + { + for (var dy = -this.edge.Y; dy <= this.edge.Y; dy++) + { + if (dx == 0 && dy == 0) + continue; + + state.Draw(offset + new Vector2(x + dist + dx, dy), g, dyItalic, this.edgeColorStack[^1]); + } + } + } + + state.SetCurrentChannel(ChannelFore); + for (var dx = this.bold ? 1 : 0; dx >= 0; dx--) + state.Draw(offset + new Vector2(x + dist + dx, 0), g, dyItalic, this.colorStack[^1]); + + width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale)); + x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale); + } + + lastRuneRepr = runeRepr; + } + + return; + + static void TouchColorStack(List stack, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return; + if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor && stack.Count > 1) + stack.RemoveAt(stack.Count - 1); + else if (expr.TryGetUInt(out var u)) + stack.Add(u); + } + + static void TouchColorTypeStack(List stack, uint[] colorTypes, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return; + if (!expr.TryGetUInt(out var u)) + return; + if (u != 0) + stack.Add(u < colorTypes.Length ? colorTypes[u] : 0u); + else if (stack.Count > 1) + stack.RemoveAt(stack.Count - 1); + } + } + + private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan sss) + { + var e = new ReadOnlySeStringSpan(sss).GetEnumerator(); + if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2) + return None; + + var payload = e.Current; + switch (payload.MacroCode) + { + case MacroCode.Icon + when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): + return (BitmapFontIcon)iconId; + case MacroCode.Icon2 + when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): + var configName = (BitmapFontIcon)iconId switch + { + ControllerShoulderLeft => SystemConfigOption.PadButton_L1, + ControllerShoulderRight => SystemConfigOption.PadButton_R1, + ControllerTriggerLeft => SystemConfigOption.PadButton_L2, + ControllerTriggerRight => SystemConfigOption.PadButton_R2, + ControllerButton3 => SystemConfigOption.PadButton_Triangle, + ControllerButton1 => SystemConfigOption.PadButton_Cross, + ControllerButton0 => SystemConfigOption.PadButton_Circle, + ControllerButton2 => SystemConfigOption.PadButton_Square, + ControllerStart => SystemConfigOption.PadButton_Start, + ControllerBack => SystemConfigOption.PadButton_Select, + ControllerAnalogLeftStick => SystemConfigOption.PadButton_LS, + ControllerAnalogLeftStickIn => SystemConfigOption.PadButton_LS, + ControllerAnalogLeftStickUpDown => SystemConfigOption.PadButton_LS, + ControllerAnalogLeftStickLeftRight => SystemConfigOption.PadButton_LS, + ControllerAnalogRightStick => SystemConfigOption.PadButton_RS, + ControllerAnalogRightStickIn => SystemConfigOption.PadButton_RS, + ControllerAnalogRightStickUpDown => SystemConfigOption.PadButton_RS, + ControllerAnalogRightStickLeftRight => SystemConfigOption.PadButton_RS, + _ => (SystemConfigOption?)null, + }; + + if (configName is null || !this.gameConfig.TryGet(configName.Value, out PadButtonValue pb)) + return (BitmapFontIcon)iconId; + + return pb switch + { + PadButtonValue.Autorun_Support => ControllerShoulderLeft, + PadButtonValue.Hotbar_Set_Change => ControllerShoulderRight, + PadButtonValue.XHB_Left_Start => ControllerTriggerLeft, + PadButtonValue.XHB_Right_Start => ControllerTriggerRight, + PadButtonValue.Jump => ControllerButton3, + PadButtonValue.Accept => ControllerButton1, + PadButtonValue.Cancel => ControllerButton0, + PadButtonValue.Map_Sub => ControllerButton2, + PadButtonValue.MainCommand => ControllerStart, + PadButtonValue.HUD_Select => ControllerBack, + PadButtonValue.Move_Operation => (BitmapFontIcon)iconId switch + { + ControllerAnalogLeftStick => ControllerAnalogLeftStick, + ControllerAnalogLeftStickIn => ControllerAnalogLeftStickIn, + ControllerAnalogLeftStickUpDown => ControllerAnalogLeftStickUpDown, + ControllerAnalogLeftStickLeftRight => ControllerAnalogLeftStickLeftRight, + ControllerAnalogRightStick => ControllerAnalogLeftStick, + ControllerAnalogRightStickIn => ControllerAnalogLeftStickIn, + ControllerAnalogRightStickUpDown => ControllerAnalogLeftStickUpDown, + ControllerAnalogRightStickLeftRight => ControllerAnalogLeftStickLeftRight, + _ => (BitmapFontIcon)iconId, + }, + PadButtonValue.Camera_Operation => (BitmapFontIcon)iconId switch + { + ControllerAnalogLeftStick => ControllerAnalogRightStick, + ControllerAnalogLeftStickIn => ControllerAnalogRightStickIn, + ControllerAnalogLeftStickUpDown => ControllerAnalogRightStickUpDown, + ControllerAnalogLeftStickLeftRight => ControllerAnalogRightStickLeftRight, + ControllerAnalogRightStick => ControllerAnalogRightStick, + ControllerAnalogRightStickIn => ControllerAnalogRightStickIn, + ControllerAnalogRightStickUpDown => ControllerAnalogRightStickUpDown, + ControllerAnalogRightStickLeftRight => ControllerAnalogRightStickLeftRight, + _ => (BitmapFontIcon)iconId, + }, + _ => (BitmapFontIcon)iconId, + }; + } + + return None; + } + + private readonly record struct TextFragment( + int From, + int To, + Vector2 Offset, + float VisibleWidth, + float AdvanceWidth, + float AdvanceWidthWithoutLastRune, + bool MandatoryBreakAfter, + bool EndsWithSoftHyphen, + char FirstRuneRepr, + char LastRuneRepr, + char LastRuneRepr2) + { + public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.MandatoryBreakAfter; + } + + private ref struct DrawState + { + public readonly ReadOnlySeStringSpan Raw; + public readonly float FontSize; + public readonly float FontSizeScale; + public readonly Vector2 ScreenOffset; + + public ImDrawListPtr DrawList; + public ImDrawListSplitterPtr Splitter; + public ImFontPtr Font; + + public DrawState( + ReadOnlySeStringSpan raw, + ImDrawListPtr drawList, + ImDrawListSplitterPtr splitter, + ImFontPtr font, + float fontSize, + Vector2 screenOffset) + { + this.Raw = raw; + this.DrawList = drawList; + this.Splitter = splitter; + this.Font = font; + this.FontSize = fontSize; + this.FontSizeScale = fontSize / font.FontSize; + this.ScreenOffset = screenOffset; + + splitter.Split(drawList, ChannelCount); + } + + public void SetCurrentChannel(int channelIndex) => this.Splitter.SetCurrentChannel(this.DrawList, channelIndex); + + public void Draw(Vector2 offset, in ImGuiHelpers.ImFontGlyphReal g, Vector2 dyItalic, uint color) => + this.Draw( + offset, + this.Font.ContainerAtlas.Textures[g.TextureIndex].TexID, + g.XY0 * this.FontSizeScale, + g.XY1 * this.FontSizeScale, + dyItalic * this.FontSizeScale, + g.UV0, + g.UV1, + color); + + public void Draw( + Vector2 offset, + nint igTextureId, + Vector2 xy0, + Vector2 xy1, + Vector2 dyItalic, + Vector2 uv0, + Vector2 uv1, + uint color = uint.MaxValue) + { + offset += this.ScreenOffset; + this.DrawList.AddImageQuad( + igTextureId, + offset + new Vector2(xy0.X + dyItalic.X, xy0.Y), + offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.X, xy0.Y), + new(uv0.X, uv0.Y), + new(uv0.X, uv1.Y), + new(uv1.X, uv1.Y), + new(uv1.X, uv0.Y), + color); + } + + public TextFragment CreateFragment( + SeStringRenderer renderer, + int from, + int to, + bool mandatoryBreakAfter, + Vector2 offset, + float wrapWidth = float.PositiveInfinity) + { + var lastNonSpace = from; + + var x = 0f; + var w = 0f; + var visibleWidth = 0f; + var advanceWidth = 0f; + var prevAdvanceWidth = 0f; + var firstRuneRepr = char.MaxValue; + var lastRuneRepr = default(char); + var lastRuneRepr2 = default(char); + var endsWithSoftHyphen = false; + foreach (var c in UtfEnumerator.From(this.Raw.Data[from..to], UtfEnumeratorFlags.Utf8SeString)) + { + prevAdvanceWidth = x; + lastRuneRepr2 = lastRuneRepr; + endsWithSoftHyphen = c.EffectiveChar == SoftHyphen; + + var byteOffset = from + c.ByteOffset; + var isBreakableWhitespace = false; + if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } && + renderer.GetBitmapFontIconFor(this.Raw.Data[byteOffset..]) is var icon and not None && + renderer.gfd.TryGetEntry((uint)icon, out var gfdEntry) && + !gfdEntry.IsEmpty) + { + var sizeScale = (this.FontSize + 1) / gfdEntry.Height; + w = Math.Max(w, x + (gfdEntry.Width * sizeScale)); + x += MathF.Round(gfdEntry.Width * sizeScale); + lastRuneRepr = default; + } + else if (ToPrintableRune(c.EffectiveChar) is { } rune) + { + var runeRepr = rune.Value is >= 0 and < char.MaxValue ? (char)rune.Value : '\uFFFE'; + if (runeRepr != 0) + { + var dist = this.Font.GetDistanceAdjustmentForPair(lastRuneRepr, runeRepr); + ref var g = ref *(ImGuiHelpers.ImFontGlyphReal*)this.Font.FindGlyph(runeRepr).NativePtr; + w = Math.Max(w, x + ((dist + g.X1) * this.FontSizeScale)); + x += MathF.Round((dist + g.AdvanceX) * this.FontSizeScale); + } + + isBreakableWhitespace = Rune.IsWhiteSpace(rune) && + UnicodeData.LineBreak[rune.Value] is not UnicodeLineBreakClass.GL; + lastRuneRepr = runeRepr; + } + else + { + continue; + } + + if (firstRuneRepr == char.MaxValue) + firstRuneRepr = lastRuneRepr; + + if (isBreakableWhitespace) + { + advanceWidth = x; + } + else + { + if (w > wrapWidth && lastNonSpace != from && !endsWithSoftHyphen) + { + to = byteOffset; + break; + } + + advanceWidth = x; + visibleWidth = w; + lastNonSpace = byteOffset + c.ByteLength; + } + } + + return new( + from, + to, + offset, + visibleWidth, + advanceWidth, + prevAdvanceWidth, + mandatoryBreakAfter, + endsWithSoftHyphen, + firstRuneRepr, + lastRuneRepr, + lastRuneRepr2); + } + } +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/DerivedGeneralCategory.txt b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/DerivedGeneralCategory.txt new file mode 100644 index 000000000..285ffa8fb --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/DerivedGeneralCategory.txt @@ -0,0 +1,4233 @@ +# DerivedGeneralCategory-15.1.0.txt +# Date: 2023-07-28, 23:34:02 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ + +# ================================================ + +# Property: General_Category + +# ================================================ + +# General_Category=Unassigned + +0378..0379 ; Cn # [2] .. +0380..0383 ; Cn # [4] .. +038B ; Cn # +038D ; Cn # +03A2 ; Cn # +0530 ; Cn # +0557..0558 ; Cn # [2] .. +058B..058C ; Cn # [2] .. +0590 ; Cn # +05C8..05CF ; Cn # [8] .. +05EB..05EE ; Cn # [4] .. +05F5..05FF ; Cn # [11] .. +070E ; Cn # +074B..074C ; Cn # [2] .. +07B2..07BF ; Cn # [14] .. +07FB..07FC ; Cn # [2] .. +082E..082F ; Cn # [2] .. +083F ; Cn # +085C..085D ; Cn # [2] .. +085F ; Cn # +086B..086F ; Cn # [5] .. +088F ; Cn # +0892..0897 ; Cn # [6] .. +0984 ; Cn # +098D..098E ; Cn # [2] .. +0991..0992 ; Cn # [2] .. +09A9 ; Cn # +09B1 ; Cn # +09B3..09B5 ; Cn # [3] .. +09BA..09BB ; Cn # [2] .. +09C5..09C6 ; Cn # [2] .. +09C9..09CA ; Cn # [2] .. +09CF..09D6 ; Cn # [8] .. +09D8..09DB ; Cn # [4] .. +09DE ; Cn # +09E4..09E5 ; Cn # [2] .. +09FF..0A00 ; Cn # [2] .. +0A04 ; Cn # +0A0B..0A0E ; Cn # [4] .. +0A11..0A12 ; Cn # [2] .. +0A29 ; Cn # +0A31 ; Cn # +0A34 ; Cn # +0A37 ; Cn # +0A3A..0A3B ; Cn # [2] .. +0A3D ; Cn # +0A43..0A46 ; Cn # [4] .. +0A49..0A4A ; Cn # [2] .. +0A4E..0A50 ; Cn # [3] .. +0A52..0A58 ; Cn # [7] .. +0A5D ; Cn # +0A5F..0A65 ; Cn # [7] .. +0A77..0A80 ; Cn # [10] .. +0A84 ; Cn # +0A8E ; Cn # +0A92 ; Cn # +0AA9 ; Cn # +0AB1 ; Cn # +0AB4 ; Cn # +0ABA..0ABB ; Cn # [2] .. +0AC6 ; Cn # +0ACA ; Cn # +0ACE..0ACF ; Cn # [2] .. +0AD1..0ADF ; Cn # [15] .. +0AE4..0AE5 ; Cn # [2] .. +0AF2..0AF8 ; Cn # [7] .. +0B00 ; Cn # +0B04 ; Cn # +0B0D..0B0E ; Cn # [2] .. +0B11..0B12 ; Cn # [2] .. +0B29 ; Cn # +0B31 ; Cn # +0B34 ; Cn # +0B3A..0B3B ; Cn # [2] .. +0B45..0B46 ; Cn # [2] .. +0B49..0B4A ; Cn # [2] .. +0B4E..0B54 ; Cn # [7] .. +0B58..0B5B ; Cn # [4] .. +0B5E ; Cn # +0B64..0B65 ; Cn # [2] .. +0B78..0B81 ; Cn # [10] .. +0B84 ; Cn # +0B8B..0B8D ; Cn # [3] .. +0B91 ; Cn # +0B96..0B98 ; Cn # [3] .. +0B9B ; Cn # +0B9D ; Cn # +0BA0..0BA2 ; Cn # [3] .. +0BA5..0BA7 ; Cn # [3] .. +0BAB..0BAD ; Cn # [3] .. +0BBA..0BBD ; Cn # [4] .. +0BC3..0BC5 ; Cn # [3] .. +0BC9 ; Cn # +0BCE..0BCF ; Cn # [2] .. +0BD1..0BD6 ; Cn # [6] .. +0BD8..0BE5 ; Cn # [14] .. +0BFB..0BFF ; Cn # [5] .. +0C0D ; Cn # +0C11 ; Cn # +0C29 ; Cn # +0C3A..0C3B ; Cn # [2] .. +0C45 ; Cn # +0C49 ; Cn # +0C4E..0C54 ; Cn # [7] .. +0C57 ; Cn # +0C5B..0C5C ; Cn # [2] .. +0C5E..0C5F ; Cn # [2] .. +0C64..0C65 ; Cn # [2] .. +0C70..0C76 ; Cn # [7] .. +0C8D ; Cn # +0C91 ; Cn # +0CA9 ; Cn # +0CB4 ; Cn # +0CBA..0CBB ; Cn # [2] .. +0CC5 ; Cn # +0CC9 ; Cn # +0CCE..0CD4 ; Cn # [7] .. +0CD7..0CDC ; Cn # [6] .. +0CDF ; Cn # +0CE4..0CE5 ; Cn # [2] .. +0CF0 ; Cn # +0CF4..0CFF ; Cn # [12] .. +0D0D ; Cn # +0D11 ; Cn # +0D45 ; Cn # +0D49 ; Cn # +0D50..0D53 ; Cn # [4] .. +0D64..0D65 ; Cn # [2] .. +0D80 ; Cn # +0D84 ; Cn # +0D97..0D99 ; Cn # [3] .. +0DB2 ; Cn # +0DBC ; Cn # +0DBE..0DBF ; Cn # [2] .. +0DC7..0DC9 ; Cn # [3] .. +0DCB..0DCE ; Cn # [4] .. +0DD5 ; Cn # +0DD7 ; Cn # +0DE0..0DE5 ; Cn # [6] .. +0DF0..0DF1 ; Cn # [2] .. +0DF5..0E00 ; Cn # [12] .. +0E3B..0E3E ; Cn # [4] .. +0E5C..0E80 ; Cn # [37] .. +0E83 ; Cn # +0E85 ; Cn # +0E8B ; Cn # +0EA4 ; Cn # +0EA6 ; Cn # +0EBE..0EBF ; Cn # [2] .. +0EC5 ; Cn # +0EC7 ; Cn # +0ECF ; Cn # +0EDA..0EDB ; Cn # [2] .. +0EE0..0EFF ; Cn # [32] .. +0F48 ; Cn # +0F6D..0F70 ; Cn # [4] .. +0F98 ; Cn # +0FBD ; Cn # +0FCD ; Cn # +0FDB..0FFF ; Cn # [37] .. +10C6 ; Cn # +10C8..10CC ; Cn # [5] .. +10CE..10CF ; Cn # [2] .. +1249 ; Cn # +124E..124F ; Cn # [2] .. +1257 ; Cn # +1259 ; Cn # +125E..125F ; Cn # [2] .. +1289 ; Cn # +128E..128F ; Cn # [2] .. +12B1 ; Cn # +12B6..12B7 ; Cn # [2] .. +12BF ; Cn # +12C1 ; Cn # +12C6..12C7 ; Cn # [2] .. +12D7 ; Cn # +1311 ; Cn # +1316..1317 ; Cn # [2] .. +135B..135C ; Cn # [2] .. +137D..137F ; Cn # [3] .. +139A..139F ; Cn # [6] .. +13F6..13F7 ; Cn # [2] .. +13FE..13FF ; Cn # [2] .. +169D..169F ; Cn # [3] .. +16F9..16FF ; Cn # [7] .. +1716..171E ; Cn # [9] .. +1737..173F ; Cn # [9] .. +1754..175F ; Cn # [12] .. +176D ; Cn # +1771 ; Cn # +1774..177F ; Cn # [12] .. +17DE..17DF ; Cn # [2] .. +17EA..17EF ; Cn # [6] .. +17FA..17FF ; Cn # [6] .. +181A..181F ; Cn # [6] .. +1879..187F ; Cn # [7] .. +18AB..18AF ; Cn # [5] .. +18F6..18FF ; Cn # [10] .. +191F ; Cn # +192C..192F ; Cn # [4] .. +193C..193F ; Cn # [4] .. +1941..1943 ; Cn # [3] .. +196E..196F ; Cn # [2] .. +1975..197F ; Cn # [11] .. +19AC..19AF ; Cn # [4] .. +19CA..19CF ; Cn # [6] .. +19DB..19DD ; Cn # [3] .. +1A1C..1A1D ; Cn # [2] .. +1A5F ; Cn # +1A7D..1A7E ; Cn # [2] .. +1A8A..1A8F ; Cn # [6] .. +1A9A..1A9F ; Cn # [6] .. +1AAE..1AAF ; Cn # [2] .. +1ACF..1AFF ; Cn # [49] .. +1B4D..1B4F ; Cn # [3] .. +1B7F ; Cn # +1BF4..1BFB ; Cn # [8] .. +1C38..1C3A ; Cn # [3] .. +1C4A..1C4C ; Cn # [3] .. +1C89..1C8F ; Cn # [7] .. +1CBB..1CBC ; Cn # [2] .. +1CC8..1CCF ; Cn # [8] .. +1CFB..1CFF ; Cn # [5] .. +1F16..1F17 ; Cn # [2] .. +1F1E..1F1F ; Cn # [2] .. +1F46..1F47 ; Cn # [2] .. +1F4E..1F4F ; Cn # [2] .. +1F58 ; Cn # +1F5A ; Cn # +1F5C ; Cn # +1F5E ; Cn # +1F7E..1F7F ; Cn # [2] .. +1FB5 ; Cn # +1FC5 ; Cn # +1FD4..1FD5 ; Cn # [2] .. +1FDC ; Cn # +1FF0..1FF1 ; Cn # [2] .. +1FF5 ; Cn # +1FFF ; Cn # +2065 ; Cn # +2072..2073 ; Cn # [2] .. +208F ; Cn # +209D..209F ; Cn # [3] .. +20C1..20CF ; Cn # [15] .. +20F1..20FF ; Cn # [15] .. +218C..218F ; Cn # [4] .. +2427..243F ; Cn # [25] .. +244B..245F ; Cn # [21] .. +2B74..2B75 ; Cn # [2] .. +2B96 ; Cn # +2CF4..2CF8 ; Cn # [5] .. +2D26 ; Cn # +2D28..2D2C ; Cn # [5] .. +2D2E..2D2F ; Cn # [2] .. +2D68..2D6E ; Cn # [7] .. +2D71..2D7E ; Cn # [14] .. +2D97..2D9F ; Cn # [9] .. +2DA7 ; Cn # +2DAF ; Cn # +2DB7 ; Cn # +2DBF ; Cn # +2DC7 ; Cn # +2DCF ; Cn # +2DD7 ; Cn # +2DDF ; Cn # +2E5E..2E7F ; Cn # [34] .. +2E9A ; Cn # +2EF4..2EFF ; Cn # [12] .. +2FD6..2FEF ; Cn # [26] .. +3040 ; Cn # +3097..3098 ; Cn # [2] .. +3100..3104 ; Cn # [5] .. +3130 ; Cn # +318F ; Cn # +31E4..31EE ; Cn # [11] .. +321F ; Cn # +A48D..A48F ; Cn # [3] .. +A4C7..A4CF ; Cn # [9] .. +A62C..A63F ; Cn # [20] .. +A6F8..A6FF ; Cn # [8] .. +A7CB..A7CF ; Cn # [5] .. +A7D2 ; Cn # +A7D4 ; Cn # +A7DA..A7F1 ; Cn # [24] .. +A82D..A82F ; Cn # [3] .. +A83A..A83F ; Cn # [6] .. +A878..A87F ; Cn # [8] .. +A8C6..A8CD ; Cn # [8] .. +A8DA..A8DF ; Cn # [6] .. +A954..A95E ; Cn # [11] .. +A97D..A97F ; Cn # [3] .. +A9CE ; Cn # +A9DA..A9DD ; Cn # [4] .. +A9FF ; Cn # +AA37..AA3F ; Cn # [9] .. +AA4E..AA4F ; Cn # [2] .. +AA5A..AA5B ; Cn # [2] .. +AAC3..AADA ; Cn # [24] .. +AAF7..AB00 ; Cn # [10] .. +AB07..AB08 ; Cn # [2] .. +AB0F..AB10 ; Cn # [2] .. +AB17..AB1F ; Cn # [9] .. +AB27 ; Cn # +AB2F ; Cn # +AB6C..AB6F ; Cn # [4] .. +ABEE..ABEF ; Cn # [2] .. +ABFA..ABFF ; Cn # [6] .. +D7A4..D7AF ; Cn # [12] .. +D7C7..D7CA ; Cn # [4] .. +D7FC..D7FF ; Cn # [4] .. +FA6E..FA6F ; Cn # [2] .. +FADA..FAFF ; Cn # [38] .. +FB07..FB12 ; Cn # [12] .. +FB18..FB1C ; Cn # [5] .. +FB37 ; Cn # +FB3D ; Cn # +FB3F ; Cn # +FB42 ; Cn # +FB45 ; Cn # +FBC3..FBD2 ; Cn # [16] .. +FD90..FD91 ; Cn # [2] .. +FDC8..FDCE ; Cn # [7] .. +FDD0..FDEF ; Cn # [32] .. +FE1A..FE1F ; Cn # [6] .. +FE53 ; Cn # +FE67 ; Cn # +FE6C..FE6F ; Cn # [4] .. +FE75 ; Cn # +FEFD..FEFE ; Cn # [2] .. +FF00 ; Cn # +FFBF..FFC1 ; Cn # [3] .. +FFC8..FFC9 ; Cn # [2] .. +FFD0..FFD1 ; Cn # [2] .. +FFD8..FFD9 ; Cn # [2] .. +FFDD..FFDF ; Cn # [3] .. +FFE7 ; Cn # +FFEF..FFF8 ; Cn # [10] .. +FFFE..FFFF ; Cn # [2] .. +1000C ; Cn # +10027 ; Cn # +1003B ; Cn # +1003E ; Cn # +1004E..1004F ; Cn # [2] .. +1005E..1007F ; Cn # [34] .. +100FB..100FF ; Cn # [5] .. +10103..10106 ; Cn # [4] .. +10134..10136 ; Cn # [3] .. +1018F ; Cn # +1019D..1019F ; Cn # [3] .. +101A1..101CF ; Cn # [47] .. +101FE..1027F ; Cn # [130] .. +1029D..1029F ; Cn # [3] .. +102D1..102DF ; Cn # [15] .. +102FC..102FF ; Cn # [4] .. +10324..1032C ; Cn # [9] .. +1034B..1034F ; Cn # [5] .. +1037B..1037F ; Cn # [5] .. +1039E ; Cn # +103C4..103C7 ; Cn # [4] .. +103D6..103FF ; Cn # [42] .. +1049E..1049F ; Cn # [2] .. +104AA..104AF ; Cn # [6] .. +104D4..104D7 ; Cn # [4] .. +104FC..104FF ; Cn # [4] .. +10528..1052F ; Cn # [8] .. +10564..1056E ; Cn # [11] .. +1057B ; Cn # +1058B ; Cn # +10593 ; Cn # +10596 ; Cn # +105A2 ; Cn # +105B2 ; Cn # +105BA ; Cn # +105BD..105FF ; Cn # [67] .. +10737..1073F ; Cn # [9] .. +10756..1075F ; Cn # [10] .. +10768..1077F ; Cn # [24] .. +10786 ; Cn # +107B1 ; Cn # +107BB..107FF ; Cn # [69] .. +10806..10807 ; Cn # [2] .. +10809 ; Cn # +10836 ; Cn # +10839..1083B ; Cn # [3] .. +1083D..1083E ; Cn # [2] .. +10856 ; Cn # +1089F..108A6 ; Cn # [8] .. +108B0..108DF ; Cn # [48] .. +108F3 ; Cn # +108F6..108FA ; Cn # [5] .. +1091C..1091E ; Cn # [3] .. +1093A..1093E ; Cn # [5] .. +10940..1097F ; Cn # [64] .. +109B8..109BB ; Cn # [4] .. +109D0..109D1 ; Cn # [2] .. +10A04 ; Cn # +10A07..10A0B ; Cn # [5] .. +10A14 ; Cn # +10A18 ; Cn # +10A36..10A37 ; Cn # [2] .. +10A3B..10A3E ; Cn # [4] .. +10A49..10A4F ; Cn # [7] .. +10A59..10A5F ; Cn # [7] .. +10AA0..10ABF ; Cn # [32] .. +10AE7..10AEA ; Cn # [4] .. +10AF7..10AFF ; Cn # [9] .. +10B36..10B38 ; Cn # [3] .. +10B56..10B57 ; Cn # [2] .. +10B73..10B77 ; Cn # [5] .. +10B92..10B98 ; Cn # [7] .. +10B9D..10BA8 ; Cn # [12] .. +10BB0..10BFF ; Cn # [80] .. +10C49..10C7F ; Cn # [55] .. +10CB3..10CBF ; Cn # [13] .. +10CF3..10CF9 ; Cn # [7] .. +10D28..10D2F ; Cn # [8] .. +10D3A..10E5F ; Cn # [294] .. +10E7F ; Cn # +10EAA ; Cn # +10EAE..10EAF ; Cn # [2] .. +10EB2..10EFC ; Cn # [75] .. +10F28..10F2F ; Cn # [8] .. +10F5A..10F6F ; Cn # [22] .. +10F8A..10FAF ; Cn # [38] .. +10FCC..10FDF ; Cn # [20] .. +10FF7..10FFF ; Cn # [9] .. +1104E..11051 ; Cn # [4] .. +11076..1107E ; Cn # [9] .. +110C3..110CC ; Cn # [10] .. +110CE..110CF ; Cn # [2] .. +110E9..110EF ; Cn # [7] .. +110FA..110FF ; Cn # [6] .. +11135 ; Cn # +11148..1114F ; Cn # [8] .. +11177..1117F ; Cn # [9] .. +111E0 ; Cn # +111F5..111FF ; Cn # [11] .. +11212 ; Cn # +11242..1127F ; Cn # [62] .. +11287 ; Cn # +11289 ; Cn # +1128E ; Cn # +1129E ; Cn # +112AA..112AF ; Cn # [6] .. +112EB..112EF ; Cn # [5] .. +112FA..112FF ; Cn # [6] .. +11304 ; Cn # +1130D..1130E ; Cn # [2] .. +11311..11312 ; Cn # [2] .. +11329 ; Cn # +11331 ; Cn # +11334 ; Cn # +1133A ; Cn # +11345..11346 ; Cn # [2] .. +11349..1134A ; Cn # [2] .. +1134E..1134F ; Cn # [2] .. +11351..11356 ; Cn # [6] .. +11358..1135C ; Cn # [5] .. +11364..11365 ; Cn # [2] .. +1136D..1136F ; Cn # [3] .. +11375..113FF ; Cn # [139] .. +1145C ; Cn # +11462..1147F ; Cn # [30] .. +114C8..114CF ; Cn # [8] .. +114DA..1157F ; Cn # [166] .. +115B6..115B7 ; Cn # [2] .. +115DE..115FF ; Cn # [34] .. +11645..1164F ; Cn # [11] .. +1165A..1165F ; Cn # [6] .. +1166D..1167F ; Cn # [19] .. +116BA..116BF ; Cn # [6] .. +116CA..116FF ; Cn # [54] .. +1171B..1171C ; Cn # [2] .. +1172C..1172F ; Cn # [4] .. +11747..117FF ; Cn # [185] .. +1183C..1189F ; Cn # [100] .. +118F3..118FE ; Cn # [12] .. +11907..11908 ; Cn # [2] .. +1190A..1190B ; Cn # [2] .. +11914 ; Cn # +11917 ; Cn # +11936 ; Cn # +11939..1193A ; Cn # [2] .. +11947..1194F ; Cn # [9] .. +1195A..1199F ; Cn # [70] .. +119A8..119A9 ; Cn # [2] .. +119D8..119D9 ; Cn # [2] .. +119E5..119FF ; Cn # [27] .. +11A48..11A4F ; Cn # [8] .. +11AA3..11AAF ; Cn # [13] .. +11AF9..11AFF ; Cn # [7] .. +11B0A..11BFF ; Cn # [246] .. +11C09 ; Cn # +11C37 ; Cn # +11C46..11C4F ; Cn # [10] .. +11C6D..11C6F ; Cn # [3] .. +11C90..11C91 ; Cn # [2] .. +11CA8 ; Cn # +11CB7..11CFF ; Cn # [73] .. +11D07 ; Cn # +11D0A ; Cn # +11D37..11D39 ; Cn # [3] .. +11D3B ; Cn # +11D3E ; Cn # +11D48..11D4F ; Cn # [8] .. +11D5A..11D5F ; Cn # [6] .. +11D66 ; Cn # +11D69 ; Cn # +11D8F ; Cn # +11D92 ; Cn # +11D99..11D9F ; Cn # [7] .. +11DAA..11EDF ; Cn # [310] .. +11EF9..11EFF ; Cn # [7] .. +11F11 ; Cn # +11F3B..11F3D ; Cn # [3] .. +11F5A..11FAF ; Cn # [86] .. +11FB1..11FBF ; Cn # [15] .. +11FF2..11FFE ; Cn # [13] .. +1239A..123FF ; Cn # [102] .. +1246F ; Cn # +12475..1247F ; Cn # [11] .. +12544..12F8F ; Cn # [2636] .. +12FF3..12FFF ; Cn # [13] .. +13456..143FF ; Cn # [4010] .. +14647..167FF ; Cn # [8633] .. +16A39..16A3F ; Cn # [7] .. +16A5F ; Cn # +16A6A..16A6D ; Cn # [4] .. +16ABF ; Cn # +16ACA..16ACF ; Cn # [6] .. +16AEE..16AEF ; Cn # [2] .. +16AF6..16AFF ; Cn # [10] .. +16B46..16B4F ; Cn # [10] .. +16B5A ; Cn # +16B62 ; Cn # +16B78..16B7C ; Cn # [5] .. +16B90..16E3F ; Cn # [688] .. +16E9B..16EFF ; Cn # [101] .. +16F4B..16F4E ; Cn # [4] .. +16F88..16F8E ; Cn # [7] .. +16FA0..16FDF ; Cn # [64] .. +16FE5..16FEF ; Cn # [11] .. +16FF2..16FFF ; Cn # [14] .. +187F8..187FF ; Cn # [8] .. +18CD6..18CFF ; Cn # [42] .. +18D09..1AFEF ; Cn # [8935] .. +1AFF4 ; Cn # +1AFFC ; Cn # +1AFFF ; Cn # +1B123..1B131 ; Cn # [15] .. +1B133..1B14F ; Cn # [29] .. +1B153..1B154 ; Cn # [2] .. +1B156..1B163 ; Cn # [14] .. +1B168..1B16F ; Cn # [8] .. +1B2FC..1BBFF ; Cn # [2308] .. +1BC6B..1BC6F ; Cn # [5] .. +1BC7D..1BC7F ; Cn # [3] .. +1BC89..1BC8F ; Cn # [7] .. +1BC9A..1BC9B ; Cn # [2] .. +1BCA4..1CEFF ; Cn # [4700] .. +1CF2E..1CF2F ; Cn # [2] .. +1CF47..1CF4F ; Cn # [9] .. +1CFC4..1CFFF ; Cn # [60] .. +1D0F6..1D0FF ; Cn # [10] .. +1D127..1D128 ; Cn # [2] .. +1D1EB..1D1FF ; Cn # [21] .. +1D246..1D2BF ; Cn # [122] .. +1D2D4..1D2DF ; Cn # [12] .. +1D2F4..1D2FF ; Cn # [12] .. +1D357..1D35F ; Cn # [9] .. +1D379..1D3FF ; Cn # [135] .. +1D455 ; Cn # +1D49D ; Cn # +1D4A0..1D4A1 ; Cn # [2] .. +1D4A3..1D4A4 ; Cn # [2] .. +1D4A7..1D4A8 ; Cn # [2] .. +1D4AD ; Cn # +1D4BA ; Cn # +1D4BC ; Cn # +1D4C4 ; Cn # +1D506 ; Cn # +1D50B..1D50C ; Cn # [2] .. +1D515 ; Cn # +1D51D ; Cn # +1D53A ; Cn # +1D53F ; Cn # +1D545 ; Cn # +1D547..1D549 ; Cn # [3] .. +1D551 ; Cn # +1D6A6..1D6A7 ; Cn # [2] .. +1D7CC..1D7CD ; Cn # [2] .. +1DA8C..1DA9A ; Cn # [15] .. +1DAA0 ; Cn # +1DAB0..1DEFF ; Cn # [1104] .. +1DF1F..1DF24 ; Cn # [6] .. +1DF2B..1DFFF ; Cn # [213] .. +1E007 ; Cn # +1E019..1E01A ; Cn # [2] .. +1E022 ; Cn # +1E025 ; Cn # +1E02B..1E02F ; Cn # [5] .. +1E06E..1E08E ; Cn # [33] .. +1E090..1E0FF ; Cn # [112] .. +1E12D..1E12F ; Cn # [3] .. +1E13E..1E13F ; Cn # [2] .. +1E14A..1E14D ; Cn # [4] .. +1E150..1E28F ; Cn # [320] .. +1E2AF..1E2BF ; Cn # [17] .. +1E2FA..1E2FE ; Cn # [5] .. +1E300..1E4CF ; Cn # [464] .. +1E4FA..1E7DF ; Cn # [742] .. +1E7E7 ; Cn # +1E7EC ; Cn # +1E7EF ; Cn # +1E7FF ; Cn # +1E8C5..1E8C6 ; Cn # [2] .. +1E8D7..1E8FF ; Cn # [41] .. +1E94C..1E94F ; Cn # [4] .. +1E95A..1E95D ; Cn # [4] .. +1E960..1EC70 ; Cn # [785] .. +1ECB5..1ED00 ; Cn # [76] .. +1ED3E..1EDFF ; Cn # [194] .. +1EE04 ; Cn # +1EE20 ; Cn # +1EE23 ; Cn # +1EE25..1EE26 ; Cn # [2] .. +1EE28 ; Cn # +1EE33 ; Cn # +1EE38 ; Cn # +1EE3A ; Cn # +1EE3C..1EE41 ; Cn # [6] .. +1EE43..1EE46 ; Cn # [4] .. +1EE48 ; Cn # +1EE4A ; Cn # +1EE4C ; Cn # +1EE50 ; Cn # +1EE53 ; Cn # +1EE55..1EE56 ; Cn # [2] .. +1EE58 ; Cn # +1EE5A ; Cn # +1EE5C ; Cn # +1EE5E ; Cn # +1EE60 ; Cn # +1EE63 ; Cn # +1EE65..1EE66 ; Cn # [2] .. +1EE6B ; Cn # +1EE73 ; Cn # +1EE78 ; Cn # +1EE7D ; Cn # +1EE7F ; Cn # +1EE8A ; Cn # +1EE9C..1EEA0 ; Cn # [5] .. +1EEA4 ; Cn # +1EEAA ; Cn # +1EEBC..1EEEF ; Cn # [52] .. +1EEF2..1EFFF ; Cn # [270] .. +1F02C..1F02F ; Cn # [4] .. +1F094..1F09F ; Cn # [12] .. +1F0AF..1F0B0 ; Cn # [2] .. +1F0C0 ; Cn # +1F0D0 ; Cn # +1F0F6..1F0FF ; Cn # [10] .. +1F1AE..1F1E5 ; Cn # [56] .. +1F203..1F20F ; Cn # [13] .. +1F23C..1F23F ; Cn # [4] .. +1F249..1F24F ; Cn # [7] .. +1F252..1F25F ; Cn # [14] .. +1F266..1F2FF ; Cn # [154] .. +1F6D8..1F6DB ; Cn # [4] .. +1F6ED..1F6EF ; Cn # [3] .. +1F6FD..1F6FF ; Cn # [3] .. +1F777..1F77A ; Cn # [4] .. +1F7DA..1F7DF ; Cn # [6] .. +1F7EC..1F7EF ; Cn # [4] .. +1F7F1..1F7FF ; Cn # [15] .. +1F80C..1F80F ; Cn # [4] .. +1F848..1F84F ; Cn # [8] .. +1F85A..1F85F ; Cn # [6] .. +1F888..1F88F ; Cn # [8] .. +1F8AE..1F8AF ; Cn # [2] .. +1F8B2..1F8FF ; Cn # [78] .. +1FA54..1FA5F ; Cn # [12] .. +1FA6E..1FA6F ; Cn # [2] .. +1FA7D..1FA7F ; Cn # [3] .. +1FA89..1FA8F ; Cn # [7] .. +1FABE ; Cn # +1FAC6..1FACD ; Cn # [8] .. +1FADC..1FADF ; Cn # [4] .. +1FAE9..1FAEF ; Cn # [7] .. +1FAF9..1FAFF ; Cn # [7] .. +1FB93 ; Cn # +1FBCB..1FBEF ; Cn # [37] .. +1FBFA..1FFFF ; Cn # [1030] .. +2A6E0..2A6FF ; Cn # [32] .. +2B73A..2B73F ; Cn # [6] .. +2B81E..2B81F ; Cn # [2] .. +2CEA2..2CEAF ; Cn # [14] .. +2EBE1..2EBEF ; Cn # [15] .. +2EE5E..2F7FF ; Cn # [2466] .. +2FA1E..2FFFF ; Cn # [1506] .. +3134B..3134F ; Cn # [5] .. +323B0..E0000 ; Cn # [711761] .. +E0002..E001F ; Cn # [30] .. +E0080..E00FF ; Cn # [128] .. +E01F0..EFFFF ; Cn # [65040] .. +FFFFE..FFFFF ; Cn # [2] .. +10FFFE..10FFFF; Cn # [2] .. + +# Total code points: 824718 + +# ================================================ + +# General_Category=Uppercase_Letter + +0041..005A ; Lu # [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +00C0..00D6 ; Lu # [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D8..00DE ; Lu # [7] LATIN CAPITAL LETTER O WITH STROKE..LATIN CAPITAL LETTER THORN +0100 ; Lu # LATIN CAPITAL LETTER A WITH MACRON +0102 ; Lu # LATIN CAPITAL LETTER A WITH BREVE +0104 ; Lu # LATIN CAPITAL LETTER A WITH OGONEK +0106 ; Lu # LATIN CAPITAL LETTER C WITH ACUTE +0108 ; Lu # LATIN CAPITAL LETTER C WITH CIRCUMFLEX +010A ; Lu # LATIN CAPITAL LETTER C WITH DOT ABOVE +010C ; Lu # LATIN CAPITAL LETTER C WITH CARON +010E ; Lu # LATIN CAPITAL LETTER D WITH CARON +0110 ; Lu # LATIN CAPITAL LETTER D WITH STROKE +0112 ; Lu # LATIN CAPITAL LETTER E WITH MACRON +0114 ; Lu # LATIN CAPITAL LETTER E WITH BREVE +0116 ; Lu # LATIN CAPITAL LETTER E WITH DOT ABOVE +0118 ; Lu # LATIN CAPITAL LETTER E WITH OGONEK +011A ; Lu # LATIN CAPITAL LETTER E WITH CARON +011C ; Lu # LATIN CAPITAL LETTER G WITH CIRCUMFLEX +011E ; Lu # LATIN CAPITAL LETTER G WITH BREVE +0120 ; Lu # LATIN CAPITAL LETTER G WITH DOT ABOVE +0122 ; Lu # LATIN CAPITAL LETTER G WITH CEDILLA +0124 ; Lu # LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0126 ; Lu # LATIN CAPITAL LETTER H WITH STROKE +0128 ; Lu # LATIN CAPITAL LETTER I WITH TILDE +012A ; Lu # LATIN CAPITAL LETTER I WITH MACRON +012C ; Lu # LATIN CAPITAL LETTER I WITH BREVE +012E ; Lu # LATIN CAPITAL LETTER I WITH OGONEK +0130 ; Lu # LATIN CAPITAL LETTER I WITH DOT ABOVE +0132 ; Lu # LATIN CAPITAL LIGATURE IJ +0134 ; Lu # LATIN CAPITAL LETTER J WITH CIRCUMFLEX +0136 ; Lu # LATIN CAPITAL LETTER K WITH CEDILLA +0139 ; Lu # LATIN CAPITAL LETTER L WITH ACUTE +013B ; Lu # LATIN CAPITAL LETTER L WITH CEDILLA +013D ; Lu # LATIN CAPITAL LETTER L WITH CARON +013F ; Lu # LATIN CAPITAL LETTER L WITH MIDDLE DOT +0141 ; Lu # LATIN CAPITAL LETTER L WITH STROKE +0143 ; Lu # LATIN CAPITAL LETTER N WITH ACUTE +0145 ; Lu # LATIN CAPITAL LETTER N WITH CEDILLA +0147 ; Lu # LATIN CAPITAL LETTER N WITH CARON +014A ; Lu # LATIN CAPITAL LETTER ENG +014C ; Lu # LATIN CAPITAL LETTER O WITH MACRON +014E ; Lu # LATIN CAPITAL LETTER O WITH BREVE +0150 ; Lu # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +0152 ; Lu # LATIN CAPITAL LIGATURE OE +0154 ; Lu # LATIN CAPITAL LETTER R WITH ACUTE +0156 ; Lu # LATIN CAPITAL LETTER R WITH CEDILLA +0158 ; Lu # LATIN CAPITAL LETTER R WITH CARON +015A ; Lu # LATIN CAPITAL LETTER S WITH ACUTE +015C ; Lu # LATIN CAPITAL LETTER S WITH CIRCUMFLEX +015E ; Lu # LATIN CAPITAL LETTER S WITH CEDILLA +0160 ; Lu # LATIN CAPITAL LETTER S WITH CARON +0162 ; Lu # LATIN CAPITAL LETTER T WITH CEDILLA +0164 ; Lu # LATIN CAPITAL LETTER T WITH CARON +0166 ; Lu # LATIN CAPITAL LETTER T WITH STROKE +0168 ; Lu # LATIN CAPITAL LETTER U WITH TILDE +016A ; Lu # LATIN CAPITAL LETTER U WITH MACRON +016C ; Lu # LATIN CAPITAL LETTER U WITH BREVE +016E ; Lu # LATIN CAPITAL LETTER U WITH RING ABOVE +0170 ; Lu # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +0172 ; Lu # LATIN CAPITAL LETTER U WITH OGONEK +0174 ; Lu # LATIN CAPITAL LETTER W WITH CIRCUMFLEX +0176 ; Lu # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0178..0179 ; Lu # [2] LATIN CAPITAL LETTER Y WITH DIAERESIS..LATIN CAPITAL LETTER Z WITH ACUTE +017B ; Lu # LATIN CAPITAL LETTER Z WITH DOT ABOVE +017D ; Lu # LATIN CAPITAL LETTER Z WITH CARON +0181..0182 ; Lu # [2] LATIN CAPITAL LETTER B WITH HOOK..LATIN CAPITAL LETTER B WITH TOPBAR +0184 ; Lu # LATIN CAPITAL LETTER TONE SIX +0186..0187 ; Lu # [2] LATIN CAPITAL LETTER OPEN O..LATIN CAPITAL LETTER C WITH HOOK +0189..018B ; Lu # [3] LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH TOPBAR +018E..0191 ; Lu # [4] LATIN CAPITAL LETTER REVERSED E..LATIN CAPITAL LETTER F WITH HOOK +0193..0194 ; Lu # [2] LATIN CAPITAL LETTER G WITH HOOK..LATIN CAPITAL LETTER GAMMA +0196..0198 ; Lu # [3] LATIN CAPITAL LETTER IOTA..LATIN CAPITAL LETTER K WITH HOOK +019C..019D ; Lu # [2] LATIN CAPITAL LETTER TURNED M..LATIN CAPITAL LETTER N WITH LEFT HOOK +019F..01A0 ; Lu # [2] LATIN CAPITAL LETTER O WITH MIDDLE TILDE..LATIN CAPITAL LETTER O WITH HORN +01A2 ; Lu # LATIN CAPITAL LETTER OI +01A4 ; Lu # LATIN CAPITAL LETTER P WITH HOOK +01A6..01A7 ; Lu # [2] LATIN LETTER YR..LATIN CAPITAL LETTER TONE TWO +01A9 ; Lu # LATIN CAPITAL LETTER ESH +01AC ; Lu # LATIN CAPITAL LETTER T WITH HOOK +01AE..01AF ; Lu # [2] LATIN CAPITAL LETTER T WITH RETROFLEX HOOK..LATIN CAPITAL LETTER U WITH HORN +01B1..01B3 ; Lu # [3] LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER Y WITH HOOK +01B5 ; Lu # LATIN CAPITAL LETTER Z WITH STROKE +01B7..01B8 ; Lu # [2] LATIN CAPITAL LETTER EZH..LATIN CAPITAL LETTER EZH REVERSED +01BC ; Lu # LATIN CAPITAL LETTER TONE FIVE +01C4 ; Lu # LATIN CAPITAL LETTER DZ WITH CARON +01C7 ; Lu # LATIN CAPITAL LETTER LJ +01CA ; Lu # LATIN CAPITAL LETTER NJ +01CD ; Lu # LATIN CAPITAL LETTER A WITH CARON +01CF ; Lu # LATIN CAPITAL LETTER I WITH CARON +01D1 ; Lu # LATIN CAPITAL LETTER O WITH CARON +01D3 ; Lu # LATIN CAPITAL LETTER U WITH CARON +01D5 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON +01D7 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE +01D9 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON +01DB ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE +01DE ; Lu # LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON +01E0 ; Lu # LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON +01E2 ; Lu # LATIN CAPITAL LETTER AE WITH MACRON +01E4 ; Lu # LATIN CAPITAL LETTER G WITH STROKE +01E6 ; Lu # LATIN CAPITAL LETTER G WITH CARON +01E8 ; Lu # LATIN CAPITAL LETTER K WITH CARON +01EA ; Lu # LATIN CAPITAL LETTER O WITH OGONEK +01EC ; Lu # LATIN CAPITAL LETTER O WITH OGONEK AND MACRON +01EE ; Lu # LATIN CAPITAL LETTER EZH WITH CARON +01F1 ; Lu # LATIN CAPITAL LETTER DZ +01F4 ; Lu # LATIN CAPITAL LETTER G WITH ACUTE +01F6..01F8 ; Lu # [3] LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER N WITH GRAVE +01FA ; Lu # LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +01FC ; Lu # LATIN CAPITAL LETTER AE WITH ACUTE +01FE ; Lu # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +0200 ; Lu # LATIN CAPITAL LETTER A WITH DOUBLE GRAVE +0202 ; Lu # LATIN CAPITAL LETTER A WITH INVERTED BREVE +0204 ; Lu # LATIN CAPITAL LETTER E WITH DOUBLE GRAVE +0206 ; Lu # LATIN CAPITAL LETTER E WITH INVERTED BREVE +0208 ; Lu # LATIN CAPITAL LETTER I WITH DOUBLE GRAVE +020A ; Lu # LATIN CAPITAL LETTER I WITH INVERTED BREVE +020C ; Lu # LATIN CAPITAL LETTER O WITH DOUBLE GRAVE +020E ; Lu # LATIN CAPITAL LETTER O WITH INVERTED BREVE +0210 ; Lu # LATIN CAPITAL LETTER R WITH DOUBLE GRAVE +0212 ; Lu # LATIN CAPITAL LETTER R WITH INVERTED BREVE +0214 ; Lu # LATIN CAPITAL LETTER U WITH DOUBLE GRAVE +0216 ; Lu # LATIN CAPITAL LETTER U WITH INVERTED BREVE +0218 ; Lu # LATIN CAPITAL LETTER S WITH COMMA BELOW +021A ; Lu # LATIN CAPITAL LETTER T WITH COMMA BELOW +021C ; Lu # LATIN CAPITAL LETTER YOGH +021E ; Lu # LATIN CAPITAL LETTER H WITH CARON +0220 ; Lu # LATIN CAPITAL LETTER N WITH LONG RIGHT LEG +0222 ; Lu # LATIN CAPITAL LETTER OU +0224 ; Lu # LATIN CAPITAL LETTER Z WITH HOOK +0226 ; Lu # LATIN CAPITAL LETTER A WITH DOT ABOVE +0228 ; Lu # LATIN CAPITAL LETTER E WITH CEDILLA +022A ; Lu # LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON +022C ; Lu # LATIN CAPITAL LETTER O WITH TILDE AND MACRON +022E ; Lu # LATIN CAPITAL LETTER O WITH DOT ABOVE +0230 ; Lu # LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON +0232 ; Lu # LATIN CAPITAL LETTER Y WITH MACRON +023A..023B ; Lu # [2] LATIN CAPITAL LETTER A WITH STROKE..LATIN CAPITAL LETTER C WITH STROKE +023D..023E ; Lu # [2] LATIN CAPITAL LETTER L WITH BAR..LATIN CAPITAL LETTER T WITH DIAGONAL STROKE +0241 ; Lu # LATIN CAPITAL LETTER GLOTTAL STOP +0243..0246 ; Lu # [4] LATIN CAPITAL LETTER B WITH STROKE..LATIN CAPITAL LETTER E WITH STROKE +0248 ; Lu # LATIN CAPITAL LETTER J WITH STROKE +024A ; Lu # LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL +024C ; Lu # LATIN CAPITAL LETTER R WITH STROKE +024E ; Lu # LATIN CAPITAL LETTER Y WITH STROKE +0370 ; Lu # GREEK CAPITAL LETTER HETA +0372 ; Lu # GREEK CAPITAL LETTER ARCHAIC SAMPI +0376 ; Lu # GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA +037F ; Lu # GREEK CAPITAL LETTER YOT +0386 ; Lu # GREEK CAPITAL LETTER ALPHA WITH TONOS +0388..038A ; Lu # [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; Lu # GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..038F ; Lu # [2] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER OMEGA WITH TONOS +0391..03A1 ; Lu # [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO +03A3..03AB ; Lu # [9] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +03CF ; Lu # GREEK CAPITAL KAI SYMBOL +03D2..03D4 ; Lu # [3] GREEK UPSILON WITH HOOK SYMBOL..GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL +03D8 ; Lu # GREEK LETTER ARCHAIC KOPPA +03DA ; Lu # GREEK LETTER STIGMA +03DC ; Lu # GREEK LETTER DIGAMMA +03DE ; Lu # GREEK LETTER KOPPA +03E0 ; Lu # GREEK LETTER SAMPI +03E2 ; Lu # COPTIC CAPITAL LETTER SHEI +03E4 ; Lu # COPTIC CAPITAL LETTER FEI +03E6 ; Lu # COPTIC CAPITAL LETTER KHEI +03E8 ; Lu # COPTIC CAPITAL LETTER HORI +03EA ; Lu # COPTIC CAPITAL LETTER GANGIA +03EC ; Lu # COPTIC CAPITAL LETTER SHIMA +03EE ; Lu # COPTIC CAPITAL LETTER DEI +03F4 ; Lu # GREEK CAPITAL THETA SYMBOL +03F7 ; Lu # GREEK CAPITAL LETTER SHO +03F9..03FA ; Lu # [2] GREEK CAPITAL LUNATE SIGMA SYMBOL..GREEK CAPITAL LETTER SAN +03FD..042F ; Lu # [51] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..CYRILLIC CAPITAL LETTER YA +0460 ; Lu # CYRILLIC CAPITAL LETTER OMEGA +0462 ; Lu # CYRILLIC CAPITAL LETTER YAT +0464 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED E +0466 ; Lu # CYRILLIC CAPITAL LETTER LITTLE YUS +0468 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS +046A ; Lu # CYRILLIC CAPITAL LETTER BIG YUS +046C ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS +046E ; Lu # CYRILLIC CAPITAL LETTER KSI +0470 ; Lu # CYRILLIC CAPITAL LETTER PSI +0472 ; Lu # CYRILLIC CAPITAL LETTER FITA +0474 ; Lu # CYRILLIC CAPITAL LETTER IZHITSA +0476 ; Lu # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT +0478 ; Lu # CYRILLIC CAPITAL LETTER UK +047A ; Lu # CYRILLIC CAPITAL LETTER ROUND OMEGA +047C ; Lu # CYRILLIC CAPITAL LETTER OMEGA WITH TITLO +047E ; Lu # CYRILLIC CAPITAL LETTER OT +0480 ; Lu # CYRILLIC CAPITAL LETTER KOPPA +048A ; Lu # CYRILLIC CAPITAL LETTER SHORT I WITH TAIL +048C ; Lu # CYRILLIC CAPITAL LETTER SEMISOFT SIGN +048E ; Lu # CYRILLIC CAPITAL LETTER ER WITH TICK +0490 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH UPTURN +0492 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH STROKE +0494 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK +0496 ; Lu # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER +0498 ; Lu # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER +049A ; Lu # CYRILLIC CAPITAL LETTER KA WITH DESCENDER +049C ; Lu # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE +049E ; Lu # CYRILLIC CAPITAL LETTER KA WITH STROKE +04A0 ; Lu # CYRILLIC CAPITAL LETTER BASHKIR KA +04A2 ; Lu # CYRILLIC CAPITAL LETTER EN WITH DESCENDER +04A4 ; Lu # CYRILLIC CAPITAL LIGATURE EN GHE +04A6 ; Lu # CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK +04A8 ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN HA +04AA ; Lu # CYRILLIC CAPITAL LETTER ES WITH DESCENDER +04AC ; Lu # CYRILLIC CAPITAL LETTER TE WITH DESCENDER +04AE ; Lu # CYRILLIC CAPITAL LETTER STRAIGHT U +04B0 ; Lu # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE +04B2 ; Lu # CYRILLIC CAPITAL LETTER HA WITH DESCENDER +04B4 ; Lu # CYRILLIC CAPITAL LIGATURE TE TSE +04B6 ; Lu # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER +04B8 ; Lu # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE +04BA ; Lu # CYRILLIC CAPITAL LETTER SHHA +04BC ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN CHE +04BE ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER +04C0..04C1 ; Lu # [2] CYRILLIC LETTER PALOCHKA..CYRILLIC CAPITAL LETTER ZHE WITH BREVE +04C3 ; Lu # CYRILLIC CAPITAL LETTER KA WITH HOOK +04C5 ; Lu # CYRILLIC CAPITAL LETTER EL WITH TAIL +04C7 ; Lu # CYRILLIC CAPITAL LETTER EN WITH HOOK +04C9 ; Lu # CYRILLIC CAPITAL LETTER EN WITH TAIL +04CB ; Lu # CYRILLIC CAPITAL LETTER KHAKASSIAN CHE +04CD ; Lu # CYRILLIC CAPITAL LETTER EM WITH TAIL +04D0 ; Lu # CYRILLIC CAPITAL LETTER A WITH BREVE +04D2 ; Lu # CYRILLIC CAPITAL LETTER A WITH DIAERESIS +04D4 ; Lu # CYRILLIC CAPITAL LIGATURE A IE +04D6 ; Lu # CYRILLIC CAPITAL LETTER IE WITH BREVE +04D8 ; Lu # CYRILLIC CAPITAL LETTER SCHWA +04DA ; Lu # CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS +04DC ; Lu # CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS +04DE ; Lu # CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS +04E0 ; Lu # CYRILLIC CAPITAL LETTER ABKHASIAN DZE +04E2 ; Lu # CYRILLIC CAPITAL LETTER I WITH MACRON +04E4 ; Lu # CYRILLIC CAPITAL LETTER I WITH DIAERESIS +04E6 ; Lu # CYRILLIC CAPITAL LETTER O WITH DIAERESIS +04E8 ; Lu # CYRILLIC CAPITAL LETTER BARRED O +04EA ; Lu # CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS +04EC ; Lu # CYRILLIC CAPITAL LETTER E WITH DIAERESIS +04EE ; Lu # CYRILLIC CAPITAL LETTER U WITH MACRON +04F0 ; Lu # CYRILLIC CAPITAL LETTER U WITH DIAERESIS +04F2 ; Lu # CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE +04F4 ; Lu # CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS +04F6 ; Lu # CYRILLIC CAPITAL LETTER GHE WITH DESCENDER +04F8 ; Lu # CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS +04FA ; Lu # CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK +04FC ; Lu # CYRILLIC CAPITAL LETTER HA WITH HOOK +04FE ; Lu # CYRILLIC CAPITAL LETTER HA WITH STROKE +0500 ; Lu # CYRILLIC CAPITAL LETTER KOMI DE +0502 ; Lu # CYRILLIC CAPITAL LETTER KOMI DJE +0504 ; Lu # CYRILLIC CAPITAL LETTER KOMI ZJE +0506 ; Lu # CYRILLIC CAPITAL LETTER KOMI DZJE +0508 ; Lu # CYRILLIC CAPITAL LETTER KOMI LJE +050A ; Lu # CYRILLIC CAPITAL LETTER KOMI NJE +050C ; Lu # CYRILLIC CAPITAL LETTER KOMI SJE +050E ; Lu # CYRILLIC CAPITAL LETTER KOMI TJE +0510 ; Lu # CYRILLIC CAPITAL LETTER REVERSED ZE +0512 ; Lu # CYRILLIC CAPITAL LETTER EL WITH HOOK +0514 ; Lu # CYRILLIC CAPITAL LETTER LHA +0516 ; Lu # CYRILLIC CAPITAL LETTER RHA +0518 ; Lu # CYRILLIC CAPITAL LETTER YAE +051A ; Lu # CYRILLIC CAPITAL LETTER QA +051C ; Lu # CYRILLIC CAPITAL LETTER WE +051E ; Lu # CYRILLIC CAPITAL LETTER ALEUT KA +0520 ; Lu # CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK +0522 ; Lu # CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK +0524 ; Lu # CYRILLIC CAPITAL LETTER PE WITH DESCENDER +0526 ; Lu # CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER +0528 ; Lu # CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK +052A ; Lu # CYRILLIC CAPITAL LETTER DZZHE +052C ; Lu # CYRILLIC CAPITAL LETTER DCHE +052E ; Lu # CYRILLIC CAPITAL LETTER EL WITH DESCENDER +0531..0556 ; Lu # [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +10A0..10C5 ; Lu # [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; Lu # GEORGIAN CAPITAL LETTER YN +10CD ; Lu # GEORGIAN CAPITAL LETTER AEN +13A0..13F5 ; Lu # [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +1C90..1CBA ; Lu # [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; Lu # [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1E00 ; Lu # LATIN CAPITAL LETTER A WITH RING BELOW +1E02 ; Lu # LATIN CAPITAL LETTER B WITH DOT ABOVE +1E04 ; Lu # LATIN CAPITAL LETTER B WITH DOT BELOW +1E06 ; Lu # LATIN CAPITAL LETTER B WITH LINE BELOW +1E08 ; Lu # LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE +1E0A ; Lu # LATIN CAPITAL LETTER D WITH DOT ABOVE +1E0C ; Lu # LATIN CAPITAL LETTER D WITH DOT BELOW +1E0E ; Lu # LATIN CAPITAL LETTER D WITH LINE BELOW +1E10 ; Lu # LATIN CAPITAL LETTER D WITH CEDILLA +1E12 ; Lu # LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW +1E14 ; Lu # LATIN CAPITAL LETTER E WITH MACRON AND GRAVE +1E16 ; Lu # LATIN CAPITAL LETTER E WITH MACRON AND ACUTE +1E18 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW +1E1A ; Lu # LATIN CAPITAL LETTER E WITH TILDE BELOW +1E1C ; Lu # LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE +1E1E ; Lu # LATIN CAPITAL LETTER F WITH DOT ABOVE +1E20 ; Lu # LATIN CAPITAL LETTER G WITH MACRON +1E22 ; Lu # LATIN CAPITAL LETTER H WITH DOT ABOVE +1E24 ; Lu # LATIN CAPITAL LETTER H WITH DOT BELOW +1E26 ; Lu # LATIN CAPITAL LETTER H WITH DIAERESIS +1E28 ; Lu # LATIN CAPITAL LETTER H WITH CEDILLA +1E2A ; Lu # LATIN CAPITAL LETTER H WITH BREVE BELOW +1E2C ; Lu # LATIN CAPITAL LETTER I WITH TILDE BELOW +1E2E ; Lu # LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE +1E30 ; Lu # LATIN CAPITAL LETTER K WITH ACUTE +1E32 ; Lu # LATIN CAPITAL LETTER K WITH DOT BELOW +1E34 ; Lu # LATIN CAPITAL LETTER K WITH LINE BELOW +1E36 ; Lu # LATIN CAPITAL LETTER L WITH DOT BELOW +1E38 ; Lu # LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON +1E3A ; Lu # LATIN CAPITAL LETTER L WITH LINE BELOW +1E3C ; Lu # LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW +1E3E ; Lu # LATIN CAPITAL LETTER M WITH ACUTE +1E40 ; Lu # LATIN CAPITAL LETTER M WITH DOT ABOVE +1E42 ; Lu # LATIN CAPITAL LETTER M WITH DOT BELOW +1E44 ; Lu # LATIN CAPITAL LETTER N WITH DOT ABOVE +1E46 ; Lu # LATIN CAPITAL LETTER N WITH DOT BELOW +1E48 ; Lu # LATIN CAPITAL LETTER N WITH LINE BELOW +1E4A ; Lu # LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW +1E4C ; Lu # LATIN CAPITAL LETTER O WITH TILDE AND ACUTE +1E4E ; Lu # LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS +1E50 ; Lu # LATIN CAPITAL LETTER O WITH MACRON AND GRAVE +1E52 ; Lu # LATIN CAPITAL LETTER O WITH MACRON AND ACUTE +1E54 ; Lu # LATIN CAPITAL LETTER P WITH ACUTE +1E56 ; Lu # LATIN CAPITAL LETTER P WITH DOT ABOVE +1E58 ; Lu # LATIN CAPITAL LETTER R WITH DOT ABOVE +1E5A ; Lu # LATIN CAPITAL LETTER R WITH DOT BELOW +1E5C ; Lu # LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON +1E5E ; Lu # LATIN CAPITAL LETTER R WITH LINE BELOW +1E60 ; Lu # LATIN CAPITAL LETTER S WITH DOT ABOVE +1E62 ; Lu # LATIN CAPITAL LETTER S WITH DOT BELOW +1E64 ; Lu # LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE +1E66 ; Lu # LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE +1E68 ; Lu # LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE +1E6A ; Lu # LATIN CAPITAL LETTER T WITH DOT ABOVE +1E6C ; Lu # LATIN CAPITAL LETTER T WITH DOT BELOW +1E6E ; Lu # LATIN CAPITAL LETTER T WITH LINE BELOW +1E70 ; Lu # LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW +1E72 ; Lu # LATIN CAPITAL LETTER U WITH DIAERESIS BELOW +1E74 ; Lu # LATIN CAPITAL LETTER U WITH TILDE BELOW +1E76 ; Lu # LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW +1E78 ; Lu # LATIN CAPITAL LETTER U WITH TILDE AND ACUTE +1E7A ; Lu # LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS +1E7C ; Lu # LATIN CAPITAL LETTER V WITH TILDE +1E7E ; Lu # LATIN CAPITAL LETTER V WITH DOT BELOW +1E80 ; Lu # LATIN CAPITAL LETTER W WITH GRAVE +1E82 ; Lu # LATIN CAPITAL LETTER W WITH ACUTE +1E84 ; Lu # LATIN CAPITAL LETTER W WITH DIAERESIS +1E86 ; Lu # LATIN CAPITAL LETTER W WITH DOT ABOVE +1E88 ; Lu # LATIN CAPITAL LETTER W WITH DOT BELOW +1E8A ; Lu # LATIN CAPITAL LETTER X WITH DOT ABOVE +1E8C ; Lu # LATIN CAPITAL LETTER X WITH DIAERESIS +1E8E ; Lu # LATIN CAPITAL LETTER Y WITH DOT ABOVE +1E90 ; Lu # LATIN CAPITAL LETTER Z WITH CIRCUMFLEX +1E92 ; Lu # LATIN CAPITAL LETTER Z WITH DOT BELOW +1E94 ; Lu # LATIN CAPITAL LETTER Z WITH LINE BELOW +1E9E ; Lu # LATIN CAPITAL LETTER SHARP S +1EA0 ; Lu # LATIN CAPITAL LETTER A WITH DOT BELOW +1EA2 ; Lu # LATIN CAPITAL LETTER A WITH HOOK ABOVE +1EA4 ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA6 ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA8 ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EAA ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE +1EAC ; Lu # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAE ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND ACUTE +1EB0 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND GRAVE +1EB2 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE +1EB4 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND TILDE +1EB6 ; Lu # LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW +1EB8 ; Lu # LATIN CAPITAL LETTER E WITH DOT BELOW +1EBA ; Lu # LATIN CAPITAL LETTER E WITH HOOK ABOVE +1EBC ; Lu # LATIN CAPITAL LETTER E WITH TILDE +1EBE ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE +1EC0 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC2 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC4 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE +1EC6 ; Lu # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC8 ; Lu # LATIN CAPITAL LETTER I WITH HOOK ABOVE +1ECA ; Lu # LATIN CAPITAL LETTER I WITH DOT BELOW +1ECC ; Lu # LATIN CAPITAL LETTER O WITH DOT BELOW +1ECE ; Lu # LATIN CAPITAL LETTER O WITH HOOK ABOVE +1ED0 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED2 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED4 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED6 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE +1ED8 ; Lu # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1EDA ; Lu # LATIN CAPITAL LETTER O WITH HORN AND ACUTE +1EDC ; Lu # LATIN CAPITAL LETTER O WITH HORN AND GRAVE +1EDE ; Lu # LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE +1EE0 ; Lu # LATIN CAPITAL LETTER O WITH HORN AND TILDE +1EE2 ; Lu # LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW +1EE4 ; Lu # LATIN CAPITAL LETTER U WITH DOT BELOW +1EE6 ; Lu # LATIN CAPITAL LETTER U WITH HOOK ABOVE +1EE8 ; Lu # LATIN CAPITAL LETTER U WITH HORN AND ACUTE +1EEA ; Lu # LATIN CAPITAL LETTER U WITH HORN AND GRAVE +1EEC ; Lu # LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE +1EEE ; Lu # LATIN CAPITAL LETTER U WITH HORN AND TILDE +1EF0 ; Lu # LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW +1EF2 ; Lu # LATIN CAPITAL LETTER Y WITH GRAVE +1EF4 ; Lu # LATIN CAPITAL LETTER Y WITH DOT BELOW +1EF6 ; Lu # LATIN CAPITAL LETTER Y WITH HOOK ABOVE +1EF8 ; Lu # LATIN CAPITAL LETTER Y WITH TILDE +1EFA ; Lu # LATIN CAPITAL LETTER MIDDLE-WELSH LL +1EFC ; Lu # LATIN CAPITAL LETTER MIDDLE-WELSH V +1EFE ; Lu # LATIN CAPITAL LETTER Y WITH LOOP +1F08..1F0F ; Lu # [8] GREEK CAPITAL LETTER ALPHA WITH PSILI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI +1F18..1F1D ; Lu # [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F28..1F2F ; Lu # [8] GREEK CAPITAL LETTER ETA WITH PSILI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI +1F38..1F3F ; Lu # [8] GREEK CAPITAL LETTER IOTA WITH PSILI..GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI +1F48..1F4D ; Lu # [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F59 ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F ; Lu # GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F68..1F6F ; Lu # [8] GREEK CAPITAL LETTER OMEGA WITH PSILI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI +1FB8..1FBB ; Lu # [4] GREEK CAPITAL LETTER ALPHA WITH VRACHY..GREEK CAPITAL LETTER ALPHA WITH OXIA +1FC8..1FCB ; Lu # [4] GREEK CAPITAL LETTER EPSILON WITH VARIA..GREEK CAPITAL LETTER ETA WITH OXIA +1FD8..1FDB ; Lu # [4] GREEK CAPITAL LETTER IOTA WITH VRACHY..GREEK CAPITAL LETTER IOTA WITH OXIA +1FE8..1FEC ; Lu # [5] GREEK CAPITAL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FF8..1FFB ; Lu # [4] GREEK CAPITAL LETTER OMICRON WITH VARIA..GREEK CAPITAL LETTER OMEGA WITH OXIA +2102 ; Lu # DOUBLE-STRUCK CAPITAL C +2107 ; Lu # EULER CONSTANT +210B..210D ; Lu # [3] SCRIPT CAPITAL H..DOUBLE-STRUCK CAPITAL H +2110..2112 ; Lu # [3] SCRIPT CAPITAL I..SCRIPT CAPITAL L +2115 ; Lu # DOUBLE-STRUCK CAPITAL N +2119..211D ; Lu # [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +2124 ; Lu # DOUBLE-STRUCK CAPITAL Z +2126 ; Lu # OHM SIGN +2128 ; Lu # BLACK-LETTER CAPITAL Z +212A..212D ; Lu # [4] KELVIN SIGN..BLACK-LETTER CAPITAL C +2130..2133 ; Lu # [4] SCRIPT CAPITAL E..SCRIPT CAPITAL M +213E..213F ; Lu # [2] DOUBLE-STRUCK CAPITAL GAMMA..DOUBLE-STRUCK CAPITAL PI +2145 ; Lu # DOUBLE-STRUCK ITALIC CAPITAL D +2183 ; Lu # ROMAN NUMERAL REVERSED ONE HUNDRED +2C00..2C2F ; Lu # [48] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI +2C60 ; Lu # LATIN CAPITAL LETTER L WITH DOUBLE BAR +2C62..2C64 ; Lu # [3] LATIN CAPITAL LETTER L WITH MIDDLE TILDE..LATIN CAPITAL LETTER R WITH TAIL +2C67 ; Lu # LATIN CAPITAL LETTER H WITH DESCENDER +2C69 ; Lu # LATIN CAPITAL LETTER K WITH DESCENDER +2C6B ; Lu # LATIN CAPITAL LETTER Z WITH DESCENDER +2C6D..2C70 ; Lu # [4] LATIN CAPITAL LETTER ALPHA..LATIN CAPITAL LETTER TURNED ALPHA +2C72 ; Lu # LATIN CAPITAL LETTER W WITH HOOK +2C75 ; Lu # LATIN CAPITAL LETTER HALF H +2C7E..2C80 ; Lu # [3] LATIN CAPITAL LETTER S WITH SWASH TAIL..COPTIC CAPITAL LETTER ALFA +2C82 ; Lu # COPTIC CAPITAL LETTER VIDA +2C84 ; Lu # COPTIC CAPITAL LETTER GAMMA +2C86 ; Lu # COPTIC CAPITAL LETTER DALDA +2C88 ; Lu # COPTIC CAPITAL LETTER EIE +2C8A ; Lu # COPTIC CAPITAL LETTER SOU +2C8C ; Lu # COPTIC CAPITAL LETTER ZATA +2C8E ; Lu # COPTIC CAPITAL LETTER HATE +2C90 ; Lu # COPTIC CAPITAL LETTER THETHE +2C92 ; Lu # COPTIC CAPITAL LETTER IAUDA +2C94 ; Lu # COPTIC CAPITAL LETTER KAPA +2C96 ; Lu # COPTIC CAPITAL LETTER LAULA +2C98 ; Lu # COPTIC CAPITAL LETTER MI +2C9A ; Lu # COPTIC CAPITAL LETTER NI +2C9C ; Lu # COPTIC CAPITAL LETTER KSI +2C9E ; Lu # COPTIC CAPITAL LETTER O +2CA0 ; Lu # COPTIC CAPITAL LETTER PI +2CA2 ; Lu # COPTIC CAPITAL LETTER RO +2CA4 ; Lu # COPTIC CAPITAL LETTER SIMA +2CA6 ; Lu # COPTIC CAPITAL LETTER TAU +2CA8 ; Lu # COPTIC CAPITAL LETTER UA +2CAA ; Lu # COPTIC CAPITAL LETTER FI +2CAC ; Lu # COPTIC CAPITAL LETTER KHI +2CAE ; Lu # COPTIC CAPITAL LETTER PSI +2CB0 ; Lu # COPTIC CAPITAL LETTER OOU +2CB2 ; Lu # COPTIC CAPITAL LETTER DIALECT-P ALEF +2CB4 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC AIN +2CB6 ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE +2CB8 ; Lu # COPTIC CAPITAL LETTER DIALECT-P KAPA +2CBA ; Lu # COPTIC CAPITAL LETTER DIALECT-P NI +2CBC ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI +2CBE ; Lu # COPTIC CAPITAL LETTER OLD COPTIC OOU +2CC0 ; Lu # COPTIC CAPITAL LETTER SAMPI +2CC2 ; Lu # COPTIC CAPITAL LETTER CROSSED SHEI +2CC4 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC SHEI +2CC6 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC ESH +2CC8 ; Lu # COPTIC CAPITAL LETTER AKHMIMIC KHEI +2CCA ; Lu # COPTIC CAPITAL LETTER DIALECT-P HORI +2CCC ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HORI +2CCE ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HA +2CD0 ; Lu # COPTIC CAPITAL LETTER L-SHAPED HA +2CD2 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HEI +2CD4 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC HAT +2CD6 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC GANGIA +2CD8 ; Lu # COPTIC CAPITAL LETTER OLD COPTIC DJA +2CDA ; Lu # COPTIC CAPITAL LETTER OLD COPTIC SHIMA +2CDC ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN SHIMA +2CDE ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN NGI +2CE0 ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN NYI +2CE2 ; Lu # COPTIC CAPITAL LETTER OLD NUBIAN WAU +2CEB ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI +2CED ; Lu # COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA +2CF2 ; Lu # COPTIC CAPITAL LETTER BOHAIRIC KHEI +A640 ; Lu # CYRILLIC CAPITAL LETTER ZEMLYA +A642 ; Lu # CYRILLIC CAPITAL LETTER DZELO +A644 ; Lu # CYRILLIC CAPITAL LETTER REVERSED DZE +A646 ; Lu # CYRILLIC CAPITAL LETTER IOTA +A648 ; Lu # CYRILLIC CAPITAL LETTER DJERV +A64A ; Lu # CYRILLIC CAPITAL LETTER MONOGRAPH UK +A64C ; Lu # CYRILLIC CAPITAL LETTER BROAD OMEGA +A64E ; Lu # CYRILLIC CAPITAL LETTER NEUTRAL YER +A650 ; Lu # CYRILLIC CAPITAL LETTER YERU WITH BACK YER +A652 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED YAT +A654 ; Lu # CYRILLIC CAPITAL LETTER REVERSED YU +A656 ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED A +A658 ; Lu # CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS +A65A ; Lu # CYRILLIC CAPITAL LETTER BLENDED YUS +A65C ; Lu # CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS +A65E ; Lu # CYRILLIC CAPITAL LETTER YN +A660 ; Lu # CYRILLIC CAPITAL LETTER REVERSED TSE +A662 ; Lu # CYRILLIC CAPITAL LETTER SOFT DE +A664 ; Lu # CYRILLIC CAPITAL LETTER SOFT EL +A666 ; Lu # CYRILLIC CAPITAL LETTER SOFT EM +A668 ; Lu # CYRILLIC CAPITAL LETTER MONOCULAR O +A66A ; Lu # CYRILLIC CAPITAL LETTER BINOCULAR O +A66C ; Lu # CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O +A680 ; Lu # CYRILLIC CAPITAL LETTER DWE +A682 ; Lu # CYRILLIC CAPITAL LETTER DZWE +A684 ; Lu # CYRILLIC CAPITAL LETTER ZHWE +A686 ; Lu # CYRILLIC CAPITAL LETTER CCHE +A688 ; Lu # CYRILLIC CAPITAL LETTER DZZE +A68A ; Lu # CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK +A68C ; Lu # CYRILLIC CAPITAL LETTER TWE +A68E ; Lu # CYRILLIC CAPITAL LETTER TSWE +A690 ; Lu # CYRILLIC CAPITAL LETTER TSSE +A692 ; Lu # CYRILLIC CAPITAL LETTER TCHE +A694 ; Lu # CYRILLIC CAPITAL LETTER HWE +A696 ; Lu # CYRILLIC CAPITAL LETTER SHWE +A698 ; Lu # CYRILLIC CAPITAL LETTER DOUBLE O +A69A ; Lu # CYRILLIC CAPITAL LETTER CROSSED O +A722 ; Lu # LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF +A724 ; Lu # LATIN CAPITAL LETTER EGYPTOLOGICAL AIN +A726 ; Lu # LATIN CAPITAL LETTER HENG +A728 ; Lu # LATIN CAPITAL LETTER TZ +A72A ; Lu # LATIN CAPITAL LETTER TRESILLO +A72C ; Lu # LATIN CAPITAL LETTER CUATRILLO +A72E ; Lu # LATIN CAPITAL LETTER CUATRILLO WITH COMMA +A732 ; Lu # LATIN CAPITAL LETTER AA +A734 ; Lu # LATIN CAPITAL LETTER AO +A736 ; Lu # LATIN CAPITAL LETTER AU +A738 ; Lu # LATIN CAPITAL LETTER AV +A73A ; Lu # LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR +A73C ; Lu # LATIN CAPITAL LETTER AY +A73E ; Lu # LATIN CAPITAL LETTER REVERSED C WITH DOT +A740 ; Lu # LATIN CAPITAL LETTER K WITH STROKE +A742 ; Lu # LATIN CAPITAL LETTER K WITH DIAGONAL STROKE +A744 ; Lu # LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE +A746 ; Lu # LATIN CAPITAL LETTER BROKEN L +A748 ; Lu # LATIN CAPITAL LETTER L WITH HIGH STROKE +A74A ; Lu # LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY +A74C ; Lu # LATIN CAPITAL LETTER O WITH LOOP +A74E ; Lu # LATIN CAPITAL LETTER OO +A750 ; Lu # LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER +A752 ; Lu # LATIN CAPITAL LETTER P WITH FLOURISH +A754 ; Lu # LATIN CAPITAL LETTER P WITH SQUIRREL TAIL +A756 ; Lu # LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER +A758 ; Lu # LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE +A75A ; Lu # LATIN CAPITAL LETTER R ROTUNDA +A75C ; Lu # LATIN CAPITAL LETTER RUM ROTUNDA +A75E ; Lu # LATIN CAPITAL LETTER V WITH DIAGONAL STROKE +A760 ; Lu # LATIN CAPITAL LETTER VY +A762 ; Lu # LATIN CAPITAL LETTER VISIGOTHIC Z +A764 ; Lu # LATIN CAPITAL LETTER THORN WITH STROKE +A766 ; Lu # LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER +A768 ; Lu # LATIN CAPITAL LETTER VEND +A76A ; Lu # LATIN CAPITAL LETTER ET +A76C ; Lu # LATIN CAPITAL LETTER IS +A76E ; Lu # LATIN CAPITAL LETTER CON +A779 ; Lu # LATIN CAPITAL LETTER INSULAR D +A77B ; Lu # LATIN CAPITAL LETTER INSULAR F +A77D..A77E ; Lu # [2] LATIN CAPITAL LETTER INSULAR G..LATIN CAPITAL LETTER TURNED INSULAR G +A780 ; Lu # LATIN CAPITAL LETTER TURNED L +A782 ; Lu # LATIN CAPITAL LETTER INSULAR R +A784 ; Lu # LATIN CAPITAL LETTER INSULAR S +A786 ; Lu # LATIN CAPITAL LETTER INSULAR T +A78B ; Lu # LATIN CAPITAL LETTER SALTILLO +A78D ; Lu # LATIN CAPITAL LETTER TURNED H +A790 ; Lu # LATIN CAPITAL LETTER N WITH DESCENDER +A792 ; Lu # LATIN CAPITAL LETTER C WITH BAR +A796 ; Lu # LATIN CAPITAL LETTER B WITH FLOURISH +A798 ; Lu # LATIN CAPITAL LETTER F WITH STROKE +A79A ; Lu # LATIN CAPITAL LETTER VOLAPUK AE +A79C ; Lu # LATIN CAPITAL LETTER VOLAPUK OE +A79E ; Lu # LATIN CAPITAL LETTER VOLAPUK UE +A7A0 ; Lu # LATIN CAPITAL LETTER G WITH OBLIQUE STROKE +A7A2 ; Lu # LATIN CAPITAL LETTER K WITH OBLIQUE STROKE +A7A4 ; Lu # LATIN CAPITAL LETTER N WITH OBLIQUE STROKE +A7A6 ; Lu # LATIN CAPITAL LETTER R WITH OBLIQUE STROKE +A7A8 ; Lu # LATIN CAPITAL LETTER S WITH OBLIQUE STROKE +A7AA..A7AE ; Lu # [5] LATIN CAPITAL LETTER H WITH HOOK..LATIN CAPITAL LETTER SMALL CAPITAL I +A7B0..A7B4 ; Lu # [5] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER BETA +A7B6 ; Lu # LATIN CAPITAL LETTER OMEGA +A7B8 ; Lu # LATIN CAPITAL LETTER U WITH STROKE +A7BA ; Lu # LATIN CAPITAL LETTER GLOTTAL A +A7BC ; Lu # LATIN CAPITAL LETTER GLOTTAL I +A7BE ; Lu # LATIN CAPITAL LETTER GLOTTAL U +A7C0 ; Lu # LATIN CAPITAL LETTER OLD POLISH O +A7C2 ; Lu # LATIN CAPITAL LETTER ANGLICANA W +A7C4..A7C7 ; Lu # [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY +A7C9 ; Lu # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY +A7D0 ; Lu # LATIN CAPITAL LETTER CLOSED INSULAR G +A7D6 ; Lu # LATIN CAPITAL LETTER MIDDLE SCOTS S +A7D8 ; Lu # LATIN CAPITAL LETTER SIGMOID S +A7F5 ; Lu # LATIN CAPITAL LETTER REVERSED HALF H +FF21..FF3A ; Lu # [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +10400..10427 ; Lu # [40] DESERET CAPITAL LETTER LONG I..DESERET CAPITAL LETTER EW +104B0..104D3 ; Lu # [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +10570..1057A ; Lu # [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; Lu # [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; Lu # [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; Lu # [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10C80..10CB2 ; Lu # [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +118A0..118BF ; Lu # [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO +16E40..16E5F ; Lu # [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +1D400..1D419 ; Lu # [26] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL BOLD CAPITAL Z +1D434..1D44D ; Lu # [26] MATHEMATICAL ITALIC CAPITAL A..MATHEMATICAL ITALIC CAPITAL Z +1D468..1D481 ; Lu # [26] MATHEMATICAL BOLD ITALIC CAPITAL A..MATHEMATICAL BOLD ITALIC CAPITAL Z +1D49C ; Lu # MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; Lu # [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; Lu # MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; Lu # [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; Lu # [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B5 ; Lu # [8] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT CAPITAL Z +1D4D0..1D4E9 ; Lu # [26] MATHEMATICAL BOLD SCRIPT CAPITAL A..MATHEMATICAL BOLD SCRIPT CAPITAL Z +1D504..1D505 ; Lu # [2] MATHEMATICAL FRAKTUR CAPITAL A..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; Lu # [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; Lu # [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; Lu # [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D538..1D539 ; Lu # [2] MATHEMATICAL DOUBLE-STRUCK CAPITAL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; Lu # [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; Lu # [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; Lu # MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; Lu # [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D56C..1D585 ; Lu # [26] MATHEMATICAL BOLD FRAKTUR CAPITAL A..MATHEMATICAL BOLD FRAKTUR CAPITAL Z +1D5A0..1D5B9 ; Lu # [26] MATHEMATICAL SANS-SERIF CAPITAL A..MATHEMATICAL SANS-SERIF CAPITAL Z +1D5D4..1D5ED ; Lu # [26] MATHEMATICAL SANS-SERIF BOLD CAPITAL A..MATHEMATICAL SANS-SERIF BOLD CAPITAL Z +1D608..1D621 ; Lu # [26] MATHEMATICAL SANS-SERIF ITALIC CAPITAL A..MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z +1D63C..1D655 ; Lu # [26] MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z +1D670..1D689 ; Lu # [26] MATHEMATICAL MONOSPACE CAPITAL A..MATHEMATICAL MONOSPACE CAPITAL Z +1D6A8..1D6C0 ; Lu # [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6E2..1D6FA ; Lu # [25] MATHEMATICAL ITALIC CAPITAL ALPHA..MATHEMATICAL ITALIC CAPITAL OMEGA +1D71C..1D734 ; Lu # [25] MATHEMATICAL BOLD ITALIC CAPITAL ALPHA..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D756..1D76E ; Lu # [25] MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D790..1D7A8 ; Lu # [25] MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7CA ; Lu # MATHEMATICAL BOLD CAPITAL DIGAMMA +1E900..1E921 ; Lu # [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA + +# Total code points: 1831 + +# ================================================ + +# General_Category=Lowercase_Letter + +0061..007A ; Ll # [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +00B5 ; Ll # MICRO SIGN +00DF..00F6 ; Ll # [24] LATIN SMALL LETTER SHARP S..LATIN SMALL LETTER O WITH DIAERESIS +00F8..00FF ; Ll # [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS +0101 ; Ll # LATIN SMALL LETTER A WITH MACRON +0103 ; Ll # LATIN SMALL LETTER A WITH BREVE +0105 ; Ll # LATIN SMALL LETTER A WITH OGONEK +0107 ; Ll # LATIN SMALL LETTER C WITH ACUTE +0109 ; Ll # LATIN SMALL LETTER C WITH CIRCUMFLEX +010B ; Ll # LATIN SMALL LETTER C WITH DOT ABOVE +010D ; Ll # LATIN SMALL LETTER C WITH CARON +010F ; Ll # LATIN SMALL LETTER D WITH CARON +0111 ; Ll # LATIN SMALL LETTER D WITH STROKE +0113 ; Ll # LATIN SMALL LETTER E WITH MACRON +0115 ; Ll # LATIN SMALL LETTER E WITH BREVE +0117 ; Ll # LATIN SMALL LETTER E WITH DOT ABOVE +0119 ; Ll # LATIN SMALL LETTER E WITH OGONEK +011B ; Ll # LATIN SMALL LETTER E WITH CARON +011D ; Ll # LATIN SMALL LETTER G WITH CIRCUMFLEX +011F ; Ll # LATIN SMALL LETTER G WITH BREVE +0121 ; Ll # LATIN SMALL LETTER G WITH DOT ABOVE +0123 ; Ll # LATIN SMALL LETTER G WITH CEDILLA +0125 ; Ll # LATIN SMALL LETTER H WITH CIRCUMFLEX +0127 ; Ll # LATIN SMALL LETTER H WITH STROKE +0129 ; Ll # LATIN SMALL LETTER I WITH TILDE +012B ; Ll # LATIN SMALL LETTER I WITH MACRON +012D ; Ll # LATIN SMALL LETTER I WITH BREVE +012F ; Ll # LATIN SMALL LETTER I WITH OGONEK +0131 ; Ll # LATIN SMALL LETTER DOTLESS I +0133 ; Ll # LATIN SMALL LIGATURE IJ +0135 ; Ll # LATIN SMALL LETTER J WITH CIRCUMFLEX +0137..0138 ; Ll # [2] LATIN SMALL LETTER K WITH CEDILLA..LATIN SMALL LETTER KRA +013A ; Ll # LATIN SMALL LETTER L WITH ACUTE +013C ; Ll # LATIN SMALL LETTER L WITH CEDILLA +013E ; Ll # LATIN SMALL LETTER L WITH CARON +0140 ; Ll # LATIN SMALL LETTER L WITH MIDDLE DOT +0142 ; Ll # LATIN SMALL LETTER L WITH STROKE +0144 ; Ll # LATIN SMALL LETTER N WITH ACUTE +0146 ; Ll # LATIN SMALL LETTER N WITH CEDILLA +0148..0149 ; Ll # [2] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +014B ; Ll # LATIN SMALL LETTER ENG +014D ; Ll # LATIN SMALL LETTER O WITH MACRON +014F ; Ll # LATIN SMALL LETTER O WITH BREVE +0151 ; Ll # LATIN SMALL LETTER O WITH DOUBLE ACUTE +0153 ; Ll # LATIN SMALL LIGATURE OE +0155 ; Ll # LATIN SMALL LETTER R WITH ACUTE +0157 ; Ll # LATIN SMALL LETTER R WITH CEDILLA +0159 ; Ll # LATIN SMALL LETTER R WITH CARON +015B ; Ll # LATIN SMALL LETTER S WITH ACUTE +015D ; Ll # LATIN SMALL LETTER S WITH CIRCUMFLEX +015F ; Ll # LATIN SMALL LETTER S WITH CEDILLA +0161 ; Ll # LATIN SMALL LETTER S WITH CARON +0163 ; Ll # LATIN SMALL LETTER T WITH CEDILLA +0165 ; Ll # LATIN SMALL LETTER T WITH CARON +0167 ; Ll # LATIN SMALL LETTER T WITH STROKE +0169 ; Ll # LATIN SMALL LETTER U WITH TILDE +016B ; Ll # LATIN SMALL LETTER U WITH MACRON +016D ; Ll # LATIN SMALL LETTER U WITH BREVE +016F ; Ll # LATIN SMALL LETTER U WITH RING ABOVE +0171 ; Ll # LATIN SMALL LETTER U WITH DOUBLE ACUTE +0173 ; Ll # LATIN SMALL LETTER U WITH OGONEK +0175 ; Ll # LATIN SMALL LETTER W WITH CIRCUMFLEX +0177 ; Ll # LATIN SMALL LETTER Y WITH CIRCUMFLEX +017A ; Ll # LATIN SMALL LETTER Z WITH ACUTE +017C ; Ll # LATIN SMALL LETTER Z WITH DOT ABOVE +017E..0180 ; Ll # [3] LATIN SMALL LETTER Z WITH CARON..LATIN SMALL LETTER B WITH STROKE +0183 ; Ll # LATIN SMALL LETTER B WITH TOPBAR +0185 ; Ll # LATIN SMALL LETTER TONE SIX +0188 ; Ll # LATIN SMALL LETTER C WITH HOOK +018C..018D ; Ll # [2] LATIN SMALL LETTER D WITH TOPBAR..LATIN SMALL LETTER TURNED DELTA +0192 ; Ll # LATIN SMALL LETTER F WITH HOOK +0195 ; Ll # LATIN SMALL LETTER HV +0199..019B ; Ll # [3] LATIN SMALL LETTER K WITH HOOK..LATIN SMALL LETTER LAMBDA WITH STROKE +019E ; Ll # LATIN SMALL LETTER N WITH LONG RIGHT LEG +01A1 ; Ll # LATIN SMALL LETTER O WITH HORN +01A3 ; Ll # LATIN SMALL LETTER OI +01A5 ; Ll # LATIN SMALL LETTER P WITH HOOK +01A8 ; Ll # LATIN SMALL LETTER TONE TWO +01AA..01AB ; Ll # [2] LATIN LETTER REVERSED ESH LOOP..LATIN SMALL LETTER T WITH PALATAL HOOK +01AD ; Ll # LATIN SMALL LETTER T WITH HOOK +01B0 ; Ll # LATIN SMALL LETTER U WITH HORN +01B4 ; Ll # LATIN SMALL LETTER Y WITH HOOK +01B6 ; Ll # LATIN SMALL LETTER Z WITH STROKE +01B9..01BA ; Ll # [2] LATIN SMALL LETTER EZH REVERSED..LATIN SMALL LETTER EZH WITH TAIL +01BD..01BF ; Ll # [3] LATIN SMALL LETTER TONE FIVE..LATIN LETTER WYNN +01C6 ; Ll # LATIN SMALL LETTER DZ WITH CARON +01C9 ; Ll # LATIN SMALL LETTER LJ +01CC ; Ll # LATIN SMALL LETTER NJ +01CE ; Ll # LATIN SMALL LETTER A WITH CARON +01D0 ; Ll # LATIN SMALL LETTER I WITH CARON +01D2 ; Ll # LATIN SMALL LETTER O WITH CARON +01D4 ; Ll # LATIN SMALL LETTER U WITH CARON +01D6 ; Ll # LATIN SMALL LETTER U WITH DIAERESIS AND MACRON +01D8 ; Ll # LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE +01DA ; Ll # LATIN SMALL LETTER U WITH DIAERESIS AND CARON +01DC..01DD ; Ll # [2] LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE..LATIN SMALL LETTER TURNED E +01DF ; Ll # LATIN SMALL LETTER A WITH DIAERESIS AND MACRON +01E1 ; Ll # LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON +01E3 ; Ll # LATIN SMALL LETTER AE WITH MACRON +01E5 ; Ll # LATIN SMALL LETTER G WITH STROKE +01E7 ; Ll # LATIN SMALL LETTER G WITH CARON +01E9 ; Ll # LATIN SMALL LETTER K WITH CARON +01EB ; Ll # LATIN SMALL LETTER O WITH OGONEK +01ED ; Ll # LATIN SMALL LETTER O WITH OGONEK AND MACRON +01EF..01F0 ; Ll # [2] LATIN SMALL LETTER EZH WITH CARON..LATIN SMALL LETTER J WITH CARON +01F3 ; Ll # LATIN SMALL LETTER DZ +01F5 ; Ll # LATIN SMALL LETTER G WITH ACUTE +01F9 ; Ll # LATIN SMALL LETTER N WITH GRAVE +01FB ; Ll # LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +01FD ; Ll # LATIN SMALL LETTER AE WITH ACUTE +01FF ; Ll # LATIN SMALL LETTER O WITH STROKE AND ACUTE +0201 ; Ll # LATIN SMALL LETTER A WITH DOUBLE GRAVE +0203 ; Ll # LATIN SMALL LETTER A WITH INVERTED BREVE +0205 ; Ll # LATIN SMALL LETTER E WITH DOUBLE GRAVE +0207 ; Ll # LATIN SMALL LETTER E WITH INVERTED BREVE +0209 ; Ll # LATIN SMALL LETTER I WITH DOUBLE GRAVE +020B ; Ll # LATIN SMALL LETTER I WITH INVERTED BREVE +020D ; Ll # LATIN SMALL LETTER O WITH DOUBLE GRAVE +020F ; Ll # LATIN SMALL LETTER O WITH INVERTED BREVE +0211 ; Ll # LATIN SMALL LETTER R WITH DOUBLE GRAVE +0213 ; Ll # LATIN SMALL LETTER R WITH INVERTED BREVE +0215 ; Ll # LATIN SMALL LETTER U WITH DOUBLE GRAVE +0217 ; Ll # LATIN SMALL LETTER U WITH INVERTED BREVE +0219 ; Ll # LATIN SMALL LETTER S WITH COMMA BELOW +021B ; Ll # LATIN SMALL LETTER T WITH COMMA BELOW +021D ; Ll # LATIN SMALL LETTER YOGH +021F ; Ll # LATIN SMALL LETTER H WITH CARON +0221 ; Ll # LATIN SMALL LETTER D WITH CURL +0223 ; Ll # LATIN SMALL LETTER OU +0225 ; Ll # LATIN SMALL LETTER Z WITH HOOK +0227 ; Ll # LATIN SMALL LETTER A WITH DOT ABOVE +0229 ; Ll # LATIN SMALL LETTER E WITH CEDILLA +022B ; Ll # LATIN SMALL LETTER O WITH DIAERESIS AND MACRON +022D ; Ll # LATIN SMALL LETTER O WITH TILDE AND MACRON +022F ; Ll # LATIN SMALL LETTER O WITH DOT ABOVE +0231 ; Ll # LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON +0233..0239 ; Ll # [7] LATIN SMALL LETTER Y WITH MACRON..LATIN SMALL LETTER QP DIGRAPH +023C ; Ll # LATIN SMALL LETTER C WITH STROKE +023F..0240 ; Ll # [2] LATIN SMALL LETTER S WITH SWASH TAIL..LATIN SMALL LETTER Z WITH SWASH TAIL +0242 ; Ll # LATIN SMALL LETTER GLOTTAL STOP +0247 ; Ll # LATIN SMALL LETTER E WITH STROKE +0249 ; Ll # LATIN SMALL LETTER J WITH STROKE +024B ; Ll # LATIN SMALL LETTER Q WITH HOOK TAIL +024D ; Ll # LATIN SMALL LETTER R WITH STROKE +024F..0293 ; Ll # [69] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER EZH WITH CURL +0295..02AF ; Ll # [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0371 ; Ll # GREEK SMALL LETTER HETA +0373 ; Ll # GREEK SMALL LETTER ARCHAIC SAMPI +0377 ; Ll # GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037B..037D ; Ll # [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +0390 ; Ll # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +03AC..03CE ; Ll # [35] GREEK SMALL LETTER ALPHA WITH TONOS..GREEK SMALL LETTER OMEGA WITH TONOS +03D0..03D1 ; Ll # [2] GREEK BETA SYMBOL..GREEK THETA SYMBOL +03D5..03D7 ; Ll # [3] GREEK PHI SYMBOL..GREEK KAI SYMBOL +03D9 ; Ll # GREEK SMALL LETTER ARCHAIC KOPPA +03DB ; Ll # GREEK SMALL LETTER STIGMA +03DD ; Ll # GREEK SMALL LETTER DIGAMMA +03DF ; Ll # GREEK SMALL LETTER KOPPA +03E1 ; Ll # GREEK SMALL LETTER SAMPI +03E3 ; Ll # COPTIC SMALL LETTER SHEI +03E5 ; Ll # COPTIC SMALL LETTER FEI +03E7 ; Ll # COPTIC SMALL LETTER KHEI +03E9 ; Ll # COPTIC SMALL LETTER HORI +03EB ; Ll # COPTIC SMALL LETTER GANGIA +03ED ; Ll # COPTIC SMALL LETTER SHIMA +03EF..03F3 ; Ll # [5] COPTIC SMALL LETTER DEI..GREEK LETTER YOT +03F5 ; Ll # GREEK LUNATE EPSILON SYMBOL +03F8 ; Ll # GREEK SMALL LETTER SHO +03FB..03FC ; Ll # [2] GREEK SMALL LETTER SAN..GREEK RHO WITH STROKE SYMBOL +0430..045F ; Ll # [48] CYRILLIC SMALL LETTER A..CYRILLIC SMALL LETTER DZHE +0461 ; Ll # CYRILLIC SMALL LETTER OMEGA +0463 ; Ll # CYRILLIC SMALL LETTER YAT +0465 ; Ll # CYRILLIC SMALL LETTER IOTIFIED E +0467 ; Ll # CYRILLIC SMALL LETTER LITTLE YUS +0469 ; Ll # CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS +046B ; Ll # CYRILLIC SMALL LETTER BIG YUS +046D ; Ll # CYRILLIC SMALL LETTER IOTIFIED BIG YUS +046F ; Ll # CYRILLIC SMALL LETTER KSI +0471 ; Ll # CYRILLIC SMALL LETTER PSI +0473 ; Ll # CYRILLIC SMALL LETTER FITA +0475 ; Ll # CYRILLIC SMALL LETTER IZHITSA +0477 ; Ll # CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT +0479 ; Ll # CYRILLIC SMALL LETTER UK +047B ; Ll # CYRILLIC SMALL LETTER ROUND OMEGA +047D ; Ll # CYRILLIC SMALL LETTER OMEGA WITH TITLO +047F ; Ll # CYRILLIC SMALL LETTER OT +0481 ; Ll # CYRILLIC SMALL LETTER KOPPA +048B ; Ll # CYRILLIC SMALL LETTER SHORT I WITH TAIL +048D ; Ll # CYRILLIC SMALL LETTER SEMISOFT SIGN +048F ; Ll # CYRILLIC SMALL LETTER ER WITH TICK +0491 ; Ll # CYRILLIC SMALL LETTER GHE WITH UPTURN +0493 ; Ll # CYRILLIC SMALL LETTER GHE WITH STROKE +0495 ; Ll # CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK +0497 ; Ll # CYRILLIC SMALL LETTER ZHE WITH DESCENDER +0499 ; Ll # CYRILLIC SMALL LETTER ZE WITH DESCENDER +049B ; Ll # CYRILLIC SMALL LETTER KA WITH DESCENDER +049D ; Ll # CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE +049F ; Ll # CYRILLIC SMALL LETTER KA WITH STROKE +04A1 ; Ll # CYRILLIC SMALL LETTER BASHKIR KA +04A3 ; Ll # CYRILLIC SMALL LETTER EN WITH DESCENDER +04A5 ; Ll # CYRILLIC SMALL LIGATURE EN GHE +04A7 ; Ll # CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK +04A9 ; Ll # CYRILLIC SMALL LETTER ABKHASIAN HA +04AB ; Ll # CYRILLIC SMALL LETTER ES WITH DESCENDER +04AD ; Ll # CYRILLIC SMALL LETTER TE WITH DESCENDER +04AF ; Ll # CYRILLIC SMALL LETTER STRAIGHT U +04B1 ; Ll # CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE +04B3 ; Ll # CYRILLIC SMALL LETTER HA WITH DESCENDER +04B5 ; Ll # CYRILLIC SMALL LIGATURE TE TSE +04B7 ; Ll # CYRILLIC SMALL LETTER CHE WITH DESCENDER +04B9 ; Ll # CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE +04BB ; Ll # CYRILLIC SMALL LETTER SHHA +04BD ; Ll # CYRILLIC SMALL LETTER ABKHASIAN CHE +04BF ; Ll # CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER +04C2 ; Ll # CYRILLIC SMALL LETTER ZHE WITH BREVE +04C4 ; Ll # CYRILLIC SMALL LETTER KA WITH HOOK +04C6 ; Ll # CYRILLIC SMALL LETTER EL WITH TAIL +04C8 ; Ll # CYRILLIC SMALL LETTER EN WITH HOOK +04CA ; Ll # CYRILLIC SMALL LETTER EN WITH TAIL +04CC ; Ll # CYRILLIC SMALL LETTER KHAKASSIAN CHE +04CE..04CF ; Ll # [2] CYRILLIC SMALL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER PALOCHKA +04D1 ; Ll # CYRILLIC SMALL LETTER A WITH BREVE +04D3 ; Ll # CYRILLIC SMALL LETTER A WITH DIAERESIS +04D5 ; Ll # CYRILLIC SMALL LIGATURE A IE +04D7 ; Ll # CYRILLIC SMALL LETTER IE WITH BREVE +04D9 ; Ll # CYRILLIC SMALL LETTER SCHWA +04DB ; Ll # CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS +04DD ; Ll # CYRILLIC SMALL LETTER ZHE WITH DIAERESIS +04DF ; Ll # CYRILLIC SMALL LETTER ZE WITH DIAERESIS +04E1 ; Ll # CYRILLIC SMALL LETTER ABKHASIAN DZE +04E3 ; Ll # CYRILLIC SMALL LETTER I WITH MACRON +04E5 ; Ll # CYRILLIC SMALL LETTER I WITH DIAERESIS +04E7 ; Ll # CYRILLIC SMALL LETTER O WITH DIAERESIS +04E9 ; Ll # CYRILLIC SMALL LETTER BARRED O +04EB ; Ll # CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS +04ED ; Ll # CYRILLIC SMALL LETTER E WITH DIAERESIS +04EF ; Ll # CYRILLIC SMALL LETTER U WITH MACRON +04F1 ; Ll # CYRILLIC SMALL LETTER U WITH DIAERESIS +04F3 ; Ll # CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE +04F5 ; Ll # CYRILLIC SMALL LETTER CHE WITH DIAERESIS +04F7 ; Ll # CYRILLIC SMALL LETTER GHE WITH DESCENDER +04F9 ; Ll # CYRILLIC SMALL LETTER YERU WITH DIAERESIS +04FB ; Ll # CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK +04FD ; Ll # CYRILLIC SMALL LETTER HA WITH HOOK +04FF ; Ll # CYRILLIC SMALL LETTER HA WITH STROKE +0501 ; Ll # CYRILLIC SMALL LETTER KOMI DE +0503 ; Ll # CYRILLIC SMALL LETTER KOMI DJE +0505 ; Ll # CYRILLIC SMALL LETTER KOMI ZJE +0507 ; Ll # CYRILLIC SMALL LETTER KOMI DZJE +0509 ; Ll # CYRILLIC SMALL LETTER KOMI LJE +050B ; Ll # CYRILLIC SMALL LETTER KOMI NJE +050D ; Ll # CYRILLIC SMALL LETTER KOMI SJE +050F ; Ll # CYRILLIC SMALL LETTER KOMI TJE +0511 ; Ll # CYRILLIC SMALL LETTER REVERSED ZE +0513 ; Ll # CYRILLIC SMALL LETTER EL WITH HOOK +0515 ; Ll # CYRILLIC SMALL LETTER LHA +0517 ; Ll # CYRILLIC SMALL LETTER RHA +0519 ; Ll # CYRILLIC SMALL LETTER YAE +051B ; Ll # CYRILLIC SMALL LETTER QA +051D ; Ll # CYRILLIC SMALL LETTER WE +051F ; Ll # CYRILLIC SMALL LETTER ALEUT KA +0521 ; Ll # CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK +0523 ; Ll # CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK +0525 ; Ll # CYRILLIC SMALL LETTER PE WITH DESCENDER +0527 ; Ll # CYRILLIC SMALL LETTER SHHA WITH DESCENDER +0529 ; Ll # CYRILLIC SMALL LETTER EN WITH LEFT HOOK +052B ; Ll # CYRILLIC SMALL LETTER DZZHE +052D ; Ll # CYRILLIC SMALL LETTER DCHE +052F ; Ll # CYRILLIC SMALL LETTER EL WITH DESCENDER +0560..0588 ; Ll # [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +10D0..10FA ; Ll # [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FD..10FF ; Ll # [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +13F8..13FD ; Ll # [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1C80..1C88 ; Ll # [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1D00..1D2B ; Ll # [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D6B..1D77 ; Ll # [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D79..1D9A ; Ll # [34] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1E01 ; Ll # LATIN SMALL LETTER A WITH RING BELOW +1E03 ; Ll # LATIN SMALL LETTER B WITH DOT ABOVE +1E05 ; Ll # LATIN SMALL LETTER B WITH DOT BELOW +1E07 ; Ll # LATIN SMALL LETTER B WITH LINE BELOW +1E09 ; Ll # LATIN SMALL LETTER C WITH CEDILLA AND ACUTE +1E0B ; Ll # LATIN SMALL LETTER D WITH DOT ABOVE +1E0D ; Ll # LATIN SMALL LETTER D WITH DOT BELOW +1E0F ; Ll # LATIN SMALL LETTER D WITH LINE BELOW +1E11 ; Ll # LATIN SMALL LETTER D WITH CEDILLA +1E13 ; Ll # LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW +1E15 ; Ll # LATIN SMALL LETTER E WITH MACRON AND GRAVE +1E17 ; Ll # LATIN SMALL LETTER E WITH MACRON AND ACUTE +1E19 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW +1E1B ; Ll # LATIN SMALL LETTER E WITH TILDE BELOW +1E1D ; Ll # LATIN SMALL LETTER E WITH CEDILLA AND BREVE +1E1F ; Ll # LATIN SMALL LETTER F WITH DOT ABOVE +1E21 ; Ll # LATIN SMALL LETTER G WITH MACRON +1E23 ; Ll # LATIN SMALL LETTER H WITH DOT ABOVE +1E25 ; Ll # LATIN SMALL LETTER H WITH DOT BELOW +1E27 ; Ll # LATIN SMALL LETTER H WITH DIAERESIS +1E29 ; Ll # LATIN SMALL LETTER H WITH CEDILLA +1E2B ; Ll # LATIN SMALL LETTER H WITH BREVE BELOW +1E2D ; Ll # LATIN SMALL LETTER I WITH TILDE BELOW +1E2F ; Ll # LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE +1E31 ; Ll # LATIN SMALL LETTER K WITH ACUTE +1E33 ; Ll # LATIN SMALL LETTER K WITH DOT BELOW +1E35 ; Ll # LATIN SMALL LETTER K WITH LINE BELOW +1E37 ; Ll # LATIN SMALL LETTER L WITH DOT BELOW +1E39 ; Ll # LATIN SMALL LETTER L WITH DOT BELOW AND MACRON +1E3B ; Ll # LATIN SMALL LETTER L WITH LINE BELOW +1E3D ; Ll # LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW +1E3F ; Ll # LATIN SMALL LETTER M WITH ACUTE +1E41 ; Ll # LATIN SMALL LETTER M WITH DOT ABOVE +1E43 ; Ll # LATIN SMALL LETTER M WITH DOT BELOW +1E45 ; Ll # LATIN SMALL LETTER N WITH DOT ABOVE +1E47 ; Ll # LATIN SMALL LETTER N WITH DOT BELOW +1E49 ; Ll # LATIN SMALL LETTER N WITH LINE BELOW +1E4B ; Ll # LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW +1E4D ; Ll # LATIN SMALL LETTER O WITH TILDE AND ACUTE +1E4F ; Ll # LATIN SMALL LETTER O WITH TILDE AND DIAERESIS +1E51 ; Ll # LATIN SMALL LETTER O WITH MACRON AND GRAVE +1E53 ; Ll # LATIN SMALL LETTER O WITH MACRON AND ACUTE +1E55 ; Ll # LATIN SMALL LETTER P WITH ACUTE +1E57 ; Ll # LATIN SMALL LETTER P WITH DOT ABOVE +1E59 ; Ll # LATIN SMALL LETTER R WITH DOT ABOVE +1E5B ; Ll # LATIN SMALL LETTER R WITH DOT BELOW +1E5D ; Ll # LATIN SMALL LETTER R WITH DOT BELOW AND MACRON +1E5F ; Ll # LATIN SMALL LETTER R WITH LINE BELOW +1E61 ; Ll # LATIN SMALL LETTER S WITH DOT ABOVE +1E63 ; Ll # LATIN SMALL LETTER S WITH DOT BELOW +1E65 ; Ll # LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE +1E67 ; Ll # LATIN SMALL LETTER S WITH CARON AND DOT ABOVE +1E69 ; Ll # LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE +1E6B ; Ll # LATIN SMALL LETTER T WITH DOT ABOVE +1E6D ; Ll # LATIN SMALL LETTER T WITH DOT BELOW +1E6F ; Ll # LATIN SMALL LETTER T WITH LINE BELOW +1E71 ; Ll # LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW +1E73 ; Ll # LATIN SMALL LETTER U WITH DIAERESIS BELOW +1E75 ; Ll # LATIN SMALL LETTER U WITH TILDE BELOW +1E77 ; Ll # LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW +1E79 ; Ll # LATIN SMALL LETTER U WITH TILDE AND ACUTE +1E7B ; Ll # LATIN SMALL LETTER U WITH MACRON AND DIAERESIS +1E7D ; Ll # LATIN SMALL LETTER V WITH TILDE +1E7F ; Ll # LATIN SMALL LETTER V WITH DOT BELOW +1E81 ; Ll # LATIN SMALL LETTER W WITH GRAVE +1E83 ; Ll # LATIN SMALL LETTER W WITH ACUTE +1E85 ; Ll # LATIN SMALL LETTER W WITH DIAERESIS +1E87 ; Ll # LATIN SMALL LETTER W WITH DOT ABOVE +1E89 ; Ll # LATIN SMALL LETTER W WITH DOT BELOW +1E8B ; Ll # LATIN SMALL LETTER X WITH DOT ABOVE +1E8D ; Ll # LATIN SMALL LETTER X WITH DIAERESIS +1E8F ; Ll # LATIN SMALL LETTER Y WITH DOT ABOVE +1E91 ; Ll # LATIN SMALL LETTER Z WITH CIRCUMFLEX +1E93 ; Ll # LATIN SMALL LETTER Z WITH DOT BELOW +1E95..1E9D ; Ll # [9] LATIN SMALL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER LONG S WITH HIGH STROKE +1E9F ; Ll # LATIN SMALL LETTER DELTA +1EA1 ; Ll # LATIN SMALL LETTER A WITH DOT BELOW +1EA3 ; Ll # LATIN SMALL LETTER A WITH HOOK ABOVE +1EA5 ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE +1EA7 ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE +1EA9 ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE +1EAB ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE +1EAD ; Ll # LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW +1EAF ; Ll # LATIN SMALL LETTER A WITH BREVE AND ACUTE +1EB1 ; Ll # LATIN SMALL LETTER A WITH BREVE AND GRAVE +1EB3 ; Ll # LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE +1EB5 ; Ll # LATIN SMALL LETTER A WITH BREVE AND TILDE +1EB7 ; Ll # LATIN SMALL LETTER A WITH BREVE AND DOT BELOW +1EB9 ; Ll # LATIN SMALL LETTER E WITH DOT BELOW +1EBB ; Ll # LATIN SMALL LETTER E WITH HOOK ABOVE +1EBD ; Ll # LATIN SMALL LETTER E WITH TILDE +1EBF ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE +1EC1 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE +1EC3 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE +1EC5 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE +1EC7 ; Ll # LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW +1EC9 ; Ll # LATIN SMALL LETTER I WITH HOOK ABOVE +1ECB ; Ll # LATIN SMALL LETTER I WITH DOT BELOW +1ECD ; Ll # LATIN SMALL LETTER O WITH DOT BELOW +1ECF ; Ll # LATIN SMALL LETTER O WITH HOOK ABOVE +1ED1 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE +1ED3 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE +1ED5 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE +1ED7 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE +1ED9 ; Ll # LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW +1EDB ; Ll # LATIN SMALL LETTER O WITH HORN AND ACUTE +1EDD ; Ll # LATIN SMALL LETTER O WITH HORN AND GRAVE +1EDF ; Ll # LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE +1EE1 ; Ll # LATIN SMALL LETTER O WITH HORN AND TILDE +1EE3 ; Ll # LATIN SMALL LETTER O WITH HORN AND DOT BELOW +1EE5 ; Ll # LATIN SMALL LETTER U WITH DOT BELOW +1EE7 ; Ll # LATIN SMALL LETTER U WITH HOOK ABOVE +1EE9 ; Ll # LATIN SMALL LETTER U WITH HORN AND ACUTE +1EEB ; Ll # LATIN SMALL LETTER U WITH HORN AND GRAVE +1EED ; Ll # LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE +1EEF ; Ll # LATIN SMALL LETTER U WITH HORN AND TILDE +1EF1 ; Ll # LATIN SMALL LETTER U WITH HORN AND DOT BELOW +1EF3 ; Ll # LATIN SMALL LETTER Y WITH GRAVE +1EF5 ; Ll # LATIN SMALL LETTER Y WITH DOT BELOW +1EF7 ; Ll # LATIN SMALL LETTER Y WITH HOOK ABOVE +1EF9 ; Ll # LATIN SMALL LETTER Y WITH TILDE +1EFB ; Ll # LATIN SMALL LETTER MIDDLE-WELSH LL +1EFD ; Ll # LATIN SMALL LETTER MIDDLE-WELSH V +1EFF..1F07 ; Ll # [9] LATIN SMALL LETTER Y WITH LOOP..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI +1F10..1F15 ; Ll # [6] GREEK SMALL LETTER EPSILON WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F27 ; Ll # [8] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI +1F30..1F37 ; Ll # [8] GREEK SMALL LETTER IOTA WITH PSILI..GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI +1F40..1F45 ; Ll # [6] GREEK SMALL LETTER OMICRON WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; Ll # [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F60..1F67 ; Ll # [8] GREEK SMALL LETTER OMEGA WITH PSILI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +1F70..1F7D ; Ll # [14] GREEK SMALL LETTER ALPHA WITH VARIA..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1F87 ; Ll # [8] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +1F90..1F97 ; Ll # [8] GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +1FA0..1FA7 ; Ll # [8] GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI +1FB0..1FB4 ; Ll # [5] GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FB7 ; Ll # [2] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI +1FBE ; Ll # GREEK PROSGEGRAMMENI +1FC2..1FC4 ; Ll # [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FC7 ; Ll # [2] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI +1FD0..1FD3 ; Ll # [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FD7 ; Ll # [2] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI +1FE0..1FE7 ; Ll # [8] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI +1FF2..1FF4 ; Ll # [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FF7 ; Ll # [2] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI +210A ; Ll # SCRIPT SMALL G +210E..210F ; Ll # [2] PLANCK CONSTANT..PLANCK CONSTANT OVER TWO PI +2113 ; Ll # SCRIPT SMALL L +212F ; Ll # SCRIPT SMALL E +2134 ; Ll # SCRIPT SMALL O +2139 ; Ll # INFORMATION SOURCE +213C..213D ; Ll # [2] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK SMALL GAMMA +2146..2149 ; Ll # [4] DOUBLE-STRUCK ITALIC SMALL D..DOUBLE-STRUCK ITALIC SMALL J +214E ; Ll # TURNED SMALL F +2184 ; Ll # LATIN SMALL LETTER REVERSED C +2C30..2C5F ; Ll # [48] GLAGOLITIC SMALL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI +2C61 ; Ll # LATIN SMALL LETTER L WITH DOUBLE BAR +2C65..2C66 ; Ll # [2] LATIN SMALL LETTER A WITH STROKE..LATIN SMALL LETTER T WITH DIAGONAL STROKE +2C68 ; Ll # LATIN SMALL LETTER H WITH DESCENDER +2C6A ; Ll # LATIN SMALL LETTER K WITH DESCENDER +2C6C ; Ll # LATIN SMALL LETTER Z WITH DESCENDER +2C71 ; Ll # LATIN SMALL LETTER V WITH RIGHT HOOK +2C73..2C74 ; Ll # [2] LATIN SMALL LETTER W WITH HOOK..LATIN SMALL LETTER V WITH CURL +2C76..2C7B ; Ll # [6] LATIN SMALL LETTER HALF H..LATIN LETTER SMALL CAPITAL TURNED E +2C81 ; Ll # COPTIC SMALL LETTER ALFA +2C83 ; Ll # COPTIC SMALL LETTER VIDA +2C85 ; Ll # COPTIC SMALL LETTER GAMMA +2C87 ; Ll # COPTIC SMALL LETTER DALDA +2C89 ; Ll # COPTIC SMALL LETTER EIE +2C8B ; Ll # COPTIC SMALL LETTER SOU +2C8D ; Ll # COPTIC SMALL LETTER ZATA +2C8F ; Ll # COPTIC SMALL LETTER HATE +2C91 ; Ll # COPTIC SMALL LETTER THETHE +2C93 ; Ll # COPTIC SMALL LETTER IAUDA +2C95 ; Ll # COPTIC SMALL LETTER KAPA +2C97 ; Ll # COPTIC SMALL LETTER LAULA +2C99 ; Ll # COPTIC SMALL LETTER MI +2C9B ; Ll # COPTIC SMALL LETTER NI +2C9D ; Ll # COPTIC SMALL LETTER KSI +2C9F ; Ll # COPTIC SMALL LETTER O +2CA1 ; Ll # COPTIC SMALL LETTER PI +2CA3 ; Ll # COPTIC SMALL LETTER RO +2CA5 ; Ll # COPTIC SMALL LETTER SIMA +2CA7 ; Ll # COPTIC SMALL LETTER TAU +2CA9 ; Ll # COPTIC SMALL LETTER UA +2CAB ; Ll # COPTIC SMALL LETTER FI +2CAD ; Ll # COPTIC SMALL LETTER KHI +2CAF ; Ll # COPTIC SMALL LETTER PSI +2CB1 ; Ll # COPTIC SMALL LETTER OOU +2CB3 ; Ll # COPTIC SMALL LETTER DIALECT-P ALEF +2CB5 ; Ll # COPTIC SMALL LETTER OLD COPTIC AIN +2CB7 ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC EIE +2CB9 ; Ll # COPTIC SMALL LETTER DIALECT-P KAPA +2CBB ; Ll # COPTIC SMALL LETTER DIALECT-P NI +2CBD ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC NI +2CBF ; Ll # COPTIC SMALL LETTER OLD COPTIC OOU +2CC1 ; Ll # COPTIC SMALL LETTER SAMPI +2CC3 ; Ll # COPTIC SMALL LETTER CROSSED SHEI +2CC5 ; Ll # COPTIC SMALL LETTER OLD COPTIC SHEI +2CC7 ; Ll # COPTIC SMALL LETTER OLD COPTIC ESH +2CC9 ; Ll # COPTIC SMALL LETTER AKHMIMIC KHEI +2CCB ; Ll # COPTIC SMALL LETTER DIALECT-P HORI +2CCD ; Ll # COPTIC SMALL LETTER OLD COPTIC HORI +2CCF ; Ll # COPTIC SMALL LETTER OLD COPTIC HA +2CD1 ; Ll # COPTIC SMALL LETTER L-SHAPED HA +2CD3 ; Ll # COPTIC SMALL LETTER OLD COPTIC HEI +2CD5 ; Ll # COPTIC SMALL LETTER OLD COPTIC HAT +2CD7 ; Ll # COPTIC SMALL LETTER OLD COPTIC GANGIA +2CD9 ; Ll # COPTIC SMALL LETTER OLD COPTIC DJA +2CDB ; Ll # COPTIC SMALL LETTER OLD COPTIC SHIMA +2CDD ; Ll # COPTIC SMALL LETTER OLD NUBIAN SHIMA +2CDF ; Ll # COPTIC SMALL LETTER OLD NUBIAN NGI +2CE1 ; Ll # COPTIC SMALL LETTER OLD NUBIAN NYI +2CE3..2CE4 ; Ll # [2] COPTIC SMALL LETTER OLD NUBIAN WAU..COPTIC SYMBOL KAI +2CEC ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI +2CEE ; Ll # COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CF3 ; Ll # COPTIC SMALL LETTER BOHAIRIC KHEI +2D00..2D25 ; Ll # [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; Ll # GEORGIAN SMALL LETTER YN +2D2D ; Ll # GEORGIAN SMALL LETTER AEN +A641 ; Ll # CYRILLIC SMALL LETTER ZEMLYA +A643 ; Ll # CYRILLIC SMALL LETTER DZELO +A645 ; Ll # CYRILLIC SMALL LETTER REVERSED DZE +A647 ; Ll # CYRILLIC SMALL LETTER IOTA +A649 ; Ll # CYRILLIC SMALL LETTER DJERV +A64B ; Ll # CYRILLIC SMALL LETTER MONOGRAPH UK +A64D ; Ll # CYRILLIC SMALL LETTER BROAD OMEGA +A64F ; Ll # CYRILLIC SMALL LETTER NEUTRAL YER +A651 ; Ll # CYRILLIC SMALL LETTER YERU WITH BACK YER +A653 ; Ll # CYRILLIC SMALL LETTER IOTIFIED YAT +A655 ; Ll # CYRILLIC SMALL LETTER REVERSED YU +A657 ; Ll # CYRILLIC SMALL LETTER IOTIFIED A +A659 ; Ll # CYRILLIC SMALL LETTER CLOSED LITTLE YUS +A65B ; Ll # CYRILLIC SMALL LETTER BLENDED YUS +A65D ; Ll # CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS +A65F ; Ll # CYRILLIC SMALL LETTER YN +A661 ; Ll # CYRILLIC SMALL LETTER REVERSED TSE +A663 ; Ll # CYRILLIC SMALL LETTER SOFT DE +A665 ; Ll # CYRILLIC SMALL LETTER SOFT EL +A667 ; Ll # CYRILLIC SMALL LETTER SOFT EM +A669 ; Ll # CYRILLIC SMALL LETTER MONOCULAR O +A66B ; Ll # CYRILLIC SMALL LETTER BINOCULAR O +A66D ; Ll # CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A681 ; Ll # CYRILLIC SMALL LETTER DWE +A683 ; Ll # CYRILLIC SMALL LETTER DZWE +A685 ; Ll # CYRILLIC SMALL LETTER ZHWE +A687 ; Ll # CYRILLIC SMALL LETTER CCHE +A689 ; Ll # CYRILLIC SMALL LETTER DZZE +A68B ; Ll # CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK +A68D ; Ll # CYRILLIC SMALL LETTER TWE +A68F ; Ll # CYRILLIC SMALL LETTER TSWE +A691 ; Ll # CYRILLIC SMALL LETTER TSSE +A693 ; Ll # CYRILLIC SMALL LETTER TCHE +A695 ; Ll # CYRILLIC SMALL LETTER HWE +A697 ; Ll # CYRILLIC SMALL LETTER SHWE +A699 ; Ll # CYRILLIC SMALL LETTER DOUBLE O +A69B ; Ll # CYRILLIC SMALL LETTER CROSSED O +A723 ; Ll # LATIN SMALL LETTER EGYPTOLOGICAL ALEF +A725 ; Ll # LATIN SMALL LETTER EGYPTOLOGICAL AIN +A727 ; Ll # LATIN SMALL LETTER HENG +A729 ; Ll # LATIN SMALL LETTER TZ +A72B ; Ll # LATIN SMALL LETTER TRESILLO +A72D ; Ll # LATIN SMALL LETTER CUATRILLO +A72F..A731 ; Ll # [3] LATIN SMALL LETTER CUATRILLO WITH COMMA..LATIN LETTER SMALL CAPITAL S +A733 ; Ll # LATIN SMALL LETTER AA +A735 ; Ll # LATIN SMALL LETTER AO +A737 ; Ll # LATIN SMALL LETTER AU +A739 ; Ll # LATIN SMALL LETTER AV +A73B ; Ll # LATIN SMALL LETTER AV WITH HORIZONTAL BAR +A73D ; Ll # LATIN SMALL LETTER AY +A73F ; Ll # LATIN SMALL LETTER REVERSED C WITH DOT +A741 ; Ll # LATIN SMALL LETTER K WITH STROKE +A743 ; Ll # LATIN SMALL LETTER K WITH DIAGONAL STROKE +A745 ; Ll # LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE +A747 ; Ll # LATIN SMALL LETTER BROKEN L +A749 ; Ll # LATIN SMALL LETTER L WITH HIGH STROKE +A74B ; Ll # LATIN SMALL LETTER O WITH LONG STROKE OVERLAY +A74D ; Ll # LATIN SMALL LETTER O WITH LOOP +A74F ; Ll # LATIN SMALL LETTER OO +A751 ; Ll # LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER +A753 ; Ll # LATIN SMALL LETTER P WITH FLOURISH +A755 ; Ll # LATIN SMALL LETTER P WITH SQUIRREL TAIL +A757 ; Ll # LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER +A759 ; Ll # LATIN SMALL LETTER Q WITH DIAGONAL STROKE +A75B ; Ll # LATIN SMALL LETTER R ROTUNDA +A75D ; Ll # LATIN SMALL LETTER RUM ROTUNDA +A75F ; Ll # LATIN SMALL LETTER V WITH DIAGONAL STROKE +A761 ; Ll # LATIN SMALL LETTER VY +A763 ; Ll # LATIN SMALL LETTER VISIGOTHIC Z +A765 ; Ll # LATIN SMALL LETTER THORN WITH STROKE +A767 ; Ll # LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER +A769 ; Ll # LATIN SMALL LETTER VEND +A76B ; Ll # LATIN SMALL LETTER ET +A76D ; Ll # LATIN SMALL LETTER IS +A76F ; Ll # LATIN SMALL LETTER CON +A771..A778 ; Ll # [8] LATIN SMALL LETTER DUM..LATIN SMALL LETTER UM +A77A ; Ll # LATIN SMALL LETTER INSULAR D +A77C ; Ll # LATIN SMALL LETTER INSULAR F +A77F ; Ll # LATIN SMALL LETTER TURNED INSULAR G +A781 ; Ll # LATIN SMALL LETTER TURNED L +A783 ; Ll # LATIN SMALL LETTER INSULAR R +A785 ; Ll # LATIN SMALL LETTER INSULAR S +A787 ; Ll # LATIN SMALL LETTER INSULAR T +A78C ; Ll # LATIN SMALL LETTER SALTILLO +A78E ; Ll # LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A791 ; Ll # LATIN SMALL LETTER N WITH DESCENDER +A793..A795 ; Ll # [3] LATIN SMALL LETTER C WITH BAR..LATIN SMALL LETTER H WITH PALATAL HOOK +A797 ; Ll # LATIN SMALL LETTER B WITH FLOURISH +A799 ; Ll # LATIN SMALL LETTER F WITH STROKE +A79B ; Ll # LATIN SMALL LETTER VOLAPUK AE +A79D ; Ll # LATIN SMALL LETTER VOLAPUK OE +A79F ; Ll # LATIN SMALL LETTER VOLAPUK UE +A7A1 ; Ll # LATIN SMALL LETTER G WITH OBLIQUE STROKE +A7A3 ; Ll # LATIN SMALL LETTER K WITH OBLIQUE STROKE +A7A5 ; Ll # LATIN SMALL LETTER N WITH OBLIQUE STROKE +A7A7 ; Ll # LATIN SMALL LETTER R WITH OBLIQUE STROKE +A7A9 ; Ll # LATIN SMALL LETTER S WITH OBLIQUE STROKE +A7AF ; Ll # LATIN LETTER SMALL CAPITAL Q +A7B5 ; Ll # LATIN SMALL LETTER BETA +A7B7 ; Ll # LATIN SMALL LETTER OMEGA +A7B9 ; Ll # LATIN SMALL LETTER U WITH STROKE +A7BB ; Ll # LATIN SMALL LETTER GLOTTAL A +A7BD ; Ll # LATIN SMALL LETTER GLOTTAL I +A7BF ; Ll # LATIN SMALL LETTER GLOTTAL U +A7C1 ; Ll # LATIN SMALL LETTER OLD POLISH O +A7C3 ; Ll # LATIN SMALL LETTER ANGLICANA W +A7C8 ; Ll # LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY +A7CA ; Ll # LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D1 ; Ll # LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; Ll # LATIN SMALL LETTER DOUBLE THORN +A7D5 ; Ll # LATIN SMALL LETTER DOUBLE WYNN +A7D7 ; Ll # LATIN SMALL LETTER MIDDLE SCOTS S +A7D9 ; Ll # LATIN SMALL LETTER SIGMOID S +A7F6 ; Ll # LATIN SMALL LETTER REVERSED HALF H +A7FA ; Ll # LATIN LETTER SMALL CAPITAL TURNED M +AB30..AB5A ; Ll # [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB60..AB68 ; Ll # [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB70..ABBF ; Ll # [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +FB00..FB06 ; Ll # [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; Ll # [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FF41..FF5A ; Ll # [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +10428..1044F ; Ll # [40] DESERET SMALL LETTER LONG I..DESERET SMALL LETTER EW +104D8..104FB ; Ll # [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10597..105A1 ; Ll # [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; Ll # [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; Ll # [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; Ll # [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10CC0..10CF2 ; Ll # [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +118C0..118DF ; Ll # [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +16E60..16E7F ; Ll # [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +1D41A..1D433 ; Ll # [26] MATHEMATICAL BOLD SMALL A..MATHEMATICAL BOLD SMALL Z +1D44E..1D454 ; Ll # [7] MATHEMATICAL ITALIC SMALL A..MATHEMATICAL ITALIC SMALL G +1D456..1D467 ; Ll # [18] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL ITALIC SMALL Z +1D482..1D49B ; Ll # [26] MATHEMATICAL BOLD ITALIC SMALL A..MATHEMATICAL BOLD ITALIC SMALL Z +1D4B6..1D4B9 ; Ll # [4] MATHEMATICAL SCRIPT SMALL A..MATHEMATICAL SCRIPT SMALL D +1D4BB ; Ll # MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; Ll # [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D4CF ; Ll # [11] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL SCRIPT SMALL Z +1D4EA..1D503 ; Ll # [26] MATHEMATICAL BOLD SCRIPT SMALL A..MATHEMATICAL BOLD SCRIPT SMALL Z +1D51E..1D537 ; Ll # [26] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL FRAKTUR SMALL Z +1D552..1D56B ; Ll # [26] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL DOUBLE-STRUCK SMALL Z +1D586..1D59F ; Ll # [26] MATHEMATICAL BOLD FRAKTUR SMALL A..MATHEMATICAL BOLD FRAKTUR SMALL Z +1D5BA..1D5D3 ; Ll # [26] MATHEMATICAL SANS-SERIF SMALL A..MATHEMATICAL SANS-SERIF SMALL Z +1D5EE..1D607 ; Ll # [26] MATHEMATICAL SANS-SERIF BOLD SMALL A..MATHEMATICAL SANS-SERIF BOLD SMALL Z +1D622..1D63B ; Ll # [26] MATHEMATICAL SANS-SERIF ITALIC SMALL A..MATHEMATICAL SANS-SERIF ITALIC SMALL Z +1D656..1D66F ; Ll # [26] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z +1D68A..1D6A5 ; Ll # [28] MATHEMATICAL MONOSPACE SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6C2..1D6DA ; Ll # [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DC..1D6E1 ; Ll # [6] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL BOLD PI SYMBOL +1D6FC..1D714 ; Ll # [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D716..1D71B ; Ll # [6] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL ITALIC PI SYMBOL +1D736..1D74E ; Ll # [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D750..1D755 ; Ll # [6] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC PI SYMBOL +1D770..1D788 ; Ll # [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D78A..1D78F ; Ll # [6] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD PI SYMBOL +1D7AA..1D7C2 ; Ll # [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C4..1D7C9 ; Ll # [6] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL +1D7CB ; Ll # MATHEMATICAL BOLD SMALL DIGAMMA +1DF00..1DF09 ; Ll # [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0B..1DF1E ; Ll # [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; Ll # [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E922..1E943 ; Ll # [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA + +# Total code points: 2233 + +# ================================================ + +# General_Category=Titlecase_Letter + +01C5 ; Lt # LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON +01C8 ; Lt # LATIN CAPITAL LETTER L WITH SMALL LETTER J +01CB ; Lt # LATIN CAPITAL LETTER N WITH SMALL LETTER J +01F2 ; Lt # LATIN CAPITAL LETTER D WITH SMALL LETTER Z +1F88..1F8F ; Lt # [8] GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1F98..1F9F ; Lt # [8] GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FA8..1FAF ; Lt # [8] GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FBC ; Lt # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FCC ; Lt # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FFC ; Lt # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI + +# Total code points: 31 + +# ================================================ + +# General_Category=Modifier_Letter + +02B0..02C1 ; Lm # [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C6..02D1 ; Lm # [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON +02E0..02E4 ; Lm # [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02EC ; Lm # MODIFIER LETTER VOICING +02EE ; Lm # MODIFIER LETTER DOUBLE APOSTROPHE +0374 ; Lm # GREEK NUMERAL SIGN +037A ; Lm # GREEK YPOGEGRAMMENI +0559 ; Lm # ARMENIAN MODIFIER LETTER LEFT HALF RING +0640 ; Lm # ARABIC TATWEEL +06E5..06E6 ; Lm # [2] ARABIC SMALL WAW..ARABIC SMALL YEH +07F4..07F5 ; Lm # [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07FA ; Lm # NKO LAJANYALAN +081A ; Lm # SAMARITAN MODIFIER LETTER EPENTHETIC YUT +0824 ; Lm # SAMARITAN MODIFIER LETTER SHORT A +0828 ; Lm # SAMARITAN MODIFIER LETTER I +08C9 ; Lm # ARABIC SMALL FARSI YEH +0971 ; Lm # DEVANAGARI SIGN HIGH SPACING DOT +0E46 ; Lm # THAI CHARACTER MAIYAMOK +0EC6 ; Lm # LAO KO LA +10FC ; Lm # MODIFIER LETTER GEORGIAN NAR +17D7 ; Lm # KHMER SIGN LEK TOO +1843 ; Lm # MONGOLIAN LETTER TODO LONG VOWEL SIGN +1AA7 ; Lm # TAI THAM SIGN MAI YAMOK +1C78..1C7D ; Lm # [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1D2C..1D6A ; Lm # [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D78 ; Lm # MODIFIER LETTER CYRILLIC EN +1D9B..1DBF ; Lm # [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +2071 ; Lm # SUPERSCRIPT LATIN SMALL LETTER I +207F ; Lm # SUPERSCRIPT LATIN SMALL LETTER N +2090..209C ; Lm # [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +2C7C..2C7D ; Lm # [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2D6F ; Lm # TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2E2F ; Lm # VERTICAL TILDE +3005 ; Lm # IDEOGRAPHIC ITERATION MARK +3031..3035 ; Lm # [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF +303B ; Lm # VERTICAL IDEOGRAPHIC ITERATION MARK +309D..309E ; Lm # [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +30FC..30FE ; Lm # [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK +A015 ; Lm # YI SYLLABLE WU +A4F8..A4FD ; Lm # [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A60C ; Lm # VAI SYLLABLE LENGTHENER +A67F ; Lm # CYRILLIC PAYEROK +A69C..A69D ; Lm # [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A717..A71F ; Lm # [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A770 ; Lm # MODIFIER LETTER US +A788 ; Lm # MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A7F2..A7F4 ; Lm # [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F8..A7F9 ; Lm # [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A9CF ; Lm # JAVANESE PANGRANGKEP +A9E6 ; Lm # MYANMAR MODIFIER LETTER SHAN REDUPLICATION +AA70 ; Lm # MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION +AADD ; Lm # TAI VIET SYMBOL SAM +AAF3..AAF4 ; Lm # [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AB5C..AB5F ; Lm # [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB69 ; Lm # MODIFIER LETTER SMALL TURNED W +FF70 ; Lm # HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF9E..FF9F ; Lm # [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +10780..10785 ; Lm # [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; Lm # [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; Lm # [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +16B40..16B43 ; Lm # [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16F93..16F9F ; Lm # [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; Lm # [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE3 ; Lm # OLD CHINESE ITERATION MARK +1AFF0..1AFF3 ; Lm # [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; Lm # [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; Lm # [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1E030..1E06D ; Lm # [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E137..1E13D ; Lm # [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E4EB ; Lm # NAG MUNDARI SIGN OJOD +1E94B ; Lm # ADLAM NASALIZATION MARK + +# Total code points: 397 + +# ================================================ + +# General_Category=Other_Letter + +00AA ; Lo # FEMININE ORDINAL INDICATOR +00BA ; Lo # MASCULINE ORDINAL INDICATOR +01BB ; Lo # LATIN LETTER TWO WITH STROKE +01C0..01C3 ; Lo # [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +0294 ; Lo # LATIN LETTER GLOTTAL STOP +05D0..05EA ; Lo # [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; Lo # [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +0620..063F ; Lo # [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0641..064A ; Lo # [10] ARABIC LETTER FEH..ARABIC LETTER YEH +066E..066F ; Lo # [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0671..06D3 ; Lo # [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D5 ; Lo # ARABIC LETTER AE +06EE..06EF ; Lo # [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06FA..06FC ; Lo # [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FF ; Lo # ARABIC LETTER HEH WITH INVERTED V +0710 ; Lo # SYRIAC LETTER ALAPH +0712..072F ; Lo # [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +074D..07A5 ; Lo # [89] SYRIAC LETTER SOGDIAN ZHAIN..THAANA LETTER WAAVU +07B1 ; Lo # THAANA LETTER NAA +07CA..07EA ; Lo # [33] NKO LETTER A..NKO LETTER JONA RA +0800..0815 ; Lo # [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +0840..0858 ; Lo # [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0860..086A ; Lo # [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; Lo # [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0889..088E ; Lo # [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +08A0..08C8 ; Lo # [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +0904..0939 ; Lo # [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093D ; Lo # DEVANAGARI SIGN AVAGRAHA +0950 ; Lo # DEVANAGARI OM +0958..0961 ; Lo # [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0972..0980 ; Lo # [15] DEVANAGARI LETTER CANDRA A..BENGALI ANJI +0985..098C ; Lo # [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; Lo # [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; Lo # [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; Lo # [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; Lo # BENGALI LETTER LA +09B6..09B9 ; Lo # [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BD ; Lo # BENGALI SIGN AVAGRAHA +09CE ; Lo # BENGALI LETTER KHANDA TA +09DC..09DD ; Lo # [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; Lo # [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09F0..09F1 ; Lo # [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09FC ; Lo # BENGALI LETTER VEDIC ANUSVARA +0A05..0A0A ; Lo # [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; Lo # [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; Lo # [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; Lo # [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; Lo # [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; Lo # [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; Lo # [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A59..0A5C ; Lo # [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; Lo # GURMUKHI LETTER FA +0A72..0A74 ; Lo # [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A85..0A8D ; Lo # [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; Lo # [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; Lo # [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; Lo # [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; Lo # [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; Lo # [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABD ; Lo # GUJARATI SIGN AVAGRAHA +0AD0 ; Lo # GUJARATI OM +0AE0..0AE1 ; Lo # [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AF9 ; Lo # GUJARATI LETTER ZHA +0B05..0B0C ; Lo # [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; Lo # [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; Lo # [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; Lo # [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; Lo # [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; Lo # [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3D ; Lo # ORIYA SIGN AVAGRAHA +0B5C..0B5D ; Lo # [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; Lo # [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B71 ; Lo # ORIYA LETTER WA +0B83 ; Lo # TAMIL SIGN VISARGA +0B85..0B8A ; Lo # [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; Lo # [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; Lo # [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; Lo # [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; Lo # TAMIL LETTER JA +0B9E..0B9F ; Lo # [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; Lo # [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; Lo # [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; Lo # [12] TAMIL LETTER MA..TAMIL LETTER HA +0BD0 ; Lo # TAMIL OM +0C05..0C0C ; Lo # [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; Lo # [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; Lo # [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; Lo # [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3D ; Lo # TELUGU SIGN AVAGRAHA +0C58..0C5A ; Lo # [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; Lo # TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; Lo # [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C80 ; Lo # KANNADA SIGN SPACING CANDRABINDU +0C85..0C8C ; Lo # [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; Lo # [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; Lo # [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; Lo # [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; Lo # [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBD ; Lo # KANNADA SIGN AVAGRAHA +0CDD..0CDE ; Lo # [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; Lo # [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CF1..0CF2 ; Lo # [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0D04..0D0C ; Lo # [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; Lo # [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; Lo # [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3D ; Lo # MALAYALAM SIGN AVAGRAHA +0D4E ; Lo # MALAYALAM LETTER DOT REPH +0D54..0D56 ; Lo # [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D5F..0D61 ; Lo # [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D7A..0D7F ; Lo # [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D85..0D96 ; Lo # [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; Lo # [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; Lo # [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; Lo # SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; Lo # [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0E01..0E30 ; Lo # [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E32..0E33 ; Lo # [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM +0E40..0E45 ; Lo # [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO +0E81..0E82 ; Lo # [2] LAO LETTER KO..LAO LETTER KHO SUNG +0E84 ; Lo # LAO LETTER KHO TAM +0E86..0E8A ; Lo # [5] LAO LETTER PALI GHA..LAO LETTER SO TAM +0E8C..0EA3 ; Lo # [24] LAO LETTER PALI JHA..LAO LETTER LO LING +0EA5 ; Lo # LAO LETTER LO LOOT +0EA7..0EB0 ; Lo # [10] LAO LETTER WO..LAO VOWEL SIGN A +0EB2..0EB3 ; Lo # [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM +0EBD ; Lo # LAO SEMIVOWEL SIGN NYO +0EC0..0EC4 ; Lo # [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EDC..0EDF ; Lo # [4] LAO HO NO..LAO LETTER KHMU NYO +0F00 ; Lo # TIBETAN SYLLABLE OM +0F40..0F47 ; Lo # [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; Lo # [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F88..0F8C ; Lo # [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +1000..102A ; Lo # [43] MYANMAR LETTER KA..MYANMAR LETTER AU +103F ; Lo # MYANMAR LETTER GREAT SA +1050..1055 ; Lo # [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL +105A..105D ; Lo # [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE +1061 ; Lo # MYANMAR LETTER SGAW KAREN SHA +1065..1066 ; Lo # [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA +106E..1070 ; Lo # [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA +1075..1081 ; Lo # [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA +108E ; Lo # MYANMAR LETTER RUMAI PALAUNG FA +1100..1248 ; Lo # [329] HANGUL CHOSEONG KIYEOK..ETHIOPIC SYLLABLE QWA +124A..124D ; Lo # [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; Lo # [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; Lo # ETHIOPIC SYLLABLE QHWA +125A..125D ; Lo # [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; Lo # [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; Lo # [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; Lo # [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; Lo # [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; Lo # [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; Lo # ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; Lo # [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; Lo # [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; Lo # [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; Lo # [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; Lo # [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +1380..138F ; Lo # [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1401..166C ; Lo # [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166F..167F ; Lo # [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1681..169A ; Lo # [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +16A0..16EA ; Lo # [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16F1..16F8 ; Lo # [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; Lo # [18] TAGALOG LETTER A..TAGALOG LETTER HA +171F..1731 ; Lo # [19] TAGALOG LETTER ARCHAIC RA..HANUNOO LETTER HA +1740..1751 ; Lo # [18] BUHID LETTER A..BUHID LETTER HA +1760..176C ; Lo # [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; Lo # [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1780..17B3 ; Lo # [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17DC ; Lo # KHMER SIGN AVAKRAHASANYA +1820..1842 ; Lo # [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1844..1878 ; Lo # [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; Lo # [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1887..18A8 ; Lo # [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18AA ; Lo # MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; Lo # [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; Lo # [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1950..196D ; Lo # [30] TAI LE LETTER KA..TAI LE LETTER AI +1970..1974 ; Lo # [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1980..19AB ; Lo # [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19B0..19C9 ; Lo # [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +1A00..1A16 ; Lo # [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1A20..1A54 ; Lo # [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA +1B05..1B33 ; Lo # [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B45..1B4C ; Lo # [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B83..1BA0 ; Lo # [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BAE..1BAF ; Lo # [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BBA..1BE5 ; Lo # [44] SUNDANESE AVAGRAHA..BATAK LETTER U +1C00..1C23 ; Lo # [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C4D..1C4F ; Lo # [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C5A..1C77 ; Lo # [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1CE9..1CEC ; Lo # [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CEE..1CF3 ; Lo # [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF5..1CF6 ; Lo # [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CFA ; Lo # VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +2135..2138 ; Lo # [4] ALEF SYMBOL..DALET SYMBOL +2D30..2D67 ; Lo # [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D80..2D96 ; Lo # [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; Lo # [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; Lo # [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; Lo # [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; Lo # [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; Lo # [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; Lo # [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; Lo # [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; Lo # [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +3006 ; Lo # IDEOGRAPHIC CLOSING MARK +303C ; Lo # MASU MARK +3041..3096 ; Lo # [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE +309F ; Lo # HIRAGANA DIGRAPH YORI +30A1..30FA ; Lo # [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FF ; Lo # KATAKANA DIGRAPH KOTO +3105..312F ; Lo # [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; Lo # [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +31A0..31BF ; Lo # [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +31F0..31FF ; Lo # [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3400..4DBF ; Lo # [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF +4E00..A014 ; Lo # [21013] CJK UNIFIED IDEOGRAPH-4E00..YI SYLLABLE E +A016..A48C ; Lo # [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A4D0..A4F7 ; Lo # [40] LISU LETTER BA..LISU LETTER OE +A500..A60B ; Lo # [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A610..A61F ; Lo # [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A62A..A62B ; Lo # [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A66E ; Lo # CYRILLIC LETTER MULTIOCULAR O +A6A0..A6E5 ; Lo # [70] BAMUM LETTER A..BAMUM LETTER KI +A78F ; Lo # LATIN LETTER SINOLOGICAL DOT +A7F7 ; Lo # LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7FB..A801 ; Lo # [7] LATIN EPIGRAPHIC LETTER REVERSED F..SYLOTI NAGRI LETTER I +A803..A805 ; Lo # [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A807..A80A ; Lo # [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80C..A822 ; Lo # [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A840..A873 ; Lo # [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A882..A8B3 ; Lo # [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8F2..A8F7 ; Lo # [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8FB ; Lo # DEVANAGARI HEADSTROKE +A8FD..A8FE ; Lo # [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A90A..A925 ; Lo # [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A930..A946 ; Lo # [23] REJANG LETTER KA..REJANG LETTER A +A960..A97C ; Lo # [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A984..A9B2 ; Lo # [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9E0..A9E4 ; Lo # [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA +A9E7..A9EF ; Lo # [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA +A9FA..A9FE ; Lo # [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA +AA00..AA28 ; Lo # [41] CHAM LETTER A..CHAM LETTER HA +AA40..AA42 ; Lo # [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA44..AA4B ; Lo # [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AA60..AA6F ; Lo # [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA +AA71..AA76 ; Lo # [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM +AA7A ; Lo # MYANMAR LETTER AITON RA +AA7E..AAAF ; Lo # [50] MYANMAR LETTER SHWE PALAUNG CHA..TAI VIET LETTER HIGH O +AAB1 ; Lo # TAI VIET VOWEL AA +AAB5..AAB6 ; Lo # [2] TAI VIET VOWEL E..TAI VIET VOWEL O +AAB9..AABD ; Lo # [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN +AAC0 ; Lo # TAI VIET TONE MAI NUENG +AAC2 ; Lo # TAI VIET TONE MAI SONG +AADB..AADC ; Lo # [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG +AAE0..AAEA ; Lo # [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAF2 ; Lo # MEETEI MAYEK ANJI +AB01..AB06 ; Lo # [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; Lo # [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; Lo # [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; Lo # [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; Lo # [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +ABC0..ABE2 ; Lo # [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +AC00..D7A3 ; Lo # [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH +D7B0..D7C6 ; Lo # [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; Lo # [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +F900..FA6D ; Lo # [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA70..FAD9 ; Lo # [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FB1D ; Lo # HEBREW LETTER YOD WITH HIRIQ +FB1F..FB28 ; Lo # [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB2A..FB36 ; Lo # [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; Lo # [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; Lo # HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; Lo # [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; Lo # [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FBB1 ; Lo # [108] HEBREW LETTER TSADI WITH DAGESH..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBD3..FD3D ; Lo # [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD50..FD8F ; Lo # [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; Lo # [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDF0..FDFB ; Lo # [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FE70..FE74 ; Lo # [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; Lo # [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FF66..FF6F ; Lo # [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU +FF71..FF9D ; Lo # [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +FFA0..FFBE ; Lo # [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; Lo # [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; Lo # [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; Lo # [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; Lo # [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +10000..1000B ; Lo # [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; Lo # [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; Lo # [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; Lo # [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; Lo # [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; Lo # [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; Lo # [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10280..1029C ; Lo # [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; Lo # [49] CARIAN LETTER A..CARIAN LETTER UUU3 +10300..1031F ; Lo # [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +1032D..10340 ; Lo # [20] OLD ITALIC LETTER YE..GOTHIC LETTER PAIRTHRA +10342..10349 ; Lo # [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +10350..10375 ; Lo # [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10380..1039D ; Lo # [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +103A0..103C3 ; Lo # [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; Lo # [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +10450..1049D ; Lo # [78] SHAVIAN LETTER PEEP..OSMANYA LETTER OO +10500..10527 ; Lo # [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; Lo # [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +10600..10736 ; Lo # [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; Lo # [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; Lo # [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10800..10805 ; Lo # [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; Lo # CYPRIOT SYLLABLE JO +1080A..10835 ; Lo # [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; Lo # [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; Lo # CYPRIOT SYLLABLE ZA +1083F..10855 ; Lo # [23] CYPRIOT SYLLABLE ZO..IMPERIAL ARAMAIC LETTER TAW +10860..10876 ; Lo # [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10880..1089E ; Lo # [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108E0..108F2 ; Lo # [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; Lo # [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +10900..10915 ; Lo # [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10920..10939 ; Lo # [26] LYDIAN LETTER A..LYDIAN LETTER C +10980..109B7 ; Lo # [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA +109BE..109BF ; Lo # [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +10A00 ; Lo # KHAROSHTHI LETTER A +10A10..10A13 ; Lo # [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; Lo # [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; Lo # [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A60..10A7C ; Lo # [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A80..10A9C ; Lo # [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10AC0..10AC7 ; Lo # [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC9..10AE4 ; Lo # [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10B00..10B35 ; Lo # [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B40..10B55 ; Lo # [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B60..10B72 ; Lo # [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B80..10B91 ; Lo # [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10C00..10C48 ; Lo # [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10D00..10D23 ; Lo # [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10E80..10EA9 ; Lo # [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EB0..10EB1 ; Lo # [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10F00..10F1C ; Lo # [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F27 ; Lo # OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; Lo # [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F70..10F81 ; Lo # [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10FB0..10FC4 ; Lo # [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FE0..10FF6 ; Lo # [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11003..11037 ; Lo # [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA +11071..11072 ; Lo # [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11075 ; Lo # BRAHMI LETTER OLD TAMIL LLA +11083..110AF ; Lo # [45] KAITHI LETTER A..KAITHI LETTER HA +110D0..110E8 ; Lo # [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +11103..11126 ; Lo # [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11144 ; Lo # CHAKMA LETTER LHAA +11147 ; Lo # CHAKMA LETTER VAA +11150..11172 ; Lo # [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11176 ; Lo # MAHAJANI LIGATURE SHRI +11183..111B2 ; Lo # [48] SHARADA LETTER A..SHARADA LETTER HA +111C1..111C4 ; Lo # [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111DA ; Lo # SHARADA EKAM +111DC ; Lo # SHARADA HEADSTROKE +11200..11211 ; Lo # [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; Lo # [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1123F..11240 ; Lo # [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11280..11286 ; Lo # [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; Lo # MULTANI LETTER GHA +1128A..1128D ; Lo # [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; Lo # [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; Lo # [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112B0..112DE ; Lo # [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +11305..1130C ; Lo # [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; Lo # [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; Lo # [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; Lo # [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; Lo # [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; Lo # [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133D ; Lo # GRANTHA SIGN AVAGRAHA +11350 ; Lo # GRANTHA OM +1135D..11361 ; Lo # [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL +11400..11434 ; Lo # [53] NEWA LETTER A..NEWA LETTER HA +11447..1144A ; Lo # [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1145F..11461 ; Lo # [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; Lo # [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114C4..114C5 ; Lo # [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C7 ; Lo # TIRHUTA OM +11580..115AE ; Lo # [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115D8..115DB ; Lo # [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +11600..1162F ; Lo # [48] MODI LETTER A..MODI LETTER LLA +11644 ; Lo # MODI SIGN HUVA +11680..116AA ; Lo # [43] TAKRI LETTER A..TAKRI LETTER RRA +116B8 ; Lo # TAKRI LETTER ARCHAIC KHA +11700..1171A ; Lo # [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA +11740..11746 ; Lo # [7] AHOM LETTER CA..AHOM LETTER LLA +11800..1182B ; Lo # [44] DOGRA LETTER A..DOGRA LETTER RRA +118FF..11906 ; Lo # [8] WARANG CITI OM..DIVES AKURU LETTER E +11909 ; Lo # DIVES AKURU LETTER O +1190C..11913 ; Lo # [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; Lo # [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; Lo # [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +1193F ; Lo # DIVES AKURU PREFIXED NASAL SIGN +11941 ; Lo # DIVES AKURU INITIAL RA +119A0..119A7 ; Lo # [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; Lo # [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119E1 ; Lo # NANDINAGARI SIGN AVAGRAHA +119E3 ; Lo # NANDINAGARI HEADSTROKE +11A00 ; Lo # ZANABAZAR SQUARE LETTER A +11A0B..11A32 ; Lo # [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A3A ; Lo # ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A50 ; Lo # SOYOMBO LETTER A +11A5C..11A89 ; Lo # [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A9D ; Lo # SOYOMBO MARK PLUTA +11AB0..11AF8 ; Lo # [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11C00..11C08 ; Lo # [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; Lo # [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C40 ; Lo # BHAIKSUKI SIGN AVAGRAHA +11C72..11C8F ; Lo # [30] MARCHEN LETTER KA..MARCHEN LETTER A +11D00..11D06 ; Lo # [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; Lo # [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; Lo # [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D46 ; Lo # MASARAM GONDI REPHA +11D60..11D65 ; Lo # [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; Lo # [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; Lo # [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D98 ; Lo # GUNJALA GONDI OM +11EE0..11EF2 ; Lo # [19] MAKASAR LETTER KA..MAKASAR ANGKA +11F02 ; Lo # KAWI SIGN REPHA +11F04..11F10 ; Lo # [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; Lo # [34] KAWI LETTER KA..KAWI LETTER JNYA +11FB0 ; Lo # LISU LETTER YHA +12000..12399 ; Lo # [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12480..12543 ; Lo # [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; Lo # [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +13000..1342F ; Lo # [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D +13441..13446 ; Lo # [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +14400..14646 ; Lo # [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; Lo # [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; Lo # [31] MRO LETTER TA..MRO LETTER TEK +16A70..16ABE ; Lo # [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AD0..16AED ; Lo # [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16B00..16B2F ; Lo # [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B63..16B77 ; Lo # [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; Lo # [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16F00..16F4A ; Lo # [75] MIAO LETTER PA..MIAO LETTER RTE +16F50 ; Lo # MIAO LETTER NASALIZATION +17000..187F7 ; Lo # [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 +18800..18CD5 ; Lo # [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08 ; Lo # [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +1B000..1B122 ; Lo # [291] KATAKANA LETTER ARCHAIC E..KATAKANA LETTER ARCHAIC WU +1B132 ; Lo # HIRAGANA LETTER SMALL KO +1B150..1B152 ; Lo # [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; Lo # KATAKANA LETTER SMALL KO +1B164..1B167 ; Lo # [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1B170..1B2FB ; Lo # [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB +1BC00..1BC6A ; Lo # [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; Lo # [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; Lo # [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; Lo # [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1DF0A ; Lo # LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1E100..1E12C ; Lo # [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E14E ; Lo # NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E290..1E2AD ; Lo # [30] TOTO LETTER PA..TOTO LETTER A +1E2C0..1E2EB ; Lo # [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E4D0..1E4EA ; Lo # [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E7E0..1E7E6 ; Lo # [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; Lo # [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; Lo # [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; Lo # [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; Lo # [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1EE00..1EE03 ; Lo # [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; Lo # [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; Lo # [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; Lo # ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; Lo # ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; Lo # [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; Lo # [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; Lo # ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; Lo # ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; Lo # ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; Lo # ARABIC MATHEMATICAL TAILED HAH +1EE49 ; Lo # ARABIC MATHEMATICAL TAILED YEH +1EE4B ; Lo # ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; Lo # [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; Lo # [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; Lo # ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; Lo # ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; Lo # ARABIC MATHEMATICAL TAILED DAD +1EE5B ; Lo # ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; Lo # ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; Lo # ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; Lo # [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; Lo # ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; Lo # [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; Lo # [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; Lo # [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; Lo # [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; Lo # ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; Lo # [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; Lo # [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; Lo # [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; Lo # [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; Lo # [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +20000..2A6DF ; Lo # [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF +2A700..2B739 ; Lo # [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B740..2B81D ; Lo # [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEA1 ; Lo # [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEB0..2EBE0 ; Lo # [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBF0..2EE5D ; Lo # [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +2F800..2FA1D ; Lo # [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +30000..3134A ; Lo # [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +31350..323AF ; Lo # [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF + +# Total code points: 132234 + +# ================================================ + +# General_Category=Nonspacing_Mark + +0300..036F ; Mn # [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X +0483..0487 ; Mn # [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0591..05BD ; Mn # [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BF ; Mn # HEBREW POINT RAFE +05C1..05C2 ; Mn # [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C4..05C5 ; Mn # [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C7 ; Mn # HEBREW POINT QAMATS QATAN +0610..061A ; Mn # [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +064B..065F ; Mn # [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0670 ; Mn # ARABIC LETTER SUPERSCRIPT ALEF +06D6..06DC ; Mn # [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DF..06E4 ; Mn # [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E7..06E8 ; Mn # [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06EA..06ED ; Mn # [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +0711 ; Mn # SYRIAC LETTER SUPERSCRIPT ALAPH +0730..074A ; Mn # [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +07A6..07B0 ; Mn # [11] THAANA ABAFILI..THAANA SUKUN +07EB..07F3 ; Mn # [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07FD ; Mn # NKO DANTAYALAN +0816..0819 ; Mn # [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081B..0823 ; Mn # [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0825..0827 ; Mn # [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0829..082D ; Mn # [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0859..085B ; Mn # [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +0898..089F ; Mn # [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08CA..08E1 ; Mn # [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E3..0902 ; Mn # [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA +093A ; Mn # DEVANAGARI VOWEL SIGN OE +093C ; Mn # DEVANAGARI SIGN NUKTA +0941..0948 ; Mn # [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +094D ; Mn # DEVANAGARI SIGN VIRAMA +0951..0957 ; Mn # [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0962..0963 ; Mn # [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0981 ; Mn # BENGALI SIGN CANDRABINDU +09BC ; Mn # BENGALI SIGN NUKTA +09C1..09C4 ; Mn # [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09CD ; Mn # BENGALI SIGN VIRAMA +09E2..09E3 ; Mn # [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09FE ; Mn # BENGALI SANDHI MARK +0A01..0A02 ; Mn # [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A3C ; Mn # GURMUKHI SIGN NUKTA +0A41..0A42 ; Mn # [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; Mn # [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; Mn # [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; Mn # GURMUKHI SIGN UDAAT +0A70..0A71 ; Mn # [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A75 ; Mn # GURMUKHI SIGN YAKASH +0A81..0A82 ; Mn # [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0ABC ; Mn # GUJARATI SIGN NUKTA +0AC1..0AC5 ; Mn # [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; Mn # [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0ACD ; Mn # GUJARATI SIGN VIRAMA +0AE2..0AE3 ; Mn # [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AFA..0AFF ; Mn # [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; Mn # ORIYA SIGN CANDRABINDU +0B3C ; Mn # ORIYA SIGN NUKTA +0B3F ; Mn # ORIYA VOWEL SIGN I +0B41..0B44 ; Mn # [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B4D ; Mn # ORIYA SIGN VIRAMA +0B55..0B56 ; Mn # [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B62..0B63 ; Mn # [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B82 ; Mn # TAMIL SIGN ANUSVARA +0BC0 ; Mn # TAMIL VOWEL SIGN II +0BCD ; Mn # TAMIL SIGN VIRAMA +0C00 ; Mn # TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C04 ; Mn # TELUGU SIGN COMBINING ANUSVARA ABOVE +0C3C ; Mn # TELUGU SIGN NUKTA +0C3E..0C40 ; Mn # [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C46..0C48 ; Mn # [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; Mn # [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; Mn # [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C62..0C63 ; Mn # [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C81 ; Mn # KANNADA SIGN CANDRABINDU +0CBC ; Mn # KANNADA SIGN NUKTA +0CBF ; Mn # KANNADA VOWEL SIGN I +0CC6 ; Mn # KANNADA VOWEL SIGN E +0CCC..0CCD ; Mn # [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CE2..0CE3 ; Mn # [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0D00..0D01 ; Mn # [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D3B..0D3C ; Mn # [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D41..0D44 ; Mn # [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D4D ; Mn # MALAYALAM SIGN VIRAMA +0D62..0D63 ; Mn # [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D81 ; Mn # SINHALA SIGN CANDRABINDU +0DCA ; Mn # SINHALA SIGN AL-LAKUNA +0DD2..0DD4 ; Mn # [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; Mn # SINHALA VOWEL SIGN DIGA PAA-PILLA +0E31 ; Mn # THAI CHARACTER MAI HAN-AKAT +0E34..0E3A ; Mn # [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E47..0E4E ; Mn # [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0EB1 ; Mn # LAO VOWEL SIGN MAI KAN +0EB4..0EBC ; Mn # [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EC8..0ECE ; Mn # [7] LAO TONE MAI EK..LAO YAMAKKAN +0F18..0F19 ; Mn # [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F35 ; Mn # TIBETAN MARK NGAS BZUNG NYI ZLA +0F37 ; Mn # TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F39 ; Mn # TIBETAN MARK TSA -PHRU +0F71..0F7E ; Mn # [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F80..0F84 ; Mn # [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F86..0F87 ; Mn # [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F8D..0F97 ; Mn # [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; Mn # [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FC6 ; Mn # TIBETAN SYMBOL PADMA GDAN +102D..1030 ; Mn # [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1032..1037 ; Mn # [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1039..103A ; Mn # [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103D..103E ; Mn # [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +1058..1059 ; Mn # [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105E..1060 ; Mn # [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1071..1074 ; Mn # [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1082 ; Mn # MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1085..1086 ; Mn # [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +108D ; Mn # MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +109D ; Mn # MYANMAR VOWEL SIGN AITON AI +135D..135F ; Mn # [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1712..1714 ; Mn # [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1732..1733 ; Mn # [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1752..1753 ; Mn # [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1772..1773 ; Mn # [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +17B4..17B5 ; Mn # [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B7..17BD ; Mn # [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17C6 ; Mn # KHMER SIGN NIKAHIT +17C9..17D3 ; Mn # [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17DD ; Mn # KHMER SIGN ATTHACAN +180B..180D ; Mn # [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180F ; Mn # MONGOLIAN FREE VARIATION SELECTOR FOUR +1885..1886 ; Mn # [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +18A9 ; Mn # MONGOLIAN LETTER ALI GALI DAGALGA +1920..1922 ; Mn # [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1927..1928 ; Mn # [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1932 ; Mn # LIMBU SMALL LETTER ANUSVARA +1939..193B ; Mn # [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1A17..1A18 ; Mn # [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A1B ; Mn # BUGINESE VOWEL SIGN AE +1A56 ; Mn # TAI THAM CONSONANT SIGN MEDIAL LA +1A58..1A5E ; Mn # [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; Mn # TAI THAM SIGN SAKOT +1A62 ; Mn # TAI THAM VOWEL SIGN MAI SAT +1A65..1A6C ; Mn # [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A73..1A7C ; Mn # [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; Mn # TAI THAM COMBINING CRYPTOGRAMMIC DOT +1AB0..1ABD ; Mn # [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABF..1ACE ; Mn # [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; Mn # [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B34 ; Mn # BALINESE SIGN REREKAN +1B36..1B3A ; Mn # [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3C ; Mn # BALINESE VOWEL SIGN LA LENGA +1B42 ; Mn # BALINESE VOWEL SIGN PEPET +1B6B..1B73 ; Mn # [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B80..1B81 ; Mn # [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1BA2..1BA5 ; Mn # [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA8..1BA9 ; Mn # [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAB..1BAD ; Mn # [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BE6 ; Mn # BATAK SIGN TOMPI +1BE8..1BE9 ; Mn # [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BED ; Mn # BATAK VOWEL SIGN KARO O +1BEF..1BF1 ; Mn # [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1C2C..1C33 ; Mn # [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C36..1C37 ; Mn # [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1CD0..1CD2 ; Mn # [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD4..1CE0 ; Mn # [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE2..1CE8 ; Mn # [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CED ; Mn # VEDIC SIGN TIRYAK +1CF4 ; Mn # VEDIC TONE CANDRA ABOVE +1CF8..1CF9 ; Mn # [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1DC0..1DFF ; Mn # [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +20D0..20DC ; Mn # [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20E1 ; Mn # COMBINING LEFT RIGHT ARROW ABOVE +20E5..20F0 ; Mn # [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2CEF..2CF1 ; Mn # [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2D7F ; Mn # TIFINAGH CONSONANT JOINER +2DE0..2DFF ; Mn # [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +302A..302D ; Mn # [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +3099..309A ; Mn # [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +A66F ; Mn # COMBINING CYRILLIC VZMET +A674..A67D ; Mn # [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A69E..A69F ; Mn # [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6F0..A6F1 ; Mn # [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A802 ; Mn # SYLOTI NAGRI SIGN DVISVARA +A806 ; Mn # SYLOTI NAGRI SIGN HASANTA +A80B ; Mn # SYLOTI NAGRI SIGN ANUSVARA +A825..A826 ; Mn # [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A82C ; Mn # SYLOTI NAGRI SIGN ALTERNATE HASANTA +A8C4..A8C5 ; Mn # [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8E0..A8F1 ; Mn # [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8FF ; Mn # DEVANAGARI VOWEL SIGN AY +A926..A92D ; Mn # [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A947..A951 ; Mn # [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A980..A982 ; Mn # [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A9B3 ; Mn # JAVANESE SIGN CECAK TELU +A9B6..A9B9 ; Mn # [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BC..A9BD ; Mn # [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9E5 ; Mn # MYANMAR SIGN SHAN SAW +AA29..AA2E ; Mn # [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA31..AA32 ; Mn # [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA35..AA36 ; Mn # [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA43 ; Mn # CHAM CONSONANT SIGN FINAL NG +AA4C ; Mn # CHAM CONSONANT SIGN FINAL M +AA7C ; Mn # MYANMAR SIGN TAI LAING TONE-2 +AAB0 ; Mn # TAI VIET MAI KANG +AAB2..AAB4 ; Mn # [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB7..AAB8 ; Mn # [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AABE..AABF ; Mn # [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC1 ; Mn # TAI VIET TONE MAI THO +AAEC..AAED ; Mn # [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAF6 ; Mn # MEETEI MAYEK VIRAMA +ABE5 ; Mn # MEETEI MAYEK VOWEL SIGN ANAP +ABE8 ; Mn # MEETEI MAYEK VOWEL SIGN UNAP +ABED ; Mn # MEETEI MAYEK APUN IYEK +FB1E ; Mn # HEBREW POINT JUDEO-SPANISH VARIKA +FE00..FE0F ; Mn # [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE20..FE2F ; Mn # [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +101FD ; Mn # PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +102E0 ; Mn # COPTIC EPACT THOUSANDS MARK +10376..1037A ; Mn # [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10A01..10A03 ; Mn # [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; Mn # [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; Mn # [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A38..10A3A ; Mn # [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; Mn # KHAROSHTHI VIRAMA +10AE5..10AE6 ; Mn # [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10D24..10D27 ; Mn # [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10EAB..10EAC ; Mn # [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EFD..10EFF ; Mn # [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F46..10F50 ; Mn # [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F82..10F85 ; Mn # [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +11001 ; Mn # BRAHMI SIGN ANUSVARA +11038..11046 ; Mn # [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA +11070 ; Mn # BRAHMI SIGN OLD TAMIL VIRAMA +11073..11074 ; Mn # [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +1107F..11081 ; Mn # [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA +110B3..110B6 ; Mn # [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B9..110BA ; Mn # [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110C2 ; Mn # KAITHI VOWEL SIGN VOCALIC R +11100..11102 ; Mn # [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11127..1112B ; Mn # [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112D..11134 ; Mn # [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11173 ; Mn # MAHAJANI SIGN NUKTA +11180..11181 ; Mn # [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +111B6..111BE ; Mn # [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111C9..111CC ; Mn # [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CF ; Mn # SHARADA SIGN INVERTED CANDRABINDU +1122F..11231 ; Mn # [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11234 ; Mn # KHOJKI SIGN ANUSVARA +11236..11237 ; Mn # [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +1123E ; Mn # KHOJKI SIGN SUKUN +11241 ; Mn # KHOJKI VOWEL SIGN VOCALIC R +112DF ; Mn # KHUDAWADI SIGN ANUSVARA +112E3..112EA ; Mn # [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +11300..11301 ; Mn # [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +1133B..1133C ; Mn # [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +11340 ; Mn # GRANTHA VOWEL SIGN II +11366..1136C ; Mn # [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; Mn # [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11438..1143F ; Mn # [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11442..11444 ; Mn # [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11446 ; Mn # NEWA SIGN NUKTA +1145E ; Mn # NEWA SANDHI MARK +114B3..114B8 ; Mn # [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114BA ; Mn # TIRHUTA VOWEL SIGN SHORT E +114BF..114C0 ; Mn # [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C2..114C3 ; Mn # [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +115B2..115B5 ; Mn # [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115BC..115BD ; Mn # [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BF..115C0 ; Mn # [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115DC..115DD ; Mn # [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11633..1163A ; Mn # [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163D ; Mn # MODI SIGN ANUSVARA +1163F..11640 ; Mn # [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +116AB ; Mn # TAKRI SIGN ANUSVARA +116AD ; Mn # TAKRI VOWEL SIGN AA +116B0..116B5 ; Mn # [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B7 ; Mn # TAKRI SIGN NUKTA +1171D..1171F ; Mn # [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11722..11725 ; Mn # [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11727..1172B ; Mn # [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +1182F..11837 ; Mn # [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11839..1183A ; Mn # [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +1193B..1193C ; Mn # [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193E ; Mn # DIVES AKURU VIRAMA +11943 ; Mn # DIVES AKURU SIGN NUKTA +119D4..119D7 ; Mn # [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; Mn # [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119E0 ; Mn # NANDINAGARI SIGN VIRAMA +11A01..11A0A ; Mn # [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A33..11A38 ; Mn # [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A3B..11A3E ; Mn # [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A47 ; Mn # ZANABAZAR SQUARE SUBJOINER +11A51..11A56 ; Mn # [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A59..11A5B ; Mn # [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A8A..11A96 ; Mn # [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A98..11A99 ; Mn # [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11C30..11C36 ; Mn # [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; Mn # [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3F ; Mn # BHAIKSUKI SIGN VIRAMA +11C92..11CA7 ; Mn # [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CAA..11CB0 ; Mn # [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB2..11CB3 ; Mn # [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB5..11CB6 ; Mn # [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D31..11D36 ; Mn # [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; Mn # MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; Mn # [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; Mn # [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D47 ; Mn # MASARAM GONDI RA-KARA +11D90..11D91 ; Mn # [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D95 ; Mn # GUNJALA GONDI SIGN ANUSVARA +11D97 ; Mn # GUNJALA GONDI VIRAMA +11EF3..11EF4 ; Mn # [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11F00..11F01 ; Mn # [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F36..11F3A ; Mn # [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F40 ; Mn # KAWI VOWEL SIGN EU +11F42 ; Mn # KAWI CONJOINER +13440 ; Mn # EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13447..13455 ; Mn # [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +16AF0..16AF4 ; Mn # [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16B30..16B36 ; Mn # [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16F4F ; Mn # MIAO SIGN CONSONANT MODIFIER BAR +16F8F..16F92 ; Mn # [4] MIAO TONE RIGHT..MIAO TONE BELOW +16FE4 ; Mn # KHITAN SMALL SCRIPT FILLER +1BC9D..1BC9E ; Mn # [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1CF00..1CF2D ; Mn # [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; Mn # [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1D167..1D169 ; Mn # [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D17B..1D182 ; Mn # [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D185..1D18B ; Mn # [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D1AA..1D1AD ; Mn # [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D242..1D244 ; Mn # [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1DA00..1DA36 ; Mn # [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA3B..1DA6C ; Mn # [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA75 ; Mn # SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA84 ; Mn # SIGNWRITING LOCATION HEAD NECK +1DA9B..1DA9F ; Mn # [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; Mn # [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1E000..1E006 ; Mn # [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; Mn # [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; Mn # [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; Mn # [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; Mn # [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E08F ; Mn # COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E130..1E136 ; Mn # [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E2AE ; Mn # TOTO SIGN RISING TONE +1E2EC..1E2EF ; Mn # [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E4EC..1E4EF ; Mn # [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E8D0..1E8D6 ; Mn # [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E944..1E94A ; Mn # [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +E0100..E01EF ; Mn # [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 + +# Total code points: 1985 + +# ================================================ + +# General_Category=Enclosing_Mark + +0488..0489 ; Me # [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +1ABE ; Me # COMBINING PARENTHESES OVERLAY +20DD..20E0 ; Me # [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E2..20E4 ; Me # [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +A670..A672 ; Me # [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN + +# Total code points: 13 + +# ================================================ + +# General_Category=Spacing_Mark + +0903 ; Mc # DEVANAGARI SIGN VISARGA +093B ; Mc # DEVANAGARI VOWEL SIGN OOE +093E..0940 ; Mc # [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0949..094C ; Mc # [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094E..094F ; Mc # [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0982..0983 ; Mc # [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +09BE..09C0 ; Mc # [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C7..09C8 ; Mc # [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; Mc # [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09D7 ; Mc # BENGALI AU LENGTH MARK +0A03 ; Mc # GURMUKHI SIGN VISARGA +0A3E..0A40 ; Mc # [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A83 ; Mc # GUJARATI SIGN VISARGA +0ABE..0AC0 ; Mc # [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC9 ; Mc # GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; Mc # [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0B02..0B03 ; Mc # [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B3E ; Mc # ORIYA VOWEL SIGN AA +0B40 ; Mc # ORIYA VOWEL SIGN II +0B47..0B48 ; Mc # [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; Mc # [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B57 ; Mc # ORIYA AU LENGTH MARK +0BBE..0BBF ; Mc # [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC1..0BC2 ; Mc # [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; Mc # [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; Mc # [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BD7 ; Mc # TAMIL AU LENGTH MARK +0C01..0C03 ; Mc # [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C41..0C44 ; Mc # [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C82..0C83 ; Mc # [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0CBE ; Mc # KANNADA VOWEL SIGN AA +0CC0..0CC4 ; Mc # [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC7..0CC8 ; Mc # [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; Mc # [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CD5..0CD6 ; Mc # [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CF3 ; Mc # KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D02..0D03 ; Mc # [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D3E..0D40 ; Mc # [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D46..0D48 ; Mc # [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; Mc # [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D57 ; Mc # MALAYALAM AU LENGTH MARK +0D82..0D83 ; Mc # [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0DCF..0DD1 ; Mc # [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD8..0DDF ; Mc # [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DF2..0DF3 ; Mc # [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0F3E..0F3F ; Mc # [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F7F ; Mc # TIBETAN SIGN RNAM BCAD +102B..102C ; Mc # [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +1031 ; Mc # MYANMAR VOWEL SIGN E +1038 ; Mc # MYANMAR SIGN VISARGA +103B..103C ; Mc # [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +1056..1057 ; Mc # [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1062..1064 ; Mc # [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1067..106D ; Mc # [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +1083..1084 ; Mc # [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1087..108C ; Mc # [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108F ; Mc # MYANMAR SIGN RUMAI PALAUNG TONE-5 +109A..109C ; Mc # [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +1715 ; Mc # TAGALOG SIGN PAMUDPOD +1734 ; Mc # HANUNOO SIGN PAMUDPOD +17B6 ; Mc # KHMER VOWEL SIGN AA +17BE..17C5 ; Mc # [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C7..17C8 ; Mc # [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +1923..1926 ; Mc # [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1929..192B ; Mc # [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; Mc # [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1933..1938 ; Mc # [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1A19..1A1A ; Mc # [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A55 ; Mc # TAI THAM CONSONANT SIGN MEDIAL RA +1A57 ; Mc # TAI THAM CONSONANT SIGN LA TANG LAI +1A61 ; Mc # TAI THAM VOWEL SIGN A +1A63..1A64 ; Mc # [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A6D..1A72 ; Mc # [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1B04 ; Mc # BALINESE SIGN BISAH +1B35 ; Mc # BALINESE VOWEL SIGN TEDUNG +1B3B ; Mc # BALINESE VOWEL SIGN RA REPA TEDUNG +1B3D..1B41 ; Mc # [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B43..1B44 ; Mc # [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG +1B82 ; Mc # SUNDANESE SIGN PANGWISAD +1BA1 ; Mc # SUNDANESE CONSONANT SIGN PAMINGKAL +1BA6..1BA7 ; Mc # [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BAA ; Mc # SUNDANESE SIGN PAMAAEH +1BE7 ; Mc # BATAK VOWEL SIGN E +1BEA..1BEC ; Mc # [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BEE ; Mc # BATAK VOWEL SIGN U +1BF2..1BF3 ; Mc # [2] BATAK PANGOLAT..BATAK PANONGONAN +1C24..1C2B ; Mc # [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C34..1C35 ; Mc # [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1CE1 ; Mc # VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CF7 ; Mc # VEDIC SIGN ATIKRAMA +302E..302F ; Mc # [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +A823..A824 ; Mc # [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A827 ; Mc # SYLOTI NAGRI VOWEL SIGN OO +A880..A881 ; Mc # [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A8B4..A8C3 ; Mc # [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A952..A953 ; Mc # [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A983 ; Mc # JAVANESE SIGN WIGNYAN +A9B4..A9B5 ; Mc # [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9BA..A9BB ; Mc # [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BE..A9C0 ; Mc # [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON +AA2F..AA30 ; Mc # [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA33..AA34 ; Mc # [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA4D ; Mc # CHAM CONSONANT SIGN FINAL H +AA7B ; Mc # MYANMAR SIGN PAO KAREN TONE +AA7D ; Mc # MYANMAR SIGN TAI LAING TONE-5 +AAEB ; Mc # MEETEI MAYEK VOWEL SIGN II +AAEE..AAEF ; Mc # [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF5 ; Mc # MEETEI MAYEK VOWEL SIGN VISARGA +ABE3..ABE4 ; Mc # [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE6..ABE7 ; Mc # [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE9..ABEA ; Mc # [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEC ; Mc # MEETEI MAYEK LUM IYEK +11000 ; Mc # BRAHMI SIGN CANDRABINDU +11002 ; Mc # BRAHMI SIGN VISARGA +11082 ; Mc # KAITHI SIGN VISARGA +110B0..110B2 ; Mc # [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B7..110B8 ; Mc # [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +1112C ; Mc # CHAKMA VOWEL SIGN E +11145..11146 ; Mc # [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11182 ; Mc # SHARADA SIGN VISARGA +111B3..111B5 ; Mc # [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111BF..111C0 ; Mc # [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111CE ; Mc # SHARADA VOWEL SIGN PRISHTHAMATRA E +1122C..1122E ; Mc # [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +11232..11233 ; Mc # [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11235 ; Mc # KHOJKI SIGN VIRAMA +112E0..112E2 ; Mc # [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +11302..11303 ; Mc # [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +1133E..1133F ; Mc # [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11341..11344 ; Mc # [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; Mc # [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134D ; Mc # [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA +11357 ; Mc # GRANTHA AU LENGTH MARK +11362..11363 ; Mc # [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11435..11437 ; Mc # [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11440..11441 ; Mc # [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11445 ; Mc # NEWA SIGN VISARGA +114B0..114B2 ; Mc # [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B9 ; Mc # TIRHUTA VOWEL SIGN E +114BB..114BE ; Mc # [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114C1 ; Mc # TIRHUTA SIGN VISARGA +115AF..115B1 ; Mc # [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B8..115BB ; Mc # [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BE ; Mc # SIDDHAM SIGN VISARGA +11630..11632 ; Mc # [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +1163B..1163C ; Mc # [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163E ; Mc # MODI SIGN VISARGA +116AC ; Mc # TAKRI SIGN VISARGA +116AE..116AF ; Mc # [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B6 ; Mc # TAKRI SIGN VIRAMA +11720..11721 ; Mc # [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11726 ; Mc # AHOM VOWEL SIGN E +1182C..1182E ; Mc # [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +11838 ; Mc # DOGRA SIGN VISARGA +11930..11935 ; Mc # [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; Mc # [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193D ; Mc # DIVES AKURU SIGN HALANTA +11940 ; Mc # DIVES AKURU MEDIAL YA +11942 ; Mc # DIVES AKURU MEDIAL RA +119D1..119D3 ; Mc # [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119DC..119DF ; Mc # [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E4 ; Mc # NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A39 ; Mc # ZANABAZAR SQUARE SIGN VISARGA +11A57..11A58 ; Mc # [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A97 ; Mc # SOYOMBO SIGN VISARGA +11C2F ; Mc # BHAIKSUKI VOWEL SIGN AA +11C3E ; Mc # BHAIKSUKI SIGN VISARGA +11CA9 ; Mc # MARCHEN SUBJOINED LETTER YA +11CB1 ; Mc # MARCHEN VOWEL SIGN I +11CB4 ; Mc # MARCHEN VOWEL SIGN O +11D8A..11D8E ; Mc # [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D93..11D94 ; Mc # [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D96 ; Mc # GUNJALA GONDI SIGN VISARGA +11EF5..11EF6 ; Mc # [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11F03 ; Mc # KAWI SIGN VISARGA +11F34..11F35 ; Mc # [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F3E..11F3F ; Mc # [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F41 ; Mc # KAWI SIGN KILLER +16F51..16F87 ; Mc # [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16FF0..16FF1 ; Mc # [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +1D165..1D166 ; Mc # [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D16D..1D172 ; Mc # [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 + +# Total code points: 452 + +# ================================================ + +# General_Category=Decimal_Number + +0030..0039 ; Nd # [10] DIGIT ZERO..DIGIT NINE +0660..0669 ; Nd # [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +06F0..06F9 ; Nd # [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +07C0..07C9 ; Nd # [10] NKO DIGIT ZERO..NKO DIGIT NINE +0966..096F ; Nd # [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +09E6..09EF ; Nd # [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +0A66..0A6F ; Nd # [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0AE6..0AEF ; Nd # [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0B66..0B6F ; Nd # [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0BE6..0BEF ; Nd # [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0C66..0C6F ; Nd # [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0CE6..0CEF ; Nd # [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0D66..0D6F ; Nd # [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0DE6..0DEF ; Nd # [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0E50..0E59 ; Nd # [10] THAI DIGIT ZERO..THAI DIGIT NINE +0ED0..0ED9 ; Nd # [10] LAO DIGIT ZERO..LAO DIGIT NINE +0F20..0F29 ; Nd # [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +1040..1049 ; Nd # [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +1090..1099 ; Nd # [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +17E0..17E9 ; Nd # [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +1810..1819 ; Nd # [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1946..194F ; Nd # [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +19D0..19D9 ; Nd # [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +1A80..1A89 ; Nd # [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; Nd # [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1B50..1B59 ; Nd # [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1BB0..1BB9 ; Nd # [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1C40..1C49 ; Nd # [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C50..1C59 ; Nd # [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +A620..A629 ; Nd # [10] VAI DIGIT ZERO..VAI DIGIT NINE +A8D0..A8D9 ; Nd # [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A900..A909 ; Nd # [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A9D0..A9D9 ; Nd # [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9F0..A9F9 ; Nd # [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +AA50..AA59 ; Nd # [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +ABF0..ABF9 ; Nd # [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +FF10..FF19 ; Nd # [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +104A0..104A9 ; Nd # [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +10D30..10D39 ; Nd # [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +11066..1106F ; Nd # [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +110F0..110F9 ; Nd # [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11136..1113F ; Nd # [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +111D0..111D9 ; Nd # [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +112F0..112F9 ; Nd # [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11450..11459 ; Nd # [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +114D0..114D9 ; Nd # [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11650..11659 ; Nd # [10] MODI DIGIT ZERO..MODI DIGIT NINE +116C0..116C9 ; Nd # [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11730..11739 ; Nd # [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +118E0..118E9 ; Nd # [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +11950..11959 ; Nd # [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +11C50..11C59 ; Nd # [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11D50..11D59 ; Nd # [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11DA0..11DA9 ; Nd # [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11F50..11F59 ; Nd # [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +16A60..16A69 ; Nd # [10] MRO DIGIT ZERO..MRO DIGIT NINE +16AC0..16AC9 ; Nd # [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16B50..16B59 ; Nd # [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +1D7CE..1D7FF ; Nd # [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1E140..1E149 ; Nd # [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E2F0..1E2F9 ; Nd # [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E4F0..1E4F9 ; Nd # [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E950..1E959 ; Nd # [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1FBF0..1FBF9 ; Nd # [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE + +# Total code points: 680 + +# ================================================ + +# General_Category=Letter_Number + +16EE..16F0 ; Nl # [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +2160..2182 ; Nl # [35] ROMAN NUMERAL ONE..ROMAN NUMERAL TEN THOUSAND +2185..2188 ; Nl # [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +3007 ; Nl # IDEOGRAPHIC NUMBER ZERO +3021..3029 ; Nl # [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE +3038..303A ; Nl # [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY +A6E6..A6EF ; Nl # [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +10140..10174 ; Nl # [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10341 ; Nl # GOTHIC LETTER NINETY +1034A ; Nl # GOTHIC LETTER NINE HUNDRED +103D1..103D5 ; Nl # [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +12400..1246E ; Nl # [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM + +# Total code points: 236 + +# ================================================ + +# General_Category=Other_Number + +00B2..00B3 ; No # [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE +00B9 ; No # SUPERSCRIPT ONE +00BC..00BE ; No # [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS +09F4..09F9 ; No # [6] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY DENOMINATOR SIXTEEN +0B72..0B77 ; No # [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0BF0..0BF2 ; No # [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND +0C78..0C7E ; No # [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR +0D58..0D5E ; No # [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH +0D70..0D78 ; No # [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS +0F2A..0F33 ; No # [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO +1369..137C ; No # [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND +17F0..17F9 ; No # [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +19DA ; No # NEW TAI LUE THAM DIGIT ONE +2070 ; No # SUPERSCRIPT ZERO +2074..2079 ; No # [6] SUPERSCRIPT FOUR..SUPERSCRIPT NINE +2080..2089 ; No # [10] SUBSCRIPT ZERO..SUBSCRIPT NINE +2150..215F ; No # [16] VULGAR FRACTION ONE SEVENTH..FRACTION NUMERATOR ONE +2189 ; No # VULGAR FRACTION ZERO THIRDS +2460..249B ; No # [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP +24EA..24FF ; No # [22] CIRCLED DIGIT ZERO..NEGATIVE CIRCLED DIGIT ZERO +2776..2793 ; No # [30] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN +2CFD ; No # COPTIC FRACTION ONE HALF +3192..3195 ; No # [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK +3220..3229 ; No # [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN +3248..324F ; No # [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE +3251..325F ; No # [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE +3280..3289 ; No # [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN +32B1..32BF ; No # [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY +A830..A835 ; No # [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS +10107..10133 ; No # [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10175..10178 ; No # [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN +1018A..1018B ; No # [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN +102E1..102FB ; No # [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED +10320..10323 ; No # [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +10858..1085F ; No # [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10879..1087F ; No # [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY +108A7..108AF ; No # [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED +108FB..108FF ; No # [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED +10916..1091B ; No # [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +109BC..109BD ; No # [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF +109C0..109CF ; No # [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY +109D2..109FF ; No # [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS +10A40..10A48 ; No # [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF +10A7D..10A7E ; No # [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY +10A9D..10A9F ; No # [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY +10AEB..10AEF ; No # [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED +10B58..10B5F ; No # [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B78..10B7F ; No # [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10BA9..10BAF ; No # [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED +10CFA..10CFF ; No # [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND +10E60..10E7E ; No # [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10F1D..10F26 ; No # [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF +10F51..10F54 ; No # [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED +10FC5..10FCB ; No # [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED +11052..11065 ; No # [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +111E1..111F4 ; No # [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND +1173A..1173B ; No # [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY +118EA..118F2 ; No # [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY +11C5A..11C6C ; No # [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK +11FC0..11FD4 ; No # [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH +16B5B..16B61 ; No # [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS +16E80..16E96 ; No # [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM +1D2C0..1D2D3 ; No # [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN +1D2E0..1D2F3 ; No # [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN +1D360..1D378 ; No # [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE +1E8C7..1E8CF ; No # [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE +1EC71..1ECAB ; No # [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE +1ECAD..1ECAF ; No # [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS +1ECB1..1ECB4 ; No # [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK +1ED01..1ED2D ; No # [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND +1ED2F..1ED3D ; No # [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH +1F100..1F10C ; No # [13] DIGIT ZERO FULL STOP..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO + +# Total code points: 915 + +# ================================================ + +# General_Category=Space_Separator + +0020 ; Zs # SPACE +00A0 ; Zs # NO-BREAK SPACE +1680 ; Zs # OGHAM SPACE MARK +2000..200A ; Zs # [11] EN QUAD..HAIR SPACE +202F ; Zs # NARROW NO-BREAK SPACE +205F ; Zs # MEDIUM MATHEMATICAL SPACE +3000 ; Zs # IDEOGRAPHIC SPACE + +# Total code points: 17 + +# ================================================ + +# General_Category=Line_Separator + +2028 ; Zl # LINE SEPARATOR + +# Total code points: 1 + +# ================================================ + +# General_Category=Paragraph_Separator + +2029 ; Zp # PARAGRAPH SEPARATOR + +# Total code points: 1 + +# ================================================ + +# General_Category=Control + +0000..001F ; Cc # [32] .. +007F..009F ; Cc # [33] .. + +# Total code points: 65 + +# ================================================ + +# General_Category=Format + +00AD ; Cf # SOFT HYPHEN +0600..0605 ; Cf # [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +061C ; Cf # ARABIC LETTER MARK +06DD ; Cf # ARABIC END OF AYAH +070F ; Cf # SYRIAC ABBREVIATION MARK +0890..0891 ; Cf # [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +08E2 ; Cf # ARABIC DISPUTED END OF AYAH +180E ; Cf # MONGOLIAN VOWEL SEPARATOR +200B..200F ; Cf # [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK +202A..202E ; Cf # [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +2060..2064 ; Cf # [5] WORD JOINER..INVISIBLE PLUS +2066..206F ; Cf # [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +FEFF ; Cf # ZERO WIDTH NO-BREAK SPACE +FFF9..FFFB ; Cf # [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +110BD ; Cf # KAITHI NUMBER SIGN +110CD ; Cf # KAITHI NUMBER SIGN ABOVE +13430..1343F ; Cf # [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +1BCA0..1BCA3 ; Cf # [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1D173..1D17A ; Cf # [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +E0001 ; Cf # LANGUAGE TAG +E0020..E007F ; Cf # [96] TAG SPACE..CANCEL TAG + +# Total code points: 170 + +# ================================================ + +# General_Category=Private_Use + +E000..F8FF ; Co # [6400] .. +F0000..FFFFD ; Co # [65534] .. +100000..10FFFD; Co # [65534] .. + +# Total code points: 137468 + +# ================================================ + +# General_Category=Surrogate + +D800..DFFF ; Cs # [2048] .. + +# Total code points: 2048 + +# ================================================ + +# General_Category=Dash_Punctuation + +002D ; Pd # HYPHEN-MINUS +058A ; Pd # ARMENIAN HYPHEN +05BE ; Pd # HEBREW PUNCTUATION MAQAF +1400 ; Pd # CANADIAN SYLLABICS HYPHEN +1806 ; Pd # MONGOLIAN TODO SOFT HYPHEN +2010..2015 ; Pd # [6] HYPHEN..HORIZONTAL BAR +2E17 ; Pd # DOUBLE OBLIQUE HYPHEN +2E1A ; Pd # HYPHEN WITH DIAERESIS +2E3A..2E3B ; Pd # [2] TWO-EM DASH..THREE-EM DASH +2E40 ; Pd # DOUBLE HYPHEN +2E5D ; Pd # OBLIQUE HYPHEN +301C ; Pd # WAVE DASH +3030 ; Pd # WAVY DASH +30A0 ; Pd # KATAKANA-HIRAGANA DOUBLE HYPHEN +FE31..FE32 ; Pd # [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH +FE58 ; Pd # SMALL EM DASH +FE63 ; Pd # SMALL HYPHEN-MINUS +FF0D ; Pd # FULLWIDTH HYPHEN-MINUS +10EAD ; Pd # YEZIDI HYPHENATION MARK + +# Total code points: 26 + +# ================================================ + +# General_Category=Open_Punctuation + +0028 ; Ps # LEFT PARENTHESIS +005B ; Ps # LEFT SQUARE BRACKET +007B ; Ps # LEFT CURLY BRACKET +0F3A ; Ps # TIBETAN MARK GUG RTAGS GYON +0F3C ; Ps # TIBETAN MARK ANG KHANG GYON +169B ; Ps # OGHAM FEATHER MARK +201A ; Ps # SINGLE LOW-9 QUOTATION MARK +201E ; Ps # DOUBLE LOW-9 QUOTATION MARK +2045 ; Ps # LEFT SQUARE BRACKET WITH QUILL +207D ; Ps # SUPERSCRIPT LEFT PARENTHESIS +208D ; Ps # SUBSCRIPT LEFT PARENTHESIS +2308 ; Ps # LEFT CEILING +230A ; Ps # LEFT FLOOR +2329 ; Ps # LEFT-POINTING ANGLE BRACKET +2768 ; Ps # MEDIUM LEFT PARENTHESIS ORNAMENT +276A ; Ps # MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276C ; Ps # MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276E ; Ps # HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770 ; Ps # HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2772 ; Ps # LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2774 ; Ps # MEDIUM LEFT CURLY BRACKET ORNAMENT +27C5 ; Ps # LEFT S-SHAPED BAG DELIMITER +27E6 ; Ps # MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E8 ; Ps # MATHEMATICAL LEFT ANGLE BRACKET +27EA ; Ps # MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EC ; Ps # MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27EE ; Ps # MATHEMATICAL LEFT FLATTENED PARENTHESIS +2983 ; Ps # LEFT WHITE CURLY BRACKET +2985 ; Ps # LEFT WHITE PARENTHESIS +2987 ; Ps # Z NOTATION LEFT IMAGE BRACKET +2989 ; Ps # Z NOTATION LEFT BINDING BRACKET +298B ; Ps # LEFT SQUARE BRACKET WITH UNDERBAR +298D ; Ps # LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298F ; Ps # LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2991 ; Ps # LEFT ANGLE BRACKET WITH DOT +2993 ; Ps # LEFT ARC LESS-THAN BRACKET +2995 ; Ps # DOUBLE LEFT ARC GREATER-THAN BRACKET +2997 ; Ps # LEFT BLACK TORTOISE SHELL BRACKET +29D8 ; Ps # LEFT WIGGLY FENCE +29DA ; Ps # LEFT DOUBLE WIGGLY FENCE +29FC ; Ps # LEFT-POINTING CURVED ANGLE BRACKET +2E22 ; Ps # TOP LEFT HALF BRACKET +2E24 ; Ps # BOTTOM LEFT HALF BRACKET +2E26 ; Ps # LEFT SIDEWAYS U BRACKET +2E28 ; Ps # LEFT DOUBLE PARENTHESIS +2E42 ; Ps # DOUBLE LOW-REVERSED-9 QUOTATION MARK +2E55 ; Ps # LEFT SQUARE BRACKET WITH STROKE +2E57 ; Ps # LEFT SQUARE BRACKET WITH DOUBLE STROKE +2E59 ; Ps # TOP HALF LEFT PARENTHESIS +2E5B ; Ps # BOTTOM HALF LEFT PARENTHESIS +3008 ; Ps # LEFT ANGLE BRACKET +300A ; Ps # LEFT DOUBLE ANGLE BRACKET +300C ; Ps # LEFT CORNER BRACKET +300E ; Ps # LEFT WHITE CORNER BRACKET +3010 ; Ps # LEFT BLACK LENTICULAR BRACKET +3014 ; Ps # LEFT TORTOISE SHELL BRACKET +3016 ; Ps # LEFT WHITE LENTICULAR BRACKET +3018 ; Ps # LEFT WHITE TORTOISE SHELL BRACKET +301A ; Ps # LEFT WHITE SQUARE BRACKET +301D ; Ps # REVERSED DOUBLE PRIME QUOTATION MARK +FD3F ; Ps # ORNATE RIGHT PARENTHESIS +FE17 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET +FE35 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +FE37 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +FE39 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET +FE3B ; Ps # PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET +FE3D ; Ps # PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET +FE3F ; Ps # PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET +FE41 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET +FE43 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET +FE47 ; Ps # PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET +FE59 ; Ps # SMALL LEFT PARENTHESIS +FE5B ; Ps # SMALL LEFT CURLY BRACKET +FE5D ; Ps # SMALL LEFT TORTOISE SHELL BRACKET +FF08 ; Ps # FULLWIDTH LEFT PARENTHESIS +FF3B ; Ps # FULLWIDTH LEFT SQUARE BRACKET +FF5B ; Ps # FULLWIDTH LEFT CURLY BRACKET +FF5F ; Ps # FULLWIDTH LEFT WHITE PARENTHESIS +FF62 ; Ps # HALFWIDTH LEFT CORNER BRACKET + +# Total code points: 79 + +# ================================================ + +# General_Category=Close_Punctuation + +0029 ; Pe # RIGHT PARENTHESIS +005D ; Pe # RIGHT SQUARE BRACKET +007D ; Pe # RIGHT CURLY BRACKET +0F3B ; Pe # TIBETAN MARK GUG RTAGS GYAS +0F3D ; Pe # TIBETAN MARK ANG KHANG GYAS +169C ; Pe # OGHAM REVERSED FEATHER MARK +2046 ; Pe # RIGHT SQUARE BRACKET WITH QUILL +207E ; Pe # SUPERSCRIPT RIGHT PARENTHESIS +208E ; Pe # SUBSCRIPT RIGHT PARENTHESIS +2309 ; Pe # RIGHT CEILING +230B ; Pe # RIGHT FLOOR +232A ; Pe # RIGHT-POINTING ANGLE BRACKET +2769 ; Pe # MEDIUM RIGHT PARENTHESIS ORNAMENT +276B ; Pe # MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276D ; Pe # MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276F ; Pe # HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2771 ; Pe # HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2773 ; Pe # LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2775 ; Pe # MEDIUM RIGHT CURLY BRACKET ORNAMENT +27C6 ; Pe # RIGHT S-SHAPED BAG DELIMITER +27E7 ; Pe # MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E9 ; Pe # MATHEMATICAL RIGHT ANGLE BRACKET +27EB ; Pe # MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27ED ; Pe # MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EF ; Pe # MATHEMATICAL RIGHT FLATTENED PARENTHESIS +2984 ; Pe # RIGHT WHITE CURLY BRACKET +2986 ; Pe # RIGHT WHITE PARENTHESIS +2988 ; Pe # Z NOTATION RIGHT IMAGE BRACKET +298A ; Pe # Z NOTATION RIGHT BINDING BRACKET +298C ; Pe # RIGHT SQUARE BRACKET WITH UNDERBAR +298E ; Pe # RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990 ; Pe # RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2992 ; Pe # RIGHT ANGLE BRACKET WITH DOT +2994 ; Pe # RIGHT ARC GREATER-THAN BRACKET +2996 ; Pe # DOUBLE RIGHT ARC LESS-THAN BRACKET +2998 ; Pe # RIGHT BLACK TORTOISE SHELL BRACKET +29D9 ; Pe # RIGHT WIGGLY FENCE +29DB ; Pe # RIGHT DOUBLE WIGGLY FENCE +29FD ; Pe # RIGHT-POINTING CURVED ANGLE BRACKET +2E23 ; Pe # TOP RIGHT HALF BRACKET +2E25 ; Pe # BOTTOM RIGHT HALF BRACKET +2E27 ; Pe # RIGHT SIDEWAYS U BRACKET +2E29 ; Pe # RIGHT DOUBLE PARENTHESIS +2E56 ; Pe # RIGHT SQUARE BRACKET WITH STROKE +2E58 ; Pe # RIGHT SQUARE BRACKET WITH DOUBLE STROKE +2E5A ; Pe # TOP HALF RIGHT PARENTHESIS +2E5C ; Pe # BOTTOM HALF RIGHT PARENTHESIS +3009 ; Pe # RIGHT ANGLE BRACKET +300B ; Pe # RIGHT DOUBLE ANGLE BRACKET +300D ; Pe # RIGHT CORNER BRACKET +300F ; Pe # RIGHT WHITE CORNER BRACKET +3011 ; Pe # RIGHT BLACK LENTICULAR BRACKET +3015 ; Pe # RIGHT TORTOISE SHELL BRACKET +3017 ; Pe # RIGHT WHITE LENTICULAR BRACKET +3019 ; Pe # RIGHT WHITE TORTOISE SHELL BRACKET +301B ; Pe # RIGHT WHITE SQUARE BRACKET +301E..301F ; Pe # [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK +FD3E ; Pe # ORNATE LEFT PARENTHESIS +FE18 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET +FE36 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +FE38 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +FE3A ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET +FE3C ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET +FE3E ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET +FE40 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET +FE42 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET +FE44 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET +FE48 ; Pe # PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET +FE5A ; Pe # SMALL RIGHT PARENTHESIS +FE5C ; Pe # SMALL RIGHT CURLY BRACKET +FE5E ; Pe # SMALL RIGHT TORTOISE SHELL BRACKET +FF09 ; Pe # FULLWIDTH RIGHT PARENTHESIS +FF3D ; Pe # FULLWIDTH RIGHT SQUARE BRACKET +FF5D ; Pe # FULLWIDTH RIGHT CURLY BRACKET +FF60 ; Pe # FULLWIDTH RIGHT WHITE PARENTHESIS +FF63 ; Pe # HALFWIDTH RIGHT CORNER BRACKET + +# Total code points: 77 + +# ================================================ + +# General_Category=Connector_Punctuation + +005F ; Pc # LOW LINE +203F..2040 ; Pc # [2] UNDERTIE..CHARACTER TIE +2054 ; Pc # INVERTED UNDERTIE +FE33..FE34 ; Pc # [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE4D..FE4F ; Pc # [3] DASHED LOW LINE..WAVY LOW LINE +FF3F ; Pc # FULLWIDTH LOW LINE + +# Total code points: 10 + +# ================================================ + +# General_Category=Other_Punctuation + +0021..0023 ; Po # [3] EXCLAMATION MARK..NUMBER SIGN +0025..0027 ; Po # [3] PERCENT SIGN..APOSTROPHE +002A ; Po # ASTERISK +002C ; Po # COMMA +002E..002F ; Po # [2] FULL STOP..SOLIDUS +003A..003B ; Po # [2] COLON..SEMICOLON +003F..0040 ; Po # [2] QUESTION MARK..COMMERCIAL AT +005C ; Po # REVERSE SOLIDUS +00A1 ; Po # INVERTED EXCLAMATION MARK +00A7 ; Po # SECTION SIGN +00B6..00B7 ; Po # [2] PILCROW SIGN..MIDDLE DOT +00BF ; Po # INVERTED QUESTION MARK +037E ; Po # GREEK QUESTION MARK +0387 ; Po # GREEK ANO TELEIA +055A..055F ; Po # [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0589 ; Po # ARMENIAN FULL STOP +05C0 ; Po # HEBREW PUNCTUATION PASEQ +05C3 ; Po # HEBREW PUNCTUATION SOF PASUQ +05C6 ; Po # HEBREW PUNCTUATION NUN HAFUKHA +05F3..05F4 ; Po # [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +0609..060A ; Po # [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN +060C..060D ; Po # [2] ARABIC COMMA..ARABIC DATE SEPARATOR +061B ; Po # ARABIC SEMICOLON +061D..061F ; Po # [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK +066A..066D ; Po # [4] ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR +06D4 ; Po # ARABIC FULL STOP +0700..070D ; Po # [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +07F7..07F9 ; Po # [3] NKO SYMBOL GBAKURUNEN..NKO EXCLAMATION MARK +0830..083E ; Po # [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +085E ; Po # MANDAIC PUNCTUATION +0964..0965 ; Po # [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0970 ; Po # DEVANAGARI ABBREVIATION SIGN +09FD ; Po # BENGALI ABBREVIATION SIGN +0A76 ; Po # GURMUKHI ABBREVIATION SIGN +0AF0 ; Po # GUJARATI ABBREVIATION SIGN +0C77 ; Po # TELUGU SIGN SIDDHAM +0C84 ; Po # KANNADA SIGN SIDDHAM +0DF4 ; Po # SINHALA PUNCTUATION KUNDDALIYA +0E4F ; Po # THAI CHARACTER FONGMAN +0E5A..0E5B ; Po # [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0F04..0F12 ; Po # [15] TIBETAN MARK INITIAL YIG MGO MDUN MA..TIBETAN MARK RGYA GRAM SHAD +0F14 ; Po # TIBETAN MARK GTER TSHEG +0F85 ; Po # TIBETAN MARK PALUTA +0FD0..0FD4 ; Po # [5] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA +0FD9..0FDA ; Po # [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS +104A..104F ; Po # [6] MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE +10FB ; Po # GEORGIAN PARAGRAPH SEPARATOR +1360..1368 ; Po # [9] ETHIOPIC SECTION MARK..ETHIOPIC PARAGRAPH SEPARATOR +166E ; Po # CANADIAN SYLLABICS FULL STOP +16EB..16ED ; Po # [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION +1735..1736 ; Po # [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +17D4..17D6 ; Po # [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH +17D8..17DA ; Po # [3] KHMER SIGN BEYYAL..KHMER SIGN KOOMUUT +1800..1805 ; Po # [6] MONGOLIAN BIRGA..MONGOLIAN FOUR DOTS +1807..180A ; Po # [4] MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER..MONGOLIAN NIRUGU +1944..1945 ; Po # [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1A1E..1A1F ; Po # [2] BUGINESE PALLAWA..BUGINESE END OF SECTION +1AA0..1AA6 ; Po # [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA8..1AAD ; Po # [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1B5A..1B60 ; Po # [7] BALINESE PANTI..BALINESE PAMENENG +1B7D..1B7E ; Po # [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG +1BFC..1BFF ; Po # [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C3B..1C3F ; Po # [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C7E..1C7F ; Po # [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1CC0..1CC7 ; Po # [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CD3 ; Po # VEDIC SIGN NIHSHVASA +2016..2017 ; Po # [2] DOUBLE VERTICAL LINE..DOUBLE LOW LINE +2020..2027 ; Po # [8] DAGGER..HYPHENATION POINT +2030..2038 ; Po # [9] PER MILLE SIGN..CARET +203B..203E ; Po # [4] REFERENCE MARK..OVERLINE +2041..2043 ; Po # [3] CARET INSERTION POINT..HYPHEN BULLET +2047..2051 ; Po # [11] DOUBLE QUESTION MARK..TWO ASTERISKS ALIGNED VERTICALLY +2053 ; Po # SWUNG DASH +2055..205E ; Po # [10] FLOWER PUNCTUATION MARK..VERTICAL FOUR DOTS +2CF9..2CFC ; Po # [4] COPTIC OLD NUBIAN FULL STOP..COPTIC OLD NUBIAN VERSE DIVIDER +2CFE..2CFF ; Po # [2] COPTIC FULL STOP..COPTIC MORPHOLOGICAL DIVIDER +2D70 ; Po # TIFINAGH SEPARATOR MARK +2E00..2E01 ; Po # [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER +2E06..2E08 ; Po # [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER +2E0B ; Po # RAISED SQUARE +2E0E..2E16 ; Po # [9] EDITORIAL CORONIS..DOTTED RIGHT-POINTING ANGLE +2E18..2E19 ; Po # [2] INVERTED INTERROBANG..PALM BRANCH +2E1B ; Po # TILDE WITH RING ABOVE +2E1E..2E1F ; Po # [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW +2E2A..2E2E ; Po # [5] TWO DOTS OVER ONE DOT PUNCTUATION..REVERSED QUESTION MARK +2E30..2E39 ; Po # [10] RING POINT..TOP HALF SECTION SIGN +2E3C..2E3F ; Po # [4] STENOGRAPHIC FULL STOP..CAPITULUM +2E41 ; Po # REVERSED COMMA +2E43..2E4F ; Po # [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER +2E52..2E54 ; Po # [3] TIRONIAN SIGN CAPITAL ET..MEDIEVAL QUESTION MARK +3001..3003 ; Po # [3] IDEOGRAPHIC COMMA..DITTO MARK +303D ; Po # PART ALTERNATION MARK +30FB ; Po # KATAKANA MIDDLE DOT +A4FE..A4FF ; Po # [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A60D..A60F ; Po # [3] VAI COMMA..VAI QUESTION MARK +A673 ; Po # SLAVONIC ASTERISK +A67E ; Po # CYRILLIC KAVYKA +A6F2..A6F7 ; Po # [6] BAMUM NJAEMLI..BAMUM QUESTION MARK +A874..A877 ; Po # [4] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD +A8CE..A8CF ; Po # [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8F8..A8FA ; Po # [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FC ; Po # DEVANAGARI SIGN SIDDHAM +A92E..A92F ; Po # [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A95F ; Po # REJANG SECTION MARK +A9C1..A9CD ; Po # [13] JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH +A9DE..A9DF ; Po # [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +AA5C..AA5F ; Po # [4] CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA +AADE..AADF ; Po # [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAF0..AAF1 ; Po # [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +ABEB ; Po # MEETEI MAYEK CHEIKHEI +FE10..FE16 ; Po # [7] PRESENTATION FORM FOR VERTICAL COMMA..PRESENTATION FORM FOR VERTICAL QUESTION MARK +FE19 ; Po # PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE30 ; Po # PRESENTATION FORM FOR VERTICAL TWO DOT LEADER +FE45..FE46 ; Po # [2] SESAME DOT..WHITE SESAME DOT +FE49..FE4C ; Po # [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE +FE50..FE52 ; Po # [3] SMALL COMMA..SMALL FULL STOP +FE54..FE57 ; Po # [4] SMALL SEMICOLON..SMALL EXCLAMATION MARK +FE5F..FE61 ; Po # [3] SMALL NUMBER SIGN..SMALL ASTERISK +FE68 ; Po # SMALL REVERSE SOLIDUS +FE6A..FE6B ; Po # [2] SMALL PERCENT SIGN..SMALL COMMERCIAL AT +FF01..FF03 ; Po # [3] FULLWIDTH EXCLAMATION MARK..FULLWIDTH NUMBER SIGN +FF05..FF07 ; Po # [3] FULLWIDTH PERCENT SIGN..FULLWIDTH APOSTROPHE +FF0A ; Po # FULLWIDTH ASTERISK +FF0C ; Po # FULLWIDTH COMMA +FF0E..FF0F ; Po # [2] FULLWIDTH FULL STOP..FULLWIDTH SOLIDUS +FF1A..FF1B ; Po # [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON +FF1F..FF20 ; Po # [2] FULLWIDTH QUESTION MARK..FULLWIDTH COMMERCIAL AT +FF3C ; Po # FULLWIDTH REVERSE SOLIDUS +FF61 ; Po # HALFWIDTH IDEOGRAPHIC FULL STOP +FF64..FF65 ; Po # [2] HALFWIDTH IDEOGRAPHIC COMMA..HALFWIDTH KATAKANA MIDDLE DOT +10100..10102 ; Po # [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +1039F ; Po # UGARITIC WORD DIVIDER +103D0 ; Po # OLD PERSIAN WORD DIVIDER +1056F ; Po # CAUCASIAN ALBANIAN CITATION MARK +10857 ; Po # IMPERIAL ARAMAIC SECTION SIGN +1091F ; Po # PHOENICIAN WORD SEPARATOR +1093F ; Po # LYDIAN TRIANGULAR MARK +10A50..10A58 ; Po # [9] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES +10A7F ; Po # OLD SOUTH ARABIAN NUMERIC INDICATOR +10AF0..10AF6 ; Po # [7] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION LINE FILLER +10B39..10B3F ; Po # [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B99..10B9C ; Po # [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT +10F55..10F59 ; Po # [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10F86..10F89 ; Po # [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS +11047..1104D ; Po # [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS +110BB..110BC ; Po # [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN +110BE..110C1 ; Po # [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA +11140..11143 ; Po # [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11174..11175 ; Po # [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK +111C5..111C8 ; Po # [4] SHARADA DANDA..SHARADA SEPARATOR +111CD ; Po # SHARADA SUTRA MARK +111DB ; Po # SHARADA SIGN SIDDHAM +111DD..111DF ; Po # [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +11238..1123D ; Po # [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN +112A9 ; Po # MULTANI SECTION MARK +1144B..1144F ; Po # [5] NEWA DANDA..NEWA ABBREVIATION SIGN +1145A..1145B ; Po # [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK +1145D ; Po # NEWA INSERTION SIGN +114C6 ; Po # TIRHUTA ABBREVIATION SIGN +115C1..115D7 ; Po # [23] SIDDHAM SIGN SIDDHAM..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES +11641..11643 ; Po # [3] MODI DANDA..MODI ABBREVIATION SIGN +11660..1166C ; Po # [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT +116B9 ; Po # TAKRI ABBREVIATION SIGN +1173C..1173E ; Po # [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI +1183B ; Po # DOGRA ABBREVIATION SIGN +11944..11946 ; Po # [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +119E2 ; Po # NANDINAGARI SIGN SIDDHAM +11A3F..11A46 ; Po # [8] ZANABAZAR SQUARE INITIAL HEAD MARK..ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK +11A9A..11A9C ; Po # [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD +11A9E..11AA2 ; Po # [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2 +11B00..11B09 ; Po # [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU +11C41..11C45 ; Po # [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2 +11C70..11C71 ; Po # [2] MARCHEN HEAD MARK..MARCHEN MARK SHAD +11EF7..11EF8 ; Po # [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F43..11F4F ; Po # [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL +11FFF ; Po # TAMIL PUNCTUATION END OF TEXT +12470..12474 ; Po # [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON +12FF1..12FF2 ; Po # [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 +16A6E..16A6F ; Po # [2] MRO DANDA..MRO DOUBLE DANDA +16AF5 ; Po # BASSA VAH FULL STOP +16B37..16B3B ; Po # [5] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS FEEM +16B44 ; Po # PAHAWH HMONG SIGN XAUS +16E97..16E9A ; Po # [4] MEDEFAIDRIN COMMA..MEDEFAIDRIN EXCLAMATION OH +16FE2 ; Po # OLD CHINESE HOOK MARK +1BC9F ; Po # DUPLOYAN PUNCTUATION CHINOOK FULL STOP +1DA87..1DA8B ; Po # [5] SIGNWRITING COMMA..SIGNWRITING PARENTHESIS +1E95E..1E95F ; Po # [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK + +# Total code points: 628 + +# ================================================ + +# General_Category=Math_Symbol + +002B ; Sm # PLUS SIGN +003C..003E ; Sm # [3] LESS-THAN SIGN..GREATER-THAN SIGN +007C ; Sm # VERTICAL LINE +007E ; Sm # TILDE +00AC ; Sm # NOT SIGN +00B1 ; Sm # PLUS-MINUS SIGN +00D7 ; Sm # MULTIPLICATION SIGN +00F7 ; Sm # DIVISION SIGN +03F6 ; Sm # GREEK REVERSED LUNATE EPSILON SYMBOL +0606..0608 ; Sm # [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY +2044 ; Sm # FRACTION SLASH +2052 ; Sm # COMMERCIAL MINUS SIGN +207A..207C ; Sm # [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN +208A..208C ; Sm # [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN +2118 ; Sm # SCRIPT CAPITAL P +2140..2144 ; Sm # [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y +214B ; Sm # TURNED AMPERSAND +2190..2194 ; Sm # [5] LEFTWARDS ARROW..LEFT RIGHT ARROW +219A..219B ; Sm # [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE +21A0 ; Sm # RIGHTWARDS TWO HEADED ARROW +21A3 ; Sm # RIGHTWARDS ARROW WITH TAIL +21A6 ; Sm # RIGHTWARDS ARROW FROM BAR +21AE ; Sm # LEFT RIGHT ARROW WITH STROKE +21CE..21CF ; Sm # [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE +21D2 ; Sm # RIGHTWARDS DOUBLE ARROW +21D4 ; Sm # LEFT RIGHT DOUBLE ARROW +21F4..22FF ; Sm # [268] RIGHT ARROW WITH SMALL CIRCLE..Z NOTATION BAG MEMBERSHIP +2320..2321 ; Sm # [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL +237C ; Sm # RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +239B..23B3 ; Sm # [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM +23DC..23E1 ; Sm # [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET +25B7 ; Sm # WHITE RIGHT-POINTING TRIANGLE +25C1 ; Sm # WHITE LEFT-POINTING TRIANGLE +25F8..25FF ; Sm # [8] UPPER LEFT TRIANGLE..LOWER RIGHT TRIANGLE +266F ; Sm # MUSIC SHARP SIGN +27C0..27C4 ; Sm # [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET +27C7..27E5 ; Sm # [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK +27F0..27FF ; Sm # [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW +2900..2982 ; Sm # [131] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..Z NOTATION TYPE COLON +2999..29D7 ; Sm # [63] DOTTED FENCE..BLACK HOURGLASS +29DC..29FB ; Sm # [32] INCOMPLETE INFINITY..TRIPLE PLUS +29FE..2AFF ; Sm # [258] TINY..N-ARY WHITE VERTICAL BAR +2B30..2B44 ; Sm # [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET +2B47..2B4C ; Sm # [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +FB29 ; Sm # HEBREW LETTER ALTERNATIVE PLUS SIGN +FE62 ; Sm # SMALL PLUS SIGN +FE64..FE66 ; Sm # [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN +FF0B ; Sm # FULLWIDTH PLUS SIGN +FF1C..FF1E ; Sm # [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN +FF5C ; Sm # FULLWIDTH VERTICAL LINE +FF5E ; Sm # FULLWIDTH TILDE +FFE2 ; Sm # FULLWIDTH NOT SIGN +FFE9..FFEC ; Sm # [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW +1D6C1 ; Sm # MATHEMATICAL BOLD NABLA +1D6DB ; Sm # MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +1D6FB ; Sm # MATHEMATICAL ITALIC NABLA +1D715 ; Sm # MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +1D735 ; Sm # MATHEMATICAL BOLD ITALIC NABLA +1D74F ; Sm # MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +1D76F ; Sm # MATHEMATICAL SANS-SERIF BOLD NABLA +1D789 ; Sm # MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +1D7A9 ; Sm # MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA +1D7C3 ; Sm # MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL +1EEF0..1EEF1 ; Sm # [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL + +# Total code points: 948 + +# ================================================ + +# General_Category=Currency_Symbol + +0024 ; Sc # DOLLAR SIGN +00A2..00A5 ; Sc # [4] CENT SIGN..YEN SIGN +058F ; Sc # ARMENIAN DRAM SIGN +060B ; Sc # AFGHANI SIGN +07FE..07FF ; Sc # [2] NKO DOROME SIGN..NKO TAMAN SIGN +09F2..09F3 ; Sc # [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN +09FB ; Sc # BENGALI GANDA MARK +0AF1 ; Sc # GUJARATI RUPEE SIGN +0BF9 ; Sc # TAMIL RUPEE SIGN +0E3F ; Sc # THAI CURRENCY SYMBOL BAHT +17DB ; Sc # KHMER CURRENCY SYMBOL RIEL +20A0..20C0 ; Sc # [33] EURO-CURRENCY SIGN..SOM SIGN +A838 ; Sc # NORTH INDIC RUPEE MARK +FDFC ; Sc # RIAL SIGN +FE69 ; Sc # SMALL DOLLAR SIGN +FF04 ; Sc # FULLWIDTH DOLLAR SIGN +FFE0..FFE1 ; Sc # [2] FULLWIDTH CENT SIGN..FULLWIDTH POUND SIGN +FFE5..FFE6 ; Sc # [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN +11FDD..11FE0 ; Sc # [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN +1E2FF ; Sc # WANCHO NGUN SIGN +1ECB0 ; Sc # INDIC SIYAQ RUPEE MARK + +# Total code points: 63 + +# ================================================ + +# General_Category=Modifier_Symbol + +005E ; Sk # CIRCUMFLEX ACCENT +0060 ; Sk # GRAVE ACCENT +00A8 ; Sk # DIAERESIS +00AF ; Sk # MACRON +00B4 ; Sk # ACUTE ACCENT +00B8 ; Sk # CEDILLA +02C2..02C5 ; Sk # [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD +02D2..02DF ; Sk # [14] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER CROSS ACCENT +02E5..02EB ; Sk # [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02ED ; Sk # MODIFIER LETTER UNASPIRATED +02EF..02FF ; Sk # [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0375 ; Sk # GREEK LOWER NUMERAL SIGN +0384..0385 ; Sk # [2] GREEK TONOS..GREEK DIALYTIKA TONOS +0888 ; Sk # ARABIC RAISED ROUND DOT +1FBD ; Sk # GREEK KORONIS +1FBF..1FC1 ; Sk # [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI +1FCD..1FCF ; Sk # [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI +1FDD..1FDF ; Sk # [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FED..1FEF ; Sk # [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA +1FFD..1FFE ; Sk # [2] GREEK OXIA..GREEK DASIA +309B..309C ; Sk # [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +A700..A716 ; Sk # [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A720..A721 ; Sk # [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A789..A78A ; Sk # [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +AB5B ; Sk # MODIFIER BREVE WITH INVERTED BREVE +AB6A..AB6B ; Sk # [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK +FBB2..FBC2 ; Sk # [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FF3E ; Sk # FULLWIDTH CIRCUMFLEX ACCENT +FF40 ; Sk # FULLWIDTH GRAVE ACCENT +FFE3 ; Sk # FULLWIDTH MACRON +1F3FB..1F3FF ; Sk # [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 + +# Total code points: 125 + +# ================================================ + +# General_Category=Other_Symbol + +00A6 ; So # BROKEN BAR +00A9 ; So # COPYRIGHT SIGN +00AE ; So # REGISTERED SIGN +00B0 ; So # DEGREE SIGN +0482 ; So # CYRILLIC THOUSANDS SIGN +058D..058E ; So # [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN +060E..060F ; So # [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA +06DE ; So # ARABIC START OF RUB EL HIZB +06E9 ; So # ARABIC PLACE OF SAJDAH +06FD..06FE ; So # [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN +07F6 ; So # NKO SYMBOL OO DENNEN +09FA ; So # BENGALI ISSHAR +0B70 ; So # ORIYA ISSHAR +0BF3..0BF8 ; So # [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN +0BFA ; So # TAMIL NUMBER SIGN +0C7F ; So # TELUGU SIGN TUUMU +0D4F ; So # MALAYALAM SIGN PARA +0D79 ; So # MALAYALAM DATE MARK +0F01..0F03 ; So # [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA +0F13 ; So # TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN +0F15..0F17 ; So # [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F1A..0F1F ; So # [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F34 ; So # TIBETAN MARK BSDUS RTAGS +0F36 ; So # TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F38 ; So # TIBETAN MARK CHE MGO +0FBE..0FC5 ; So # [8] TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE +0FC7..0FCC ; So # [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCE..0FCF ; So # [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM +0FD5..0FD8 ; So # [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS +109E..109F ; So # [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION +1390..1399 ; So # [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +166D ; So # CANADIAN SYLLABICS CHI SIGN +1940 ; So # LIMBU SIGN LOO +19DE..19FF ; So # [34] NEW TAI LUE SIGN LAE..KHMER SYMBOL DAP-PRAM ROC +1B61..1B6A ; So # [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE +1B74..1B7C ; So # [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +2100..2101 ; So # [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT +2103..2106 ; So # [4] DEGREE CELSIUS..CADA UNA +2108..2109 ; So # [2] SCRUPLE..DEGREE FAHRENHEIT +2114 ; So # L B BAR SYMBOL +2116..2117 ; So # [2] NUMERO SIGN..SOUND RECORDING COPYRIGHT +211E..2123 ; So # [6] PRESCRIPTION TAKE..VERSICLE +2125 ; So # OUNCE SIGN +2127 ; So # INVERTED OHM SIGN +2129 ; So # TURNED GREEK SMALL LETTER IOTA +212E ; So # ESTIMATED SYMBOL +213A..213B ; So # [2] ROTATED CAPITAL Q..FACSIMILE SIGN +214A ; So # PROPERTY LINE +214C..214D ; So # [2] PER SIGN..AKTIESELSKAB +214F ; So # SYMBOL FOR SAMARITAN SOURCE +218A..218B ; So # [2] TURNED DIGIT TWO..TURNED DIGIT THREE +2195..2199 ; So # [5] UP DOWN ARROW..SOUTH WEST ARROW +219C..219F ; So # [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW +21A1..21A2 ; So # [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL +21A4..21A5 ; So # [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR +21A7..21AD ; So # [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW +21AF..21CD ; So # [31] DOWNWARDS ZIGZAG ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE +21D0..21D1 ; So # [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW +21D3 ; So # DOWNWARDS DOUBLE ARROW +21D5..21F3 ; So # [31] UP DOWN DOUBLE ARROW..UP DOWN WHITE ARROW +2300..2307 ; So # [8] DIAMETER SIGN..WAVY LINE +230C..231F ; So # [20] BOTTOM RIGHT CROP..BOTTOM RIGHT CORNER +2322..2328 ; So # [7] FROWN..KEYBOARD +232B..237B ; So # [81] ERASE TO THE LEFT..NOT CHECK MARK +237D..239A ; So # [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL +23B4..23DB ; So # [40] TOP SQUARE BRACKET..FUSE +23E2..2426 ; So # [69] WHITE TRAPEZIUM..SYMBOL FOR SUBSTITUTE FORM TWO +2440..244A ; So # [11] OCR HOOK..OCR DOUBLE BACKSLASH +249C..24E9 ; So # [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z +2500..25B6 ; So # [183] BOX DRAWINGS LIGHT HORIZONTAL..BLACK RIGHT-POINTING TRIANGLE +25B8..25C0 ; So # [9] BLACK RIGHT-POINTING SMALL TRIANGLE..BLACK LEFT-POINTING TRIANGLE +25C2..25F7 ; So # [54] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE CIRCLE WITH UPPER RIGHT QUADRANT +2600..266E ; So # [111] BLACK SUN WITH RAYS..MUSIC NATURAL SIGN +2670..2767 ; So # [248] WEST SYRIAC CROSS..ROTATED FLORAL HEART BULLET +2794..27BF ; So # [44] HEAVY WIDE-HEADED RIGHTWARDS ARROW..DOUBLE CURLY LOOP +2800..28FF ; So # [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678 +2B00..2B2F ; So # [48] NORTH EAST WHITE ARROW..WHITE VERTICAL ELLIPSE +2B45..2B46 ; So # [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW +2B4D..2B73 ; So # [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR +2B76..2B95 ; So # [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B97..2BFF ; So # [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2CE5..2CEA ; So # [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA +2E50..2E51 ; So # [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E80..2E99 ; So # [26] CJK RADICAL REPEAT..CJK RADICAL RAP +2E9B..2EF3 ; So # [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2F00..2FD5 ; So # [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FF0..2FFF ; So # [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION +3004 ; So # JAPANESE INDUSTRIAL STANDARD SYMBOL +3012..3013 ; So # [2] POSTAL MARK..GETA MARK +3020 ; So # POSTAL MARK FACE +3036..3037 ; So # [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL +303E..303F ; So # [2] IDEOGRAPHIC VARIATION INDICATOR..IDEOGRAPHIC HALF FILL SPACE +3190..3191 ; So # [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK +3196..319F ; So # [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31C0..31E3 ; So # [36] CJK STROKE T..CJK STROKE Q +31EF ; So # IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION +3200..321E ; So # [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +322A..3247 ; So # [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO +3250 ; So # PARTNERSHIP SIGN +3260..327F ; So # [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL +328A..32B0 ; So # [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT +32C0..33FF ; So # [320] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE GAL +4DC0..4DFF ; So # [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +A490..A4C6 ; So # [55] YI RADICAL QOT..YI RADICAL KE +A828..A82B ; So # [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A836..A837 ; So # [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK +A839 ; So # NORTH INDIC QUANTITY MARK +AA77..AA79 ; So # [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +FD40..FD4F ; So # [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FDCF ; So # ARABIC LIGATURE SALAAMUHU ALAYNAA +FDFD..FDFF ; So # [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL +FFE4 ; So # FULLWIDTH BROKEN BAR +FFE8 ; So # HALFWIDTH FORMS LIGHT VERTICAL +FFED..FFEE ; So # [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE +FFFC..FFFD ; So # [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHARACTER +10137..1013F ; So # [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT +10179..10189 ; So # [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN +1018C..1018E ; So # [3] GREEK SINUSOID SIGN..NOMISMA SIGN +10190..1019C ; So # [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL +101A0 ; So # GREEK SYMBOL TAU RHO +101D0..101FC ; So # [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +10877..10878 ; So # [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON +10AC8 ; So # MANICHAEAN SIGN UD +1173F ; So # AHOM SYMBOL VI +11FD5..11FDC ; So # [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI +11FE1..11FF1 ; So # [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA +16B3C..16B3F ; So # [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB +16B45 ; So # PAHAWH HMONG SIGN CIM TSOV ROG +1BC9C ; So # DUPLOYAN SIGN O WITH CROSS +1CF50..1CFC3 ; So # [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK +1D000..1D0F5 ; So # [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D100..1D126 ; So # [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D129..1D164 ; So # [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE +1D16A..1D16C ; So # [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3 +1D183..1D184 ; So # [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN +1D18C..1D1A9 ; So # [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH +1D1AE..1D1EA ; So # [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON +1D200..1D241 ; So # [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 +1D245 ; So # GREEK MUSICAL LEIMMA +1D300..1D356 ; So # [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D800..1D9FF ; So # [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD +1DA37..1DA3A ; So # [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE +1DA6D..1DA74 ; So # [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING +1DA76..1DA83 ; So # [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH +1DA85..1DA86 ; So # [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS +1E14F ; So # NYIAKENG PUACHUE HMONG CIRCLED CA +1ECAC ; So # INDIC SIYAQ PLACEHOLDER +1ED2E ; So # OTTOMAN SIYAQ MARRATAN +1F000..1F02B ; So # [44] MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F030..1F093 ; So # [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F0A0..1F0AE ; So # [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0B1..1F0BF ; So # [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER +1F0C1..1F0CF ; So # [15] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER +1F0D1..1F0F5 ; So # [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 +1F10D..1F1AD ; So # [161] CIRCLED ZERO WITH SLASH..MASK WORK SYMBOL +1F1E6..1F202 ; So # [29] REGIONAL INDICATOR SYMBOL LETTER A..SQUARED KATAKANA SA +1F210..1F23B ; So # [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D +1F240..1F248 ; So # [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F250..1F251 ; So # [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F260..1F265 ; So # [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F300..1F3FA ; So # [251] CYCLONE..AMPHORA +1F400..1F6D7 ; So # [728] RAT..ELEVATOR +1F6DC..1F6EC ; So # [17] WIRELESS..AIRPLANE ARRIVING +1F6F0..1F6FC ; So # [13] SATELLITE..ROLLER SKATE +1F700..1F776 ; So # [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE +1F77B..1F7D9 ; So # [95] HAUMEA..NINE POINTED WHITE STAR +1F7E0..1F7EB ; So # [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE +1F7F0 ; So # HEAVY EQUALS SIGN +1F800..1F80B ; So # [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD +1F810..1F847 ; So # [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW +1F850..1F859 ; So # [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW +1F860..1F887 ; So # [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW +1F890..1F8AD ; So # [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8B0..1F8B1 ; So # [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST +1F900..1FA53 ; So # [340] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS KNIGHT-BISHOP +1FA60..1FA6D ; So # [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER +1FA70..1FA7C ; So # [13] BALLET SHOES..CRUTCH +1FA80..1FA88 ; So # [9] YO-YO..FLUTE +1FA90..1FABD ; So # [46] RINGED PLANET..WING +1FABF..1FAC5 ; So # [7] GOOSE..PERSON WITH CROWN +1FACE..1FADB ; So # [14] MOOSE..PEA POD +1FAE0..1FAE8 ; So # [9] MELTING FACE..SHAKING FACE +1FAF0..1FAF8 ; So # [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FB00..1FB92 ; So # [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA ; So # [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON + +# Total code points: 6639 + +# ================================================ + +# General_Category=Initial_Punctuation + +00AB ; Pi # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +2018 ; Pi # LEFT SINGLE QUOTATION MARK +201B..201C ; Pi # [2] SINGLE HIGH-REVERSED-9 QUOTATION MARK..LEFT DOUBLE QUOTATION MARK +201F ; Pi # DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2039 ; Pi # SINGLE LEFT-POINTING ANGLE QUOTATION MARK +2E02 ; Pi # LEFT SUBSTITUTION BRACKET +2E04 ; Pi # LEFT DOTTED SUBSTITUTION BRACKET +2E09 ; Pi # LEFT TRANSPOSITION BRACKET +2E0C ; Pi # LEFT RAISED OMISSION BRACKET +2E1C ; Pi # LEFT LOW PARAPHRASE BRACKET +2E20 ; Pi # LEFT VERTICAL BAR WITH QUILL + +# Total code points: 12 + +# ================================================ + +# General_Category=Final_Punctuation + +00BB ; Pf # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +2019 ; Pf # RIGHT SINGLE QUOTATION MARK +201D ; Pf # RIGHT DOUBLE QUOTATION MARK +203A ; Pf # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +2E03 ; Pf # RIGHT SUBSTITUTION BRACKET +2E05 ; Pf # RIGHT DOTTED SUBSTITUTION BRACKET +2E0A ; Pf # RIGHT TRANSPOSITION BRACKET +2E0D ; Pf # RIGHT RAISED OMISSION BRACKET +2E1D ; Pf # RIGHT LOW PARAPHRASE BRACKET +2E21 ; Pf # RIGHT VERTICAL BAR WITH QUILL + +# Total code points: 10 + +# EOF diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/EastAsianWidth.txt b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/EastAsianWidth.txt new file mode 100644 index 000000000..02df4df47 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/EastAsianWidth.txt @@ -0,0 +1,2621 @@ +# EastAsianWidth-15.1.0.txt +# Date: 2023-07-28, 23:34:08 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ +# +# East_Asian_Width Property +# +# This file is a normative contributory data file in the +# Unicode Character Database. +# +# The format is two fields separated by a semicolon. +# Field 0: Unicode code point value or range of code point values +# Field 1: East_Asian_Width property, consisting of one of the following values: +# "A", "F", "H", "N", "Na", "W" +# - All code points, assigned or unassigned, that are not listed +# explicitly are given the value "N". +# - The unassigned code points in the following blocks default to "W": +# CJK Unified Ideographs Extension A: U+3400..U+4DBF +# CJK Unified Ideographs: U+4E00..U+9FFF +# CJK Compatibility Ideographs: U+F900..U+FAFF +# - All undesignated code points in Planes 2 and 3, whether inside or +# outside of allocated blocks, default to "W": +# Plane 2: U+20000..U+2FFFD +# Plane 3: U+30000..U+3FFFD +# +# Character ranges are specified as for other property files in the +# Unicode Character Database. +# +# The comments following the number sign "#" list the General_Category +# property value or the L& alias of the derived value LC, the Unicode +# character name or names, and, in lines with ranges of code points, +# the code point count in square brackets. +# +# For more information, see UAX #11: East Asian Width, +# at https://www.unicode.org/reports/tr11/ +# +# @missing: 0000..10FFFF; N +0000..001F ; N # Cc [32] .. +0020 ; Na # Zs SPACE +0021..0023 ; Na # Po [3] EXCLAMATION MARK..NUMBER SIGN +0024 ; Na # Sc DOLLAR SIGN +0025..0027 ; Na # Po [3] PERCENT SIGN..APOSTROPHE +0028 ; Na # Ps LEFT PARENTHESIS +0029 ; Na # Pe RIGHT PARENTHESIS +002A ; Na # Po ASTERISK +002B ; Na # Sm PLUS SIGN +002C ; Na # Po COMMA +002D ; Na # Pd HYPHEN-MINUS +002E..002F ; Na # Po [2] FULL STOP..SOLIDUS +0030..0039 ; Na # Nd [10] DIGIT ZERO..DIGIT NINE +003A..003B ; Na # Po [2] COLON..SEMICOLON +003C..003E ; Na # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN +003F..0040 ; Na # Po [2] QUESTION MARK..COMMERCIAL AT +0041..005A ; Na # Lu [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +005B ; Na # Ps LEFT SQUARE BRACKET +005C ; Na # Po REVERSE SOLIDUS +005D ; Na # Pe RIGHT SQUARE BRACKET +005E ; Na # Sk CIRCUMFLEX ACCENT +005F ; Na # Pc LOW LINE +0060 ; Na # Sk GRAVE ACCENT +0061..007A ; Na # Ll [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +007B ; Na # Ps LEFT CURLY BRACKET +007C ; Na # Sm VERTICAL LINE +007D ; Na # Pe RIGHT CURLY BRACKET +007E ; Na # Sm TILDE +007F ; N # Cc +0080..009F ; N # Cc [32] .. +00A0 ; N # Zs NO-BREAK SPACE +00A1 ; A # Po INVERTED EXCLAMATION MARK +00A2..00A3 ; Na # Sc [2] CENT SIGN..POUND SIGN +00A4 ; A # Sc CURRENCY SIGN +00A5 ; Na # Sc YEN SIGN +00A6 ; Na # So BROKEN BAR +00A7 ; A # Po SECTION SIGN +00A8 ; A # Sk DIAERESIS +00A9 ; N # So COPYRIGHT SIGN +00AA ; A # Lo FEMININE ORDINAL INDICATOR +00AB ; N # Pi LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00AC ; Na # Sm NOT SIGN +00AD ; A # Cf SOFT HYPHEN +00AE ; A # So REGISTERED SIGN +00AF ; Na # Sk MACRON +00B0 ; A # So DEGREE SIGN +00B1 ; A # Sm PLUS-MINUS SIGN +00B2..00B3 ; A # No [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE +00B4 ; A # Sk ACUTE ACCENT +00B5 ; N # Ll MICRO SIGN +00B6..00B7 ; A # Po [2] PILCROW SIGN..MIDDLE DOT +00B8 ; A # Sk CEDILLA +00B9 ; A # No SUPERSCRIPT ONE +00BA ; A # Lo MASCULINE ORDINAL INDICATOR +00BB ; N # Pf RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +00BC..00BE ; A # No [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS +00BF ; A # Po INVERTED QUESTION MARK +00C0..00C5 ; N # Lu [6] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER A WITH RING ABOVE +00C6 ; A # Lu LATIN CAPITAL LETTER AE +00C7..00CF ; N # Lu [9] LATIN CAPITAL LETTER C WITH CEDILLA..LATIN CAPITAL LETTER I WITH DIAERESIS +00D0 ; A # Lu LATIN CAPITAL LETTER ETH +00D1..00D6 ; N # Lu [6] LATIN CAPITAL LETTER N WITH TILDE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D7 ; A # Sm MULTIPLICATION SIGN +00D8 ; A # Lu LATIN CAPITAL LETTER O WITH STROKE +00D9..00DD ; N # Lu [5] LATIN CAPITAL LETTER U WITH GRAVE..LATIN CAPITAL LETTER Y WITH ACUTE +00DE..00E1 ; A # L& [4] LATIN CAPITAL LETTER THORN..LATIN SMALL LETTER A WITH ACUTE +00E2..00E5 ; N # Ll [4] LATIN SMALL LETTER A WITH CIRCUMFLEX..LATIN SMALL LETTER A WITH RING ABOVE +00E6 ; A # Ll LATIN SMALL LETTER AE +00E7 ; N # Ll LATIN SMALL LETTER C WITH CEDILLA +00E8..00EA ; A # Ll [3] LATIN SMALL LETTER E WITH GRAVE..LATIN SMALL LETTER E WITH CIRCUMFLEX +00EB ; N # Ll LATIN SMALL LETTER E WITH DIAERESIS +00EC..00ED ; A # Ll [2] LATIN SMALL LETTER I WITH GRAVE..LATIN SMALL LETTER I WITH ACUTE +00EE..00EF ; N # Ll [2] LATIN SMALL LETTER I WITH CIRCUMFLEX..LATIN SMALL LETTER I WITH DIAERESIS +00F0 ; A # Ll LATIN SMALL LETTER ETH +00F1 ; N # Ll LATIN SMALL LETTER N WITH TILDE +00F2..00F3 ; A # Ll [2] LATIN SMALL LETTER O WITH GRAVE..LATIN SMALL LETTER O WITH ACUTE +00F4..00F6 ; N # Ll [3] LATIN SMALL LETTER O WITH CIRCUMFLEX..LATIN SMALL LETTER O WITH DIAERESIS +00F7 ; A # Sm DIVISION SIGN +00F8..00FA ; A # Ll [3] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER U WITH ACUTE +00FB ; N # Ll LATIN SMALL LETTER U WITH CIRCUMFLEX +00FC ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS +00FD ; N # Ll LATIN SMALL LETTER Y WITH ACUTE +00FE ; A # Ll LATIN SMALL LETTER THORN +00FF ; N # Ll LATIN SMALL LETTER Y WITH DIAERESIS +0100 ; N # Lu LATIN CAPITAL LETTER A WITH MACRON +0101 ; A # Ll LATIN SMALL LETTER A WITH MACRON +0102..0110 ; N # L& [15] LATIN CAPITAL LETTER A WITH BREVE..LATIN CAPITAL LETTER D WITH STROKE +0111 ; A # Ll LATIN SMALL LETTER D WITH STROKE +0112 ; N # Lu LATIN CAPITAL LETTER E WITH MACRON +0113 ; A # Ll LATIN SMALL LETTER E WITH MACRON +0114..011A ; N # L& [7] LATIN CAPITAL LETTER E WITH BREVE..LATIN CAPITAL LETTER E WITH CARON +011B ; A # Ll LATIN SMALL LETTER E WITH CARON +011C..0125 ; N # L& [10] LATIN CAPITAL LETTER G WITH CIRCUMFLEX..LATIN SMALL LETTER H WITH CIRCUMFLEX +0126..0127 ; A # L& [2] LATIN CAPITAL LETTER H WITH STROKE..LATIN SMALL LETTER H WITH STROKE +0128..012A ; N # L& [3] LATIN CAPITAL LETTER I WITH TILDE..LATIN CAPITAL LETTER I WITH MACRON +012B ; A # Ll LATIN SMALL LETTER I WITH MACRON +012C..0130 ; N # L& [5] LATIN CAPITAL LETTER I WITH BREVE..LATIN CAPITAL LETTER I WITH DOT ABOVE +0131..0133 ; A # L& [3] LATIN SMALL LETTER DOTLESS I..LATIN SMALL LIGATURE IJ +0134..0137 ; N # L& [4] LATIN CAPITAL LETTER J WITH CIRCUMFLEX..LATIN SMALL LETTER K WITH CEDILLA +0138 ; A # Ll LATIN SMALL LETTER KRA +0139..013E ; N # L& [6] LATIN CAPITAL LETTER L WITH ACUTE..LATIN SMALL LETTER L WITH CARON +013F..0142 ; A # L& [4] LATIN CAPITAL LETTER L WITH MIDDLE DOT..LATIN SMALL LETTER L WITH STROKE +0143 ; N # Lu LATIN CAPITAL LETTER N WITH ACUTE +0144 ; A # Ll LATIN SMALL LETTER N WITH ACUTE +0145..0147 ; N # L& [3] LATIN CAPITAL LETTER N WITH CEDILLA..LATIN CAPITAL LETTER N WITH CARON +0148..014B ; A # L& [4] LATIN SMALL LETTER N WITH CARON..LATIN SMALL LETTER ENG +014C ; N # Lu LATIN CAPITAL LETTER O WITH MACRON +014D ; A # Ll LATIN SMALL LETTER O WITH MACRON +014E..0151 ; N # L& [4] LATIN CAPITAL LETTER O WITH BREVE..LATIN SMALL LETTER O WITH DOUBLE ACUTE +0152..0153 ; A # L& [2] LATIN CAPITAL LIGATURE OE..LATIN SMALL LIGATURE OE +0154..0165 ; N # L& [18] LATIN CAPITAL LETTER R WITH ACUTE..LATIN SMALL LETTER T WITH CARON +0166..0167 ; A # L& [2] LATIN CAPITAL LETTER T WITH STROKE..LATIN SMALL LETTER T WITH STROKE +0168..016A ; N # L& [3] LATIN CAPITAL LETTER U WITH TILDE..LATIN CAPITAL LETTER U WITH MACRON +016B ; A # Ll LATIN SMALL LETTER U WITH MACRON +016C..017F ; N # L& [20] LATIN CAPITAL LETTER U WITH BREVE..LATIN SMALL LETTER LONG S +0180..01BA ; N # L& [59] LATIN SMALL LETTER B WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL +01BB ; N # Lo LATIN LETTER TWO WITH STROKE +01BC..01BF ; N # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN +01C0..01C3 ; N # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +01C4..01CD ; N # L& [10] LATIN CAPITAL LETTER DZ WITH CARON..LATIN CAPITAL LETTER A WITH CARON +01CE ; A # Ll LATIN SMALL LETTER A WITH CARON +01CF ; N # Lu LATIN CAPITAL LETTER I WITH CARON +01D0 ; A # Ll LATIN SMALL LETTER I WITH CARON +01D1 ; N # Lu LATIN CAPITAL LETTER O WITH CARON +01D2 ; A # Ll LATIN SMALL LETTER O WITH CARON +01D3 ; N # Lu LATIN CAPITAL LETTER U WITH CARON +01D4 ; A # Ll LATIN SMALL LETTER U WITH CARON +01D5 ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON +01D6 ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND MACRON +01D7 ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE +01D8 ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE +01D9 ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON +01DA ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND CARON +01DB ; N # Lu LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE +01DC ; A # Ll LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE +01DD..024F ; N # L& [115] LATIN SMALL LETTER TURNED E..LATIN SMALL LETTER Y WITH STROKE +0250 ; N # Ll LATIN SMALL LETTER TURNED A +0251 ; A # Ll LATIN SMALL LETTER ALPHA +0252..0260 ; N # Ll [15] LATIN SMALL LETTER TURNED ALPHA..LATIN SMALL LETTER G WITH HOOK +0261 ; A # Ll LATIN SMALL LETTER SCRIPT G +0262..0293 ; N # Ll [50] LATIN LETTER SMALL CAPITAL G..LATIN SMALL LETTER EZH WITH CURL +0294 ; N # Lo LATIN LETTER GLOTTAL STOP +0295..02AF ; N # Ll [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +02B0..02C1 ; N # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C2..02C3 ; N # Sk [2] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER RIGHT ARROWHEAD +02C4 ; A # Sk MODIFIER LETTER UP ARROWHEAD +02C5 ; N # Sk MODIFIER LETTER DOWN ARROWHEAD +02C6 ; N # Lm MODIFIER LETTER CIRCUMFLEX ACCENT +02C7 ; A # Lm CARON +02C8 ; N # Lm MODIFIER LETTER VERTICAL LINE +02C9..02CB ; A # Lm [3] MODIFIER LETTER MACRON..MODIFIER LETTER GRAVE ACCENT +02CC ; N # Lm MODIFIER LETTER LOW VERTICAL LINE +02CD ; A # Lm MODIFIER LETTER LOW MACRON +02CE..02CF ; N # Lm [2] MODIFIER LETTER LOW GRAVE ACCENT..MODIFIER LETTER LOW ACUTE ACCENT +02D0 ; A # Lm MODIFIER LETTER TRIANGULAR COLON +02D1 ; N # Lm MODIFIER LETTER HALF TRIANGULAR COLON +02D2..02D7 ; N # Sk [6] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER MINUS SIGN +02D8..02DB ; A # Sk [4] BREVE..OGONEK +02DC ; N # Sk SMALL TILDE +02DD ; A # Sk DOUBLE ACUTE ACCENT +02DE ; N # Sk MODIFIER LETTER RHOTIC HOOK +02DF ; A # Sk MODIFIER LETTER CROSS ACCENT +02E0..02E4 ; N # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02E5..02EB ; N # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02EC ; N # Lm MODIFIER LETTER VOICING +02ED ; N # Sk MODIFIER LETTER UNASPIRATED +02EE ; N # Lm MODIFIER LETTER DOUBLE APOSTROPHE +02EF..02FF ; N # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0300..036F ; A # Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X +0370..0373 ; N # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI +0374 ; N # Lm GREEK NUMERAL SIGN +0375 ; N # Sk GREEK LOWER NUMERAL SIGN +0376..0377 ; N # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037A ; N # Lm GREEK YPOGEGRAMMENI +037B..037D ; N # Ll [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +037E ; N # Po GREEK QUESTION MARK +037F ; N # Lu GREEK CAPITAL LETTER YOT +0384..0385 ; N # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS +0386 ; N # Lu GREEK CAPITAL LETTER ALPHA WITH TONOS +0387 ; N # Po GREEK ANO TELEIA +0388..038A ; N # Lu [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; N # Lu GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..0390 ; N # L& [3] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +0391..03A1 ; A # Lu [17] GREEK CAPITAL LETTER ALPHA..GREEK CAPITAL LETTER RHO +03A3..03A9 ; A # Lu [7] GREEK CAPITAL LETTER SIGMA..GREEK CAPITAL LETTER OMEGA +03AA..03B0 ; N # L& [7] GREEK CAPITAL LETTER IOTA WITH DIALYTIKA..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS +03B1..03C1 ; A # Ll [17] GREEK SMALL LETTER ALPHA..GREEK SMALL LETTER RHO +03C2 ; N # Ll GREEK SMALL LETTER FINAL SIGMA +03C3..03C9 ; A # Ll [7] GREEK SMALL LETTER SIGMA..GREEK SMALL LETTER OMEGA +03CA..03F5 ; N # L& [44] GREEK SMALL LETTER IOTA WITH DIALYTIKA..GREEK LUNATE EPSILON SYMBOL +03F6 ; N # Sm GREEK REVERSED LUNATE EPSILON SYMBOL +03F7..03FF ; N # L& [9] GREEK CAPITAL LETTER SHO..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL +0400 ; N # Lu CYRILLIC CAPITAL LETTER IE WITH GRAVE +0401 ; A # Lu CYRILLIC CAPITAL LETTER IO +0402..040F ; N # Lu [14] CYRILLIC CAPITAL LETTER DJE..CYRILLIC CAPITAL LETTER DZHE +0410..044F ; A # L& [64] CYRILLIC CAPITAL LETTER A..CYRILLIC SMALL LETTER YA +0450 ; N # Ll CYRILLIC SMALL LETTER IE WITH GRAVE +0451 ; A # Ll CYRILLIC SMALL LETTER IO +0452..0481 ; N # L& [48] CYRILLIC SMALL LETTER DJE..CYRILLIC SMALL LETTER KOPPA +0482 ; N # So CYRILLIC THOUSANDS SIGN +0483..0487 ; N # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0488..0489 ; N # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +048A..04FF ; N # L& [118] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER HA WITH STROKE +0500..052F ; N # L& [48] CYRILLIC CAPITAL LETTER KOMI DE..CYRILLIC SMALL LETTER EL WITH DESCENDER +0531..0556 ; N # Lu [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +0559 ; N # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING +055A..055F ; N # Po [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0560..0588 ; N # Ll [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +0589 ; N # Po ARMENIAN FULL STOP +058A ; N # Pd ARMENIAN HYPHEN +058D..058E ; N # So [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN +058F ; N # Sc ARMENIAN DRAM SIGN +0591..05BD ; N # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BE ; N # Pd HEBREW PUNCTUATION MAQAF +05BF ; N # Mn HEBREW POINT RAFE +05C0 ; N # Po HEBREW PUNCTUATION PASEQ +05C1..05C2 ; N # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C3 ; N # Po HEBREW PUNCTUATION SOF PASUQ +05C4..05C5 ; N # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C6 ; N # Po HEBREW PUNCTUATION NUN HAFUKHA +05C7 ; N # Mn HEBREW POINT QAMATS QATAN +05D0..05EA ; N # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; N # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +05F3..05F4 ; N # Po [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +0600..0605 ; N # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +0606..0608 ; N # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY +0609..060A ; N # Po [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN +060B ; N # Sc AFGHANI SIGN +060C..060D ; N # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR +060E..060F ; N # So [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA +0610..061A ; N # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +061B ; N # Po ARABIC SEMICOLON +061C ; N # Cf ARABIC LETTER MARK +061D..061F ; N # Po [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK +0620..063F ; N # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0640 ; N # Lm ARABIC TATWEEL +0641..064A ; N # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH +064B..065F ; N # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0660..0669 ; N # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +066A..066D ; N # Po [4] ARABIC PERCENT SIGN..ARABIC FIVE POINTED STAR +066E..066F ; N # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0670 ; N # Mn ARABIC LETTER SUPERSCRIPT ALEF +0671..06D3 ; N # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D4 ; N # Po ARABIC FULL STOP +06D5 ; N # Lo ARABIC LETTER AE +06D6..06DC ; N # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DD ; N # Cf ARABIC END OF AYAH +06DE ; N # So ARABIC START OF RUB EL HIZB +06DF..06E4 ; N # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E5..06E6 ; N # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH +06E7..06E8 ; N # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06E9 ; N # So ARABIC PLACE OF SAJDAH +06EA..06ED ; N # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +06EE..06EF ; N # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06F0..06F9 ; N # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +06FA..06FC ; N # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FD..06FE ; N # So [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN +06FF ; N # Lo ARABIC LETTER HEH WITH INVERTED V +0700..070D ; N # Po [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +070F ; N # Cf SYRIAC ABBREVIATION MARK +0710 ; N # Lo SYRIAC LETTER ALAPH +0711 ; N # Mn SYRIAC LETTER SUPERSCRIPT ALAPH +0712..072F ; N # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +0730..074A ; N # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +074D..074F ; N # Lo [3] SYRIAC LETTER SOGDIAN ZHAIN..SYRIAC LETTER SOGDIAN FE +0750..077F ; N # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE +0780..07A5 ; N # Lo [38] THAANA LETTER HAA..THAANA LETTER WAAVU +07A6..07B0 ; N # Mn [11] THAANA ABAFILI..THAANA SUKUN +07B1 ; N # Lo THAANA LETTER NAA +07C0..07C9 ; N # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE +07CA..07EA ; N # Lo [33] NKO LETTER A..NKO LETTER JONA RA +07EB..07F3 ; N # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07F4..07F5 ; N # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07F6 ; N # So NKO SYMBOL OO DENNEN +07F7..07F9 ; N # Po [3] NKO SYMBOL GBAKURUNEN..NKO EXCLAMATION MARK +07FA ; N # Lm NKO LAJANYALAN +07FD ; N # Mn NKO DANTAYALAN +07FE..07FF ; N # Sc [2] NKO DOROME SIGN..NKO TAMAN SIGN +0800..0815 ; N # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +0816..0819 ; N # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081A ; N # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT +081B..0823 ; N # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0824 ; N # Lm SAMARITAN MODIFIER LETTER SHORT A +0825..0827 ; N # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0828 ; N # Lm SAMARITAN MODIFIER LETTER I +0829..082D ; N # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0830..083E ; N # Po [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +0840..0858 ; N # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0859..085B ; N # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +085E ; N # Po MANDAIC PUNCTUATION +0860..086A ; N # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; N # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0888 ; N # Sk ARABIC RAISED ROUND DOT +0889..088E ; N # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0890..0891 ; N # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +0898..089F ; N # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08A0..08C8 ; N # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +08C9 ; N # Lm ARABIC SMALL FARSI YEH +08CA..08E1 ; N # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E2 ; N # Cf ARABIC DISPUTED END OF AYAH +08E3..08FF ; N # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA +0900..0902 ; N # Mn [3] DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI SIGN ANUSVARA +0903 ; N # Mc DEVANAGARI SIGN VISARGA +0904..0939 ; N # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093A ; N # Mn DEVANAGARI VOWEL SIGN OE +093B ; N # Mc DEVANAGARI VOWEL SIGN OOE +093C ; N # Mn DEVANAGARI SIGN NUKTA +093D ; N # Lo DEVANAGARI SIGN AVAGRAHA +093E..0940 ; N # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0941..0948 ; N # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +0949..094C ; N # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094D ; N # Mn DEVANAGARI SIGN VIRAMA +094E..094F ; N # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0950 ; N # Lo DEVANAGARI OM +0951..0957 ; N # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0958..0961 ; N # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0962..0963 ; N # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0964..0965 ; N # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0966..096F ; N # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +0970 ; N # Po DEVANAGARI ABBREVIATION SIGN +0971 ; N # Lm DEVANAGARI SIGN HIGH SPACING DOT +0972..097F ; N # Lo [14] DEVANAGARI LETTER CANDRA A..DEVANAGARI LETTER BBA +0980 ; N # Lo BENGALI ANJI +0981 ; N # Mn BENGALI SIGN CANDRABINDU +0982..0983 ; N # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +0985..098C ; N # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; N # Lo [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; N # Lo [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; N # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; N # Lo BENGALI LETTER LA +09B6..09B9 ; N # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BC ; N # Mn BENGALI SIGN NUKTA +09BD ; N # Lo BENGALI SIGN AVAGRAHA +09BE..09C0 ; N # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C1..09C4 ; N # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09C7..09C8 ; N # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; N # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09CD ; N # Mn BENGALI SIGN VIRAMA +09CE ; N # Lo BENGALI LETTER KHANDA TA +09D7 ; N # Mc BENGALI AU LENGTH MARK +09DC..09DD ; N # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; N # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09E2..09E3 ; N # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09E6..09EF ; N # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +09F0..09F1 ; N # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09F2..09F3 ; N # Sc [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN +09F4..09F9 ; N # No [6] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY DENOMINATOR SIXTEEN +09FA ; N # So BENGALI ISSHAR +09FB ; N # Sc BENGALI GANDA MARK +09FC ; N # Lo BENGALI LETTER VEDIC ANUSVARA +09FD ; N # Po BENGALI ABBREVIATION SIGN +09FE ; N # Mn BENGALI SANDHI MARK +0A01..0A02 ; N # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A03 ; N # Mc GURMUKHI SIGN VISARGA +0A05..0A0A ; N # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; N # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; N # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; N # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; N # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; N # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; N # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A3C ; N # Mn GURMUKHI SIGN NUKTA +0A3E..0A40 ; N # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A41..0A42 ; N # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; N # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; N # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; N # Mn GURMUKHI SIGN UDAAT +0A59..0A5C ; N # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; N # Lo GURMUKHI LETTER FA +0A66..0A6F ; N # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0A70..0A71 ; N # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A72..0A74 ; N # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A75 ; N # Mn GURMUKHI SIGN YAKASH +0A76 ; N # Po GURMUKHI ABBREVIATION SIGN +0A81..0A82 ; N # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0A83 ; N # Mc GUJARATI SIGN VISARGA +0A85..0A8D ; N # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; N # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; N # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; N # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; N # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; N # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABC ; N # Mn GUJARATI SIGN NUKTA +0ABD ; N # Lo GUJARATI SIGN AVAGRAHA +0ABE..0AC0 ; N # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC1..0AC5 ; N # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; N # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0AC9 ; N # Mc GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; N # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0ACD ; N # Mn GUJARATI SIGN VIRAMA +0AD0 ; N # Lo GUJARATI OM +0AE0..0AE1 ; N # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AE2..0AE3 ; N # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AE6..0AEF ; N # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0AF0 ; N # Po GUJARATI ABBREVIATION SIGN +0AF1 ; N # Sc GUJARATI RUPEE SIGN +0AF9 ; N # Lo GUJARATI LETTER ZHA +0AFA..0AFF ; N # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; N # Mn ORIYA SIGN CANDRABINDU +0B02..0B03 ; N # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B05..0B0C ; N # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; N # Lo [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; N # Lo [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; N # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; N # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; N # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3C ; N # Mn ORIYA SIGN NUKTA +0B3D ; N # Lo ORIYA SIGN AVAGRAHA +0B3E ; N # Mc ORIYA VOWEL SIGN AA +0B3F ; N # Mn ORIYA VOWEL SIGN I +0B40 ; N # Mc ORIYA VOWEL SIGN II +0B41..0B44 ; N # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B47..0B48 ; N # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; N # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B4D ; N # Mn ORIYA SIGN VIRAMA +0B55..0B56 ; N # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B57 ; N # Mc ORIYA AU LENGTH MARK +0B5C..0B5D ; N # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; N # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B62..0B63 ; N # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B66..0B6F ; N # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B70 ; N # So ORIYA ISSHAR +0B71 ; N # Lo ORIYA LETTER WA +0B72..0B77 ; N # No [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0B82 ; N # Mn TAMIL SIGN ANUSVARA +0B83 ; N # Lo TAMIL SIGN VISARGA +0B85..0B8A ; N # Lo [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; N # Lo [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; N # Lo [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; N # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; N # Lo TAMIL LETTER JA +0B9E..0B9F ; N # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; N # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; N # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; N # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA +0BBE..0BBF ; N # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC0 ; N # Mn TAMIL VOWEL SIGN II +0BC1..0BC2 ; N # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; N # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; N # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BCD ; N # Mn TAMIL SIGN VIRAMA +0BD0 ; N # Lo TAMIL OM +0BD7 ; N # Mc TAMIL AU LENGTH MARK +0BE6..0BEF ; N # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0BF0..0BF2 ; N # No [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND +0BF3..0BF8 ; N # So [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN +0BF9 ; N # Sc TAMIL RUPEE SIGN +0BFA ; N # So TAMIL NUMBER SIGN +0C00 ; N # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C01..0C03 ; N # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04 ; N # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE +0C05..0C0C ; N # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; N # Lo [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; N # Lo [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; N # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3C ; N # Mn TELUGU SIGN NUKTA +0C3D ; N # Lo TELUGU SIGN AVAGRAHA +0C3E..0C40 ; N # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C41..0C44 ; N # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C46..0C48 ; N # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; N # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; N # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C58..0C5A ; N # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; N # Lo TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; N # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C62..0C63 ; N # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C66..0C6F ; N # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C77 ; N # Po TELUGU SIGN SIDDHAM +0C78..0C7E ; N # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR +0C7F ; N # So TELUGU SIGN TUUMU +0C80 ; N # Lo KANNADA SIGN SPACING CANDRABINDU +0C81 ; N # Mn KANNADA SIGN CANDRABINDU +0C82..0C83 ; N # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0C84 ; N # Po KANNADA SIGN SIDDHAM +0C85..0C8C ; N # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; N # Lo [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; N # Lo [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; N # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; N # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBC ; N # Mn KANNADA SIGN NUKTA +0CBD ; N # Lo KANNADA SIGN AVAGRAHA +0CBE ; N # Mc KANNADA VOWEL SIGN AA +0CBF ; N # Mn KANNADA VOWEL SIGN I +0CC0..0CC4 ; N # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC6 ; N # Mn KANNADA VOWEL SIGN E +0CC7..0CC8 ; N # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; N # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CCC..0CCD ; N # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CD5..0CD6 ; N # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CDD..0CDE ; N # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; N # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CE2..0CE3 ; N # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0CE6..0CEF ; N # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0CF1..0CF2 ; N # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0CF3 ; N # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D00..0D01 ; N # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D02..0D03 ; N # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D04..0D0C ; N # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; N # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; N # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3B..0D3C ; N # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D3D ; N # Lo MALAYALAM SIGN AVAGRAHA +0D3E..0D40 ; N # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D41..0D44 ; N # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D46..0D48 ; N # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; N # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D4D ; N # Mn MALAYALAM SIGN VIRAMA +0D4E ; N # Lo MALAYALAM LETTER DOT REPH +0D4F ; N # So MALAYALAM SIGN PARA +0D54..0D56 ; N # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D57 ; N # Mc MALAYALAM AU LENGTH MARK +0D58..0D5E ; N # No [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH +0D5F..0D61 ; N # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D62..0D63 ; N # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D66..0D6F ; N # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D70..0D78 ; N # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS +0D79 ; N # So MALAYALAM DATE MARK +0D7A..0D7F ; N # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D81 ; N # Mn SINHALA SIGN CANDRABINDU +0D82..0D83 ; N # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0D85..0D96 ; N # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; N # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; N # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; N # Lo SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; N # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0DCA ; N # Mn SINHALA SIGN AL-LAKUNA +0DCF..0DD1 ; N # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD2..0DD4 ; N # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; N # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD8..0DDF ; N # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DE6..0DEF ; N # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0DF2..0DF3 ; N # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0DF4 ; N # Po SINHALA PUNCTUATION KUNDDALIYA +0E01..0E30 ; N # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E31 ; N # Mn THAI CHARACTER MAI HAN-AKAT +0E32..0E33 ; N # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM +0E34..0E3A ; N # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E3F ; N # Sc THAI CURRENCY SYMBOL BAHT +0E40..0E45 ; N # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO +0E46 ; N # Lm THAI CHARACTER MAIYAMOK +0E47..0E4E ; N # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0E4F ; N # Po THAI CHARACTER FONGMAN +0E50..0E59 ; N # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE +0E5A..0E5B ; N # Po [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0E81..0E82 ; N # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG +0E84 ; N # Lo LAO LETTER KHO TAM +0E86..0E8A ; N # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM +0E8C..0EA3 ; N # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING +0EA5 ; N # Lo LAO LETTER LO LOOT +0EA7..0EB0 ; N # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A +0EB1 ; N # Mn LAO VOWEL SIGN MAI KAN +0EB2..0EB3 ; N # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM +0EB4..0EBC ; N # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EBD ; N # Lo LAO SEMIVOWEL SIGN NYO +0EC0..0EC4 ; N # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EC6 ; N # Lm LAO KO LA +0EC8..0ECE ; N # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN +0ED0..0ED9 ; N # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE +0EDC..0EDF ; N # Lo [4] LAO HO NO..LAO LETTER KHMU NYO +0F00 ; N # Lo TIBETAN SYLLABLE OM +0F01..0F03 ; N # So [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA +0F04..0F12 ; N # Po [15] TIBETAN MARK INITIAL YIG MGO MDUN MA..TIBETAN MARK RGYA GRAM SHAD +0F13 ; N # So TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN +0F14 ; N # Po TIBETAN MARK GTER TSHEG +0F15..0F17 ; N # So [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F18..0F19 ; N # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F1A..0F1F ; N # So [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F20..0F29 ; N # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +0F2A..0F33 ; N # No [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO +0F34 ; N # So TIBETAN MARK BSDUS RTAGS +0F35 ; N # Mn TIBETAN MARK NGAS BZUNG NYI ZLA +0F36 ; N # So TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F37 ; N # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F38 ; N # So TIBETAN MARK CHE MGO +0F39 ; N # Mn TIBETAN MARK TSA -PHRU +0F3A ; N # Ps TIBETAN MARK GUG RTAGS GYON +0F3B ; N # Pe TIBETAN MARK GUG RTAGS GYAS +0F3C ; N # Ps TIBETAN MARK ANG KHANG GYON +0F3D ; N # Pe TIBETAN MARK ANG KHANG GYAS +0F3E..0F3F ; N # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F40..0F47 ; N # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; N # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F71..0F7E ; N # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F7F ; N # Mc TIBETAN SIGN RNAM BCAD +0F80..0F84 ; N # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F85 ; N # Po TIBETAN MARK PALUTA +0F86..0F87 ; N # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F88..0F8C ; N # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +0F8D..0F97 ; N # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; N # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FBE..0FC5 ; N # So [8] TIBETAN KU RU KHA..TIBETAN SYMBOL RDO RJE +0FC6 ; N # Mn TIBETAN SYMBOL PADMA GDAN +0FC7..0FCC ; N # So [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCE..0FCF ; N # So [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM +0FD0..0FD4 ; N # Po [5] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA +0FD5..0FD8 ; N # So [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS +0FD9..0FDA ; N # Po [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS +1000..102A ; N # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU +102B..102C ; N # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +102D..1030 ; N # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1031 ; N # Mc MYANMAR VOWEL SIGN E +1032..1037 ; N # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1038 ; N # Mc MYANMAR SIGN VISARGA +1039..103A ; N # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103B..103C ; N # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +103D..103E ; N # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +103F ; N # Lo MYANMAR LETTER GREAT SA +1040..1049 ; N # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +104A..104F ; N # Po [6] MYANMAR SIGN LITTLE SECTION..MYANMAR SYMBOL GENITIVE +1050..1055 ; N # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL +1056..1057 ; N # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1058..1059 ; N # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105A..105D ; N # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE +105E..1060 ; N # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1061 ; N # Lo MYANMAR LETTER SGAW KAREN SHA +1062..1064 ; N # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1065..1066 ; N # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA +1067..106D ; N # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +106E..1070 ; N # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA +1071..1074 ; N # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1075..1081 ; N # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA +1082 ; N # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1083..1084 ; N # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1085..1086 ; N # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +1087..108C ; N # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108D ; N # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +108E ; N # Lo MYANMAR LETTER RUMAI PALAUNG FA +108F ; N # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5 +1090..1099 ; N # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +109A..109C ; N # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +109D ; N # Mn MYANMAR VOWEL SIGN AITON AI +109E..109F ; N # So [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION +10A0..10C5 ; N # Lu [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; N # Lu GEORGIAN CAPITAL LETTER YN +10CD ; N # Lu GEORGIAN CAPITAL LETTER AEN +10D0..10FA ; N # Ll [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FB ; N # Po GEORGIAN PARAGRAPH SEPARATOR +10FC ; N # Lm MODIFIER LETTER GEORGIAN NAR +10FD..10FF ; N # Ll [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1100..115F ; W # Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER +1160..11FF ; N # Lo [160] HANGUL JUNGSEONG FILLER..HANGUL JONGSEONG SSANGNIEUN +1200..1248 ; N # Lo [73] ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA +124A..124D ; N # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; N # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; N # Lo ETHIOPIC SYLLABLE QHWA +125A..125D ; N # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; N # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; N # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; N # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; N # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; N # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; N # Lo ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; N # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; N # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; N # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; N # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; N # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +135D..135F ; N # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1360..1368 ; N # Po [9] ETHIOPIC SECTION MARK..ETHIOPIC PARAGRAPH SEPARATOR +1369..137C ; N # No [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND +1380..138F ; N # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1390..1399 ; N # So [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +13A0..13F5 ; N # Lu [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +13F8..13FD ; N # Ll [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1400 ; N # Pd CANADIAN SYLLABICS HYPHEN +1401..166C ; N # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166D ; N # So CANADIAN SYLLABICS CHI SIGN +166E ; N # Po CANADIAN SYLLABICS FULL STOP +166F..167F ; N # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1680 ; N # Zs OGHAM SPACE MARK +1681..169A ; N # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +169B ; N # Ps OGHAM FEATHER MARK +169C ; N # Pe OGHAM REVERSED FEATHER MARK +16A0..16EA ; N # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EB..16ED ; N # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION +16EE..16F0 ; N # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +16F1..16F8 ; N # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; N # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA +1712..1714 ; N # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1715 ; N # Mc TAGALOG SIGN PAMUDPOD +171F ; N # Lo TAGALOG LETTER ARCHAIC RA +1720..1731 ; N # Lo [18] HANUNOO LETTER A..HANUNOO LETTER HA +1732..1733 ; N # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1734 ; N # Mc HANUNOO SIGN PAMUDPOD +1735..1736 ; N # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +1740..1751 ; N # Lo [18] BUHID LETTER A..BUHID LETTER HA +1752..1753 ; N # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1760..176C ; N # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; N # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1772..1773 ; N # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +1780..17B3 ; N # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17B4..17B5 ; N # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B6 ; N # Mc KHMER VOWEL SIGN AA +17B7..17BD ; N # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17BE..17C5 ; N # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C6 ; N # Mn KHMER SIGN NIKAHIT +17C7..17C8 ; N # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +17C9..17D3 ; N # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17D4..17D6 ; N # Po [3] KHMER SIGN KHAN..KHMER SIGN CAMNUC PII KUUH +17D7 ; N # Lm KHMER SIGN LEK TOO +17D8..17DA ; N # Po [3] KHMER SIGN BEYYAL..KHMER SIGN KOOMUUT +17DB ; N # Sc KHMER CURRENCY SYMBOL RIEL +17DC ; N # Lo KHMER SIGN AVAKRAHASANYA +17DD ; N # Mn KHMER SIGN ATTHACAN +17E0..17E9 ; N # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +17F0..17F9 ; N # No [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +1800..1805 ; N # Po [6] MONGOLIAN BIRGA..MONGOLIAN FOUR DOTS +1806 ; N # Pd MONGOLIAN TODO SOFT HYPHEN +1807..180A ; N # Po [4] MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER..MONGOLIAN NIRUGU +180B..180D ; N # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180E ; N # Cf MONGOLIAN VOWEL SEPARATOR +180F ; N # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR +1810..1819 ; N # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1820..1842 ; N # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1843 ; N # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN +1844..1878 ; N # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; N # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1885..1886 ; N # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +1887..18A8 ; N # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18A9 ; N # Mn MONGOLIAN LETTER ALI GALI DAGALGA +18AA ; N # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; N # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; N # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1920..1922 ; N # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1923..1926 ; N # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1927..1928 ; N # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1929..192B ; N # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; N # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1932 ; N # Mn LIMBU SMALL LETTER ANUSVARA +1933..1938 ; N # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1939..193B ; N # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1940 ; N # So LIMBU SIGN LOO +1944..1945 ; N # Po [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1946..194F ; N # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +1950..196D ; N # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI +1970..1974 ; N # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1980..19AB ; N # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19B0..19C9 ; N # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +19D0..19D9 ; N # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +19DA ; N # No NEW TAI LUE THAM DIGIT ONE +19DE..19DF ; N # So [2] NEW TAI LUE SIGN LAE..NEW TAI LUE SIGN LAEV +19E0..19FF ; N # So [32] KHMER SYMBOL PATHAMASAT..KHMER SYMBOL DAP-PRAM ROC +1A00..1A16 ; N # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1A17..1A18 ; N # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A19..1A1A ; N # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A1B ; N # Mn BUGINESE VOWEL SIGN AE +1A1E..1A1F ; N # Po [2] BUGINESE PALLAWA..BUGINESE END OF SECTION +1A20..1A54 ; N # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA +1A55 ; N # Mc TAI THAM CONSONANT SIGN MEDIAL RA +1A56 ; N # Mn TAI THAM CONSONANT SIGN MEDIAL LA +1A57 ; N # Mc TAI THAM CONSONANT SIGN LA TANG LAI +1A58..1A5E ; N # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; N # Mn TAI THAM SIGN SAKOT +1A61 ; N # Mc TAI THAM VOWEL SIGN A +1A62 ; N # Mn TAI THAM VOWEL SIGN MAI SAT +1A63..1A64 ; N # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A65..1A6C ; N # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A6D..1A72 ; N # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1A73..1A7C ; N # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; N # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT +1A80..1A89 ; N # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; N # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1AA0..1AA6 ; N # Po [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA7 ; N # Lm TAI THAM SIGN MAI YAMOK +1AA8..1AAD ; N # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1AB0..1ABD ; N # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABE ; N # Me COMBINING PARENTHESES OVERLAY +1ABF..1ACE ; N # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; N # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B04 ; N # Mc BALINESE SIGN BISAH +1B05..1B33 ; N # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B34 ; N # Mn BALINESE SIGN REREKAN +1B35 ; N # Mc BALINESE VOWEL SIGN TEDUNG +1B36..1B3A ; N # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3B ; N # Mc BALINESE VOWEL SIGN RA REPA TEDUNG +1B3C ; N # Mn BALINESE VOWEL SIGN LA LENGA +1B3D..1B41 ; N # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B42 ; N # Mn BALINESE VOWEL SIGN PEPET +1B43..1B44 ; N # Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG +1B45..1B4C ; N # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B50..1B59 ; N # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1B5A..1B60 ; N # Po [7] BALINESE PANTI..BALINESE PAMENENG +1B61..1B6A ; N # So [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE +1B6B..1B73 ; N # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B74..1B7C ; N # So [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +1B7D..1B7E ; N # Po [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG +1B80..1B81 ; N # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1B82 ; N # Mc SUNDANESE SIGN PANGWISAD +1B83..1BA0 ; N # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BA1 ; N # Mc SUNDANESE CONSONANT SIGN PAMINGKAL +1BA2..1BA5 ; N # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA6..1BA7 ; N # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BA8..1BA9 ; N # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAA ; N # Mc SUNDANESE SIGN PAMAAEH +1BAB..1BAD ; N # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BAE..1BAF ; N # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BB0..1BB9 ; N # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1BBA..1BBF ; N # Lo [6] SUNDANESE AVAGRAHA..SUNDANESE LETTER FINAL M +1BC0..1BE5 ; N # Lo [38] BATAK LETTER A..BATAK LETTER U +1BE6 ; N # Mn BATAK SIGN TOMPI +1BE7 ; N # Mc BATAK VOWEL SIGN E +1BE8..1BE9 ; N # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BEA..1BEC ; N # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BED ; N # Mn BATAK VOWEL SIGN KARO O +1BEE ; N # Mc BATAK VOWEL SIGN U +1BEF..1BF1 ; N # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1BF2..1BF3 ; N # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN +1BFC..1BFF ; N # Po [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C00..1C23 ; N # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C24..1C2B ; N # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C2C..1C33 ; N # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C34..1C35 ; N # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1C36..1C37 ; N # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1C3B..1C3F ; N # Po [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C40..1C49 ; N # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C4D..1C4F ; N # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C50..1C59 ; N # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +1C5A..1C77 ; N # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1C78..1C7D ; N # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1C7E..1C7F ; N # Po [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1C80..1C88 ; N # Ll [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1C90..1CBA ; N # Lu [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; N # Lu [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1CC0..1CC7 ; N # Po [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CD0..1CD2 ; N # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD3 ; N # Po VEDIC SIGN NIHSHVASA +1CD4..1CE0 ; N # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE1 ; N # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CE2..1CE8 ; N # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CE9..1CEC ; N # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CED ; N # Mn VEDIC SIGN TIRYAK +1CEE..1CF3 ; N # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF4 ; N # Mn VEDIC TONE CANDRA ABOVE +1CF5..1CF6 ; N # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CF7 ; N # Mc VEDIC SIGN ATIKRAMA +1CF8..1CF9 ; N # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1CFA ; N # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +1D00..1D2B ; N # Ll [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D2C..1D6A ; N # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D6B..1D77 ; N # Ll [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D78 ; N # Lm MODIFIER LETTER CYRILLIC EN +1D79..1D7F ; N # Ll [7] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER UPSILON WITH STROKE +1D80..1D9A ; N # Ll [27] LATIN SMALL LETTER B WITH PALATAL HOOK..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1D9B..1DBF ; N # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +1DC0..1DFF ; N # Mn [64] COMBINING DOTTED GRAVE ACCENT..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +1E00..1EFF ; N # L& [256] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER Y WITH LOOP +1F00..1F15 ; N # L& [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F18..1F1D ; N # Lu [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F45 ; N # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F48..1F4D ; N # Lu [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; N # Ll [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F59 ; N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; N # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F..1F7D ; N # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1FB4 ; N # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FBC ; N # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FBD ; N # Sk GREEK KORONIS +1FBE ; N # Ll GREEK PROSGEGRAMMENI +1FBF..1FC1 ; N # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI +1FC2..1FC4 ; N # Ll [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FCC ; N # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FCD..1FCF ; N # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI +1FD0..1FD3 ; N # Ll [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FDB ; N # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA +1FDD..1FDF ; N # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FE0..1FEC ; N # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FED..1FEF ; N # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA +1FF2..1FF4 ; N # Ll [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FFC ; N # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI +1FFD..1FFE ; N # Sk [2] GREEK OXIA..GREEK DASIA +2000..200A ; N # Zs [11] EN QUAD..HAIR SPACE +200B..200F ; N # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK +2010 ; A # Pd HYPHEN +2011..2012 ; N # Pd [2] NON-BREAKING HYPHEN..FIGURE DASH +2013..2015 ; A # Pd [3] EN DASH..HORIZONTAL BAR +2016 ; A # Po DOUBLE VERTICAL LINE +2017 ; N # Po DOUBLE LOW LINE +2018 ; A # Pi LEFT SINGLE QUOTATION MARK +2019 ; A # Pf RIGHT SINGLE QUOTATION MARK +201A ; N # Ps SINGLE LOW-9 QUOTATION MARK +201B ; N # Pi SINGLE HIGH-REVERSED-9 QUOTATION MARK +201C ; A # Pi LEFT DOUBLE QUOTATION MARK +201D ; A # Pf RIGHT DOUBLE QUOTATION MARK +201E ; N # Ps DOUBLE LOW-9 QUOTATION MARK +201F ; N # Pi DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2020..2022 ; A # Po [3] DAGGER..BULLET +2023 ; N # Po TRIANGULAR BULLET +2024..2027 ; A # Po [4] ONE DOT LEADER..HYPHENATION POINT +2028 ; N # Zl LINE SEPARATOR +2029 ; N # Zp PARAGRAPH SEPARATOR +202A..202E ; N # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +202F ; N # Zs NARROW NO-BREAK SPACE +2030 ; A # Po PER MILLE SIGN +2031 ; N # Po PER TEN THOUSAND SIGN +2032..2033 ; A # Po [2] PRIME..DOUBLE PRIME +2034 ; N # Po TRIPLE PRIME +2035 ; A # Po REVERSED PRIME +2036..2038 ; N # Po [3] REVERSED DOUBLE PRIME..CARET +2039 ; N # Pi SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A ; N # Pf SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +203B ; A # Po REFERENCE MARK +203C..203D ; N # Po [2] DOUBLE EXCLAMATION MARK..INTERROBANG +203E ; A # Po OVERLINE +203F..2040 ; N # Pc [2] UNDERTIE..CHARACTER TIE +2041..2043 ; N # Po [3] CARET INSERTION POINT..HYPHEN BULLET +2044 ; N # Sm FRACTION SLASH +2045 ; N # Ps LEFT SQUARE BRACKET WITH QUILL +2046 ; N # Pe RIGHT SQUARE BRACKET WITH QUILL +2047..2051 ; N # Po [11] DOUBLE QUESTION MARK..TWO ASTERISKS ALIGNED VERTICALLY +2052 ; N # Sm COMMERCIAL MINUS SIGN +2053 ; N # Po SWUNG DASH +2054 ; N # Pc INVERTED UNDERTIE +2055..205E ; N # Po [10] FLOWER PUNCTUATION MARK..VERTICAL FOUR DOTS +205F ; N # Zs MEDIUM MATHEMATICAL SPACE +2060..2064 ; N # Cf [5] WORD JOINER..INVISIBLE PLUS +2066..206F ; N # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +2070 ; N # No SUPERSCRIPT ZERO +2071 ; N # Lm SUPERSCRIPT LATIN SMALL LETTER I +2074 ; A # No SUPERSCRIPT FOUR +2075..2079 ; N # No [5] SUPERSCRIPT FIVE..SUPERSCRIPT NINE +207A..207C ; N # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN +207D ; N # Ps SUPERSCRIPT LEFT PARENTHESIS +207E ; N # Pe SUPERSCRIPT RIGHT PARENTHESIS +207F ; A # Lm SUPERSCRIPT LATIN SMALL LETTER N +2080 ; N # No SUBSCRIPT ZERO +2081..2084 ; A # No [4] SUBSCRIPT ONE..SUBSCRIPT FOUR +2085..2089 ; N # No [5] SUBSCRIPT FIVE..SUBSCRIPT NINE +208A..208C ; N # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN +208D ; N # Ps SUBSCRIPT LEFT PARENTHESIS +208E ; N # Pe SUBSCRIPT RIGHT PARENTHESIS +2090..209C ; N # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +20A0..20A8 ; N # Sc [9] EURO-CURRENCY SIGN..RUPEE SIGN +20A9 ; H # Sc WON SIGN +20AA..20AB ; N # Sc [2] NEW SHEQEL SIGN..DONG SIGN +20AC ; A # Sc EURO SIGN +20AD..20C0 ; N # Sc [20] KIP SIGN..SOM SIGN +20D0..20DC ; N # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20DD..20E0 ; N # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E1 ; N # Mn COMBINING LEFT RIGHT ARROW ABOVE +20E2..20E4 ; N # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +20E5..20F0 ; N # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2100..2101 ; N # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT +2102 ; N # Lu DOUBLE-STRUCK CAPITAL C +2103 ; A # So DEGREE CELSIUS +2104 ; N # So CENTRE LINE SYMBOL +2105 ; A # So CARE OF +2106 ; N # So CADA UNA +2107 ; N # Lu EULER CONSTANT +2108 ; N # So SCRUPLE +2109 ; A # So DEGREE FAHRENHEIT +210A..2112 ; N # L& [9] SCRIPT SMALL G..SCRIPT CAPITAL L +2113 ; A # Ll SCRIPT SMALL L +2114 ; N # So L B BAR SYMBOL +2115 ; N # Lu DOUBLE-STRUCK CAPITAL N +2116 ; A # So NUMERO SIGN +2117 ; N # So SOUND RECORDING COPYRIGHT +2118 ; N # Sm SCRIPT CAPITAL P +2119..211D ; N # Lu [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +211E..2120 ; N # So [3] PRESCRIPTION TAKE..SERVICE MARK +2121..2122 ; A # So [2] TELEPHONE SIGN..TRADE MARK SIGN +2123 ; N # So VERSICLE +2124 ; N # Lu DOUBLE-STRUCK CAPITAL Z +2125 ; N # So OUNCE SIGN +2126 ; A # Lu OHM SIGN +2127 ; N # So INVERTED OHM SIGN +2128 ; N # Lu BLACK-LETTER CAPITAL Z +2129 ; N # So TURNED GREEK SMALL LETTER IOTA +212A ; N # Lu KELVIN SIGN +212B ; A # Lu ANGSTROM SIGN +212C..212D ; N # Lu [2] SCRIPT CAPITAL B..BLACK-LETTER CAPITAL C +212E ; N # So ESTIMATED SYMBOL +212F..2134 ; N # L& [6] SCRIPT SMALL E..SCRIPT SMALL O +2135..2138 ; N # Lo [4] ALEF SYMBOL..DALET SYMBOL +2139 ; N # Ll INFORMATION SOURCE +213A..213B ; N # So [2] ROTATED CAPITAL Q..FACSIMILE SIGN +213C..213F ; N # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI +2140..2144 ; N # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y +2145..2149 ; N # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J +214A ; N # So PROPERTY LINE +214B ; N # Sm TURNED AMPERSAND +214C..214D ; N # So [2] PER SIGN..AKTIESELSKAB +214E ; N # Ll TURNED SMALL F +214F ; N # So SYMBOL FOR SAMARITAN SOURCE +2150..2152 ; N # No [3] VULGAR FRACTION ONE SEVENTH..VULGAR FRACTION ONE TENTH +2153..2154 ; A # No [2] VULGAR FRACTION ONE THIRD..VULGAR FRACTION TWO THIRDS +2155..215A ; N # No [6] VULGAR FRACTION ONE FIFTH..VULGAR FRACTION FIVE SIXTHS +215B..215E ; A # No [4] VULGAR FRACTION ONE EIGHTH..VULGAR FRACTION SEVEN EIGHTHS +215F ; N # No FRACTION NUMERATOR ONE +2160..216B ; A # Nl [12] ROMAN NUMERAL ONE..ROMAN NUMERAL TWELVE +216C..216F ; N # Nl [4] ROMAN NUMERAL FIFTY..ROMAN NUMERAL ONE THOUSAND +2170..2179 ; A # Nl [10] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL TEN +217A..2182 ; N # Nl [9] SMALL ROMAN NUMERAL ELEVEN..ROMAN NUMERAL TEN THOUSAND +2183..2184 ; N # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C +2185..2188 ; N # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +2189 ; A # No VULGAR FRACTION ZERO THIRDS +218A..218B ; N # So [2] TURNED DIGIT TWO..TURNED DIGIT THREE +2190..2194 ; A # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW +2195..2199 ; A # So [5] UP DOWN ARROW..SOUTH WEST ARROW +219A..219B ; N # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE +219C..219F ; N # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW +21A0 ; N # Sm RIGHTWARDS TWO HEADED ARROW +21A1..21A2 ; N # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL +21A3 ; N # Sm RIGHTWARDS ARROW WITH TAIL +21A4..21A5 ; N # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR +21A6 ; N # Sm RIGHTWARDS ARROW FROM BAR +21A7..21AD ; N # So [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW +21AE ; N # Sm LEFT RIGHT ARROW WITH STROKE +21AF..21B7 ; N # So [9] DOWNWARDS ZIGZAG ARROW..CLOCKWISE TOP SEMICIRCLE ARROW +21B8..21B9 ; A # So [2] NORTH WEST ARROW TO LONG BAR..LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR +21BA..21CD ; N # So [20] ANTICLOCKWISE OPEN CIRCLE ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE +21CE..21CF ; N # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE +21D0..21D1 ; N # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW +21D2 ; A # Sm RIGHTWARDS DOUBLE ARROW +21D3 ; N # So DOWNWARDS DOUBLE ARROW +21D4 ; A # Sm LEFT RIGHT DOUBLE ARROW +21D5..21E6 ; N # So [18] UP DOWN DOUBLE ARROW..LEFTWARDS WHITE ARROW +21E7 ; A # So UPWARDS WHITE ARROW +21E8..21F3 ; N # So [12] RIGHTWARDS WHITE ARROW..UP DOWN WHITE ARROW +21F4..21FF ; N # Sm [12] RIGHT ARROW WITH SMALL CIRCLE..LEFT RIGHT OPEN-HEADED ARROW +2200 ; A # Sm FOR ALL +2201 ; N # Sm COMPLEMENT +2202..2203 ; A # Sm [2] PARTIAL DIFFERENTIAL..THERE EXISTS +2204..2206 ; N # Sm [3] THERE DOES NOT EXIST..INCREMENT +2207..2208 ; A # Sm [2] NABLA..ELEMENT OF +2209..220A ; N # Sm [2] NOT AN ELEMENT OF..SMALL ELEMENT OF +220B ; A # Sm CONTAINS AS MEMBER +220C..220E ; N # Sm [3] DOES NOT CONTAIN AS MEMBER..END OF PROOF +220F ; A # Sm N-ARY PRODUCT +2210 ; N # Sm N-ARY COPRODUCT +2211 ; A # Sm N-ARY SUMMATION +2212..2214 ; N # Sm [3] MINUS SIGN..DOT PLUS +2215 ; A # Sm DIVISION SLASH +2216..2219 ; N # Sm [4] SET MINUS..BULLET OPERATOR +221A ; A # Sm SQUARE ROOT +221B..221C ; N # Sm [2] CUBE ROOT..FOURTH ROOT +221D..2220 ; A # Sm [4] PROPORTIONAL TO..ANGLE +2221..2222 ; N # Sm [2] MEASURED ANGLE..SPHERICAL ANGLE +2223 ; A # Sm DIVIDES +2224 ; N # Sm DOES NOT DIVIDE +2225 ; A # Sm PARALLEL TO +2226 ; N # Sm NOT PARALLEL TO +2227..222C ; A # Sm [6] LOGICAL AND..DOUBLE INTEGRAL +222D ; N # Sm TRIPLE INTEGRAL +222E ; A # Sm CONTOUR INTEGRAL +222F..2233 ; N # Sm [5] SURFACE INTEGRAL..ANTICLOCKWISE CONTOUR INTEGRAL +2234..2237 ; A # Sm [4] THEREFORE..PROPORTION +2238..223B ; N # Sm [4] DOT MINUS..HOMOTHETIC +223C..223D ; A # Sm [2] TILDE OPERATOR..REVERSED TILDE +223E..2247 ; N # Sm [10] INVERTED LAZY S..NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +2248 ; A # Sm ALMOST EQUAL TO +2249..224B ; N # Sm [3] NOT ALMOST EQUAL TO..TRIPLE TILDE +224C ; A # Sm ALL EQUAL TO +224D..2251 ; N # Sm [5] EQUIVALENT TO..GEOMETRICALLY EQUAL TO +2252 ; A # Sm APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253..225F ; N # Sm [13] IMAGE OF OR APPROXIMATELY EQUAL TO..QUESTIONED EQUAL TO +2260..2261 ; A # Sm [2] NOT EQUAL TO..IDENTICAL TO +2262..2263 ; N # Sm [2] NOT IDENTICAL TO..STRICTLY EQUIVALENT TO +2264..2267 ; A # Sm [4] LESS-THAN OR EQUAL TO..GREATER-THAN OVER EQUAL TO +2268..2269 ; N # Sm [2] LESS-THAN BUT NOT EQUAL TO..GREATER-THAN BUT NOT EQUAL TO +226A..226B ; A # Sm [2] MUCH LESS-THAN..MUCH GREATER-THAN +226C..226D ; N # Sm [2] BETWEEN..NOT EQUIVALENT TO +226E..226F ; A # Sm [2] NOT LESS-THAN..NOT GREATER-THAN +2270..2281 ; N # Sm [18] NEITHER LESS-THAN NOR EQUAL TO..DOES NOT SUCCEED +2282..2283 ; A # Sm [2] SUBSET OF..SUPERSET OF +2284..2285 ; N # Sm [2] NOT A SUBSET OF..NOT A SUPERSET OF +2286..2287 ; A # Sm [2] SUBSET OF OR EQUAL TO..SUPERSET OF OR EQUAL TO +2288..2294 ; N # Sm [13] NEITHER A SUBSET OF NOR EQUAL TO..SQUARE CUP +2295 ; A # Sm CIRCLED PLUS +2296..2298 ; N # Sm [3] CIRCLED MINUS..CIRCLED DIVISION SLASH +2299 ; A # Sm CIRCLED DOT OPERATOR +229A..22A4 ; N # Sm [11] CIRCLED RING OPERATOR..DOWN TACK +22A5 ; A # Sm UP TACK +22A6..22BE ; N # Sm [25] ASSERTION..RIGHT ANGLE WITH ARC +22BF ; A # Sm RIGHT TRIANGLE +22C0..22FF ; N # Sm [64] N-ARY LOGICAL AND..Z NOTATION BAG MEMBERSHIP +2300..2307 ; N # So [8] DIAMETER SIGN..WAVY LINE +2308 ; N # Ps LEFT CEILING +2309 ; N # Pe RIGHT CEILING +230A ; N # Ps LEFT FLOOR +230B ; N # Pe RIGHT FLOOR +230C..2311 ; N # So [6] BOTTOM RIGHT CROP..SQUARE LOZENGE +2312 ; A # So ARC +2313..2319 ; N # So [7] SEGMENT..TURNED NOT SIGN +231A..231B ; W # So [2] WATCH..HOURGLASS +231C..231F ; N # So [4] TOP LEFT CORNER..BOTTOM RIGHT CORNER +2320..2321 ; N # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL +2322..2328 ; N # So [7] FROWN..KEYBOARD +2329 ; W # Ps LEFT-POINTING ANGLE BRACKET +232A ; W # Pe RIGHT-POINTING ANGLE BRACKET +232B..237B ; N # So [81] ERASE TO THE LEFT..NOT CHECK MARK +237C ; N # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +237D..239A ; N # So [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL +239B..23B3 ; N # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM +23B4..23DB ; N # So [40] TOP SQUARE BRACKET..FUSE +23DC..23E1 ; N # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET +23E2..23E8 ; N # So [7] WHITE TRAPEZIUM..DECIMAL EXPONENT SYMBOL +23E9..23EC ; W # So [4] BLACK RIGHT-POINTING DOUBLE TRIANGLE..BLACK DOWN-POINTING DOUBLE TRIANGLE +23ED..23EF ; N # So [3] BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR..BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR +23F0 ; W # So ALARM CLOCK +23F1..23F2 ; N # So [2] STOPWATCH..TIMER CLOCK +23F3 ; W # So HOURGLASS WITH FLOWING SAND +23F4..23FF ; N # So [12] BLACK MEDIUM LEFT-POINTING TRIANGLE..OBSERVER EYE SYMBOL +2400..2426 ; N # So [39] SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO +2440..244A ; N # So [11] OCR HOOK..OCR DOUBLE BACKSLASH +2460..249B ; A # No [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP +249C..24E9 ; A # So [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z +24EA ; N # No CIRCLED DIGIT ZERO +24EB..24FF ; A # No [21] NEGATIVE CIRCLED NUMBER ELEVEN..NEGATIVE CIRCLED DIGIT ZERO +2500..254B ; A # So [76] BOX DRAWINGS LIGHT HORIZONTAL..BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL +254C..254F ; N # So [4] BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL..BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL +2550..2573 ; A # So [36] BOX DRAWINGS DOUBLE HORIZONTAL..BOX DRAWINGS LIGHT DIAGONAL CROSS +2574..257F ; N # So [12] BOX DRAWINGS LIGHT LEFT..BOX DRAWINGS HEAVY UP AND LIGHT DOWN +2580..258F ; A # So [16] UPPER HALF BLOCK..LEFT ONE EIGHTH BLOCK +2590..2591 ; N # So [2] RIGHT HALF BLOCK..LIGHT SHADE +2592..2595 ; A # So [4] MEDIUM SHADE..RIGHT ONE EIGHTH BLOCK +2596..259F ; N # So [10] QUADRANT LOWER LEFT..QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT +25A0..25A1 ; A # So [2] BLACK SQUARE..WHITE SQUARE +25A2 ; N # So WHITE SQUARE WITH ROUNDED CORNERS +25A3..25A9 ; A # So [7] WHITE SQUARE CONTAINING BLACK SMALL SQUARE..SQUARE WITH DIAGONAL CROSSHATCH FILL +25AA..25B1 ; N # So [8] BLACK SMALL SQUARE..WHITE PARALLELOGRAM +25B2..25B3 ; A # So [2] BLACK UP-POINTING TRIANGLE..WHITE UP-POINTING TRIANGLE +25B4..25B5 ; N # So [2] BLACK UP-POINTING SMALL TRIANGLE..WHITE UP-POINTING SMALL TRIANGLE +25B6 ; A # So BLACK RIGHT-POINTING TRIANGLE +25B7 ; A # Sm WHITE RIGHT-POINTING TRIANGLE +25B8..25BB ; N # So [4] BLACK RIGHT-POINTING SMALL TRIANGLE..WHITE RIGHT-POINTING POINTER +25BC..25BD ; A # So [2] BLACK DOWN-POINTING TRIANGLE..WHITE DOWN-POINTING TRIANGLE +25BE..25BF ; N # So [2] BLACK DOWN-POINTING SMALL TRIANGLE..WHITE DOWN-POINTING SMALL TRIANGLE +25C0 ; A # So BLACK LEFT-POINTING TRIANGLE +25C1 ; A # Sm WHITE LEFT-POINTING TRIANGLE +25C2..25C5 ; N # So [4] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE LEFT-POINTING POINTER +25C6..25C8 ; A # So [3] BLACK DIAMOND..WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND +25C9..25CA ; N # So [2] FISHEYE..LOZENGE +25CB ; A # So WHITE CIRCLE +25CC..25CD ; N # So [2] DOTTED CIRCLE..CIRCLE WITH VERTICAL FILL +25CE..25D1 ; A # So [4] BULLSEYE..CIRCLE WITH RIGHT HALF BLACK +25D2..25E1 ; N # So [16] CIRCLE WITH LOWER HALF BLACK..LOWER HALF CIRCLE +25E2..25E5 ; A # So [4] BLACK LOWER RIGHT TRIANGLE..BLACK UPPER RIGHT TRIANGLE +25E6..25EE ; N # So [9] WHITE BULLET..UP-POINTING TRIANGLE WITH RIGHT HALF BLACK +25EF ; A # So LARGE CIRCLE +25F0..25F7 ; N # So [8] WHITE SQUARE WITH UPPER LEFT QUADRANT..WHITE CIRCLE WITH UPPER RIGHT QUADRANT +25F8..25FC ; N # Sm [5] UPPER LEFT TRIANGLE..BLACK MEDIUM SQUARE +25FD..25FE ; W # Sm [2] WHITE MEDIUM SMALL SQUARE..BLACK MEDIUM SMALL SQUARE +25FF ; N # Sm LOWER RIGHT TRIANGLE +2600..2604 ; N # So [5] BLACK SUN WITH RAYS..COMET +2605..2606 ; A # So [2] BLACK STAR..WHITE STAR +2607..2608 ; N # So [2] LIGHTNING..THUNDERSTORM +2609 ; A # So SUN +260A..260D ; N # So [4] ASCENDING NODE..OPPOSITION +260E..260F ; A # So [2] BLACK TELEPHONE..WHITE TELEPHONE +2610..2613 ; N # So [4] BALLOT BOX..SALTIRE +2614..2615 ; W # So [2] UMBRELLA WITH RAIN DROPS..HOT BEVERAGE +2616..261B ; N # So [6] WHITE SHOGI PIECE..BLACK RIGHT POINTING INDEX +261C ; A # So WHITE LEFT POINTING INDEX +261D ; N # So WHITE UP POINTING INDEX +261E ; A # So WHITE RIGHT POINTING INDEX +261F..263F ; N # So [33] WHITE DOWN POINTING INDEX..MERCURY +2640 ; A # So FEMALE SIGN +2641 ; N # So EARTH +2642 ; A # So MALE SIGN +2643..2647 ; N # So [5] JUPITER..PLUTO +2648..2653 ; W # So [12] ARIES..PISCES +2654..265F ; N # So [12] WHITE CHESS KING..BLACK CHESS PAWN +2660..2661 ; A # So [2] BLACK SPADE SUIT..WHITE HEART SUIT +2662 ; N # So WHITE DIAMOND SUIT +2663..2665 ; A # So [3] BLACK CLUB SUIT..BLACK HEART SUIT +2666 ; N # So BLACK DIAMOND SUIT +2667..266A ; A # So [4] WHITE CLUB SUIT..EIGHTH NOTE +266B ; N # So BEAMED EIGHTH NOTES +266C..266D ; A # So [2] BEAMED SIXTEENTH NOTES..MUSIC FLAT SIGN +266E ; N # So MUSIC NATURAL SIGN +266F ; A # Sm MUSIC SHARP SIGN +2670..267E ; N # So [15] WEST SYRIAC CROSS..PERMANENT PAPER SIGN +267F ; W # So WHEELCHAIR SYMBOL +2680..2692 ; N # So [19] DIE FACE-1..HAMMER AND PICK +2693 ; W # So ANCHOR +2694..269D ; N # So [10] CROSSED SWORDS..OUTLINED WHITE STAR +269E..269F ; A # So [2] THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT +26A0 ; N # So WARNING SIGN +26A1 ; W # So HIGH VOLTAGE SIGN +26A2..26A9 ; N # So [8] DOUBLED FEMALE SIGN..HORIZONTAL MALE WITH STROKE SIGN +26AA..26AB ; W # So [2] MEDIUM WHITE CIRCLE..MEDIUM BLACK CIRCLE +26AC..26BC ; N # So [17] MEDIUM SMALL WHITE CIRCLE..SESQUIQUADRATE +26BD..26BE ; W # So [2] SOCCER BALL..BASEBALL +26BF ; A # So SQUARED KEY +26C0..26C3 ; N # So [4] WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING +26C4..26C5 ; W # So [2] SNOWMAN WITHOUT SNOW..SUN BEHIND CLOUD +26C6..26CD ; A # So [8] RAIN..DISABLED CAR +26CE ; W # So OPHIUCHUS +26CF..26D3 ; A # So [5] PICK..CHAINS +26D4 ; W # So NO ENTRY +26D5..26E1 ; A # So [13] ALTERNATE ONE-WAY LEFT WAY TRAFFIC..RESTRICTED LEFT ENTRY-2 +26E2 ; N # So ASTRONOMICAL SYMBOL FOR URANUS +26E3 ; A # So HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE +26E4..26E7 ; N # So [4] PENTAGRAM..INVERTED PENTAGRAM +26E8..26E9 ; A # So [2] BLACK CROSS ON SHIELD..SHINTO SHRINE +26EA ; W # So CHURCH +26EB..26F1 ; A # So [7] CASTLE..UMBRELLA ON GROUND +26F2..26F3 ; W # So [2] FOUNTAIN..FLAG IN HOLE +26F4 ; A # So FERRY +26F5 ; W # So SAILBOAT +26F6..26F9 ; A # So [4] SQUARE FOUR CORNERS..PERSON WITH BALL +26FA ; W # So TENT +26FB..26FC ; A # So [2] JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD ; W # So FUEL PUMP +26FE..26FF ; A # So [2] CUP ON BLACK SQUARE..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE +2700..2704 ; N # So [5] BLACK SAFETY SCISSORS..WHITE SCISSORS +2705 ; W # So WHITE HEAVY CHECK MARK +2706..2709 ; N # So [4] TELEPHONE LOCATION SIGN..ENVELOPE +270A..270B ; W # So [2] RAISED FIST..RAISED HAND +270C..2727 ; N # So [28] VICTORY HAND..WHITE FOUR POINTED STAR +2728 ; W # So SPARKLES +2729..273C ; N # So [20] STRESS OUTLINED WHITE STAR..OPEN CENTRE TEARDROP-SPOKED ASTERISK +273D ; A # So HEAVY TEARDROP-SPOKED ASTERISK +273E..274B ; N # So [14] SIX PETALLED BLACK AND WHITE FLORETTE..HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK +274C ; W # So CROSS MARK +274D ; N # So SHADOWED WHITE CIRCLE +274E ; W # So NEGATIVE SQUARED CROSS MARK +274F..2752 ; N # So [4] LOWER RIGHT DROP-SHADOWED WHITE SQUARE..UPPER RIGHT SHADOWED WHITE SQUARE +2753..2755 ; W # So [3] BLACK QUESTION MARK ORNAMENT..WHITE EXCLAMATION MARK ORNAMENT +2756 ; N # So BLACK DIAMOND MINUS WHITE X +2757 ; W # So HEAVY EXCLAMATION MARK SYMBOL +2758..2767 ; N # So [16] LIGHT VERTICAL BAR..ROTATED FLORAL HEART BULLET +2768 ; N # Ps MEDIUM LEFT PARENTHESIS ORNAMENT +2769 ; N # Pe MEDIUM RIGHT PARENTHESIS ORNAMENT +276A ; N # Ps MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276B ; N # Pe MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276C ; N # Ps MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276D ; N # Pe MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276E ; N # Ps HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +276F ; N # Pe HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770 ; N # Ps HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2771 ; N # Pe HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2772 ; N # Ps LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2773 ; N # Pe LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2774 ; N # Ps MEDIUM LEFT CURLY BRACKET ORNAMENT +2775 ; N # Pe MEDIUM RIGHT CURLY BRACKET ORNAMENT +2776..277F ; A # No [10] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED NUMBER TEN +2780..2793 ; N # No [20] DINGBAT CIRCLED SANS-SERIF DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN +2794 ; N # So HEAVY WIDE-HEADED RIGHTWARDS ARROW +2795..2797 ; W # So [3] HEAVY PLUS SIGN..HEAVY DIVISION SIGN +2798..27AF ; N # So [24] HEAVY SOUTH EAST ARROW..NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW +27B0 ; W # So CURLY LOOP +27B1..27BE ; N # So [14] NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW..OPEN-OUTLINED RIGHTWARDS ARROW +27BF ; W # So DOUBLE CURLY LOOP +27C0..27C4 ; N # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET +27C5 ; N # Ps LEFT S-SHAPED BAG DELIMITER +27C6 ; N # Pe RIGHT S-SHAPED BAG DELIMITER +27C7..27E5 ; N # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK +27E6 ; Na # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E7 ; Na # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E8 ; Na # Ps MATHEMATICAL LEFT ANGLE BRACKET +27E9 ; Na # Pe MATHEMATICAL RIGHT ANGLE BRACKET +27EA ; Na # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EB ; Na # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27EC ; Na # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27ED ; Na # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EE ; N # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS +27EF ; N # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS +27F0..27FF ; N # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW +2800..28FF ; N # So [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678 +2900..297F ; N # Sm [128] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..DOWN FISH TAIL +2980..2982 ; N # Sm [3] TRIPLE VERTICAL BAR DELIMITER..Z NOTATION TYPE COLON +2983 ; N # Ps LEFT WHITE CURLY BRACKET +2984 ; N # Pe RIGHT WHITE CURLY BRACKET +2985 ; Na # Ps LEFT WHITE PARENTHESIS +2986 ; Na # Pe RIGHT WHITE PARENTHESIS +2987 ; N # Ps Z NOTATION LEFT IMAGE BRACKET +2988 ; N # Pe Z NOTATION RIGHT IMAGE BRACKET +2989 ; N # Ps Z NOTATION LEFT BINDING BRACKET +298A ; N # Pe Z NOTATION RIGHT BINDING BRACKET +298B ; N # Ps LEFT SQUARE BRACKET WITH UNDERBAR +298C ; N # Pe RIGHT SQUARE BRACKET WITH UNDERBAR +298D ; N # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298E ; N # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +298F ; N # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990 ; N # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2991 ; N # Ps LEFT ANGLE BRACKET WITH DOT +2992 ; N # Pe RIGHT ANGLE BRACKET WITH DOT +2993 ; N # Ps LEFT ARC LESS-THAN BRACKET +2994 ; N # Pe RIGHT ARC GREATER-THAN BRACKET +2995 ; N # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET +2996 ; N # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET +2997 ; N # Ps LEFT BLACK TORTOISE SHELL BRACKET +2998 ; N # Pe RIGHT BLACK TORTOISE SHELL BRACKET +2999..29D7 ; N # Sm [63] DOTTED FENCE..BLACK HOURGLASS +29D8 ; N # Ps LEFT WIGGLY FENCE +29D9 ; N # Pe RIGHT WIGGLY FENCE +29DA ; N # Ps LEFT DOUBLE WIGGLY FENCE +29DB ; N # Pe RIGHT DOUBLE WIGGLY FENCE +29DC..29FB ; N # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS +29FC ; N # Ps LEFT-POINTING CURVED ANGLE BRACKET +29FD ; N # Pe RIGHT-POINTING CURVED ANGLE BRACKET +29FE..29FF ; N # Sm [2] TINY..MINY +2A00..2AFF ; N # Sm [256] N-ARY CIRCLED DOT OPERATOR..N-ARY WHITE VERTICAL BAR +2B00..2B1A ; N # So [27] NORTH EAST WHITE ARROW..DOTTED SQUARE +2B1B..2B1C ; W # So [2] BLACK LARGE SQUARE..WHITE LARGE SQUARE +2B1D..2B2F ; N # So [19] BLACK VERY SMALL SQUARE..WHITE VERTICAL ELLIPSE +2B30..2B44 ; N # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET +2B45..2B46 ; N # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW +2B47..2B4C ; N # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +2B4D..2B4F ; N # So [3] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..SHORT BACKSLANTED SOUTH ARROW +2B50 ; W # So WHITE MEDIUM STAR +2B51..2B54 ; N # So [4] BLACK SMALL STAR..WHITE RIGHT-POINTING PENTAGON +2B55 ; W # So HEAVY LARGE CIRCLE +2B56..2B59 ; A # So [4] HEAVY OVAL WITH OVAL INSIDE..HEAVY CIRCLED SALTIRE +2B5A..2B73 ; N # So [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR +2B76..2B95 ; N # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B97..2BFF ; N # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2C00..2C5F ; N # L& [96] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI +2C60..2C7B ; N # L& [28] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN LETTER SMALL CAPITAL TURNED E +2C7C..2C7D ; N # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2C7E..2C7F ; N # Lu [2] LATIN CAPITAL LETTER S WITH SWASH TAIL..LATIN CAPITAL LETTER Z WITH SWASH TAIL +2C80..2CE4 ; N # L& [101] COPTIC CAPITAL LETTER ALFA..COPTIC SYMBOL KAI +2CE5..2CEA ; N # So [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA +2CEB..2CEE ; N # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CEF..2CF1 ; N # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2CF2..2CF3 ; N # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI +2CF9..2CFC ; N # Po [4] COPTIC OLD NUBIAN FULL STOP..COPTIC OLD NUBIAN VERSE DIVIDER +2CFD ; N # No COPTIC FRACTION ONE HALF +2CFE..2CFF ; N # Po [2] COPTIC FULL STOP..COPTIC MORPHOLOGICAL DIVIDER +2D00..2D25 ; N # Ll [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; N # Ll GEORGIAN SMALL LETTER YN +2D2D ; N # Ll GEORGIAN SMALL LETTER AEN +2D30..2D67 ; N # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D6F ; N # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2D70 ; N # Po TIFINAGH SEPARATOR MARK +2D7F ; N # Mn TIFINAGH CONSONANT JOINER +2D80..2D96 ; N # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; N # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; N # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; N # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; N # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; N # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; N # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; N # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; N # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2DE0..2DFF ; N # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +2E00..2E01 ; N # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER +2E02 ; N # Pi LEFT SUBSTITUTION BRACKET +2E03 ; N # Pf RIGHT SUBSTITUTION BRACKET +2E04 ; N # Pi LEFT DOTTED SUBSTITUTION BRACKET +2E05 ; N # Pf RIGHT DOTTED SUBSTITUTION BRACKET +2E06..2E08 ; N # Po [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER +2E09 ; N # Pi LEFT TRANSPOSITION BRACKET +2E0A ; N # Pf RIGHT TRANSPOSITION BRACKET +2E0B ; N # Po RAISED SQUARE +2E0C ; N # Pi LEFT RAISED OMISSION BRACKET +2E0D ; N # Pf RIGHT RAISED OMISSION BRACKET +2E0E..2E16 ; N # Po [9] EDITORIAL CORONIS..DOTTED RIGHT-POINTING ANGLE +2E17 ; N # Pd DOUBLE OBLIQUE HYPHEN +2E18..2E19 ; N # Po [2] INVERTED INTERROBANG..PALM BRANCH +2E1A ; N # Pd HYPHEN WITH DIAERESIS +2E1B ; N # Po TILDE WITH RING ABOVE +2E1C ; N # Pi LEFT LOW PARAPHRASE BRACKET +2E1D ; N # Pf RIGHT LOW PARAPHRASE BRACKET +2E1E..2E1F ; N # Po [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW +2E20 ; N # Pi LEFT VERTICAL BAR WITH QUILL +2E21 ; N # Pf RIGHT VERTICAL BAR WITH QUILL +2E22 ; N # Ps TOP LEFT HALF BRACKET +2E23 ; N # Pe TOP RIGHT HALF BRACKET +2E24 ; N # Ps BOTTOM LEFT HALF BRACKET +2E25 ; N # Pe BOTTOM RIGHT HALF BRACKET +2E26 ; N # Ps LEFT SIDEWAYS U BRACKET +2E27 ; N # Pe RIGHT SIDEWAYS U BRACKET +2E28 ; N # Ps LEFT DOUBLE PARENTHESIS +2E29 ; N # Pe RIGHT DOUBLE PARENTHESIS +2E2A..2E2E ; N # Po [5] TWO DOTS OVER ONE DOT PUNCTUATION..REVERSED QUESTION MARK +2E2F ; N # Lm VERTICAL TILDE +2E30..2E39 ; N # Po [10] RING POINT..TOP HALF SECTION SIGN +2E3A..2E3B ; N # Pd [2] TWO-EM DASH..THREE-EM DASH +2E3C..2E3F ; N # Po [4] STENOGRAPHIC FULL STOP..CAPITULUM +2E40 ; N # Pd DOUBLE HYPHEN +2E41 ; N # Po REVERSED COMMA +2E42 ; N # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK +2E43..2E4F ; N # Po [13] DASH WITH LEFT UPTURN..CORNISH VERSE DIVIDER +2E50..2E51 ; N # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E52..2E54 ; N # Po [3] TIRONIAN SIGN CAPITAL ET..MEDIEVAL QUESTION MARK +2E55 ; N # Ps LEFT SQUARE BRACKET WITH STROKE +2E56 ; N # Pe RIGHT SQUARE BRACKET WITH STROKE +2E57 ; N # Ps LEFT SQUARE BRACKET WITH DOUBLE STROKE +2E58 ; N # Pe RIGHT SQUARE BRACKET WITH DOUBLE STROKE +2E59 ; N # Ps TOP HALF LEFT PARENTHESIS +2E5A ; N # Pe TOP HALF RIGHT PARENTHESIS +2E5B ; N # Ps BOTTOM HALF LEFT PARENTHESIS +2E5C ; N # Pe BOTTOM HALF RIGHT PARENTHESIS +2E5D ; N # Pd OBLIQUE HYPHEN +2E80..2E99 ; W # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP +2E9B..2EF3 ; W # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2F00..2FD5 ; W # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FF0..2FFF ; W # So [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION +3000 ; F # Zs IDEOGRAPHIC SPACE +3001..3003 ; W # Po [3] IDEOGRAPHIC COMMA..DITTO MARK +3004 ; W # So JAPANESE INDUSTRIAL STANDARD SYMBOL +3005 ; W # Lm IDEOGRAPHIC ITERATION MARK +3006 ; W # Lo IDEOGRAPHIC CLOSING MARK +3007 ; W # Nl IDEOGRAPHIC NUMBER ZERO +3008 ; W # Ps LEFT ANGLE BRACKET +3009 ; W # Pe RIGHT ANGLE BRACKET +300A ; W # Ps LEFT DOUBLE ANGLE BRACKET +300B ; W # Pe RIGHT DOUBLE ANGLE BRACKET +300C ; W # Ps LEFT CORNER BRACKET +300D ; W # Pe RIGHT CORNER BRACKET +300E ; W # Ps LEFT WHITE CORNER BRACKET +300F ; W # Pe RIGHT WHITE CORNER BRACKET +3010 ; W # Ps LEFT BLACK LENTICULAR BRACKET +3011 ; W # Pe RIGHT BLACK LENTICULAR BRACKET +3012..3013 ; W # So [2] POSTAL MARK..GETA MARK +3014 ; W # Ps LEFT TORTOISE SHELL BRACKET +3015 ; W # Pe RIGHT TORTOISE SHELL BRACKET +3016 ; W # Ps LEFT WHITE LENTICULAR BRACKET +3017 ; W # Pe RIGHT WHITE LENTICULAR BRACKET +3018 ; W # Ps LEFT WHITE TORTOISE SHELL BRACKET +3019 ; W # Pe RIGHT WHITE TORTOISE SHELL BRACKET +301A ; W # Ps LEFT WHITE SQUARE BRACKET +301B ; W # Pe RIGHT WHITE SQUARE BRACKET +301C ; W # Pd WAVE DASH +301D ; W # Ps REVERSED DOUBLE PRIME QUOTATION MARK +301E..301F ; W # Pe [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK +3020 ; W # So POSTAL MARK FACE +3021..3029 ; W # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE +302A..302D ; W # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +302E..302F ; W # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +3030 ; W # Pd WAVY DASH +3031..3035 ; W # Lm [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF +3036..3037 ; W # So [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL +3038..303A ; W # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY +303B ; W # Lm VERTICAL IDEOGRAPHIC ITERATION MARK +303C ; W # Lo MASU MARK +303D ; W # Po PART ALTERNATION MARK +303E ; W # So IDEOGRAPHIC VARIATION INDICATOR +303F ; N # So IDEOGRAPHIC HALF FILL SPACE +3041..3096 ; W # Lo [86] HIRAGANA LETTER SMALL A..HIRAGANA LETTER SMALL KE +3099..309A ; W # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309B..309C ; W # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309D..309E ; W # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +309F ; W # Lo HIRAGANA DIGRAPH YORI +30A0 ; W # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN +30A1..30FA ; W # Lo [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO +30FB ; W # Po KATAKANA MIDDLE DOT +30FC..30FE ; W # Lm [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK +30FF ; W # Lo KATAKANA DIGRAPH KOTO +3105..312F ; W # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; W # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +3190..3191 ; W # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK +3192..3195 ; W # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK +3196..319F ; W # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31A0..31BF ; W # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +31C0..31E3 ; W # So [36] CJK STROKE T..CJK STROKE Q +31EF ; W # So IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION +31F0..31FF ; W # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3200..321E ; W # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +3220..3229 ; W # No [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN +322A..3247 ; W # So [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO +3248..324F ; A # No [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE +3250 ; W # So PARTNERSHIP SIGN +3251..325F ; W # No [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE +3260..327F ; W # So [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL +3280..3289 ; W # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN +328A..32B0 ; W # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT +32B1..32BF ; W # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY +32C0..32FF ; W # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA +3300..33FF ; W # So [256] SQUARE APAATO..SQUARE GAL +3400..4DBF ; W # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF +4DC0..4DFF ; N # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +4E00..9FFF ; W # Lo [20992] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FFF +A000..A014 ; W # Lo [21] YI SYLLABLE IT..YI SYLLABLE E +A015 ; W # Lm YI SYLLABLE WU +A016..A48C ; W # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A490..A4C6 ; W # So [55] YI RADICAL QOT..YI RADICAL KE +A4D0..A4F7 ; N # Lo [40] LISU LETTER BA..LISU LETTER OE +A4F8..A4FD ; N # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A4FE..A4FF ; N # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A500..A60B ; N # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A60C ; N # Lm VAI SYLLABLE LENGTHENER +A60D..A60F ; N # Po [3] VAI COMMA..VAI QUESTION MARK +A610..A61F ; N # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A620..A629 ; N # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE +A62A..A62B ; N # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A640..A66D ; N # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A66E ; N # Lo CYRILLIC LETTER MULTIOCULAR O +A66F ; N # Mn COMBINING CYRILLIC VZMET +A670..A672 ; N # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN +A673 ; N # Po SLAVONIC ASTERISK +A674..A67D ; N # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A67E ; N # Po CYRILLIC KAVYKA +A67F ; N # Lm CYRILLIC PAYEROK +A680..A69B ; N # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O +A69C..A69D ; N # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A69E..A69F ; N # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6A0..A6E5 ; N # Lo [70] BAMUM LETTER A..BAMUM LETTER KI +A6E6..A6EF ; N # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +A6F0..A6F1 ; N # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A6F2..A6F7 ; N # Po [6] BAMUM NJAEMLI..BAMUM QUESTION MARK +A700..A716 ; N # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A717..A71F ; N # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A720..A721 ; N # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A722..A76F ; N # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON +A770 ; N # Lm MODIFIER LETTER US +A771..A787 ; N # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T +A788 ; N # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A789..A78A ; N # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +A78B..A78E ; N # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A78F ; N # Lo LATIN LETTER SINOLOGICAL DOT +A790..A7CA ; N # L& [59] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D0..A7D1 ; N # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; N # Ll LATIN SMALL LETTER DOUBLE THORN +A7D5..A7D9 ; N # L& [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S +A7F2..A7F4 ; N # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F5..A7F6 ; N # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H +A7F7 ; N # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7F8..A7F9 ; N # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A7FA ; N # Ll LATIN LETTER SMALL CAPITAL TURNED M +A7FB..A7FF ; N # Lo [5] LATIN EPIGRAPHIC LETTER REVERSED F..LATIN EPIGRAPHIC LETTER ARCHAIC M +A800..A801 ; N # Lo [2] SYLOTI NAGRI LETTER A..SYLOTI NAGRI LETTER I +A802 ; N # Mn SYLOTI NAGRI SIGN DVISVARA +A803..A805 ; N # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A806 ; N # Mn SYLOTI NAGRI SIGN HASANTA +A807..A80A ; N # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80B ; N # Mn SYLOTI NAGRI SIGN ANUSVARA +A80C..A822 ; N # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A823..A824 ; N # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A825..A826 ; N # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A827 ; N # Mc SYLOTI NAGRI VOWEL SIGN OO +A828..A82B ; N # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A82C ; N # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA +A830..A835 ; N # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS +A836..A837 ; N # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK +A838 ; N # Sc NORTH INDIC RUPEE MARK +A839 ; N # So NORTH INDIC QUANTITY MARK +A840..A873 ; N # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A874..A877 ; N # Po [4] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA MARK DOUBLE SHAD +A880..A881 ; N # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A882..A8B3 ; N # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8B4..A8C3 ; N # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A8C4..A8C5 ; N # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8CE..A8CF ; N # Po [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8D0..A8D9 ; N # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A8E0..A8F1 ; N # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8F2..A8F7 ; N # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8F8..A8FA ; N # Po [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FB ; N # Lo DEVANAGARI HEADSTROKE +A8FC ; N # Po DEVANAGARI SIGN SIDDHAM +A8FD..A8FE ; N # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A8FF ; N # Mn DEVANAGARI VOWEL SIGN AY +A900..A909 ; N # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A90A..A925 ; N # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A926..A92D ; N # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A92E..A92F ; N # Po [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A930..A946 ; N # Lo [23] REJANG LETTER KA..REJANG LETTER A +A947..A951 ; N # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A952..A953 ; N # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A95F ; N # Po REJANG SECTION MARK +A960..A97C ; W # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A980..A982 ; N # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A983 ; N # Mc JAVANESE SIGN WIGNYAN +A984..A9B2 ; N # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9B3 ; N # Mn JAVANESE SIGN CECAK TELU +A9B4..A9B5 ; N # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9B6..A9B9 ; N # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BA..A9BB ; N # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BC..A9BD ; N # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9BE..A9C0 ; N # Mc [3] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE PANGKON +A9C1..A9CD ; N # Po [13] JAVANESE LEFT RERENGGAN..JAVANESE TURNED PADA PISELEH +A9CF ; N # Lm JAVANESE PANGRANGKEP +A9D0..A9D9 ; N # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9DE..A9DF ; N # Po [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +A9E0..A9E4 ; N # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA +A9E5 ; N # Mn MYANMAR SIGN SHAN SAW +A9E6 ; N # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION +A9E7..A9EF ; N # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA +A9F0..A9F9 ; N # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +A9FA..A9FE ; N # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA +AA00..AA28 ; N # Lo [41] CHAM LETTER A..CHAM LETTER HA +AA29..AA2E ; N # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA2F..AA30 ; N # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA31..AA32 ; N # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA33..AA34 ; N # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA35..AA36 ; N # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA40..AA42 ; N # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA43 ; N # Mn CHAM CONSONANT SIGN FINAL NG +AA44..AA4B ; N # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AA4C ; N # Mn CHAM CONSONANT SIGN FINAL M +AA4D ; N # Mc CHAM CONSONANT SIGN FINAL H +AA50..AA59 ; N # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +AA5C..AA5F ; N # Po [4] CHAM PUNCTUATION SPIRAL..CHAM PUNCTUATION TRIPLE DANDA +AA60..AA6F ; N # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA +AA70 ; N # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION +AA71..AA76 ; N # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM +AA77..AA79 ; N # So [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +AA7A ; N # Lo MYANMAR LETTER AITON RA +AA7B ; N # Mc MYANMAR SIGN PAO KAREN TONE +AA7C ; N # Mn MYANMAR SIGN TAI LAING TONE-2 +AA7D ; N # Mc MYANMAR SIGN TAI LAING TONE-5 +AA7E..AA7F ; N # Lo [2] MYANMAR LETTER SHWE PALAUNG CHA..MYANMAR LETTER SHWE PALAUNG SHA +AA80..AAAF ; N # Lo [48] TAI VIET LETTER LOW KO..TAI VIET LETTER HIGH O +AAB0 ; N # Mn TAI VIET MAI KANG +AAB1 ; N # Lo TAI VIET VOWEL AA +AAB2..AAB4 ; N # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB5..AAB6 ; N # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O +AAB7..AAB8 ; N # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AAB9..AABD ; N # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN +AABE..AABF ; N # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC0 ; N # Lo TAI VIET TONE MAI NUENG +AAC1 ; N # Mn TAI VIET TONE MAI THO +AAC2 ; N # Lo TAI VIET TONE MAI SONG +AADB..AADC ; N # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG +AADD ; N # Lm TAI VIET SYMBOL SAM +AADE..AADF ; N # Po [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAE0..AAEA ; N # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAEB ; N # Mc MEETEI MAYEK VOWEL SIGN II +AAEC..AAED ; N # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAEE..AAEF ; N # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF0..AAF1 ; N # Po [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +AAF2 ; N # Lo MEETEI MAYEK ANJI +AAF3..AAF4 ; N # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AAF5 ; N # Mc MEETEI MAYEK VOWEL SIGN VISARGA +AAF6 ; N # Mn MEETEI MAYEK VIRAMA +AB01..AB06 ; N # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; N # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; N # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; N # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; N # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +AB30..AB5A ; N # Ll [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB5B ; N # Sk MODIFIER BREVE WITH INVERTED BREVE +AB5C..AB5F ; N # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB60..AB68 ; N # Ll [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB69 ; N # Lm MODIFIER LETTER SMALL TURNED W +AB6A..AB6B ; N # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK +AB70..ABBF ; N # Ll [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +ABC0..ABE2 ; N # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +ABE3..ABE4 ; N # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE5 ; N # Mn MEETEI MAYEK VOWEL SIGN ANAP +ABE6..ABE7 ; N # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE8 ; N # Mn MEETEI MAYEK VOWEL SIGN UNAP +ABE9..ABEA ; N # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEB ; N # Po MEETEI MAYEK CHEIKHEI +ABEC ; N # Mc MEETEI MAYEK LUM IYEK +ABED ; N # Mn MEETEI MAYEK APUN IYEK +ABF0..ABF9 ; N # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +AC00..D7A3 ; W # Lo [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH +D7B0..D7C6 ; N # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; N # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +D800..DB7F ; N # Cs [896] .. +DB80..DBFF ; N # Cs [128] .. +DC00..DFFF ; N # Cs [1024] .. +E000..F8FF ; A # Co [6400] .. +F900..FA6D ; W # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA6E..FA6F ; W # Cn [2] .. +FA70..FAD9 ; W # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FADA..FAFF ; W # Cn [38] .. +FB00..FB06 ; N # Ll [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; N # Ll [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FB1D ; N # Lo HEBREW LETTER YOD WITH HIRIQ +FB1E ; N # Mn HEBREW POINT JUDEO-SPANISH VARIKA +FB1F..FB28 ; N # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB29 ; N # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN +FB2A..FB36 ; N # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; N # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; N # Lo HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; N # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; N # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FB4F ; N # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATURE ALEF LAMED +FB50..FBB1 ; N # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBB2..FBC2 ; N # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBD3..FD3D ; N # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD3E ; N # Pe ORNATE LEFT PARENTHESIS +FD3F ; N # Ps ORNATE RIGHT PARENTHESIS +FD40..FD4F ; N # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FD50..FD8F ; N # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; N # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDCF ; N # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDF0..FDFB ; N # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FDFC ; N # Sc RIAL SIGN +FDFD..FDFF ; N # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL +FE00..FE0F ; A # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE10..FE16 ; W # Po [7] PRESENTATION FORM FOR VERTICAL COMMA..PRESENTATION FORM FOR VERTICAL QUESTION MARK +FE17 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET +FE18 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET +FE19 ; W # Po PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE20..FE2F ; N # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +FE30 ; W # Po PRESENTATION FORM FOR VERTICAL TWO DOT LEADER +FE31..FE32 ; W # Pd [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH +FE33..FE34 ; W # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE35 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +FE36 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +FE37 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +FE38 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +FE39 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET +FE3A ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET +FE3B ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET +FE3C ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET +FE3D ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET +FE3E ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET +FE3F ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET +FE40 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET +FE41 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET +FE42 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET +FE43 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET +FE44 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET +FE45..FE46 ; W # Po [2] SESAME DOT..WHITE SESAME DOT +FE47 ; W # Ps PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET +FE48 ; W # Pe PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET +FE49..FE4C ; W # Po [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE +FE4D..FE4F ; W # Pc [3] DASHED LOW LINE..WAVY LOW LINE +FE50..FE52 ; W # Po [3] SMALL COMMA..SMALL FULL STOP +FE54..FE57 ; W # Po [4] SMALL SEMICOLON..SMALL EXCLAMATION MARK +FE58 ; W # Pd SMALL EM DASH +FE59 ; W # Ps SMALL LEFT PARENTHESIS +FE5A ; W # Pe SMALL RIGHT PARENTHESIS +FE5B ; W # Ps SMALL LEFT CURLY BRACKET +FE5C ; W # Pe SMALL RIGHT CURLY BRACKET +FE5D ; W # Ps SMALL LEFT TORTOISE SHELL BRACKET +FE5E ; W # Pe SMALL RIGHT TORTOISE SHELL BRACKET +FE5F..FE61 ; W # Po [3] SMALL NUMBER SIGN..SMALL ASTERISK +FE62 ; W # Sm SMALL PLUS SIGN +FE63 ; W # Pd SMALL HYPHEN-MINUS +FE64..FE66 ; W # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN +FE68 ; W # Po SMALL REVERSE SOLIDUS +FE69 ; W # Sc SMALL DOLLAR SIGN +FE6A..FE6B ; W # Po [2] SMALL PERCENT SIGN..SMALL COMMERCIAL AT +FE70..FE74 ; N # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; N # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FEFF ; N # Cf ZERO WIDTH NO-BREAK SPACE +FF01..FF03 ; F # Po [3] FULLWIDTH EXCLAMATION MARK..FULLWIDTH NUMBER SIGN +FF04 ; F # Sc FULLWIDTH DOLLAR SIGN +FF05..FF07 ; F # Po [3] FULLWIDTH PERCENT SIGN..FULLWIDTH APOSTROPHE +FF08 ; F # Ps FULLWIDTH LEFT PARENTHESIS +FF09 ; F # Pe FULLWIDTH RIGHT PARENTHESIS +FF0A ; F # Po FULLWIDTH ASTERISK +FF0B ; F # Sm FULLWIDTH PLUS SIGN +FF0C ; F # Po FULLWIDTH COMMA +FF0D ; F # Pd FULLWIDTH HYPHEN-MINUS +FF0E..FF0F ; F # Po [2] FULLWIDTH FULL STOP..FULLWIDTH SOLIDUS +FF10..FF19 ; F # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +FF1A..FF1B ; F # Po [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON +FF1C..FF1E ; F # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN +FF1F..FF20 ; F # Po [2] FULLWIDTH QUESTION MARK..FULLWIDTH COMMERCIAL AT +FF21..FF3A ; F # Lu [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +FF3B ; F # Ps FULLWIDTH LEFT SQUARE BRACKET +FF3C ; F # Po FULLWIDTH REVERSE SOLIDUS +FF3D ; F # Pe FULLWIDTH RIGHT SQUARE BRACKET +FF3E ; F # Sk FULLWIDTH CIRCUMFLEX ACCENT +FF3F ; F # Pc FULLWIDTH LOW LINE +FF40 ; F # Sk FULLWIDTH GRAVE ACCENT +FF41..FF5A ; F # Ll [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +FF5B ; F # Ps FULLWIDTH LEFT CURLY BRACKET +FF5C ; F # Sm FULLWIDTH VERTICAL LINE +FF5D ; F # Pe FULLWIDTH RIGHT CURLY BRACKET +FF5E ; F # Sm FULLWIDTH TILDE +FF5F ; F # Ps FULLWIDTH LEFT WHITE PARENTHESIS +FF60 ; F # Pe FULLWIDTH RIGHT WHITE PARENTHESIS +FF61 ; H # Po HALFWIDTH IDEOGRAPHIC FULL STOP +FF62 ; H # Ps HALFWIDTH LEFT CORNER BRACKET +FF63 ; H # Pe HALFWIDTH RIGHT CORNER BRACKET +FF64..FF65 ; H # Po [2] HALFWIDTH IDEOGRAPHIC COMMA..HALFWIDTH KATAKANA MIDDLE DOT +FF66..FF6F ; H # Lo [10] HALFWIDTH KATAKANA LETTER WO..HALFWIDTH KATAKANA LETTER SMALL TU +FF70 ; H # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF71..FF9D ; H # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +FF9E..FF9F ; H # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +FFA0..FFBE ; H # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; H # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; H # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; H # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; H # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +FFE0..FFE1 ; F # Sc [2] FULLWIDTH CENT SIGN..FULLWIDTH POUND SIGN +FFE2 ; F # Sm FULLWIDTH NOT SIGN +FFE3 ; F # Sk FULLWIDTH MACRON +FFE4 ; F # So FULLWIDTH BROKEN BAR +FFE5..FFE6 ; F # Sc [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN +FFE8 ; H # So HALFWIDTH FORMS LIGHT VERTICAL +FFE9..FFEC ; H # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW +FFED..FFEE ; H # So [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE +FFF9..FFFB ; N # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +FFFC ; N # So OBJECT REPLACEMENT CHARACTER +FFFD ; A # So REPLACEMENT CHARACTER +10000..1000B ; N # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; N # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; N # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; N # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; N # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; N # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; N # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10100..10102 ; N # Po [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +10107..10133 ; N # No [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10137..1013F ; N # So [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT +10140..10174 ; N # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10175..10178 ; N # No [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN +10179..10189 ; N # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN +1018A..1018B ; N # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN +1018C..1018E ; N # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN +10190..1019C ; N # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL +101A0 ; N # So GREEK SYMBOL TAU RHO +101D0..101FC ; N # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +101FD ; N # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +10280..1029C ; N # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; N # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3 +102E0 ; N # Mn COPTIC EPACT THOUSANDS MARK +102E1..102FB ; N # No [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED +10300..1031F ; N # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +10320..10323 ; N # No [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +1032D..1032F ; N # Lo [3] OLD ITALIC LETTER YE..OLD ITALIC LETTER SOUTHERN TSE +10330..10340 ; N # Lo [17] GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA +10341 ; N # Nl GOTHIC LETTER NINETY +10342..10349 ; N # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A ; N # Nl GOTHIC LETTER NINE HUNDRED +10350..10375 ; N # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10376..1037A ; N # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10380..1039D ; N # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +1039F ; N # Po UGARITIC WORD DIVIDER +103A0..103C3 ; N # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; N # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +103D0 ; N # Po OLD PERSIAN WORD DIVIDER +103D1..103D5 ; N # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +10400..1044F ; N # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW +10450..1047F ; N # Lo [48] SHAVIAN LETTER PEEP..SHAVIAN LETTER YEW +10480..1049D ; N # Lo [30] OSMANYA LETTER ALEF..OSMANYA LETTER OO +104A0..104A9 ; N # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +104B0..104D3 ; N # Lu [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +104D8..104FB ; N # Ll [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10500..10527 ; N # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; N # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +1056F ; N # Po CAUCASIAN ALBANIAN CITATION MARK +10570..1057A ; N # Lu [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; N # Lu [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; N # Lu [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; N # Lu [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10597..105A1 ; N # Ll [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; N # Ll [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; N # Ll [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; N # Ll [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10600..10736 ; N # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; N # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; N # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10780..10785 ; N # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; N # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; N # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +10800..10805 ; N # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; N # Lo CYPRIOT SYLLABLE JO +1080A..10835 ; N # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; N # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; N # Lo CYPRIOT SYLLABLE ZA +1083F ; N # Lo CYPRIOT SYLLABLE ZO +10840..10855 ; N # Lo [22] IMPERIAL ARAMAIC LETTER ALEPH..IMPERIAL ARAMAIC LETTER TAW +10857 ; N # Po IMPERIAL ARAMAIC SECTION SIGN +10858..1085F ; N # No [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10860..10876 ; N # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10877..10878 ; N # So [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON +10879..1087F ; N # No [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY +10880..1089E ; N # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108A7..108AF ; N # No [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED +108E0..108F2 ; N # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; N # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +108FB..108FF ; N # No [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED +10900..10915 ; N # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10916..1091B ; N # No [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +1091F ; N # Po PHOENICIAN WORD SEPARATOR +10920..10939 ; N # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +1093F ; N # Po LYDIAN TRIANGULAR MARK +10980..1099F ; N # Lo [32] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2 +109A0..109B7 ; N # Lo [24] MEROITIC CURSIVE LETTER A..MEROITIC CURSIVE LETTER DA +109BC..109BD ; N # No [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF +109BE..109BF ; N # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +109C0..109CF ; N # No [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY +109D2..109FF ; N # No [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS +10A00 ; N # Lo KHAROSHTHI LETTER A +10A01..10A03 ; N # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; N # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; N # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A10..10A13 ; N # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; N # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; N # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A38..10A3A ; N # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; N # Mn KHAROSHTHI VIRAMA +10A40..10A48 ; N # No [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF +10A50..10A58 ; N # Po [9] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION LINES +10A60..10A7C ; N # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A7D..10A7E ; N # No [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY +10A7F ; N # Po OLD SOUTH ARABIAN NUMERIC INDICATOR +10A80..10A9C ; N # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10A9D..10A9F ; N # No [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY +10AC0..10AC7 ; N # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC8 ; N # So MANICHAEAN SIGN UD +10AC9..10AE4 ; N # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10AE5..10AE6 ; N # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10AEB..10AEF ; N # No [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED +10AF0..10AF6 ; N # Po [7] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION LINE FILLER +10B00..10B35 ; N # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B39..10B3F ; N # Po [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B40..10B55 ; N # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B58..10B5F ; N # No [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B60..10B72 ; N # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B78..10B7F ; N # No [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10B80..10B91 ; N # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10B99..10B9C ; N # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT +10BA9..10BAF ; N # No [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED +10C00..10C48 ; N # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10C80..10CB2 ; N # Lu [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +10CC0..10CF2 ; N # Ll [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +10CFA..10CFF ; N # No [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND +10D00..10D23 ; N # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10D24..10D27 ; N # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10D30..10D39 ; N # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +10E60..10E7E ; N # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E80..10EA9 ; N # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EAB..10EAC ; N # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EAD ; N # Pd YEZIDI HYPHENATION MARK +10EB0..10EB1 ; N # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10EFD..10EFF ; N # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F00..10F1C ; N # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F1D..10F26 ; N # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF +10F27 ; N # Lo OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; N # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F46..10F50 ; N # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F51..10F54 ; N # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED +10F55..10F59 ; N # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10F70..10F81 ; N # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10F82..10F85 ; N # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +10F86..10F89 ; N # Po [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS +10FB0..10FC4 ; N # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FC5..10FCB ; N # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED +10FE0..10FF6 ; N # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11000 ; N # Mc BRAHMI SIGN CANDRABINDU +11001 ; N # Mn BRAHMI SIGN ANUSVARA +11002 ; N # Mc BRAHMI SIGN VISARGA +11003..11037 ; N # Lo [53] BRAHMI SIGN JIHVAMULIYA..BRAHMI LETTER OLD TAMIL NNNA +11038..11046 ; N # Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA +11047..1104D ; N # Po [7] BRAHMI DANDA..BRAHMI PUNCTUATION LOTUS +11052..11065 ; N # No [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +11066..1106F ; N # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +11070 ; N # Mn BRAHMI SIGN OLD TAMIL VIRAMA +11071..11072 ; N # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11073..11074 ; N # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +11075 ; N # Lo BRAHMI LETTER OLD TAMIL LLA +1107F ; N # Mn BRAHMI NUMBER JOINER +11080..11081 ; N # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA +11082 ; N # Mc KAITHI SIGN VISARGA +11083..110AF ; N # Lo [45] KAITHI LETTER A..KAITHI LETTER HA +110B0..110B2 ; N # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B3..110B6 ; N # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B7..110B8 ; N # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +110B9..110BA ; N # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110BB..110BC ; N # Po [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN +110BD ; N # Cf KAITHI NUMBER SIGN +110BE..110C1 ; N # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA +110C2 ; N # Mn KAITHI VOWEL SIGN VOCALIC R +110CD ; N # Cf KAITHI NUMBER SIGN ABOVE +110D0..110E8 ; N # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +110F0..110F9 ; N # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11100..11102 ; N # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11103..11126 ; N # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11127..1112B ; N # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112C ; N # Mc CHAKMA VOWEL SIGN E +1112D..11134 ; N # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11136..1113F ; N # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +11140..11143 ; N # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11144 ; N # Lo CHAKMA LETTER LHAA +11145..11146 ; N # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11147 ; N # Lo CHAKMA LETTER VAA +11150..11172 ; N # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11173 ; N # Mn MAHAJANI SIGN NUKTA +11174..11175 ; N # Po [2] MAHAJANI ABBREVIATION SIGN..MAHAJANI SECTION MARK +11176 ; N # Lo MAHAJANI LIGATURE SHRI +11180..11181 ; N # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +11182 ; N # Mc SHARADA SIGN VISARGA +11183..111B2 ; N # Lo [48] SHARADA LETTER A..SHARADA LETTER HA +111B3..111B5 ; N # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111B6..111BE ; N # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111BF..111C0 ; N # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111C1..111C4 ; N # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111C5..111C8 ; N # Po [4] SHARADA DANDA..SHARADA SEPARATOR +111C9..111CC ; N # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CD ; N # Po SHARADA SUTRA MARK +111CE ; N # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E +111CF ; N # Mn SHARADA SIGN INVERTED CANDRABINDU +111D0..111D9 ; N # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +111DA ; N # Lo SHARADA EKAM +111DB ; N # Po SHARADA SIGN SIDDHAM +111DC ; N # Lo SHARADA HEADSTROKE +111DD..111DF ; N # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +111E1..111F4 ; N # No [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND +11200..11211 ; N # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; N # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1122C..1122E ; N # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +1122F..11231 ; N # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11232..11233 ; N # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11234 ; N # Mn KHOJKI SIGN ANUSVARA +11235 ; N # Mc KHOJKI SIGN VIRAMA +11236..11237 ; N # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +11238..1123D ; N # Po [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN +1123E ; N # Mn KHOJKI SIGN SUKUN +1123F..11240 ; N # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11241 ; N # Mn KHOJKI VOWEL SIGN VOCALIC R +11280..11286 ; N # Lo [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; N # Lo MULTANI LETTER GHA +1128A..1128D ; N # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; N # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; N # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112A9 ; N # Po MULTANI SECTION MARK +112B0..112DE ; N # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +112DF ; N # Mn KHUDAWADI SIGN ANUSVARA +112E0..112E2 ; N # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +112E3..112EA ; N # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +112F0..112F9 ; N # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11300..11301 ; N # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +11302..11303 ; N # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +11305..1130C ; N # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; N # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; N # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; N # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; N # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; N # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133B..1133C ; N # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +1133D ; N # Lo GRANTHA SIGN AVAGRAHA +1133E..1133F ; N # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11340 ; N # Mn GRANTHA VOWEL SIGN II +11341..11344 ; N # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; N # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134D ; N # Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA +11350 ; N # Lo GRANTHA OM +11357 ; N # Mc GRANTHA AU LENGTH MARK +1135D..11361 ; N # Lo [5] GRANTHA SIGN PLUTA..GRANTHA LETTER VOCALIC LL +11362..11363 ; N # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11366..1136C ; N # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; N # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11400..11434 ; N # Lo [53] NEWA LETTER A..NEWA LETTER HA +11435..11437 ; N # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11438..1143F ; N # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11440..11441 ; N # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11442..11444 ; N # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11445 ; N # Mc NEWA SIGN VISARGA +11446 ; N # Mn NEWA SIGN NUKTA +11447..1144A ; N # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1144B..1144F ; N # Po [5] NEWA DANDA..NEWA ABBREVIATION SIGN +11450..11459 ; N # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +1145A..1145B ; N # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK +1145D ; N # Po NEWA INSERTION SIGN +1145E ; N # Mn NEWA SANDHI MARK +1145F..11461 ; N # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; N # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114B0..114B2 ; N # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B3..114B8 ; N # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114B9 ; N # Mc TIRHUTA VOWEL SIGN E +114BA ; N # Mn TIRHUTA VOWEL SIGN SHORT E +114BB..114BE ; N # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114BF..114C0 ; N # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C1 ; N # Mc TIRHUTA SIGN VISARGA +114C2..114C3 ; N # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +114C4..114C5 ; N # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C6 ; N # Po TIRHUTA ABBREVIATION SIGN +114C7 ; N # Lo TIRHUTA OM +114D0..114D9 ; N # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11580..115AE ; N # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115AF..115B1 ; N # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B2..115B5 ; N # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115B8..115BB ; N # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BC..115BD ; N # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BE ; N # Mc SIDDHAM SIGN VISARGA +115BF..115C0 ; N # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115C1..115D7 ; N # Po [23] SIDDHAM SIGN SIDDHAM..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES +115D8..115DB ; N # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +115DC..115DD ; N # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11600..1162F ; N # Lo [48] MODI LETTER A..MODI LETTER LLA +11630..11632 ; N # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +11633..1163A ; N # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163B..1163C ; N # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163D ; N # Mn MODI SIGN ANUSVARA +1163E ; N # Mc MODI SIGN VISARGA +1163F..11640 ; N # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +11641..11643 ; N # Po [3] MODI DANDA..MODI ABBREVIATION SIGN +11644 ; N # Lo MODI SIGN HUVA +11650..11659 ; N # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE +11660..1166C ; N # Po [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT +11680..116AA ; N # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA +116AB ; N # Mn TAKRI SIGN ANUSVARA +116AC ; N # Mc TAKRI SIGN VISARGA +116AD ; N # Mn TAKRI VOWEL SIGN AA +116AE..116AF ; N # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B0..116B5 ; N # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B6 ; N # Mc TAKRI SIGN VIRAMA +116B7 ; N # Mn TAKRI SIGN NUKTA +116B8 ; N # Lo TAKRI LETTER ARCHAIC KHA +116B9 ; N # Po TAKRI ABBREVIATION SIGN +116C0..116C9 ; N # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11700..1171A ; N # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA +1171D..1171F ; N # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11720..11721 ; N # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11722..11725 ; N # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11726 ; N # Mc AHOM VOWEL SIGN E +11727..1172B ; N # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +11730..11739 ; N # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +1173A..1173B ; N # No [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY +1173C..1173E ; N # Po [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI +1173F ; N # So AHOM SYMBOL VI +11740..11746 ; N # Lo [7] AHOM LETTER CA..AHOM LETTER LLA +11800..1182B ; N # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA +1182C..1182E ; N # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +1182F..11837 ; N # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11838 ; N # Mc DOGRA SIGN VISARGA +11839..1183A ; N # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +1183B ; N # Po DOGRA ABBREVIATION SIGN +118A0..118DF ; N # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +118E0..118E9 ; N # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +118EA..118F2 ; N # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY +118FF ; N # Lo WARANG CITI OM +11900..11906 ; N # Lo [7] DIVES AKURU LETTER A..DIVES AKURU LETTER E +11909 ; N # Lo DIVES AKURU LETTER O +1190C..11913 ; N # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; N # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; N # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +11930..11935 ; N # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; N # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193B..1193C ; N # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193D ; N # Mc DIVES AKURU SIGN HALANTA +1193E ; N # Mn DIVES AKURU VIRAMA +1193F ; N # Lo DIVES AKURU PREFIXED NASAL SIGN +11940 ; N # Mc DIVES AKURU MEDIAL YA +11941 ; N # Lo DIVES AKURU INITIAL RA +11942 ; N # Mc DIVES AKURU MEDIAL RA +11943 ; N # Mn DIVES AKURU SIGN NUKTA +11944..11946 ; N # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +11950..11959 ; N # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +119A0..119A7 ; N # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; N # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119D1..119D3 ; N # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119D4..119D7 ; N # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; N # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119DC..119DF ; N # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E0 ; N # Mn NANDINAGARI SIGN VIRAMA +119E1 ; N # Lo NANDINAGARI SIGN AVAGRAHA +119E2 ; N # Po NANDINAGARI SIGN SIDDHAM +119E3 ; N # Lo NANDINAGARI HEADSTROKE +119E4 ; N # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A00 ; N # Lo ZANABAZAR SQUARE LETTER A +11A01..11A0A ; N # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A0B..11A32 ; N # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A33..11A38 ; N # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A39 ; N # Mc ZANABAZAR SQUARE SIGN VISARGA +11A3A ; N # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A3B..11A3E ; N # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A3F..11A46 ; N # Po [8] ZANABAZAR SQUARE INITIAL HEAD MARK..ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK +11A47 ; N # Mn ZANABAZAR SQUARE SUBJOINER +11A50 ; N # Lo SOYOMBO LETTER A +11A51..11A56 ; N # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A57..11A58 ; N # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A59..11A5B ; N # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A5C..11A89 ; N # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A8A..11A96 ; N # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A97 ; N # Mc SOYOMBO SIGN VISARGA +11A98..11A99 ; N # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11A9A..11A9C ; N # Po [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD +11A9D ; N # Lo SOYOMBO MARK PLUTA +11A9E..11AA2 ; N # Po [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2 +11AB0..11ABF ; N # Lo [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA +11AC0..11AF8 ; N # Lo [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL +11B00..11B09 ; N # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU +11C00..11C08 ; N # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; N # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C2F ; N # Mc BHAIKSUKI VOWEL SIGN AA +11C30..11C36 ; N # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; N # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3E ; N # Mc BHAIKSUKI SIGN VISARGA +11C3F ; N # Mn BHAIKSUKI SIGN VIRAMA +11C40 ; N # Lo BHAIKSUKI SIGN AVAGRAHA +11C41..11C45 ; N # Po [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2 +11C50..11C59 ; N # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11C5A..11C6C ; N # No [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK +11C70..11C71 ; N # Po [2] MARCHEN HEAD MARK..MARCHEN MARK SHAD +11C72..11C8F ; N # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A +11C92..11CA7 ; N # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CA9 ; N # Mc MARCHEN SUBJOINED LETTER YA +11CAA..11CB0 ; N # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB1 ; N # Mc MARCHEN VOWEL SIGN I +11CB2..11CB3 ; N # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB4 ; N # Mc MARCHEN VOWEL SIGN O +11CB5..11CB6 ; N # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D00..11D06 ; N # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; N # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; N # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D31..11D36 ; N # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; N # Mn MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; N # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; N # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D46 ; N # Lo MASARAM GONDI REPHA +11D47 ; N # Mn MASARAM GONDI RA-KARA +11D50..11D59 ; N # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11D60..11D65 ; N # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; N # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; N # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D8A..11D8E ; N # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D90..11D91 ; N # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D93..11D94 ; N # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D95 ; N # Mn GUNJALA GONDI SIGN ANUSVARA +11D96 ; N # Mc GUNJALA GONDI SIGN VISARGA +11D97 ; N # Mn GUNJALA GONDI VIRAMA +11D98 ; N # Lo GUNJALA GONDI OM +11DA0..11DA9 ; N # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11EE0..11EF2 ; N # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA +11EF3..11EF4 ; N # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11EF5..11EF6 ; N # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11EF7..11EF8 ; N # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F00..11F01 ; N # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F02 ; N # Lo KAWI SIGN REPHA +11F03 ; N # Mc KAWI SIGN VISARGA +11F04..11F10 ; N # Lo [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; N # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA +11F34..11F35 ; N # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F36..11F3A ; N # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F3E..11F3F ; N # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F40 ; N # Mn KAWI VOWEL SIGN EU +11F41 ; N # Mc KAWI SIGN KILLER +11F42 ; N # Mn KAWI CONJOINER +11F43..11F4F ; N # Po [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL +11F50..11F59 ; N # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +11FB0 ; N # Lo LISU LETTER YHA +11FC0..11FD4 ; N # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH +11FD5..11FDC ; N # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI +11FDD..11FE0 ; N # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN +11FE1..11FF1 ; N # So [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA +11FFF ; N # Po TAMIL PUNCTUATION END OF TEXT +12000..12399 ; N # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12400..1246E ; N # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM +12470..12474 ; N # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON +12480..12543 ; N # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; N # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +12FF1..12FF2 ; N # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 +13000..1342F ; N # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D +13430..1343F ; N # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +13440 ; N # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13441..13446 ; N # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +13447..13455 ; N # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +14400..14646 ; N # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; N # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; N # Lo [31] MRO LETTER TA..MRO LETTER TEK +16A60..16A69 ; N # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE +16A6E..16A6F ; N # Po [2] MRO DANDA..MRO DOUBLE DANDA +16A70..16ABE ; N # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AC0..16AC9 ; N # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16AD0..16AED ; N # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16AF0..16AF4 ; N # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16AF5 ; N # Po BASSA VAH FULL STOP +16B00..16B2F ; N # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B30..16B36 ; N # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16B37..16B3B ; N # Po [5] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN VOS FEEM +16B3C..16B3F ; N # So [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB +16B40..16B43 ; N # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16B44 ; N # Po PAHAWH HMONG SIGN XAUS +16B45 ; N # So PAHAWH HMONG SIGN CIM TSOV ROG +16B50..16B59 ; N # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +16B5B..16B61 ; N # No [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS +16B63..16B77 ; N # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; N # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16E40..16E7F ; N # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16E80..16E96 ; N # No [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM +16E97..16E9A ; N # Po [4] MEDEFAIDRIN COMMA..MEDEFAIDRIN EXCLAMATION OH +16F00..16F4A ; N # Lo [75] MIAO LETTER PA..MIAO LETTER RTE +16F4F ; N # Mn MIAO SIGN CONSONANT MODIFIER BAR +16F50 ; N # Lo MIAO LETTER NASALIZATION +16F51..16F87 ; N # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16F8F..16F92 ; N # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW +16F93..16F9F ; N # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; W # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE2 ; W # Po OLD CHINESE HOOK MARK +16FE3 ; W # Lm OLD CHINESE ITERATION MARK +16FE4 ; W # Mn KHITAN SMALL SCRIPT FILLER +16FF0..16FF1 ; W # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +17000..187F7 ; W # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 +18800..18AFF ; W # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 +18B00..18CD5 ; W # Lo [470] KHITAN SMALL SCRIPT CHARACTER-18B00..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08 ; W # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +1AFF0..1AFF3 ; W # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; W # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; W # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1B000..1B0FF ; W # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2 +1B100..1B122 ; W # Lo [35] HENTAIGANA LETTER RE-3..KATAKANA LETTER ARCHAIC WU +1B132 ; W # Lo HIRAGANA LETTER SMALL KO +1B150..1B152 ; W # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; W # Lo KATAKANA LETTER SMALL KO +1B164..1B167 ; W # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1B170..1B2FB ; W # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB +1BC00..1BC6A ; N # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; N # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; N # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; N # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1BC9C ; N # So DUPLOYAN SIGN O WITH CROSS +1BC9D..1BC9E ; N # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1BC9F ; N # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP +1BCA0..1BCA3 ; N # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1CF00..1CF2D ; N # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; N # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1CF50..1CFC3 ; N # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK +1D000..1D0F5 ; N # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D100..1D126 ; N # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D129..1D164 ; N # So [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE +1D165..1D166 ; N # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D167..1D169 ; N # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D16A..1D16C ; N # So [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3 +1D16D..1D172 ; N # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 +1D173..1D17A ; N # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +1D17B..1D182 ; N # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D183..1D184 ; N # So [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN +1D185..1D18B ; N # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D18C..1D1A9 ; N # So [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH +1D1AA..1D1AD ; N # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D1AE..1D1EA ; N # So [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON +1D200..1D241 ; N # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 +1D242..1D244 ; N # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1D245 ; N # So GREEK MUSICAL LEIMMA +1D2C0..1D2D3 ; N # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN +1D2E0..1D2F3 ; N # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN +1D300..1D356 ; N # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D360..1D378 ; N # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE +1D400..1D454 ; N # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G +1D456..1D49C ; N # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; N # Lu [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; N # Lu MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; N # Lu [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; N # Lu [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B9 ; N # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D +1D4BB ; N # Ll MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; N # Ll [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D505 ; N # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; N # Lu [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; N # Lu [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; N # Lu [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D51E..1D539 ; N # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; N # Lu [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; N # Lu [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; N # Lu MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; N # Lu [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D552..1D6A5 ; N # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6A8..1D6C0 ; N # Lu [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6C1 ; N # Sm MATHEMATICAL BOLD NABLA +1D6C2..1D6DA ; N # Ll [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DB ; N # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +1D6DC..1D6FA ; N # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA +1D6FB ; N # Sm MATHEMATICAL ITALIC NABLA +1D6FC..1D714 ; N # Ll [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D715 ; N # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +1D716..1D734 ; N # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D735 ; N # Sm MATHEMATICAL BOLD ITALIC NABLA +1D736..1D74E ; N # Ll [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D74F ; N # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +1D750..1D76E ; N # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D76F ; N # Sm MATHEMATICAL SANS-SERIF BOLD NABLA +1D770..1D788 ; N # Ll [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D789 ; N # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +1D78A..1D7A8 ; N # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7A9 ; N # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA +1D7AA..1D7C2 ; N # Ll [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C3 ; N # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL +1D7C4..1D7CB ; N # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA +1D7CE..1D7FF ; N # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1D800..1D9FF ; N # So [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD +1DA00..1DA36 ; N # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA37..1DA3A ; N # So [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE +1DA3B..1DA6C ; N # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA6D..1DA74 ; N # So [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING +1DA75 ; N # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA76..1DA83 ; N # So [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH +1DA84 ; N # Mn SIGNWRITING LOCATION HEAD NECK +1DA85..1DA86 ; N # So [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS +1DA87..1DA8B ; N # Po [5] SIGNWRITING COMMA..SIGNWRITING PARENTHESIS +1DA9B..1DA9F ; N # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; N # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1DF00..1DF09 ; N # Ll [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0A ; N # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1DF0B..1DF1E ; N # Ll [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; N # Ll [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E000..1E006 ; N # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; N # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; N # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; N # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; N # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E030..1E06D ; N # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E08F ; N # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E100..1E12C ; N # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E130..1E136 ; N # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E137..1E13D ; N # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E140..1E149 ; N # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E14E ; N # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E14F ; N # So NYIAKENG PUACHUE HMONG CIRCLED CA +1E290..1E2AD ; N # Lo [30] TOTO LETTER PA..TOTO LETTER A +1E2AE ; N # Mn TOTO SIGN RISING TONE +1E2C0..1E2EB ; N # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E2EC..1E2EF ; N # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E2F0..1E2F9 ; N # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E2FF ; N # Sc WANCHO NGUN SIGN +1E4D0..1E4EA ; N # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E4EB ; N # Lm NAG MUNDARI SIGN OJOD +1E4EC..1E4EF ; N # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E4F0..1E4F9 ; N # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E7E0..1E7E6 ; N # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; N # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; N # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; N # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; N # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1E8C7..1E8CF ; N # No [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE +1E8D0..1E8D6 ; N # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E900..1E943 ; N # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA +1E944..1E94A ; N # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +1E94B ; N # Lm ADLAM NASALIZATION MARK +1E950..1E959 ; N # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1E95E..1E95F ; N # Po [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK +1EC71..1ECAB ; N # No [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE +1ECAC ; N # So INDIC SIYAQ PLACEHOLDER +1ECAD..1ECAF ; N # No [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS +1ECB0 ; N # Sc INDIC SIYAQ RUPEE MARK +1ECB1..1ECB4 ; N # No [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK +1ED01..1ED2D ; N # No [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND +1ED2E ; N # So OTTOMAN SIYAQ MARRATAN +1ED2F..1ED3D ; N # No [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH +1EE00..1EE03 ; N # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; N # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; N # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; N # Lo ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; N # Lo ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; N # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; N # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; N # Lo ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; N # Lo ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; N # Lo ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; N # Lo ARABIC MATHEMATICAL TAILED HAH +1EE49 ; N # Lo ARABIC MATHEMATICAL TAILED YEH +1EE4B ; N # Lo ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; N # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; N # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; N # Lo ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; N # Lo ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; N # Lo ARABIC MATHEMATICAL TAILED DAD +1EE5B ; N # Lo ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; N # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; N # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; N # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; N # Lo ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; N # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; N # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; N # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; N # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; N # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; N # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; N # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; N # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; N # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; N # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +1EEF0..1EEF1 ; N # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1F000..1F003 ; N # So [4] MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND +1F004 ; W # So MAHJONG TILE RED DRAGON +1F005..1F02B ; N # So [39] MAHJONG TILE GREEN DRAGON..MAHJONG TILE BACK +1F030..1F093 ; N # So [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F0A0..1F0AE ; N # So [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0B1..1F0BF ; N # So [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER +1F0C1..1F0CE ; N # So [14] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD KING OF DIAMONDS +1F0CF ; W # So PLAYING CARD BLACK JOKER +1F0D1..1F0F5 ; N # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 +1F100..1F10A ; A # No [11] DIGIT ZERO FULL STOP..DIGIT NINE COMMA +1F10B..1F10C ; N # No [2] DINGBAT CIRCLED SANS-SERIF DIGIT ZERO..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO +1F10D..1F10F ; N # So [3] CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F110..1F12D ; A # So [30] PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED CD +1F12E..1F12F ; N # So [2] CIRCLED WZ..COPYLEFT SYMBOL +1F130..1F169 ; A # So [58] SQUARED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z +1F16A..1F16F ; N # So [6] RAISED MC SIGN..CIRCLED HUMAN FIGURE +1F170..1F18D ; A # So [30] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED SA +1F18E ; W # So NEGATIVE SQUARED AB +1F18F..1F190 ; A # So [2] NEGATIVE SQUARED WC..SQUARE DJ +1F191..1F19A ; W # So [10] SQUARED CL..SQUARED VS +1F19B..1F1AC ; A # So [18] SQUARED THREE D..SQUARED VOD +1F1AD ; N # So MASK WORK SYMBOL +1F1E6..1F1FF ; N # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z +1F200..1F202 ; W # So [3] SQUARE HIRAGANA HOKA..SQUARED KATAKANA SA +1F210..1F23B ; W # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D +1F240..1F248 ; W # So [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F250..1F251 ; W # So [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F260..1F265 ; W # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F300..1F320 ; W # So [33] CYCLONE..SHOOTING STAR +1F321..1F32C ; N # So [12] THERMOMETER..WIND BLOWING FACE +1F32D..1F335 ; W # So [9] HOT DOG..CACTUS +1F336 ; N # So HOT PEPPER +1F337..1F37C ; W # So [70] TULIP..BABY BOTTLE +1F37D ; N # So FORK AND KNIFE WITH PLATE +1F37E..1F393 ; W # So [22] BOTTLE WITH POPPING CORK..GRADUATION CAP +1F394..1F39F ; N # So [12] HEART WITH TIP ON THE LEFT..ADMISSION TICKETS +1F3A0..1F3CA ; W # So [43] CAROUSEL HORSE..SWIMMER +1F3CB..1F3CE ; N # So [4] WEIGHT LIFTER..RACING CAR +1F3CF..1F3D3 ; W # So [5] CRICKET BAT AND BALL..TABLE TENNIS PADDLE AND BALL +1F3D4..1F3DF ; N # So [12] SNOW CAPPED MOUNTAIN..STADIUM +1F3E0..1F3F0 ; W # So [17] HOUSE BUILDING..EUROPEAN CASTLE +1F3F1..1F3F3 ; N # So [3] WHITE PENNANT..WAVING WHITE FLAG +1F3F4 ; W # So WAVING BLACK FLAG +1F3F5..1F3F7 ; N # So [3] ROSETTE..LABEL +1F3F8..1F3FA ; W # So [3] BADMINTON RACQUET AND SHUTTLECOCK..AMPHORA +1F3FB..1F3FF ; W # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 +1F400..1F43E ; W # So [63] RAT..PAW PRINTS +1F43F ; N # So CHIPMUNK +1F440 ; W # So EYES +1F441 ; N # So EYE +1F442..1F4FC ; W # So [187] EAR..VIDEOCASSETTE +1F4FD..1F4FE ; N # So [2] FILM PROJECTOR..PORTABLE STEREO +1F4FF..1F53D ; W # So [63] PRAYER BEADS..DOWN-POINTING SMALL RED TRIANGLE +1F53E..1F54A ; N # So [13] LOWER RIGHT SHADOWED WHITE CIRCLE..DOVE OF PEACE +1F54B..1F54E ; W # So [4] KAABA..MENORAH WITH NINE BRANCHES +1F54F ; N # So BOWL OF HYGIEIA +1F550..1F567 ; W # So [24] CLOCK FACE ONE OCLOCK..CLOCK FACE TWELVE-THIRTY +1F568..1F579 ; N # So [18] RIGHT SPEAKER..JOYSTICK +1F57A ; W # So MAN DANCING +1F57B..1F594 ; N # So [26] LEFT HAND TELEPHONE RECEIVER..REVERSED VICTORY HAND +1F595..1F596 ; W # So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS +1F597..1F5A3 ; N # So [13] WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; W # So BLACK HEART +1F5A5..1F5FA ; N # So [86] DESKTOP COMPUTER..WORLD MAP +1F5FB..1F5FF ; W # So [5] MOUNT FUJI..MOYAI +1F600..1F64F ; W # So [80] GRINNING FACE..PERSON WITH FOLDED HANDS +1F650..1F67F ; N # So [48] NORTH WEST POINTING LEAF..REVERSE CHECKER BOARD +1F680..1F6C5 ; W # So [70] ROCKET..LEFT LUGGAGE +1F6C6..1F6CB ; N # So [6] TRIANGLE WITH ROUNDED CORNERS..COUCH AND LAMP +1F6CC ; W # So SLEEPING ACCOMMODATION +1F6CD..1F6CF ; N # So [3] SHOPPING BAGS..BED +1F6D0..1F6D2 ; W # So [3] PLACE OF WORSHIP..SHOPPING TROLLEY +1F6D3..1F6D4 ; N # So [2] STUPA..PAGODA +1F6D5..1F6D7 ; W # So [3] HINDU TEMPLE..ELEVATOR +1F6DC..1F6DF ; W # So [4] WIRELESS..RING BUOY +1F6E0..1F6EA ; N # So [11] HAMMER AND WRENCH..NORTHEAST-POINTING AIRPLANE +1F6EB..1F6EC ; W # So [2] AIRPLANE DEPARTURE..AIRPLANE ARRIVING +1F6F0..1F6F3 ; N # So [4] SATELLITE..PASSENGER SHIP +1F6F4..1F6FC ; W # So [9] SCOOTER..ROLLER SKATE +1F700..1F776 ; N # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE +1F77B..1F77F ; N # So [5] HAUMEA..ORCUS +1F780..1F7D9 ; N # So [90] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..NINE POINTED WHITE STAR +1F7E0..1F7EB ; W # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE +1F7F0 ; W # So HEAVY EQUALS SIGN +1F800..1F80B ; N # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD +1F810..1F847 ; N # So [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW +1F850..1F859 ; N # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW +1F860..1F887 ; N # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW +1F890..1F8AD ; N # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8B0..1F8B1 ; N # So [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST +1F900..1F90B ; N # So [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT +1F90C..1F93A ; W # So [47] PINCHED FINGERS..FENCER +1F93B ; N # So MODERN PENTATHLON +1F93C..1F945 ; W # So [10] WRESTLERS..GOAL NET +1F946 ; N # So RIFLE +1F947..1F9FF ; W # So [185] FIRST PLACE MEDAL..NAZAR AMULET +1FA00..1FA53 ; N # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP +1FA60..1FA6D ; N # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER +1FA70..1FA7C ; W # So [13] BALLET SHOES..CRUTCH +1FA80..1FA88 ; W # So [9] YO-YO..FLUTE +1FA90..1FABD ; W # So [46] RINGED PLANET..WING +1FABF..1FAC5 ; W # So [7] GOOSE..PERSON WITH CROWN +1FACE..1FADB ; W # So [14] MOOSE..PEA POD +1FAE0..1FAE8 ; W # So [9] MELTING FACE..SHAKING FACE +1FAF0..1FAF8 ; W # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FB00..1FB92 ; N # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA ; N # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON +1FBF0..1FBF9 ; N # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +20000..2A6DF ; W # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF +2A6E0..2A6FF ; W # Cn [32] .. +2A700..2B739 ; W # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B73A..2B73F ; W # Cn [6] .. +2B740..2B81D ; W # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B81E..2B81F ; W # Cn [2] .. +2B820..2CEA1 ; W # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEA2..2CEAF ; W # Cn [14] .. +2CEB0..2EBE0 ; W # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBE1..2EBEF ; W # Cn [15] .. +2EBF0..2EE5D ; W # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +2EE5E..2F7FF ; W # Cn [2466] .. +2F800..2FA1D ; W # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +2FA1E..2FA1F ; W # Cn [2] .. +2FA20..2FFFD ; W # Cn [1502] .. +30000..3134A ; W # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +3134B..3134F ; W # Cn [5] .. +31350..323AF ; W # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +323B0..3FFFD ; W # Cn [56398] .. +E0001 ; N # Cf LANGUAGE TAG +E0020..E007F ; N # Cf [96] TAG SPACE..CANCEL TAG +E0100..E01EF ; A # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 +F0000..FFFFD ; A # Co [65534] .. +100000..10FFFD ; A # Co [65534] .. + +# EOF diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreak.txt b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreak.txt new file mode 100644 index 000000000..b6bc67939 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreak.txt @@ -0,0 +1,3608 @@ +# LineBreak-15.1.0.txt +# Date: 2023-07-28, 13:19:22 GMT [KW] +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Unicode Character Database +# For documentation, see https://www.unicode.org/reports/tr44/ +# +# Line_Break Property +# +# This file is a normative contributory data file in the +# Unicode Character Database. +# +# The format is two fields separated by a semicolon. +# Field 0: Unicode code point value or range of code point values +# Field 1: Line_Break property, consisting of one of the following values: +# Non-tailorable: +# "BK", "CM", "CR", "GL", "LF", "NL", "SP", "WJ", "ZW", "ZWJ" +# Tailorable: +# "AI", "AK", "AL", "AP", "AS", "B2", "BA", "BB", "CB", "CJ", +# "CL", "CP", "EB", "EM", "EX", "H2", "H3", "HL", "HY", "ID", +# "IN", "IS", "JL", "JT", "JV", "NS", "NU", "OP", "PO", "PR", +# "QU", "RI", "SA", "SG", "SY", "VF", "VI", "XX" +# - All code points, assigned and unassigned, that are not listed +# explicitly are given the value "XX". +# - The unassigned code points in the following blocks default to "ID": +# CJK Unified Ideographs Extension A: U+3400..U+4DBF +# CJK Unified Ideographs: U+4E00..U+9FFF +# CJK Compatibility Ideographs: U+F900..U+FAFF +# - All undesignated code points in Planes 2 and 3, whether inside or +# outside of allocated blocks, default to "ID": +# Plane 2: U+20000..U+2FFFD +# Plane 3: U+30000..U+3FFFD +# - All unassigned code points in the following Plane 1 ranges, whether +# inside or outside of allocated blocks, also default to "ID": +# Plane 1 range: U+1F000..U+1FAFF +# Plane 1 range: U+1FC00..U+1FFFD +# - The unassigned code points in the following block default to "PR": +# Currency Symbols: U+20A0..U+20CF +# +# Character ranges are specified as for other property files in the +# Unicode Character Database. +# +# The comments following the number sign "#" list the General_Category +# property value or the L& alias of the derived value LC, the Unicode +# character name or names, and, in lines with ranges of code points, +# the code point count in square brackets. +# +# For more information, see UAX #14: Unicode Line Breaking Algorithm, +# at https://www.unicode.org/reports/tr14/ +# +# @missing: 0000..10FFFF; XX +0000..0008 ; CM # Cc [9] .. +0009 ; BA # Cc +000A ; LF # Cc +000B..000C ; BK # Cc [2] .. +000D ; CR # Cc +000E..001F ; CM # Cc [18] .. +0020 ; SP # Zs SPACE +0021 ; EX # Po EXCLAMATION MARK +0022 ; QU # Po QUOTATION MARK +0023 ; AL # Po NUMBER SIGN +0024 ; PR # Sc DOLLAR SIGN +0025 ; PO # Po PERCENT SIGN +0026 ; AL # Po AMPERSAND +0027 ; QU # Po APOSTROPHE +0028 ; OP # Ps LEFT PARENTHESIS +0029 ; CP # Pe RIGHT PARENTHESIS +002A ; AL # Po ASTERISK +002B ; PR # Sm PLUS SIGN +002C ; IS # Po COMMA +002D ; HY # Pd HYPHEN-MINUS +002E ; IS # Po FULL STOP +002F ; SY # Po SOLIDUS +0030..0039 ; NU # Nd [10] DIGIT ZERO..DIGIT NINE +003A..003B ; IS # Po [2] COLON..SEMICOLON +003C..003E ; AL # Sm [3] LESS-THAN SIGN..GREATER-THAN SIGN +003F ; EX # Po QUESTION MARK +0040 ; AL # Po COMMERCIAL AT +0041..005A ; AL # Lu [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z +005B ; OP # Ps LEFT SQUARE BRACKET +005C ; PR # Po REVERSE SOLIDUS +005D ; CP # Pe RIGHT SQUARE BRACKET +005E ; AL # Sk CIRCUMFLEX ACCENT +005F ; AL # Pc LOW LINE +0060 ; AL # Sk GRAVE ACCENT +0061..007A ; AL # Ll [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z +007B ; OP # Ps LEFT CURLY BRACKET +007C ; BA # Sm VERTICAL LINE +007D ; CL # Pe RIGHT CURLY BRACKET +007E ; AL # Sm TILDE +007F ; CM # Cc +0080..0084 ; CM # Cc [5] .. +0085 ; NL # Cc +0086..009F ; CM # Cc [26] .. +00A0 ; GL # Zs NO-BREAK SPACE +00A1 ; OP # Po INVERTED EXCLAMATION MARK +00A2 ; PO # Sc CENT SIGN +00A3..00A5 ; PR # Sc [3] POUND SIGN..YEN SIGN +00A6 ; AL # So BROKEN BAR +00A7 ; AI # Po SECTION SIGN +00A8 ; AI # Sk DIAERESIS +00A9 ; AL # So COPYRIGHT SIGN +00AA ; AI # Lo FEMININE ORDINAL INDICATOR +00AB ; QU # Pi LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00AC ; AL # Sm NOT SIGN +00AD ; BA # Cf SOFT HYPHEN +00AE ; AL # So REGISTERED SIGN +00AF ; AL # Sk MACRON +00B0 ; PO # So DEGREE SIGN +00B1 ; PR # Sm PLUS-MINUS SIGN +00B2..00B3 ; AI # No [2] SUPERSCRIPT TWO..SUPERSCRIPT THREE +00B4 ; BB # Sk ACUTE ACCENT +00B5 ; AL # Ll MICRO SIGN +00B6..00B7 ; AI # Po [2] PILCROW SIGN..MIDDLE DOT +00B8 ; AI # Sk CEDILLA +00B9 ; AI # No SUPERSCRIPT ONE +00BA ; AI # Lo MASCULINE ORDINAL INDICATOR +00BB ; QU # Pf RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +00BC..00BE ; AI # No [3] VULGAR FRACTION ONE QUARTER..VULGAR FRACTION THREE QUARTERS +00BF ; OP # Po INVERTED QUESTION MARK +00C0..00D6 ; AL # Lu [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS +00D7 ; AI # Sm MULTIPLICATION SIGN +00D8..00F6 ; AL # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS +00F7 ; AI # Sm DIVISION SIGN +00F8..00FF ; AL # Ll [8] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER Y WITH DIAERESIS +0100..017F ; AL # L& [128] LATIN CAPITAL LETTER A WITH MACRON..LATIN SMALL LETTER LONG S +0180..01BA ; AL # L& [59] LATIN SMALL LETTER B WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL +01BB ; AL # Lo LATIN LETTER TWO WITH STROKE +01BC..01BF ; AL # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN +01C0..01C3 ; AL # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +01C4..024F ; AL # L& [140] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER Y WITH STROKE +0250..0293 ; AL # Ll [68] LATIN SMALL LETTER TURNED A..LATIN SMALL LETTER EZH WITH CURL +0294 ; AL # Lo LATIN LETTER GLOTTAL STOP +0295..02AF ; AL # Ll [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +02B0..02C1 ; AL # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP +02C2..02C5 ; AL # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD +02C6 ; AL # Lm MODIFIER LETTER CIRCUMFLEX ACCENT +02C7 ; AI # Lm CARON +02C8 ; BB # Lm MODIFIER LETTER VERTICAL LINE +02C9..02CB ; AI # Lm [3] MODIFIER LETTER MACRON..MODIFIER LETTER GRAVE ACCENT +02CC ; BB # Lm MODIFIER LETTER LOW VERTICAL LINE +02CD ; AI # Lm MODIFIER LETTER LOW MACRON +02CE..02CF ; AL # Lm [2] MODIFIER LETTER LOW GRAVE ACCENT..MODIFIER LETTER LOW ACUTE ACCENT +02D0 ; AI # Lm MODIFIER LETTER TRIANGULAR COLON +02D1 ; AL # Lm MODIFIER LETTER HALF TRIANGULAR COLON +02D2..02D7 ; AL # Sk [6] MODIFIER LETTER CENTRED RIGHT HALF RING..MODIFIER LETTER MINUS SIGN +02D8..02DB ; AI # Sk [4] BREVE..OGONEK +02DC ; AL # Sk SMALL TILDE +02DD ; AI # Sk DOUBLE ACUTE ACCENT +02DE ; AL # Sk MODIFIER LETTER RHOTIC HOOK +02DF ; BB # Sk MODIFIER LETTER CROSS ACCENT +02E0..02E4 ; AL # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP +02E5..02EB ; AL # Sk [7] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER YANG DEPARTING TONE MARK +02EC ; AL # Lm MODIFIER LETTER VOICING +02ED ; AL # Sk MODIFIER LETTER UNASPIRATED +02EE ; AL # Lm MODIFIER LETTER DOUBLE APOSTROPHE +02EF..02FF ; AL # Sk [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW +0300..034E ; CM # Mn [79] COMBINING GRAVE ACCENT..COMBINING UPWARDS ARROW BELOW +034F ; GL # Mn COMBINING GRAPHEME JOINER +0350..035B ; CM # Mn [12] COMBINING RIGHT ARROWHEAD ABOVE..COMBINING ZIGZAG ABOVE +035C..0362 ; GL # Mn [7] COMBINING DOUBLE BREVE BELOW..COMBINING DOUBLE RIGHTWARDS ARROW BELOW +0363..036F ; CM # Mn [13] COMBINING LATIN SMALL LETTER A..COMBINING LATIN SMALL LETTER X +0370..0373 ; AL # L& [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI +0374 ; AL # Lm GREEK NUMERAL SIGN +0375 ; AL # Sk GREEK LOWER NUMERAL SIGN +0376..0377 ; AL # L& [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037A ; AL # Lm GREEK YPOGEGRAMMENI +037B..037D ; AL # Ll [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +037E ; IS # Po GREEK QUESTION MARK +037F ; AL # Lu GREEK CAPITAL LETTER YOT +0384..0385 ; AL # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS +0386 ; AL # Lu GREEK CAPITAL LETTER ALPHA WITH TONOS +0387 ; AL # Po GREEK ANO TELEIA +0388..038A ; AL # Lu [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS +038C ; AL # Lu GREEK CAPITAL LETTER OMICRON WITH TONOS +038E..03A1 ; AL # L& [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO +03A3..03F5 ; AL # L& [83] GREEK CAPITAL LETTER SIGMA..GREEK LUNATE EPSILON SYMBOL +03F6 ; AL # Sm GREEK REVERSED LUNATE EPSILON SYMBOL +03F7..03FF ; AL # L& [9] GREEK CAPITAL LETTER SHO..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL +0400..0481 ; AL # L& [130] CYRILLIC CAPITAL LETTER IE WITH GRAVE..CYRILLIC SMALL LETTER KOPPA +0482 ; AL # So CYRILLIC THOUSANDS SIGN +0483..0487 ; CM # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE +0488..0489 ; CM # Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN +048A..04FF ; AL # L& [118] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER HA WITH STROKE +0500..052F ; AL # L& [48] CYRILLIC CAPITAL LETTER KOMI DE..CYRILLIC SMALL LETTER EL WITH DESCENDER +0531..0556 ; AL # Lu [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH +0559 ; AL # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING +055A..055F ; AL # Po [6] ARMENIAN APOSTROPHE..ARMENIAN ABBREVIATION MARK +0560..0588 ; AL # Ll [41] ARMENIAN SMALL LETTER TURNED AYB..ARMENIAN SMALL LETTER YI WITH STROKE +0589 ; IS # Po ARMENIAN FULL STOP +058A ; BA # Pd ARMENIAN HYPHEN +058D..058E ; AL # So [2] RIGHT-FACING ARMENIAN ETERNITY SIGN..LEFT-FACING ARMENIAN ETERNITY SIGN +058F ; PR # Sc ARMENIAN DRAM SIGN +0591..05BD ; CM # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG +05BE ; BA # Pd HEBREW PUNCTUATION MAQAF +05BF ; CM # Mn HEBREW POINT RAFE +05C0 ; AL # Po HEBREW PUNCTUATION PASEQ +05C1..05C2 ; CM # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT +05C3 ; AL # Po HEBREW PUNCTUATION SOF PASUQ +05C4..05C5 ; CM # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C6 ; EX # Po HEBREW PUNCTUATION NUN HAFUKHA +05C7 ; CM # Mn HEBREW POINT QAMATS QATAN +05D0..05EA ; HL # Lo [27] HEBREW LETTER ALEF..HEBREW LETTER TAV +05EF..05F2 ; HL # Lo [4] HEBREW YOD TRIANGLE..HEBREW LIGATURE YIDDISH DOUBLE YOD +05F3..05F4 ; AL # Po [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM +0600..0605 ; NU # Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE +0606..0608 ; AL # Sm [3] ARABIC-INDIC CUBE ROOT..ARABIC RAY +0609..060A ; PO # Po [2] ARABIC-INDIC PER MILLE SIGN..ARABIC-INDIC PER TEN THOUSAND SIGN +060B ; PO # Sc AFGHANI SIGN +060C..060D ; IS # Po [2] ARABIC COMMA..ARABIC DATE SEPARATOR +060E..060F ; AL # So [2] ARABIC POETIC VERSE SIGN..ARABIC SIGN MISRA +0610..061A ; CM # Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA +061B ; EX # Po ARABIC SEMICOLON +061C ; CM # Cf ARABIC LETTER MARK +061D..061F ; EX # Po [3] ARABIC END OF TEXT MARK..ARABIC QUESTION MARK +0620..063F ; AL # Lo [32] ARABIC LETTER KASHMIRI YEH..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +0640 ; AL # Lm ARABIC TATWEEL +0641..064A ; AL # Lo [10] ARABIC LETTER FEH..ARABIC LETTER YEH +064B..065F ; CM # Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW +0660..0669 ; NU # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE +066A ; PO # Po ARABIC PERCENT SIGN +066B..066C ; NU # Po [2] ARABIC DECIMAL SEPARATOR..ARABIC THOUSANDS SEPARATOR +066D ; AL # Po ARABIC FIVE POINTED STAR +066E..066F ; AL # Lo [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF +0670 ; CM # Mn ARABIC LETTER SUPERSCRIPT ALEF +0671..06D3 ; AL # Lo [99] ARABIC LETTER ALEF WASLA..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE +06D4 ; EX # Po ARABIC FULL STOP +06D5 ; AL # Lo ARABIC LETTER AE +06D6..06DC ; CM # Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN +06DD ; NU # Cf ARABIC END OF AYAH +06DE ; AL # So ARABIC START OF RUB EL HIZB +06DF..06E4 ; CM # Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA +06E5..06E6 ; AL # Lm [2] ARABIC SMALL WAW..ARABIC SMALL YEH +06E7..06E8 ; CM # Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON +06E9 ; AL # So ARABIC PLACE OF SAJDAH +06EA..06ED ; CM # Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +06EE..06EF ; AL # Lo [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V +06F0..06F9 ; NU # Nd [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE +06FA..06FC ; AL # Lo [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +06FD..06FE ; AL # So [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN +06FF ; AL # Lo ARABIC LETTER HEH WITH INVERTED V +0700..070D ; AL # Po [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS +070F ; AL # Cf SYRIAC ABBREVIATION MARK +0710 ; AL # Lo SYRIAC LETTER ALAPH +0711 ; CM # Mn SYRIAC LETTER SUPERSCRIPT ALAPH +0712..072F ; AL # Lo [30] SYRIAC LETTER BETH..SYRIAC LETTER PERSIAN DHALATH +0730..074A ; CM # Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH +074D..074F ; AL # Lo [3] SYRIAC LETTER SOGDIAN ZHAIN..SYRIAC LETTER SOGDIAN FE +0750..077F ; AL # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE +0780..07A5 ; AL # Lo [38] THAANA LETTER HAA..THAANA LETTER WAAVU +07A6..07B0 ; CM # Mn [11] THAANA ABAFILI..THAANA SUKUN +07B1 ; AL # Lo THAANA LETTER NAA +07C0..07C9 ; NU # Nd [10] NKO DIGIT ZERO..NKO DIGIT NINE +07CA..07EA ; AL # Lo [33] NKO LETTER A..NKO LETTER JONA RA +07EB..07F3 ; CM # Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE +07F4..07F5 ; AL # Lm [2] NKO HIGH TONE APOSTROPHE..NKO LOW TONE APOSTROPHE +07F6 ; AL # So NKO SYMBOL OO DENNEN +07F7 ; AL # Po NKO SYMBOL GBAKURUNEN +07F8 ; IS # Po NKO COMMA +07F9 ; EX # Po NKO EXCLAMATION MARK +07FA ; AL # Lm NKO LAJANYALAN +07FD ; CM # Mn NKO DANTAYALAN +07FE..07FF ; PR # Sc [2] NKO DOROME SIGN..NKO TAMAN SIGN +0800..0815 ; AL # Lo [22] SAMARITAN LETTER ALAF..SAMARITAN LETTER TAAF +0816..0819 ; CM # Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH +081A ; AL # Lm SAMARITAN MODIFIER LETTER EPENTHETIC YUT +081B..0823 ; CM # Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A +0824 ; AL # Lm SAMARITAN MODIFIER LETTER SHORT A +0825..0827 ; CM # Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U +0828 ; AL # Lm SAMARITAN MODIFIER LETTER I +0829..082D ; CM # Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA +0830..083E ; AL # Po [15] SAMARITAN PUNCTUATION NEQUDAA..SAMARITAN PUNCTUATION ANNAAU +0840..0858 ; AL # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN +0859..085B ; CM # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK +085E ; AL # Po MANDAIC PUNCTUATION +0860..086A ; AL # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA +0870..0887 ; AL # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +0888 ; AL # Sk ARABIC RAISED ROUND DOT +0889..088E ; AL # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0890..0891 ; NU # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE +0898..089F ; CM # Mn [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08A0..08C8 ; AL # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF +08C9 ; AL # Lm ARABIC SMALL FARSI YEH +08CA..08E1 ; CM # Mn [24] ARABIC SMALL HIGH FARSI YEH..ARABIC SMALL HIGH SIGN SAFHA +08E2 ; NU # Cf ARABIC DISPUTED END OF AYAH +08E3..08FF ; CM # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA +0900..0902 ; CM # Mn [3] DEVANAGARI SIGN INVERTED CANDRABINDU..DEVANAGARI SIGN ANUSVARA +0903 ; CM # Mc DEVANAGARI SIGN VISARGA +0904..0939 ; AL # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA +093A ; CM # Mn DEVANAGARI VOWEL SIGN OE +093B ; CM # Mc DEVANAGARI VOWEL SIGN OOE +093C ; CM # Mn DEVANAGARI SIGN NUKTA +093D ; AL # Lo DEVANAGARI SIGN AVAGRAHA +093E..0940 ; CM # Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II +0941..0948 ; CM # Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI +0949..094C ; CM # Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU +094D ; CM # Mn DEVANAGARI SIGN VIRAMA +094E..094F ; CM # Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW +0950 ; AL # Lo DEVANAGARI OM +0951..0957 ; CM # Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE +0958..0961 ; AL # Lo [10] DEVANAGARI LETTER QA..DEVANAGARI LETTER VOCALIC LL +0962..0963 ; CM # Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL +0964..0965 ; BA # Po [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA +0966..096F ; NU # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE +0970 ; AL # Po DEVANAGARI ABBREVIATION SIGN +0971 ; AL # Lm DEVANAGARI SIGN HIGH SPACING DOT +0972..097F ; AL # Lo [14] DEVANAGARI LETTER CANDRA A..DEVANAGARI LETTER BBA +0980 ; AL # Lo BENGALI ANJI +0981 ; CM # Mn BENGALI SIGN CANDRABINDU +0982..0983 ; CM # Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA +0985..098C ; AL # Lo [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +098F..0990 ; AL # Lo [2] BENGALI LETTER E..BENGALI LETTER AI +0993..09A8 ; AL # Lo [22] BENGALI LETTER O..BENGALI LETTER NA +09AA..09B0 ; AL # Lo [7] BENGALI LETTER PA..BENGALI LETTER RA +09B2 ; AL # Lo BENGALI LETTER LA +09B6..09B9 ; AL # Lo [4] BENGALI LETTER SHA..BENGALI LETTER HA +09BC ; CM # Mn BENGALI SIGN NUKTA +09BD ; AL # Lo BENGALI SIGN AVAGRAHA +09BE..09C0 ; CM # Mc [3] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN II +09C1..09C4 ; CM # Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR +09C7..09C8 ; CM # Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI +09CB..09CC ; CM # Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU +09CD ; CM # Mn BENGALI SIGN VIRAMA +09CE ; AL # Lo BENGALI LETTER KHANDA TA +09D7 ; CM # Mc BENGALI AU LENGTH MARK +09DC..09DD ; AL # Lo [2] BENGALI LETTER RRA..BENGALI LETTER RHA +09DF..09E1 ; AL # Lo [3] BENGALI LETTER YYA..BENGALI LETTER VOCALIC LL +09E2..09E3 ; CM # Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL +09E6..09EF ; NU # Nd [10] BENGALI DIGIT ZERO..BENGALI DIGIT NINE +09F0..09F1 ; AL # Lo [2] BENGALI LETTER RA WITH MIDDLE DIAGONAL..BENGALI LETTER RA WITH LOWER DIAGONAL +09F2..09F3 ; PO # Sc [2] BENGALI RUPEE MARK..BENGALI RUPEE SIGN +09F4..09F8 ; AL # No [5] BENGALI CURRENCY NUMERATOR ONE..BENGALI CURRENCY NUMERATOR ONE LESS THAN THE DENOMINATOR +09F9 ; PO # No BENGALI CURRENCY DENOMINATOR SIXTEEN +09FA ; AL # So BENGALI ISSHAR +09FB ; PR # Sc BENGALI GANDA MARK +09FC ; AL # Lo BENGALI LETTER VEDIC ANUSVARA +09FD ; AL # Po BENGALI ABBREVIATION SIGN +09FE ; CM # Mn BENGALI SANDHI MARK +0A01..0A02 ; CM # Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI +0A03 ; CM # Mc GURMUKHI SIGN VISARGA +0A05..0A0A ; AL # Lo [6] GURMUKHI LETTER A..GURMUKHI LETTER UU +0A0F..0A10 ; AL # Lo [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI +0A13..0A28 ; AL # Lo [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA +0A2A..0A30 ; AL # Lo [7] GURMUKHI LETTER PA..GURMUKHI LETTER RA +0A32..0A33 ; AL # Lo [2] GURMUKHI LETTER LA..GURMUKHI LETTER LLA +0A35..0A36 ; AL # Lo [2] GURMUKHI LETTER VA..GURMUKHI LETTER SHA +0A38..0A39 ; AL # Lo [2] GURMUKHI LETTER SA..GURMUKHI LETTER HA +0A3C ; CM # Mn GURMUKHI SIGN NUKTA +0A3E..0A40 ; CM # Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II +0A41..0A42 ; CM # Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU +0A47..0A48 ; CM # Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI +0A4B..0A4D ; CM # Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA +0A51 ; CM # Mn GURMUKHI SIGN UDAAT +0A59..0A5C ; AL # Lo [4] GURMUKHI LETTER KHHA..GURMUKHI LETTER RRA +0A5E ; AL # Lo GURMUKHI LETTER FA +0A66..0A6F ; NU # Nd [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0A70..0A71 ; CM # Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A72..0A74 ; AL # Lo [3] GURMUKHI IRI..GURMUKHI EK ONKAR +0A75 ; CM # Mn GURMUKHI SIGN YAKASH +0A76 ; AL # Po GURMUKHI ABBREVIATION SIGN +0A81..0A82 ; CM # Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA +0A83 ; CM # Mc GUJARATI SIGN VISARGA +0A85..0A8D ; AL # Lo [9] GUJARATI LETTER A..GUJARATI VOWEL CANDRA E +0A8F..0A91 ; AL # Lo [3] GUJARATI LETTER E..GUJARATI VOWEL CANDRA O +0A93..0AA8 ; AL # Lo [22] GUJARATI LETTER O..GUJARATI LETTER NA +0AAA..0AB0 ; AL # Lo [7] GUJARATI LETTER PA..GUJARATI LETTER RA +0AB2..0AB3 ; AL # Lo [2] GUJARATI LETTER LA..GUJARATI LETTER LLA +0AB5..0AB9 ; AL # Lo [5] GUJARATI LETTER VA..GUJARATI LETTER HA +0ABC ; CM # Mn GUJARATI SIGN NUKTA +0ABD ; AL # Lo GUJARATI SIGN AVAGRAHA +0ABE..0AC0 ; CM # Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II +0AC1..0AC5 ; CM # Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E +0AC7..0AC8 ; CM # Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI +0AC9 ; CM # Mc GUJARATI VOWEL SIGN CANDRA O +0ACB..0ACC ; CM # Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU +0ACD ; CM # Mn GUJARATI SIGN VIRAMA +0AD0 ; AL # Lo GUJARATI OM +0AE0..0AE1 ; AL # Lo [2] GUJARATI LETTER VOCALIC RR..GUJARATI LETTER VOCALIC LL +0AE2..0AE3 ; CM # Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL +0AE6..0AEF ; NU # Nd [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE +0AF0 ; AL # Po GUJARATI ABBREVIATION SIGN +0AF1 ; PR # Sc GUJARATI RUPEE SIGN +0AF9 ; AL # Lo GUJARATI LETTER ZHA +0AFA..0AFF ; CM # Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B01 ; CM # Mn ORIYA SIGN CANDRABINDU +0B02..0B03 ; CM # Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA +0B05..0B0C ; AL # Lo [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B0F..0B10 ; AL # Lo [2] ORIYA LETTER E..ORIYA LETTER AI +0B13..0B28 ; AL # Lo [22] ORIYA LETTER O..ORIYA LETTER NA +0B2A..0B30 ; AL # Lo [7] ORIYA LETTER PA..ORIYA LETTER RA +0B32..0B33 ; AL # Lo [2] ORIYA LETTER LA..ORIYA LETTER LLA +0B35..0B39 ; AL # Lo [5] ORIYA LETTER VA..ORIYA LETTER HA +0B3C ; CM # Mn ORIYA SIGN NUKTA +0B3D ; AL # Lo ORIYA SIGN AVAGRAHA +0B3E ; CM # Mc ORIYA VOWEL SIGN AA +0B3F ; CM # Mn ORIYA VOWEL SIGN I +0B40 ; CM # Mc ORIYA VOWEL SIGN II +0B41..0B44 ; CM # Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR +0B47..0B48 ; CM # Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI +0B4B..0B4C ; CM # Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU +0B4D ; CM # Mn ORIYA SIGN VIRAMA +0B55..0B56 ; CM # Mn [2] ORIYA SIGN OVERLINE..ORIYA AI LENGTH MARK +0B57 ; CM # Mc ORIYA AU LENGTH MARK +0B5C..0B5D ; AL # Lo [2] ORIYA LETTER RRA..ORIYA LETTER RHA +0B5F..0B61 ; AL # Lo [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL +0B62..0B63 ; CM # Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B66..0B6F ; NU # Nd [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B70 ; AL # So ORIYA ISSHAR +0B71 ; AL # Lo ORIYA LETTER WA +0B72..0B77 ; AL # No [6] ORIYA FRACTION ONE QUARTER..ORIYA FRACTION THREE SIXTEENTHS +0B82 ; CM # Mn TAMIL SIGN ANUSVARA +0B83 ; AL # Lo TAMIL SIGN VISARGA +0B85..0B8A ; AL # Lo [6] TAMIL LETTER A..TAMIL LETTER UU +0B8E..0B90 ; AL # Lo [3] TAMIL LETTER E..TAMIL LETTER AI +0B92..0B95 ; AL # Lo [4] TAMIL LETTER O..TAMIL LETTER KA +0B99..0B9A ; AL # Lo [2] TAMIL LETTER NGA..TAMIL LETTER CA +0B9C ; AL # Lo TAMIL LETTER JA +0B9E..0B9F ; AL # Lo [2] TAMIL LETTER NYA..TAMIL LETTER TTA +0BA3..0BA4 ; AL # Lo [2] TAMIL LETTER NNA..TAMIL LETTER TA +0BA8..0BAA ; AL # Lo [3] TAMIL LETTER NA..TAMIL LETTER PA +0BAE..0BB9 ; AL # Lo [12] TAMIL LETTER MA..TAMIL LETTER HA +0BBE..0BBF ; CM # Mc [2] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN I +0BC0 ; CM # Mn TAMIL VOWEL SIGN II +0BC1..0BC2 ; CM # Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU +0BC6..0BC8 ; CM # Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI +0BCA..0BCC ; CM # Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU +0BCD ; CM # Mn TAMIL SIGN VIRAMA +0BD0 ; AL # Lo TAMIL OM +0BD7 ; CM # Mc TAMIL AU LENGTH MARK +0BE6..0BEF ; NU # Nd [10] TAMIL DIGIT ZERO..TAMIL DIGIT NINE +0BF0..0BF2 ; AL # No [3] TAMIL NUMBER TEN..TAMIL NUMBER ONE THOUSAND +0BF3..0BF8 ; AL # So [6] TAMIL DAY SIGN..TAMIL AS ABOVE SIGN +0BF9 ; PR # Sc TAMIL RUPEE SIGN +0BFA ; AL # So TAMIL NUMBER SIGN +0C00 ; CM # Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE +0C01..0C03 ; CM # Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA +0C04 ; CM # Mn TELUGU SIGN COMBINING ANUSVARA ABOVE +0C05..0C0C ; AL # Lo [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C0E..0C10 ; AL # Lo [3] TELUGU LETTER E..TELUGU LETTER AI +0C12..0C28 ; AL # Lo [23] TELUGU LETTER O..TELUGU LETTER NA +0C2A..0C39 ; AL # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA +0C3C ; CM # Mn TELUGU SIGN NUKTA +0C3D ; AL # Lo TELUGU SIGN AVAGRAHA +0C3E..0C40 ; CM # Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II +0C41..0C44 ; CM # Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR +0C46..0C48 ; CM # Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI +0C4A..0C4D ; CM # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA +0C55..0C56 ; CM # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK +0C58..0C5A ; AL # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA +0C5D ; AL # Lo TELUGU LETTER NAKAARA POLLU +0C60..0C61 ; AL # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL +0C62..0C63 ; CM # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C66..0C6F ; NU # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C77 ; BB # Po TELUGU SIGN SIDDHAM +0C78..0C7E ; AL # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR +0C7F ; AL # So TELUGU SIGN TUUMU +0C80 ; AL # Lo KANNADA SIGN SPACING CANDRABINDU +0C81 ; CM # Mn KANNADA SIGN CANDRABINDU +0C82..0C83 ; CM # Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA +0C84 ; BB # Po KANNADA SIGN SIDDHAM +0C85..0C8C ; AL # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C8E..0C90 ; AL # Lo [3] KANNADA LETTER E..KANNADA LETTER AI +0C92..0CA8 ; AL # Lo [23] KANNADA LETTER O..KANNADA LETTER NA +0CAA..0CB3 ; AL # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CB5..0CB9 ; AL # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA +0CBC ; CM # Mn KANNADA SIGN NUKTA +0CBD ; AL # Lo KANNADA SIGN AVAGRAHA +0CBE ; CM # Mc KANNADA VOWEL SIGN AA +0CBF ; CM # Mn KANNADA VOWEL SIGN I +0CC0..0CC4 ; CM # Mc [5] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN VOCALIC RR +0CC6 ; CM # Mn KANNADA VOWEL SIGN E +0CC7..0CC8 ; CM # Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI +0CCA..0CCB ; CM # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO +0CCC..0CCD ; CM # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA +0CD5..0CD6 ; CM # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CDD..0CDE ; AL # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CE0..0CE1 ; AL # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CE2..0CE3 ; CM # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0CE6..0CEF ; NU # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE +0CF1..0CF2 ; AL # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0CF3 ; CM # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D00..0D01 ; CM # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU +0D02..0D03 ; CM # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA +0D04..0D0C ; AL # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L +0D0E..0D10 ; AL # Lo [3] MALAYALAM LETTER E..MALAYALAM LETTER AI +0D12..0D3A ; AL # Lo [41] MALAYALAM LETTER O..MALAYALAM LETTER TTTA +0D3B..0D3C ; CM # Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D3D ; AL # Lo MALAYALAM SIGN AVAGRAHA +0D3E..0D40 ; CM # Mc [3] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN II +0D41..0D44 ; CM # Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR +0D46..0D48 ; CM # Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI +0D4A..0D4C ; CM # Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU +0D4D ; CM # Mn MALAYALAM SIGN VIRAMA +0D4E ; AL # Lo MALAYALAM LETTER DOT REPH +0D4F ; AL # So MALAYALAM SIGN PARA +0D54..0D56 ; AL # Lo [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D57 ; CM # Mc MALAYALAM AU LENGTH MARK +0D58..0D5E ; AL # No [7] MALAYALAM FRACTION ONE ONE-HUNDRED-AND-SIXTIETH..MALAYALAM FRACTION ONE FIFTH +0D5F..0D61 ; AL # Lo [3] MALAYALAM LETTER ARCHAIC II..MALAYALAM LETTER VOCALIC LL +0D62..0D63 ; CM # Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D66..0D6F ; NU # Nd [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D70..0D78 ; AL # No [9] MALAYALAM NUMBER TEN..MALAYALAM FRACTION THREE SIXTEENTHS +0D79 ; PO # So MALAYALAM DATE MARK +0D7A..0D7F ; AL # Lo [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K +0D81 ; CM # Mn SINHALA SIGN CANDRABINDU +0D82..0D83 ; CM # Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA +0D85..0D96 ; AL # Lo [18] SINHALA LETTER AYANNA..SINHALA LETTER AUYANNA +0D9A..0DB1 ; AL # Lo [24] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER DANTAJA NAYANNA +0DB3..0DBB ; AL # Lo [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA +0DBD ; AL # Lo SINHALA LETTER DANTAJA LAYANNA +0DC0..0DC6 ; AL # Lo [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA +0DCA ; CM # Mn SINHALA SIGN AL-LAKUNA +0DCF..0DD1 ; CM # Mc [3] SINHALA VOWEL SIGN AELA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA +0DD2..0DD4 ; CM # Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA +0DD6 ; CM # Mn SINHALA VOWEL SIGN DIGA PAA-PILLA +0DD8..0DDF ; CM # Mc [8] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN GAYANUKITTA +0DE6..0DEF ; NU # Nd [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0DF2..0DF3 ; CM # Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA +0DF4 ; AL # Po SINHALA PUNCTUATION KUNDDALIYA +0E01..0E30 ; SA # Lo [48] THAI CHARACTER KO KAI..THAI CHARACTER SARA A +0E31 ; SA # Mn THAI CHARACTER MAI HAN-AKAT +0E32..0E33 ; SA # Lo [2] THAI CHARACTER SARA AA..THAI CHARACTER SARA AM +0E34..0E3A ; SA # Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU +0E3F ; PR # Sc THAI CURRENCY SYMBOL BAHT +0E40..0E45 ; SA # Lo [6] THAI CHARACTER SARA E..THAI CHARACTER LAKKHANGYAO +0E46 ; SA # Lm THAI CHARACTER MAIYAMOK +0E47..0E4E ; SA # Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN +0E4F ; AL # Po THAI CHARACTER FONGMAN +0E50..0E59 ; NU # Nd [10] THAI DIGIT ZERO..THAI DIGIT NINE +0E5A..0E5B ; BA # Po [2] THAI CHARACTER ANGKHANKHU..THAI CHARACTER KHOMUT +0E81..0E82 ; SA # Lo [2] LAO LETTER KO..LAO LETTER KHO SUNG +0E84 ; SA # Lo LAO LETTER KHO TAM +0E86..0E8A ; SA # Lo [5] LAO LETTER PALI GHA..LAO LETTER SO TAM +0E8C..0EA3 ; SA # Lo [24] LAO LETTER PALI JHA..LAO LETTER LO LING +0EA5 ; SA # Lo LAO LETTER LO LOOT +0EA7..0EB0 ; SA # Lo [10] LAO LETTER WO..LAO VOWEL SIGN A +0EB1 ; SA # Mn LAO VOWEL SIGN MAI KAN +0EB2..0EB3 ; SA # Lo [2] LAO VOWEL SIGN AA..LAO VOWEL SIGN AM +0EB4..0EBC ; SA # Mn [9] LAO VOWEL SIGN I..LAO SEMIVOWEL SIGN LO +0EBD ; SA # Lo LAO SEMIVOWEL SIGN NYO +0EC0..0EC4 ; SA # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI +0EC6 ; SA # Lm LAO KO LA +0EC8..0ECE ; SA # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN +0ED0..0ED9 ; NU # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE +0EDC..0EDF ; SA # Lo [4] LAO HO NO..LAO LETTER KHMU NYO +0F00 ; AL # Lo TIBETAN SYLLABLE OM +0F01..0F03 ; BB # So [3] TIBETAN MARK GTER YIG MGO TRUNCATED A..TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA +0F04 ; BB # Po TIBETAN MARK INITIAL YIG MGO MDUN MA +0F05 ; AL # Po TIBETAN MARK CLOSING YIG MGO SGAB MA +0F06..0F07 ; BB # Po [2] TIBETAN MARK CARET YIG MGO PHUR SHAD MA..TIBETAN MARK YIG MGO TSHEG SHAD MA +0F08 ; GL # Po TIBETAN MARK SBRUL SHAD +0F09..0F0A ; BB # Po [2] TIBETAN MARK BSKUR YIG MGO..TIBETAN MARK BKA- SHOG YIG MGO +0F0B ; BA # Po TIBETAN MARK INTERSYLLABIC TSHEG +0F0C ; GL # Po TIBETAN MARK DELIMITER TSHEG BSTAR +0F0D..0F11 ; EX # Po [5] TIBETAN MARK SHAD..TIBETAN MARK RIN CHEN SPUNGS SHAD +0F12 ; GL # Po TIBETAN MARK RGYA GRAM SHAD +0F13 ; AL # So TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN +0F14 ; EX # Po TIBETAN MARK GTER TSHEG +0F15..0F17 ; AL # So [3] TIBETAN LOGOTYPE SIGN CHAD RTAGS..TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS +0F18..0F19 ; CM # Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F1A..0F1F ; AL # So [6] TIBETAN SIGN RDEL DKAR GCIG..TIBETAN SIGN RDEL DKAR RDEL NAG +0F20..0F29 ; NU # Nd [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE +0F2A..0F33 ; AL # No [10] TIBETAN DIGIT HALF ONE..TIBETAN DIGIT HALF ZERO +0F34 ; BA # So TIBETAN MARK BSDUS RTAGS +0F35 ; CM # Mn TIBETAN MARK NGAS BZUNG NYI ZLA +0F36 ; AL # So TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN +0F37 ; CM # Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F38 ; AL # So TIBETAN MARK CHE MGO +0F39 ; CM # Mn TIBETAN MARK TSA -PHRU +0F3A ; OP # Ps TIBETAN MARK GUG RTAGS GYON +0F3B ; CL # Pe TIBETAN MARK GUG RTAGS GYAS +0F3C ; OP # Ps TIBETAN MARK ANG KHANG GYON +0F3D ; CL # Pe TIBETAN MARK ANG KHANG GYAS +0F3E..0F3F ; CM # Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES +0F40..0F47 ; AL # Lo [8] TIBETAN LETTER KA..TIBETAN LETTER JA +0F49..0F6C ; AL # Lo [36] TIBETAN LETTER NYA..TIBETAN LETTER RRA +0F71..0F7E ; CM # Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO +0F7F ; BA # Mc TIBETAN SIGN RNAM BCAD +0F80..0F84 ; CM # Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA +0F85 ; BA # Po TIBETAN MARK PALUTA +0F86..0F87 ; CM # Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS +0F88..0F8C ; AL # Lo [5] TIBETAN SIGN LCE TSA CAN..TIBETAN SIGN INVERTED MCHU CAN +0F8D..0F97 ; CM # Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA +0F99..0FBC ; CM # Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA +0FBE..0FBF ; BA # So [2] TIBETAN KU RU KHA..TIBETAN KU RU KHA BZHI MIG CAN +0FC0..0FC5 ; AL # So [6] TIBETAN CANTILLATION SIGN HEAVY BEAT..TIBETAN SYMBOL RDO RJE +0FC6 ; CM # Mn TIBETAN SYMBOL PADMA GDAN +0FC7..0FCC ; AL # So [6] TIBETAN SYMBOL RDO RJE RGYA GRAM..TIBETAN SYMBOL NOR BU BZHI -KHYIL +0FCE..0FCF ; AL # So [2] TIBETAN SIGN RDEL NAG RDEL DKAR..TIBETAN SIGN RDEL NAG GSUM +0FD0..0FD1 ; BB # Po [2] TIBETAN MARK BSKA- SHOG GI MGO RGYAN..TIBETAN MARK MNYAM YIG GI MGO RGYAN +0FD2 ; BA # Po TIBETAN MARK NYIS TSHEG +0FD3 ; BB # Po TIBETAN MARK INITIAL BRDA RNYING YIG MGO MDUN MA +0FD4 ; AL # Po TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA +0FD5..0FD8 ; AL # So [4] RIGHT-FACING SVASTI SIGN..LEFT-FACING SVASTI SIGN WITH DOTS +0FD9..0FDA ; GL # Po [2] TIBETAN MARK LEADING MCHAN RTAGS..TIBETAN MARK TRAILING MCHAN RTAGS +1000..102A ; SA # Lo [43] MYANMAR LETTER KA..MYANMAR LETTER AU +102B..102C ; SA # Mc [2] MYANMAR VOWEL SIGN TALL AA..MYANMAR VOWEL SIGN AA +102D..1030 ; SA # Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU +1031 ; SA # Mc MYANMAR VOWEL SIGN E +1032..1037 ; SA # Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW +1038 ; SA # Mc MYANMAR SIGN VISARGA +1039..103A ; SA # Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT +103B..103C ; SA # Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA +103D..103E ; SA # Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA +103F ; SA # Lo MYANMAR LETTER GREAT SA +1040..1049 ; NU # Nd [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE +104A..104B ; BA # Po [2] MYANMAR SIGN LITTLE SECTION..MYANMAR SIGN SECTION +104C..104F ; AL # Po [4] MYANMAR SYMBOL LOCATIVE..MYANMAR SYMBOL GENITIVE +1050..1055 ; SA # Lo [6] MYANMAR LETTER SHA..MYANMAR LETTER VOCALIC LL +1056..1057 ; SA # Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR +1058..1059 ; SA # Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL +105A..105D ; SA # Lo [4] MYANMAR LETTER MON NGA..MYANMAR LETTER MON BBE +105E..1060 ; SA # Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA +1061 ; SA # Lo MYANMAR LETTER SGAW KAREN SHA +1062..1064 ; SA # Mc [3] MYANMAR VOWEL SIGN SGAW KAREN EU..MYANMAR TONE MARK SGAW KAREN KE PHO +1065..1066 ; SA # Lo [2] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR LETTER WESTERN PWO KAREN PWA +1067..106D ; SA # Mc [7] MYANMAR VOWEL SIGN WESTERN PWO KAREN EU..MYANMAR SIGN WESTERN PWO KAREN TONE-5 +106E..1070 ; SA # Lo [3] MYANMAR LETTER EASTERN PWO KAREN NNA..MYANMAR LETTER EASTERN PWO KAREN GHWA +1071..1074 ; SA # Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE +1075..1081 ; SA # Lo [13] MYANMAR LETTER SHAN KA..MYANMAR LETTER SHAN HA +1082 ; SA # Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA +1083..1084 ; SA # Mc [2] MYANMAR VOWEL SIGN SHAN AA..MYANMAR VOWEL SIGN SHAN E +1085..1086 ; SA # Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y +1087..108C ; SA # Mc [6] MYANMAR SIGN SHAN TONE-2..MYANMAR SIGN SHAN COUNCIL TONE-3 +108D ; SA # Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE +108E ; SA # Lo MYANMAR LETTER RUMAI PALAUNG FA +108F ; SA # Mc MYANMAR SIGN RUMAI PALAUNG TONE-5 +1090..1099 ; NU # Nd [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +109A..109C ; SA # Mc [3] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON A +109D ; SA # Mn MYANMAR VOWEL SIGN AITON AI +109E..109F ; SA # So [2] MYANMAR SYMBOL SHAN ONE..MYANMAR SYMBOL SHAN EXCLAMATION +10A0..10C5 ; AL # Lu [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE +10C7 ; AL # Lu GEORGIAN CAPITAL LETTER YN +10CD ; AL # Lu GEORGIAN CAPITAL LETTER AEN +10D0..10FA ; AL # Ll [43] GEORGIAN LETTER AN..GEORGIAN LETTER AIN +10FB ; AL # Po GEORGIAN PARAGRAPH SEPARATOR +10FC ; AL # Lm MODIFIER LETTER GEORGIAN NAR +10FD..10FF ; AL # Ll [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1100..115F ; JL # Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER +1160..11A7 ; JV # Lo [72] HANGUL JUNGSEONG FILLER..HANGUL JUNGSEONG O-YAE +11A8..11FF ; JT # Lo [88] HANGUL JONGSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN +1200..1248 ; AL # Lo [73] ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE QWA +124A..124D ; AL # Lo [4] ETHIOPIC SYLLABLE QWI..ETHIOPIC SYLLABLE QWE +1250..1256 ; AL # Lo [7] ETHIOPIC SYLLABLE QHA..ETHIOPIC SYLLABLE QHO +1258 ; AL # Lo ETHIOPIC SYLLABLE QHWA +125A..125D ; AL # Lo [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE +1260..1288 ; AL # Lo [41] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XWA +128A..128D ; AL # Lo [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE +1290..12B0 ; AL # Lo [33] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KWA +12B2..12B5 ; AL # Lo [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE +12B8..12BE ; AL # Lo [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO +12C0 ; AL # Lo ETHIOPIC SYLLABLE KXWA +12C2..12C5 ; AL # Lo [4] ETHIOPIC SYLLABLE KXWI..ETHIOPIC SYLLABLE KXWE +12C8..12D6 ; AL # Lo [15] ETHIOPIC SYLLABLE WA..ETHIOPIC SYLLABLE PHARYNGEAL O +12D8..1310 ; AL # Lo [57] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE GWA +1312..1315 ; AL # Lo [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE +1318..135A ; AL # Lo [67] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE FYA +135D..135F ; CM # Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK +1360 ; AL # Po ETHIOPIC SECTION MARK +1361 ; BA # Po ETHIOPIC WORDSPACE +1362..1368 ; AL # Po [7] ETHIOPIC FULL STOP..ETHIOPIC PARAGRAPH SEPARATOR +1369..137C ; AL # No [20] ETHIOPIC DIGIT ONE..ETHIOPIC NUMBER TEN THOUSAND +1380..138F ; AL # Lo [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +1390..1399 ; AL # So [10] ETHIOPIC TONAL MARK YIZET..ETHIOPIC TONAL MARK KURT +13A0..13F5 ; AL # Lu [86] CHEROKEE LETTER A..CHEROKEE LETTER MV +13F8..13FD ; AL # Ll [6] CHEROKEE SMALL LETTER YE..CHEROKEE SMALL LETTER MV +1400 ; BA # Pd CANADIAN SYLLABICS HYPHEN +1401..166C ; AL # Lo [620] CANADIAN SYLLABICS E..CANADIAN SYLLABICS CARRIER TTSA +166D ; AL # So CANADIAN SYLLABICS CHI SIGN +166E ; AL # Po CANADIAN SYLLABICS FULL STOP +166F..167F ; AL # Lo [17] CANADIAN SYLLABICS QAI..CANADIAN SYLLABICS BLACKFOOT W +1680 ; BA # Zs OGHAM SPACE MARK +1681..169A ; AL # Lo [26] OGHAM LETTER BEITH..OGHAM LETTER PEITH +169B ; OP # Ps OGHAM FEATHER MARK +169C ; CL # Pe OGHAM REVERSED FEATHER MARK +16A0..16EA ; AL # Lo [75] RUNIC LETTER FEHU FEOH FE F..RUNIC LETTER X +16EB..16ED ; BA # Po [3] RUNIC SINGLE PUNCTUATION..RUNIC CROSS PUNCTUATION +16EE..16F0 ; AL # Nl [3] RUNIC ARLAUG SYMBOL..RUNIC BELGTHOR SYMBOL +16F1..16F8 ; AL # Lo [8] RUNIC LETTER K..RUNIC LETTER FRANKS CASKET AESC +1700..1711 ; AL # Lo [18] TAGALOG LETTER A..TAGALOG LETTER HA +1712..1714 ; CM # Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA +1715 ; CM # Mc TAGALOG SIGN PAMUDPOD +171F ; AL # Lo TAGALOG LETTER ARCHAIC RA +1720..1731 ; AL # Lo [18] HANUNOO LETTER A..HANUNOO LETTER HA +1732..1733 ; CM # Mn [2] HANUNOO VOWEL SIGN I..HANUNOO VOWEL SIGN U +1734 ; CM # Mc HANUNOO SIGN PAMUDPOD +1735..1736 ; BA # Po [2] PHILIPPINE SINGLE PUNCTUATION..PHILIPPINE DOUBLE PUNCTUATION +1740..1751 ; AL # Lo [18] BUHID LETTER A..BUHID LETTER HA +1752..1753 ; CM # Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U +1760..176C ; AL # Lo [13] TAGBANWA LETTER A..TAGBANWA LETTER YA +176E..1770 ; AL # Lo [3] TAGBANWA LETTER LA..TAGBANWA LETTER SA +1772..1773 ; CM # Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U +1780..17B3 ; SA # Lo [52] KHMER LETTER KA..KHMER INDEPENDENT VOWEL QAU +17B4..17B5 ; SA # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA +17B6 ; SA # Mc KHMER VOWEL SIGN AA +17B7..17BD ; SA # Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA +17BE..17C5 ; SA # Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU +17C6 ; SA # Mn KHMER SIGN NIKAHIT +17C7..17C8 ; SA # Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU +17C9..17D3 ; SA # Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT +17D4..17D5 ; BA # Po [2] KHMER SIGN KHAN..KHMER SIGN BARIYOOSAN +17D6 ; NS # Po KHMER SIGN CAMNUC PII KUUH +17D7 ; SA # Lm KHMER SIGN LEK TOO +17D8 ; BA # Po KHMER SIGN BEYYAL +17D9 ; AL # Po KHMER SIGN PHNAEK MUAN +17DA ; BA # Po KHMER SIGN KOOMUUT +17DB ; PR # Sc KHMER CURRENCY SYMBOL RIEL +17DC ; SA # Lo KHMER SIGN AVAKRAHASANYA +17DD ; SA # Mn KHMER SIGN ATTHACAN +17E0..17E9 ; NU # Nd [10] KHMER DIGIT ZERO..KHMER DIGIT NINE +17F0..17F9 ; AL # No [10] KHMER SYMBOL LEK ATTAK SON..KHMER SYMBOL LEK ATTAK PRAM-BUON +1800..1801 ; AL # Po [2] MONGOLIAN BIRGA..MONGOLIAN ELLIPSIS +1802..1803 ; EX # Po [2] MONGOLIAN COMMA..MONGOLIAN FULL STOP +1804..1805 ; BA # Po [2] MONGOLIAN COLON..MONGOLIAN FOUR DOTS +1806 ; BB # Pd MONGOLIAN TODO SOFT HYPHEN +1807 ; AL # Po MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER +1808..1809 ; EX # Po [2] MONGOLIAN MANCHU COMMA..MONGOLIAN MANCHU FULL STOP +180A ; AL # Po MONGOLIAN NIRUGU +180B..180D ; CM # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE +180E ; GL # Cf MONGOLIAN VOWEL SEPARATOR +180F ; CM # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR +1810..1819 ; NU # Nd [10] MONGOLIAN DIGIT ZERO..MONGOLIAN DIGIT NINE +1820..1842 ; AL # Lo [35] MONGOLIAN LETTER A..MONGOLIAN LETTER CHI +1843 ; AL # Lm MONGOLIAN LETTER TODO LONG VOWEL SIGN +1844..1878 ; AL # Lo [53] MONGOLIAN LETTER TODO E..MONGOLIAN LETTER CHA WITH TWO DOTS +1880..1884 ; AL # Lo [5] MONGOLIAN LETTER ALI GALI ANUSVARA ONE..MONGOLIAN LETTER ALI GALI INVERTED UBADAMA +1885..1886 ; CM # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA +1887..18A8 ; AL # Lo [34] MONGOLIAN LETTER ALI GALI A..MONGOLIAN LETTER MANCHU ALI GALI BHA +18A9 ; CM # Mn MONGOLIAN LETTER ALI GALI DAGALGA +18AA ; AL # Lo MONGOLIAN LETTER MANCHU ALI GALI LHA +18B0..18F5 ; AL # Lo [70] CANADIAN SYLLABICS OY..CANADIAN SYLLABICS CARRIER DENTAL S +1900..191E ; AL # Lo [31] LIMBU VOWEL-CARRIER LETTER..LIMBU LETTER TRA +1920..1922 ; CM # Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U +1923..1926 ; CM # Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU +1927..1928 ; CM # Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O +1929..192B ; CM # Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA +1930..1931 ; CM # Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA +1932 ; CM # Mn LIMBU SMALL LETTER ANUSVARA +1933..1938 ; CM # Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA +1939..193B ; CM # Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I +1940 ; AL # So LIMBU SIGN LOO +1944..1945 ; EX # Po [2] LIMBU EXCLAMATION MARK..LIMBU QUESTION MARK +1946..194F ; NU # Nd [10] LIMBU DIGIT ZERO..LIMBU DIGIT NINE +1950..196D ; SA # Lo [30] TAI LE LETTER KA..TAI LE LETTER AI +1970..1974 ; SA # Lo [5] TAI LE LETTER TONE-2..TAI LE LETTER TONE-6 +1980..19AB ; SA # Lo [44] NEW TAI LUE LETTER HIGH QA..NEW TAI LUE LETTER LOW SUA +19B0..19C9 ; SA # Lo [26] NEW TAI LUE VOWEL SIGN VOWEL SHORTENER..NEW TAI LUE TONE MARK-2 +19D0..19D9 ; NU # Nd [10] NEW TAI LUE DIGIT ZERO..NEW TAI LUE DIGIT NINE +19DA ; SA # No NEW TAI LUE THAM DIGIT ONE +19DE..19DF ; SA # So [2] NEW TAI LUE SIGN LAE..NEW TAI LUE SIGN LAEV +19E0..19FF ; AL # So [32] KHMER SYMBOL PATHAMASAT..KHMER SYMBOL DAP-PRAM ROC +1A00..1A16 ; AL # Lo [23] BUGINESE LETTER KA..BUGINESE LETTER HA +1A17..1A18 ; CM # Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U +1A19..1A1A ; CM # Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O +1A1B ; CM # Mn BUGINESE VOWEL SIGN AE +1A1E..1A1F ; AL # Po [2] BUGINESE PALLAWA..BUGINESE END OF SECTION +1A20..1A54 ; SA # Lo [53] TAI THAM LETTER HIGH KA..TAI THAM LETTER GREAT SA +1A55 ; SA # Mc TAI THAM CONSONANT SIGN MEDIAL RA +1A56 ; SA # Mn TAI THAM CONSONANT SIGN MEDIAL LA +1A57 ; SA # Mc TAI THAM CONSONANT SIGN LA TANG LAI +1A58..1A5E ; SA # Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA +1A60 ; SA # Mn TAI THAM SIGN SAKOT +1A61 ; SA # Mc TAI THAM VOWEL SIGN A +1A62 ; SA # Mn TAI THAM VOWEL SIGN MAI SAT +1A63..1A64 ; SA # Mc [2] TAI THAM VOWEL SIGN AA..TAI THAM VOWEL SIGN TALL AA +1A65..1A6C ; SA # Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW +1A6D..1A72 ; SA # Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI +1A73..1A7C ; SA # Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN +1A7F ; CM # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT +1A80..1A89 ; NU # Nd [10] TAI THAM HORA DIGIT ZERO..TAI THAM HORA DIGIT NINE +1A90..1A99 ; NU # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE +1AA0..1AA6 ; SA # Po [7] TAI THAM SIGN WIANG..TAI THAM SIGN REVERSED ROTATED RANA +1AA7 ; SA # Lm TAI THAM SIGN MAI YAMOK +1AA8..1AAD ; SA # Po [6] TAI THAM SIGN KAAN..TAI THAM SIGN CAANG +1AB0..1ABD ; CM # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW +1ABE ; CM # Me COMBINING PARENTHESES OVERLAY +1ABF..1ACE ; CM # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1B00..1B03 ; CM # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG +1B04 ; CM # Mc BALINESE SIGN BISAH +1B05..1B33 ; AK # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA +1B34 ; CM # Mn BALINESE SIGN REREKAN +1B35 ; CM # Mc BALINESE VOWEL SIGN TEDUNG +1B36..1B3A ; CM # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA +1B3B ; CM # Mc BALINESE VOWEL SIGN RA REPA TEDUNG +1B3C ; CM # Mn BALINESE VOWEL SIGN LA LENGA +1B3D..1B41 ; CM # Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG +1B42 ; CM # Mn BALINESE VOWEL SIGN PEPET +1B43 ; CM # Mc BALINESE VOWEL SIGN PEPET TEDUNG +1B44 ; VI # Mc BALINESE ADEG ADEG +1B45..1B4C ; AK # Lo [8] BALINESE LETTER KAF SASAK..BALINESE LETTER ARCHAIC JNYA +1B50..1B59 ; ID # Nd [10] BALINESE DIGIT ZERO..BALINESE DIGIT NINE +1B5A..1B5B ; BA # Po [2] BALINESE PANTI..BALINESE PAMADA +1B5C ; ID # Po BALINESE WINDU +1B5D..1B60 ; BA # Po [4] BALINESE CARIK PAMUNGKAH..BALINESE PAMENENG +1B61..1B6A ; ID # So [10] BALINESE MUSICAL SYMBOL DONG..BALINESE MUSICAL SYMBOL DANG GEDE +1B6B..1B73 ; CM # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG +1B74..1B7C ; ID # So [9] BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG..BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING +1B7D..1B7E ; BA # Po [2] BALINESE PANTI LANTANG..BALINESE PAMADA LANTANG +1B80..1B81 ; CM # Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR +1B82 ; CM # Mc SUNDANESE SIGN PANGWISAD +1B83..1BA0 ; AL # Lo [30] SUNDANESE LETTER A..SUNDANESE LETTER HA +1BA1 ; CM # Mc SUNDANESE CONSONANT SIGN PAMINGKAL +1BA2..1BA5 ; CM # Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU +1BA6..1BA7 ; CM # Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG +1BA8..1BA9 ; CM # Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG +1BAA ; CM # Mc SUNDANESE SIGN PAMAAEH +1BAB..1BAD ; CM # Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA +1BAE..1BAF ; AL # Lo [2] SUNDANESE LETTER KHA..SUNDANESE LETTER SYA +1BB0..1BB9 ; NU # Nd [10] SUNDANESE DIGIT ZERO..SUNDANESE DIGIT NINE +1BBA..1BBF ; AL # Lo [6] SUNDANESE AVAGRAHA..SUNDANESE LETTER FINAL M +1BC0..1BE5 ; AS # Lo [38] BATAK LETTER A..BATAK LETTER U +1BE6 ; CM # Mn BATAK SIGN TOMPI +1BE7 ; CM # Mc BATAK VOWEL SIGN E +1BE8..1BE9 ; CM # Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE +1BEA..1BEC ; CM # Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O +1BED ; CM # Mn BATAK VOWEL SIGN KARO O +1BEE ; CM # Mc BATAK VOWEL SIGN U +1BEF..1BF1 ; CM # Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H +1BF2..1BF3 ; VF # Mc [2] BATAK PANGOLAT..BATAK PANONGONAN +1BFC..1BFF ; AL # Po [4] BATAK SYMBOL BINDU NA METEK..BATAK SYMBOL BINDU PANGOLAT +1C00..1C23 ; AL # Lo [36] LEPCHA LETTER KA..LEPCHA LETTER A +1C24..1C2B ; CM # Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU +1C2C..1C33 ; CM # Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T +1C34..1C35 ; CM # Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG +1C36..1C37 ; CM # Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA +1C3B..1C3F ; BA # Po [5] LEPCHA PUNCTUATION TA-ROL..LEPCHA PUNCTUATION TSHOOK +1C40..1C49 ; NU # Nd [10] LEPCHA DIGIT ZERO..LEPCHA DIGIT NINE +1C4D..1C4F ; AL # Lo [3] LEPCHA LETTER TTA..LEPCHA LETTER DDA +1C50..1C59 ; NU # Nd [10] OL CHIKI DIGIT ZERO..OL CHIKI DIGIT NINE +1C5A..1C77 ; AL # Lo [30] OL CHIKI LETTER LA..OL CHIKI LETTER OH +1C78..1C7D ; AL # Lm [6] OL CHIKI MU TTUDDAG..OL CHIKI AHAD +1C7E..1C7F ; BA # Po [2] OL CHIKI PUNCTUATION MUCAAD..OL CHIKI PUNCTUATION DOUBLE MUCAAD +1C80..1C88 ; AL # Ll [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK +1C90..1CBA ; AL # Lu [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN +1CBD..1CBF ; AL # Lu [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN +1CC0..1CC7 ; AL # Po [8] SUNDANESE PUNCTUATION BINDU SURYA..SUNDANESE PUNCTUATION BINDU BA SATANGA +1CD0..1CD2 ; CM # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA +1CD3 ; AL # Po VEDIC SIGN NIHSHVASA +1CD4..1CE0 ; CM # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA +1CE1 ; CM # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA +1CE2..1CE8 ; CM # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CE9..1CEC ; AL # Lo [4] VEDIC SIGN ANUSVARA ANTARGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CED ; CM # Mn VEDIC SIGN TIRYAK +1CEE..1CF3 ; AL # Lo [6] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ROTATED ARDHAVISARGA +1CF4 ; CM # Mn VEDIC TONE CANDRA ABOVE +1CF5..1CF6 ; AL # Lo [2] VEDIC SIGN JIHVAMULIYA..VEDIC SIGN UPADHMANIYA +1CF7 ; CM # Mc VEDIC SIGN ATIKRAMA +1CF8..1CF9 ; CM # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1CFA ; AL # Lo VEDIC SIGN DOUBLE ANUSVARA ANTARGOMUKHA +1D00..1D2B ; AL # Ll [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL +1D2C..1D6A ; AL # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D6B..1D77 ; AL # Ll [13] LATIN SMALL LETTER UE..LATIN SMALL LETTER TURNED G +1D78 ; AL # Lm MODIFIER LETTER CYRILLIC EN +1D79..1D7F ; AL # Ll [7] LATIN SMALL LETTER INSULAR G..LATIN SMALL LETTER UPSILON WITH STROKE +1D80..1D9A ; AL # Ll [27] LATIN SMALL LETTER B WITH PALATAL HOOK..LATIN SMALL LETTER EZH WITH RETROFLEX HOOK +1D9B..1DBF ; AL # Lm [37] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL THETA +1DC0..1DCC ; CM # Mn [13] COMBINING DOTTED GRAVE ACCENT..COMBINING MACRON-BREVE +1DCD ; GL # Mn COMBINING DOUBLE CIRCUMFLEX ABOVE +1DCE..1DFB ; CM # Mn [46] COMBINING OGONEK ABOVE..COMBINING DELETION MARK +1DFC ; GL # Mn COMBINING DOUBLE INVERTED BREVE BELOW +1DFD..1DFF ; CM # Mn [3] COMBINING ALMOST EQUAL TO BELOW..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +1E00..1EFF ; AL # L& [256] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER Y WITH LOOP +1F00..1F15 ; AL # L& [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F18..1F1D ; AL # Lu [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F45 ; AL # L& [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F48..1F4D ; AL # Lu [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; AL # Ll [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F59 ; AL # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; AL # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; AL # Lu GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F..1F7D ; AL # L& [31] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER OMEGA WITH OXIA +1F80..1FB4 ; AL # L& [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI +1FB6..1FBC ; AL # L& [7] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FBD ; AL # Sk GREEK KORONIS +1FBE ; AL # Ll GREEK PROSGEGRAMMENI +1FBF..1FC1 ; AL # Sk [3] GREEK PSILI..GREEK DIALYTIKA AND PERISPOMENI +1FC2..1FC4 ; AL # Ll [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FCC ; AL # L& [7] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FCD..1FCF ; AL # Sk [3] GREEK PSILI AND VARIA..GREEK PSILI AND PERISPOMENI +1FD0..1FD3 ; AL # Ll [4] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA +1FD6..1FDB ; AL # L& [6] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH OXIA +1FDD..1FDF ; AL # Sk [3] GREEK DASIA AND VARIA..GREEK DASIA AND PERISPOMENI +1FE0..1FEC ; AL # L& [13] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK CAPITAL LETTER RHO WITH DASIA +1FED..1FEF ; AL # Sk [3] GREEK DIALYTIKA AND VARIA..GREEK VARIA +1FF2..1FF4 ; AL # Ll [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FFC ; AL # L& [7] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI +1FFD ; BB # Sk GREEK OXIA +1FFE ; AL # Sk GREEK DASIA +2000..2006 ; BA # Zs [7] EN QUAD..SIX-PER-EM SPACE +2007 ; GL # Zs FIGURE SPACE +2008..200A ; BA # Zs [3] PUNCTUATION SPACE..HAIR SPACE +200B ; ZW # Cf ZERO WIDTH SPACE +200C ; CM # Cf ZERO WIDTH NON-JOINER +200D ; ZWJ# Cf ZERO WIDTH JOINER +200E..200F ; CM # Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK +2010 ; BA # Pd HYPHEN +2011 ; GL # Pd NON-BREAKING HYPHEN +2012..2013 ; BA # Pd [2] FIGURE DASH..EN DASH +2014 ; B2 # Pd EM DASH +2015 ; AI # Pd HORIZONTAL BAR +2016 ; AI # Po DOUBLE VERTICAL LINE +2017 ; AL # Po DOUBLE LOW LINE +2018 ; QU # Pi LEFT SINGLE QUOTATION MARK +2019 ; QU # Pf RIGHT SINGLE QUOTATION MARK +201A ; OP # Ps SINGLE LOW-9 QUOTATION MARK +201B..201C ; QU # Pi [2] SINGLE HIGH-REVERSED-9 QUOTATION MARK..LEFT DOUBLE QUOTATION MARK +201D ; QU # Pf RIGHT DOUBLE QUOTATION MARK +201E ; OP # Ps DOUBLE LOW-9 QUOTATION MARK +201F ; QU # Pi DOUBLE HIGH-REVERSED-9 QUOTATION MARK +2020..2021 ; AI # Po [2] DAGGER..DOUBLE DAGGER +2022..2023 ; AL # Po [2] BULLET..TRIANGULAR BULLET +2024..2026 ; IN # Po [3] ONE DOT LEADER..HORIZONTAL ELLIPSIS +2027 ; BA # Po HYPHENATION POINT +2028 ; BK # Zl LINE SEPARATOR +2029 ; BK # Zp PARAGRAPH SEPARATOR +202A..202E ; CM # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE +202F ; GL # Zs NARROW NO-BREAK SPACE +2030..2037 ; PO # Po [8] PER MILLE SIGN..REVERSED TRIPLE PRIME +2038 ; AL # Po CARET +2039 ; QU # Pi SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A ; QU # Pf SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +203B ; AI # Po REFERENCE MARK +203C..203D ; NS # Po [2] DOUBLE EXCLAMATION MARK..INTERROBANG +203E ; AL # Po OVERLINE +203F..2040 ; AL # Pc [2] UNDERTIE..CHARACTER TIE +2041..2043 ; AL # Po [3] CARET INSERTION POINT..HYPHEN BULLET +2044 ; IS # Sm FRACTION SLASH +2045 ; OP # Ps LEFT SQUARE BRACKET WITH QUILL +2046 ; CL # Pe RIGHT SQUARE BRACKET WITH QUILL +2047..2049 ; NS # Po [3] DOUBLE QUESTION MARK..EXCLAMATION QUESTION MARK +204A..2051 ; AL # Po [8] TIRONIAN SIGN ET..TWO ASTERISKS ALIGNED VERTICALLY +2052 ; AL # Sm COMMERCIAL MINUS SIGN +2053 ; AL # Po SWUNG DASH +2054 ; AL # Pc INVERTED UNDERTIE +2055 ; AL # Po FLOWER PUNCTUATION MARK +2056 ; BA # Po THREE DOT PUNCTUATION +2057 ; PO # Po QUADRUPLE PRIME +2058..205B ; BA # Po [4] FOUR DOT PUNCTUATION..FOUR DOT MARK +205C ; AL # Po DOTTED CROSS +205D..205E ; BA # Po [2] TRICOLON..VERTICAL FOUR DOTS +205F ; BA # Zs MEDIUM MATHEMATICAL SPACE +2060 ; WJ # Cf WORD JOINER +2061..2064 ; AL # Cf [4] FUNCTION APPLICATION..INVISIBLE PLUS +2066..206F ; CM # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES +2070 ; AL # No SUPERSCRIPT ZERO +2071 ; AL # Lm SUPERSCRIPT LATIN SMALL LETTER I +2074 ; AI # No SUPERSCRIPT FOUR +2075..2079 ; AL # No [5] SUPERSCRIPT FIVE..SUPERSCRIPT NINE +207A..207C ; AL # Sm [3] SUPERSCRIPT PLUS SIGN..SUPERSCRIPT EQUALS SIGN +207D ; OP # Ps SUPERSCRIPT LEFT PARENTHESIS +207E ; CL # Pe SUPERSCRIPT RIGHT PARENTHESIS +207F ; AI # Lm SUPERSCRIPT LATIN SMALL LETTER N +2080 ; AL # No SUBSCRIPT ZERO +2081..2084 ; AI # No [4] SUBSCRIPT ONE..SUBSCRIPT FOUR +2085..2089 ; AL # No [5] SUBSCRIPT FIVE..SUBSCRIPT NINE +208A..208C ; AL # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN +208D ; OP # Ps SUBSCRIPT LEFT PARENTHESIS +208E ; CL # Pe SUBSCRIPT RIGHT PARENTHESIS +2090..209C ; AL # Lm [13] LATIN SUBSCRIPT SMALL LETTER A..LATIN SUBSCRIPT SMALL LETTER T +20A0..20A6 ; PR # Sc [7] EURO-CURRENCY SIGN..NAIRA SIGN +20A7 ; PO # Sc PESETA SIGN +20A8..20B5 ; PR # Sc [14] RUPEE SIGN..CEDI SIGN +20B6 ; PO # Sc LIVRE TOURNOIS SIGN +20B7..20BA ; PR # Sc [4] SPESMILO SIGN..TURKISH LIRA SIGN +20BB ; PO # Sc NORDIC MARK SIGN +20BC..20BD ; PR # Sc [2] MANAT SIGN..RUBLE SIGN +20BE ; PO # Sc LARI SIGN +20BF ; PR # Sc BITCOIN SIGN +20C0 ; PO # Sc SOM SIGN +20C1..20CF ; PR # Cn [15] .. +20D0..20DC ; CM # Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE +20DD..20E0 ; CM # Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH +20E1 ; CM # Mn COMBINING LEFT RIGHT ARROW ABOVE +20E2..20E4 ; CM # Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE +20E5..20F0 ; CM # Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE +2100..2101 ; AL # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT +2102 ; AL # Lu DOUBLE-STRUCK CAPITAL C +2103 ; PO # So DEGREE CELSIUS +2104 ; AL # So CENTRE LINE SYMBOL +2105 ; AI # So CARE OF +2106 ; AL # So CADA UNA +2107 ; AL # Lu EULER CONSTANT +2108 ; AL # So SCRUPLE +2109 ; PO # So DEGREE FAHRENHEIT +210A..2112 ; AL # L& [9] SCRIPT SMALL G..SCRIPT CAPITAL L +2113 ; AI # Ll SCRIPT SMALL L +2114 ; AL # So L B BAR SYMBOL +2115 ; AL # Lu DOUBLE-STRUCK CAPITAL N +2116 ; PR # So NUMERO SIGN +2117 ; AL # So SOUND RECORDING COPYRIGHT +2118 ; AL # Sm SCRIPT CAPITAL P +2119..211D ; AL # Lu [5] DOUBLE-STRUCK CAPITAL P..DOUBLE-STRUCK CAPITAL R +211E..2120 ; AL # So [3] PRESCRIPTION TAKE..SERVICE MARK +2121..2122 ; AI # So [2] TELEPHONE SIGN..TRADE MARK SIGN +2123 ; AL # So VERSICLE +2124 ; AL # Lu DOUBLE-STRUCK CAPITAL Z +2125 ; AL # So OUNCE SIGN +2126 ; AL # Lu OHM SIGN +2127 ; AL # So INVERTED OHM SIGN +2128 ; AL # Lu BLACK-LETTER CAPITAL Z +2129 ; AL # So TURNED GREEK SMALL LETTER IOTA +212A ; AL # Lu KELVIN SIGN +212B ; AI # Lu ANGSTROM SIGN +212C..212D ; AL # Lu [2] SCRIPT CAPITAL B..BLACK-LETTER CAPITAL C +212E ; AL # So ESTIMATED SYMBOL +212F..2134 ; AL # L& [6] SCRIPT SMALL E..SCRIPT SMALL O +2135..2138 ; AL # Lo [4] ALEF SYMBOL..DALET SYMBOL +2139 ; AL # Ll INFORMATION SOURCE +213A..213B ; AL # So [2] ROTATED CAPITAL Q..FACSIMILE SIGN +213C..213F ; AL # L& [4] DOUBLE-STRUCK SMALL PI..DOUBLE-STRUCK CAPITAL PI +2140..2144 ; AL # Sm [5] DOUBLE-STRUCK N-ARY SUMMATION..TURNED SANS-SERIF CAPITAL Y +2145..2149 ; AL # L& [5] DOUBLE-STRUCK ITALIC CAPITAL D..DOUBLE-STRUCK ITALIC SMALL J +214A ; AL # So PROPERTY LINE +214B ; AL # Sm TURNED AMPERSAND +214C..214D ; AL # So [2] PER SIGN..AKTIESELSKAB +214E ; AL # Ll TURNED SMALL F +214F ; AL # So SYMBOL FOR SAMARITAN SOURCE +2150..2153 ; AL # No [4] VULGAR FRACTION ONE SEVENTH..VULGAR FRACTION ONE THIRD +2154..2155 ; AI # No [2] VULGAR FRACTION TWO THIRDS..VULGAR FRACTION ONE FIFTH +2156..215A ; AL # No [5] VULGAR FRACTION TWO FIFTHS..VULGAR FRACTION FIVE SIXTHS +215B ; AI # No VULGAR FRACTION ONE EIGHTH +215C..215D ; AL # No [2] VULGAR FRACTION THREE EIGHTHS..VULGAR FRACTION FIVE EIGHTHS +215E ; AI # No VULGAR FRACTION SEVEN EIGHTHS +215F ; AL # No FRACTION NUMERATOR ONE +2160..216B ; AI # Nl [12] ROMAN NUMERAL ONE..ROMAN NUMERAL TWELVE +216C..216F ; AL # Nl [4] ROMAN NUMERAL FIFTY..ROMAN NUMERAL ONE THOUSAND +2170..2179 ; AI # Nl [10] SMALL ROMAN NUMERAL ONE..SMALL ROMAN NUMERAL TEN +217A..2182 ; AL # Nl [9] SMALL ROMAN NUMERAL ELEVEN..ROMAN NUMERAL TEN THOUSAND +2183..2184 ; AL # L& [2] ROMAN NUMERAL REVERSED ONE HUNDRED..LATIN SMALL LETTER REVERSED C +2185..2188 ; AL # Nl [4] ROMAN NUMERAL SIX LATE FORM..ROMAN NUMERAL ONE HUNDRED THOUSAND +2189 ; AI # No VULGAR FRACTION ZERO THIRDS +218A..218B ; AL # So [2] TURNED DIGIT TWO..TURNED DIGIT THREE +2190..2194 ; AI # Sm [5] LEFTWARDS ARROW..LEFT RIGHT ARROW +2195..2199 ; AI # So [5] UP DOWN ARROW..SOUTH WEST ARROW +219A..219B ; AL # Sm [2] LEFTWARDS ARROW WITH STROKE..RIGHTWARDS ARROW WITH STROKE +219C..219F ; AL # So [4] LEFTWARDS WAVE ARROW..UPWARDS TWO HEADED ARROW +21A0 ; AL # Sm RIGHTWARDS TWO HEADED ARROW +21A1..21A2 ; AL # So [2] DOWNWARDS TWO HEADED ARROW..LEFTWARDS ARROW WITH TAIL +21A3 ; AL # Sm RIGHTWARDS ARROW WITH TAIL +21A4..21A5 ; AL # So [2] LEFTWARDS ARROW FROM BAR..UPWARDS ARROW FROM BAR +21A6 ; AL # Sm RIGHTWARDS ARROW FROM BAR +21A7..21AD ; AL # So [7] DOWNWARDS ARROW FROM BAR..LEFT RIGHT WAVE ARROW +21AE ; AL # Sm LEFT RIGHT ARROW WITH STROKE +21AF..21CD ; AL # So [31] DOWNWARDS ZIGZAG ARROW..LEFTWARDS DOUBLE ARROW WITH STROKE +21CE..21CF ; AL # Sm [2] LEFT RIGHT DOUBLE ARROW WITH STROKE..RIGHTWARDS DOUBLE ARROW WITH STROKE +21D0..21D1 ; AL # So [2] LEFTWARDS DOUBLE ARROW..UPWARDS DOUBLE ARROW +21D2 ; AI # Sm RIGHTWARDS DOUBLE ARROW +21D3 ; AL # So DOWNWARDS DOUBLE ARROW +21D4 ; AI # Sm LEFT RIGHT DOUBLE ARROW +21D5..21F3 ; AL # So [31] UP DOWN DOUBLE ARROW..UP DOWN WHITE ARROW +21F4..21FF ; AL # Sm [12] RIGHT ARROW WITH SMALL CIRCLE..LEFT RIGHT OPEN-HEADED ARROW +2200 ; AI # Sm FOR ALL +2201 ; AL # Sm COMPLEMENT +2202..2203 ; AI # Sm [2] PARTIAL DIFFERENTIAL..THERE EXISTS +2204..2206 ; AL # Sm [3] THERE DOES NOT EXIST..INCREMENT +2207..2208 ; AI # Sm [2] NABLA..ELEMENT OF +2209..220A ; AL # Sm [2] NOT AN ELEMENT OF..SMALL ELEMENT OF +220B ; AI # Sm CONTAINS AS MEMBER +220C..220E ; AL # Sm [3] DOES NOT CONTAIN AS MEMBER..END OF PROOF +220F ; AI # Sm N-ARY PRODUCT +2210 ; AL # Sm N-ARY COPRODUCT +2211 ; AI # Sm N-ARY SUMMATION +2212..2213 ; PR # Sm [2] MINUS SIGN..MINUS-OR-PLUS SIGN +2214 ; AL # Sm DOT PLUS +2215 ; AI # Sm DIVISION SLASH +2216..2219 ; AL # Sm [4] SET MINUS..BULLET OPERATOR +221A ; AI # Sm SQUARE ROOT +221B..221C ; AL # Sm [2] CUBE ROOT..FOURTH ROOT +221D..2220 ; AI # Sm [4] PROPORTIONAL TO..ANGLE +2221..2222 ; AL # Sm [2] MEASURED ANGLE..SPHERICAL ANGLE +2223 ; AI # Sm DIVIDES +2224 ; AL # Sm DOES NOT DIVIDE +2225 ; AI # Sm PARALLEL TO +2226 ; AL # Sm NOT PARALLEL TO +2227..222C ; AI # Sm [6] LOGICAL AND..DOUBLE INTEGRAL +222D ; AL # Sm TRIPLE INTEGRAL +222E ; AI # Sm CONTOUR INTEGRAL +222F..2233 ; AL # Sm [5] SURFACE INTEGRAL..ANTICLOCKWISE CONTOUR INTEGRAL +2234..2237 ; AI # Sm [4] THEREFORE..PROPORTION +2238..223B ; AL # Sm [4] DOT MINUS..HOMOTHETIC +223C..223D ; AI # Sm [2] TILDE OPERATOR..REVERSED TILDE +223E..2247 ; AL # Sm [10] INVERTED LAZY S..NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +2248 ; AI # Sm ALMOST EQUAL TO +2249..224B ; AL # Sm [3] NOT ALMOST EQUAL TO..TRIPLE TILDE +224C ; AI # Sm ALL EQUAL TO +224D..2251 ; AL # Sm [5] EQUIVALENT TO..GEOMETRICALLY EQUAL TO +2252 ; AI # Sm APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253..225F ; AL # Sm [13] IMAGE OF OR APPROXIMATELY EQUAL TO..QUESTIONED EQUAL TO +2260..2261 ; AI # Sm [2] NOT EQUAL TO..IDENTICAL TO +2262..2263 ; AL # Sm [2] NOT IDENTICAL TO..STRICTLY EQUIVALENT TO +2264..2267 ; AI # Sm [4] LESS-THAN OR EQUAL TO..GREATER-THAN OVER EQUAL TO +2268..2269 ; AL # Sm [2] LESS-THAN BUT NOT EQUAL TO..GREATER-THAN BUT NOT EQUAL TO +226A..226B ; AI # Sm [2] MUCH LESS-THAN..MUCH GREATER-THAN +226C..226D ; AL # Sm [2] BETWEEN..NOT EQUIVALENT TO +226E..226F ; AI # Sm [2] NOT LESS-THAN..NOT GREATER-THAN +2270..2281 ; AL # Sm [18] NEITHER LESS-THAN NOR EQUAL TO..DOES NOT SUCCEED +2282..2283 ; AI # Sm [2] SUBSET OF..SUPERSET OF +2284..2285 ; AL # Sm [2] NOT A SUBSET OF..NOT A SUPERSET OF +2286..2287 ; AI # Sm [2] SUBSET OF OR EQUAL TO..SUPERSET OF OR EQUAL TO +2288..2294 ; AL # Sm [13] NEITHER A SUBSET OF NOR EQUAL TO..SQUARE CUP +2295 ; AI # Sm CIRCLED PLUS +2296..2298 ; AL # Sm [3] CIRCLED MINUS..CIRCLED DIVISION SLASH +2299 ; AI # Sm CIRCLED DOT OPERATOR +229A..22A4 ; AL # Sm [11] CIRCLED RING OPERATOR..DOWN TACK +22A5 ; AI # Sm UP TACK +22A6..22BE ; AL # Sm [25] ASSERTION..RIGHT ANGLE WITH ARC +22BF ; AI # Sm RIGHT TRIANGLE +22C0..22EE ; AL # Sm [47] N-ARY LOGICAL AND..VERTICAL ELLIPSIS +22EF ; IN # Sm MIDLINE HORIZONTAL ELLIPSIS +22F0..22FF ; AL # Sm [16] UP RIGHT DIAGONAL ELLIPSIS..Z NOTATION BAG MEMBERSHIP +2300..2307 ; AL # So [8] DIAMETER SIGN..WAVY LINE +2308 ; OP # Ps LEFT CEILING +2309 ; CL # Pe RIGHT CEILING +230A ; OP # Ps LEFT FLOOR +230B ; CL # Pe RIGHT FLOOR +230C..2311 ; AL # So [6] BOTTOM RIGHT CROP..SQUARE LOZENGE +2312 ; AI # So ARC +2313..2319 ; AL # So [7] SEGMENT..TURNED NOT SIGN +231A..231B ; ID # So [2] WATCH..HOURGLASS +231C..231F ; AL # So [4] TOP LEFT CORNER..BOTTOM RIGHT CORNER +2320..2321 ; AL # Sm [2] TOP HALF INTEGRAL..BOTTOM HALF INTEGRAL +2322..2328 ; AL # So [7] FROWN..KEYBOARD +2329 ; OP # Ps LEFT-POINTING ANGLE BRACKET +232A ; CL # Pe RIGHT-POINTING ANGLE BRACKET +232B..237B ; AL # So [81] ERASE TO THE LEFT..NOT CHECK MARK +237C ; AL # Sm RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +237D..239A ; AL # So [30] SHOULDERED OPEN BOX..CLEAR SCREEN SYMBOL +239B..23B3 ; AL # Sm [25] LEFT PARENTHESIS UPPER HOOK..SUMMATION BOTTOM +23B4..23DB ; AL # So [40] TOP SQUARE BRACKET..FUSE +23DC..23E1 ; AL # Sm [6] TOP PARENTHESIS..BOTTOM TORTOISE SHELL BRACKET +23E2..23EF ; AL # So [14] WHITE TRAPEZIUM..BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR +23F0..23F3 ; ID # So [4] ALARM CLOCK..HOURGLASS WITH FLOWING SAND +23F4..23FF ; AL # So [12] BLACK MEDIUM LEFT-POINTING TRIANGLE..OBSERVER EYE SYMBOL +2400..2426 ; AL # So [39] SYMBOL FOR NULL..SYMBOL FOR SUBSTITUTE FORM TWO +2440..244A ; AL # So [11] OCR HOOK..OCR DOUBLE BACKSLASH +2460..249B ; AI # No [60] CIRCLED DIGIT ONE..NUMBER TWENTY FULL STOP +249C..24E9 ; AI # So [78] PARENTHESIZED LATIN SMALL LETTER A..CIRCLED LATIN SMALL LETTER Z +24EA..24FE ; AI # No [21] CIRCLED DIGIT ZERO..DOUBLE CIRCLED NUMBER TEN +24FF ; AL # No NEGATIVE CIRCLED DIGIT ZERO +2500..254B ; AI # So [76] BOX DRAWINGS LIGHT HORIZONTAL..BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL +254C..254F ; AL # So [4] BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL..BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL +2550..2574 ; AI # So [37] BOX DRAWINGS DOUBLE HORIZONTAL..BOX DRAWINGS LIGHT LEFT +2575..257F ; AL # So [11] BOX DRAWINGS LIGHT UP..BOX DRAWINGS HEAVY UP AND LIGHT DOWN +2580..258F ; AI # So [16] UPPER HALF BLOCK..LEFT ONE EIGHTH BLOCK +2590..2591 ; AL # So [2] RIGHT HALF BLOCK..LIGHT SHADE +2592..2595 ; AI # So [4] MEDIUM SHADE..RIGHT ONE EIGHTH BLOCK +2596..259F ; AL # So [10] QUADRANT LOWER LEFT..QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT +25A0..25A1 ; AI # So [2] BLACK SQUARE..WHITE SQUARE +25A2 ; AL # So WHITE SQUARE WITH ROUNDED CORNERS +25A3..25A9 ; AI # So [7] WHITE SQUARE CONTAINING BLACK SMALL SQUARE..SQUARE WITH DIAGONAL CROSSHATCH FILL +25AA..25B1 ; AL # So [8] BLACK SMALL SQUARE..WHITE PARALLELOGRAM +25B2..25B3 ; AI # So [2] BLACK UP-POINTING TRIANGLE..WHITE UP-POINTING TRIANGLE +25B4..25B5 ; AL # So [2] BLACK UP-POINTING SMALL TRIANGLE..WHITE UP-POINTING SMALL TRIANGLE +25B6 ; AI # So BLACK RIGHT-POINTING TRIANGLE +25B7 ; AI # Sm WHITE RIGHT-POINTING TRIANGLE +25B8..25BB ; AL # So [4] BLACK RIGHT-POINTING SMALL TRIANGLE..WHITE RIGHT-POINTING POINTER +25BC..25BD ; AI # So [2] BLACK DOWN-POINTING TRIANGLE..WHITE DOWN-POINTING TRIANGLE +25BE..25BF ; AL # So [2] BLACK DOWN-POINTING SMALL TRIANGLE..WHITE DOWN-POINTING SMALL TRIANGLE +25C0 ; AI # So BLACK LEFT-POINTING TRIANGLE +25C1 ; AI # Sm WHITE LEFT-POINTING TRIANGLE +25C2..25C5 ; AL # So [4] BLACK LEFT-POINTING SMALL TRIANGLE..WHITE LEFT-POINTING POINTER +25C6..25C8 ; AI # So [3] BLACK DIAMOND..WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND +25C9..25CA ; AL # So [2] FISHEYE..LOZENGE +25CB ; AI # So WHITE CIRCLE +25CC..25CD ; AL # So [2] DOTTED CIRCLE..CIRCLE WITH VERTICAL FILL +25CE..25D1 ; AI # So [4] BULLSEYE..CIRCLE WITH RIGHT HALF BLACK +25D2..25E1 ; AL # So [16] CIRCLE WITH LOWER HALF BLACK..LOWER HALF CIRCLE +25E2..25E5 ; AI # So [4] BLACK LOWER RIGHT TRIANGLE..BLACK UPPER RIGHT TRIANGLE +25E6..25EE ; AL # So [9] WHITE BULLET..UP-POINTING TRIANGLE WITH RIGHT HALF BLACK +25EF ; AI # So LARGE CIRCLE +25F0..25F7 ; AL # So [8] WHITE SQUARE WITH UPPER LEFT QUADRANT..WHITE CIRCLE WITH UPPER RIGHT QUADRANT +25F8..25FF ; AL # Sm [8] UPPER LEFT TRIANGLE..LOWER RIGHT TRIANGLE +2600..2603 ; ID # So [4] BLACK SUN WITH RAYS..SNOWMAN +2604 ; AL # So COMET +2605..2606 ; AI # So [2] BLACK STAR..WHITE STAR +2607..2608 ; AL # So [2] LIGHTNING..THUNDERSTORM +2609 ; AI # So SUN +260A..260D ; AL # So [4] ASCENDING NODE..OPPOSITION +260E..260F ; AI # So [2] BLACK TELEPHONE..WHITE TELEPHONE +2610..2613 ; AL # So [4] BALLOT BOX..SALTIRE +2614..2615 ; ID # So [2] UMBRELLA WITH RAIN DROPS..HOT BEVERAGE +2616..2617 ; AI # So [2] WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; ID # So SHAMROCK +2619 ; AL # So REVERSED ROTATED FLORAL HEART BULLET +261A..261C ; ID # So [3] BLACK LEFT POINTING INDEX..WHITE LEFT POINTING INDEX +261D ; EB # So WHITE UP POINTING INDEX +261E..261F ; ID # So [2] WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX +2620..2638 ; AL # So [25] SKULL AND CROSSBONES..WHEEL OF DHARMA +2639..263B ; ID # So [3] WHITE FROWNING FACE..BLACK SMILING FACE +263C..263F ; AL # So [4] WHITE SUN WITH RAYS..MERCURY +2640 ; AI # So FEMALE SIGN +2641 ; AL # So EARTH +2642 ; AI # So MALE SIGN +2643..265F ; AL # So [29] JUPITER..BLACK CHESS PAWN +2660..2661 ; AI # So [2] BLACK SPADE SUIT..WHITE HEART SUIT +2662 ; AL # So WHITE DIAMOND SUIT +2663..2665 ; AI # So [3] BLACK CLUB SUIT..BLACK HEART SUIT +2666 ; AL # So BLACK DIAMOND SUIT +2667 ; AI # So WHITE CLUB SUIT +2668 ; ID # So HOT SPRINGS +2669..266A ; AI # So [2] QUARTER NOTE..EIGHTH NOTE +266B ; AL # So BEAMED EIGHTH NOTES +266C..266D ; AI # So [2] BEAMED SIXTEENTH NOTES..MUSIC FLAT SIGN +266E ; AL # So MUSIC NATURAL SIGN +266F ; AI # Sm MUSIC SHARP SIGN +2670..267E ; AL # So [15] WEST SYRIAC CROSS..PERMANENT PAPER SIGN +267F ; ID # So WHEELCHAIR SYMBOL +2680..269D ; AL # So [30] DIE FACE-1..OUTLINED WHITE STAR +269E..269F ; AI # So [2] THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT +26A0..26BC ; AL # So [29] WARNING SIGN..SESQUIQUADRATE +26BD..26C8 ; ID # So [12] SOCCER BALL..THUNDER CLOUD AND RAIN +26C9..26CC ; AI # So [4] TURNED WHITE SHOGI PIECE..CROSSING LANES +26CD ; ID # So DISABLED CAR +26CE ; AL # So OPHIUCHUS +26CF..26D1 ; ID # So [3] PICK..HELMET WITH WHITE CROSS +26D2 ; AI # So CIRCLED CROSSING LANES +26D3..26D4 ; ID # So [2] CHAINS..NO ENTRY +26D5..26D7 ; AI # So [3] ALTERNATE ONE-WAY LEFT WAY TRAFFIC..WHITE TWO-WAY LEFT WAY TRAFFIC +26D8..26D9 ; ID # So [2] BLACK LEFT LANE MERGE..WHITE LEFT LANE MERGE +26DA..26DB ; AI # So [2] DRIVE SLOW SIGN..HEAVY WHITE DOWN-POINTING TRIANGLE +26DC ; ID # So LEFT CLOSED ENTRY +26DD..26DE ; AI # So [2] SQUARED SALTIRE..FALLING DIAGONAL IN WHITE CIRCLE IN BLACK SQUARE +26DF..26E1 ; ID # So [3] BLACK TRUCK..RESTRICTED LEFT ENTRY-2 +26E2 ; AL # So ASTRONOMICAL SYMBOL FOR URANUS +26E3 ; AI # So HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE +26E4..26E7 ; AL # So [4] PENTAGRAM..INVERTED PENTAGRAM +26E8..26E9 ; AI # So [2] BLACK CROSS ON SHIELD..SHINTO SHRINE +26EA ; ID # So CHURCH +26EB..26F0 ; AI # So [6] CASTLE..MOUNTAIN +26F1..26F5 ; ID # So [5] UMBRELLA ON GROUND..SAILBOAT +26F6 ; AI # So SQUARE FOUR CORNERS +26F7..26F8 ; ID # So [2] SKIER..ICE SKATE +26F9 ; EB # So PERSON WITH BALL +26FA ; ID # So TENT +26FB..26FC ; AI # So [2] JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD..26FF ; ID # So [3] FUEL PUMP..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE +2700..2704 ; ID # So [5] BLACK SAFETY SCISSORS..WHITE SCISSORS +2705..2707 ; AL # So [3] WHITE HEAVY CHECK MARK..TAPE DRIVE +2708..2709 ; ID # So [2] AIRPLANE..ENVELOPE +270A..270D ; EB # So [4] RAISED FIST..WRITING HAND +270E..2756 ; AL # So [73] LOWER RIGHT PENCIL..BLACK DIAMOND MINUS WHITE X +2757 ; AI # So HEAVY EXCLAMATION MARK SYMBOL +2758..275A ; AL # So [3] LIGHT VERTICAL BAR..HEAVY VERTICAL BAR +275B..2760 ; QU # So [6] HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT..HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT +2761 ; AL # So CURVED STEM PARAGRAPH SIGN ORNAMENT +2762..2763 ; EX # So [2] HEAVY EXCLAMATION MARK ORNAMENT..HEAVY HEART EXCLAMATION MARK ORNAMENT +2764 ; ID # So HEAVY BLACK HEART +2765..2767 ; AL # So [3] ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET +2768 ; OP # Ps MEDIUM LEFT PARENTHESIS ORNAMENT +2769 ; CL # Pe MEDIUM RIGHT PARENTHESIS ORNAMENT +276A ; OP # Ps MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276B ; CL # Pe MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276C ; OP # Ps MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276D ; CL # Pe MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276E ; OP # Ps HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +276F ; CL # Pe HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770 ; OP # Ps HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2771 ; CL # Pe HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2772 ; OP # Ps LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2773 ; CL # Pe LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2774 ; OP # Ps MEDIUM LEFT CURLY BRACKET ORNAMENT +2775 ; CL # Pe MEDIUM RIGHT CURLY BRACKET ORNAMENT +2776..2793 ; AI # No [30] DINGBAT NEGATIVE CIRCLED DIGIT ONE..DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN +2794..27BF ; AL # So [44] HEAVY WIDE-HEADED RIGHTWARDS ARROW..DOUBLE CURLY LOOP +27C0..27C4 ; AL # Sm [5] THREE DIMENSIONAL ANGLE..OPEN SUPERSET +27C5 ; OP # Ps LEFT S-SHAPED BAG DELIMITER +27C6 ; CL # Pe RIGHT S-SHAPED BAG DELIMITER +27C7..27E5 ; AL # Sm [31] OR WITH DOT INSIDE..WHITE SQUARE WITH RIGHTWARDS TICK +27E6 ; OP # Ps MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E7 ; CL # Pe MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E8 ; OP # Ps MATHEMATICAL LEFT ANGLE BRACKET +27E9 ; CL # Pe MATHEMATICAL RIGHT ANGLE BRACKET +27EA ; OP # Ps MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EB ; CL # Pe MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27EC ; OP # Ps MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27ED ; CL # Pe MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EE ; OP # Ps MATHEMATICAL LEFT FLATTENED PARENTHESIS +27EF ; CL # Pe MATHEMATICAL RIGHT FLATTENED PARENTHESIS +27F0..27FF ; AL # Sm [16] UPWARDS QUADRUPLE ARROW..LONG RIGHTWARDS SQUIGGLE ARROW +2800..28FF ; AL # So [256] BRAILLE PATTERN BLANK..BRAILLE PATTERN DOTS-12345678 +2900..297F ; AL # Sm [128] RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE..DOWN FISH TAIL +2980..2982 ; AL # Sm [3] TRIPLE VERTICAL BAR DELIMITER..Z NOTATION TYPE COLON +2983 ; OP # Ps LEFT WHITE CURLY BRACKET +2984 ; CL # Pe RIGHT WHITE CURLY BRACKET +2985 ; OP # Ps LEFT WHITE PARENTHESIS +2986 ; CL # Pe RIGHT WHITE PARENTHESIS +2987 ; OP # Ps Z NOTATION LEFT IMAGE BRACKET +2988 ; CL # Pe Z NOTATION RIGHT IMAGE BRACKET +2989 ; OP # Ps Z NOTATION LEFT BINDING BRACKET +298A ; CL # Pe Z NOTATION RIGHT BINDING BRACKET +298B ; OP # Ps LEFT SQUARE BRACKET WITH UNDERBAR +298C ; CL # Pe RIGHT SQUARE BRACKET WITH UNDERBAR +298D ; OP # Ps LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298E ; CL # Pe RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +298F ; OP # Ps LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990 ; CL # Pe RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2991 ; OP # Ps LEFT ANGLE BRACKET WITH DOT +2992 ; CL # Pe RIGHT ANGLE BRACKET WITH DOT +2993 ; OP # Ps LEFT ARC LESS-THAN BRACKET +2994 ; CL # Pe RIGHT ARC GREATER-THAN BRACKET +2995 ; OP # Ps DOUBLE LEFT ARC GREATER-THAN BRACKET +2996 ; CL # Pe DOUBLE RIGHT ARC LESS-THAN BRACKET +2997 ; OP # Ps LEFT BLACK TORTOISE SHELL BRACKET +2998 ; CL # Pe RIGHT BLACK TORTOISE SHELL BRACKET +2999..29D7 ; AL # Sm [63] DOTTED FENCE..BLACK HOURGLASS +29D8 ; OP # Ps LEFT WIGGLY FENCE +29D9 ; CL # Pe RIGHT WIGGLY FENCE +29DA ; OP # Ps LEFT DOUBLE WIGGLY FENCE +29DB ; CL # Pe RIGHT DOUBLE WIGGLY FENCE +29DC..29FB ; AL # Sm [32] INCOMPLETE INFINITY..TRIPLE PLUS +29FC ; OP # Ps LEFT-POINTING CURVED ANGLE BRACKET +29FD ; CL # Pe RIGHT-POINTING CURVED ANGLE BRACKET +29FE..29FF ; AL # Sm [2] TINY..MINY +2A00..2AFF ; AL # Sm [256] N-ARY CIRCLED DOT OPERATOR..N-ARY WHITE VERTICAL BAR +2B00..2B2F ; AL # So [48] NORTH EAST WHITE ARROW..WHITE VERTICAL ELLIPSE +2B30..2B44 ; AL # Sm [21] LEFT ARROW WITH SMALL CIRCLE..RIGHTWARDS ARROW THROUGH SUPERSET +2B45..2B46 ; AL # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW +2B47..2B4C ; AL # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR +2B4D..2B54 ; AL # So [8] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..WHITE RIGHT-POINTING PENTAGON +2B55..2B59 ; AI # So [5] HEAVY LARGE CIRCLE..HEAVY CIRCLED SALTIRE +2B5A..2B73 ; AL # So [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR +2B76..2B95 ; AL # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B97..2BFF ; AL # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2C00..2C5F ; AL # L& [96] GLAGOLITIC CAPITAL LETTER AZU..GLAGOLITIC SMALL LETTER CAUDATE CHRIVI +2C60..2C7B ; AL # L& [28] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN LETTER SMALL CAPITAL TURNED E +2C7C..2C7D ; AL # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V +2C7E..2C7F ; AL # Lu [2] LATIN CAPITAL LETTER S WITH SWASH TAIL..LATIN CAPITAL LETTER Z WITH SWASH TAIL +2C80..2CE4 ; AL # L& [101] COPTIC CAPITAL LETTER ALFA..COPTIC SYMBOL KAI +2CE5..2CEA ; AL # So [6] COPTIC SYMBOL MI RO..COPTIC SYMBOL SHIMA SIMA +2CEB..2CEE ; AL # L& [4] COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI..COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA +2CEF..2CF1 ; CM # Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS +2CF2..2CF3 ; AL # L& [2] COPTIC CAPITAL LETTER BOHAIRIC KHEI..COPTIC SMALL LETTER BOHAIRIC KHEI +2CF9 ; EX # Po COPTIC OLD NUBIAN FULL STOP +2CFA..2CFC ; BA # Po [3] COPTIC OLD NUBIAN DIRECT QUESTION MARK..COPTIC OLD NUBIAN VERSE DIVIDER +2CFD ; AL # No COPTIC FRACTION ONE HALF +2CFE ; EX # Po COPTIC FULL STOP +2CFF ; BA # Po COPTIC MORPHOLOGICAL DIVIDER +2D00..2D25 ; AL # Ll [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE +2D27 ; AL # Ll GEORGIAN SMALL LETTER YN +2D2D ; AL # Ll GEORGIAN SMALL LETTER AEN +2D30..2D67 ; AL # Lo [56] TIFINAGH LETTER YA..TIFINAGH LETTER YO +2D6F ; AL # Lm TIFINAGH MODIFIER LETTER LABIALIZATION MARK +2D70 ; BA # Po TIFINAGH SEPARATOR MARK +2D7F ; CM # Mn TIFINAGH CONSONANT JOINER +2D80..2D96 ; AL # Lo [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; AL # Lo [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; AL # Lo [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; AL # Lo [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; AL # Lo [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; AL # Lo [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; AL # Lo [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; AL # Lo [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; AL # Lo [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +2DE0..2DFF ; CM # Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS +2E00..2E01 ; QU # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER +2E02 ; QU # Pi LEFT SUBSTITUTION BRACKET +2E03 ; QU # Pf RIGHT SUBSTITUTION BRACKET +2E04 ; QU # Pi LEFT DOTTED SUBSTITUTION BRACKET +2E05 ; QU # Pf RIGHT DOTTED SUBSTITUTION BRACKET +2E06..2E08 ; QU # Po [3] RAISED INTERPOLATION MARKER..DOTTED TRANSPOSITION MARKER +2E09 ; QU # Pi LEFT TRANSPOSITION BRACKET +2E0A ; QU # Pf RIGHT TRANSPOSITION BRACKET +2E0B ; QU # Po RAISED SQUARE +2E0C ; QU # Pi LEFT RAISED OMISSION BRACKET +2E0D ; QU # Pf RIGHT RAISED OMISSION BRACKET +2E0E..2E15 ; BA # Po [8] EDITORIAL CORONIS..UPWARDS ANCORA +2E16 ; AL # Po DOTTED RIGHT-POINTING ANGLE +2E17 ; BA # Pd DOUBLE OBLIQUE HYPHEN +2E18 ; OP # Po INVERTED INTERROBANG +2E19 ; BA # Po PALM BRANCH +2E1A ; AL # Pd HYPHEN WITH DIAERESIS +2E1B ; AL # Po TILDE WITH RING ABOVE +2E1C ; QU # Pi LEFT LOW PARAPHRASE BRACKET +2E1D ; QU # Pf RIGHT LOW PARAPHRASE BRACKET +2E1E..2E1F ; AL # Po [2] TILDE WITH DOT ABOVE..TILDE WITH DOT BELOW +2E20 ; QU # Pi LEFT VERTICAL BAR WITH QUILL +2E21 ; QU # Pf RIGHT VERTICAL BAR WITH QUILL +2E22 ; OP # Ps TOP LEFT HALF BRACKET +2E23 ; CL # Pe TOP RIGHT HALF BRACKET +2E24 ; OP # Ps BOTTOM LEFT HALF BRACKET +2E25 ; CL # Pe BOTTOM RIGHT HALF BRACKET +2E26 ; OP # Ps LEFT SIDEWAYS U BRACKET +2E27 ; CL # Pe RIGHT SIDEWAYS U BRACKET +2E28 ; OP # Ps LEFT DOUBLE PARENTHESIS +2E29 ; CL # Pe RIGHT DOUBLE PARENTHESIS +2E2A..2E2D ; BA # Po [4] TWO DOTS OVER ONE DOT PUNCTUATION..FIVE DOT MARK +2E2E ; EX # Po REVERSED QUESTION MARK +2E2F ; AL # Lm VERTICAL TILDE +2E30..2E31 ; BA # Po [2] RING POINT..WORD SEPARATOR MIDDLE DOT +2E32 ; AL # Po TURNED COMMA +2E33..2E34 ; BA # Po [2] RAISED DOT..RAISED COMMA +2E35..2E39 ; AL # Po [5] TURNED SEMICOLON..TOP HALF SECTION SIGN +2E3A..2E3B ; B2 # Pd [2] TWO-EM DASH..THREE-EM DASH +2E3C..2E3E ; BA # Po [3] STENOGRAPHIC FULL STOP..WIGGLY VERTICAL LINE +2E3F ; AL # Po CAPITULUM +2E40 ; BA # Pd DOUBLE HYPHEN +2E41 ; BA # Po REVERSED COMMA +2E42 ; OP # Ps DOUBLE LOW-REVERSED-9 QUOTATION MARK +2E43..2E4A ; BA # Po [8] DASH WITH LEFT UPTURN..DOTTED SOLIDUS +2E4B ; AL # Po TRIPLE DAGGER +2E4C ; BA # Po MEDIEVAL COMMA +2E4D ; AL # Po PARAGRAPHUS MARK +2E4E..2E4F ; BA # Po [2] PUNCTUS ELEVATUS MARK..CORNISH VERSE DIVIDER +2E50..2E51 ; AL # So [2] CROSS PATTY WITH RIGHT CROSSBAR..CROSS PATTY WITH LEFT CROSSBAR +2E52 ; AL # Po TIRONIAN SIGN CAPITAL ET +2E53..2E54 ; EX # Po [2] MEDIEVAL EXCLAMATION MARK..MEDIEVAL QUESTION MARK +2E55 ; OP # Ps LEFT SQUARE BRACKET WITH STROKE +2E56 ; CL # Pe RIGHT SQUARE BRACKET WITH STROKE +2E57 ; OP # Ps LEFT SQUARE BRACKET WITH DOUBLE STROKE +2E58 ; CL # Pe RIGHT SQUARE BRACKET WITH DOUBLE STROKE +2E59 ; OP # Ps TOP HALF LEFT PARENTHESIS +2E5A ; CL # Pe TOP HALF RIGHT PARENTHESIS +2E5B ; OP # Ps BOTTOM HALF LEFT PARENTHESIS +2E5C ; CL # Pe BOTTOM HALF RIGHT PARENTHESIS +2E5D ; BA # Pd OBLIQUE HYPHEN +2E80..2E99 ; ID # So [26] CJK RADICAL REPEAT..CJK RADICAL RAP +2E9B..2EF3 ; ID # So [89] CJK RADICAL CHOKE..CJK RADICAL C-SIMPLIFIED TURTLE +2F00..2FD5 ; ID # So [214] KANGXI RADICAL ONE..KANGXI RADICAL FLUTE +2FF0..2FFF ; ID # So [16] IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION +3000 ; BA # Zs IDEOGRAPHIC SPACE +3001..3002 ; CL # Po [2] IDEOGRAPHIC COMMA..IDEOGRAPHIC FULL STOP +3003 ; ID # Po DITTO MARK +3004 ; ID # So JAPANESE INDUSTRIAL STANDARD SYMBOL +3005 ; NS # Lm IDEOGRAPHIC ITERATION MARK +3006 ; ID # Lo IDEOGRAPHIC CLOSING MARK +3007 ; ID # Nl IDEOGRAPHIC NUMBER ZERO +3008 ; OP # Ps LEFT ANGLE BRACKET +3009 ; CL # Pe RIGHT ANGLE BRACKET +300A ; OP # Ps LEFT DOUBLE ANGLE BRACKET +300B ; CL # Pe RIGHT DOUBLE ANGLE BRACKET +300C ; OP # Ps LEFT CORNER BRACKET +300D ; CL # Pe RIGHT CORNER BRACKET +300E ; OP # Ps LEFT WHITE CORNER BRACKET +300F ; CL # Pe RIGHT WHITE CORNER BRACKET +3010 ; OP # Ps LEFT BLACK LENTICULAR BRACKET +3011 ; CL # Pe RIGHT BLACK LENTICULAR BRACKET +3012..3013 ; ID # So [2] POSTAL MARK..GETA MARK +3014 ; OP # Ps LEFT TORTOISE SHELL BRACKET +3015 ; CL # Pe RIGHT TORTOISE SHELL BRACKET +3016 ; OP # Ps LEFT WHITE LENTICULAR BRACKET +3017 ; CL # Pe RIGHT WHITE LENTICULAR BRACKET +3018 ; OP # Ps LEFT WHITE TORTOISE SHELL BRACKET +3019 ; CL # Pe RIGHT WHITE TORTOISE SHELL BRACKET +301A ; OP # Ps LEFT WHITE SQUARE BRACKET +301B ; CL # Pe RIGHT WHITE SQUARE BRACKET +301C ; NS # Pd WAVE DASH +301D ; OP # Ps REVERSED DOUBLE PRIME QUOTATION MARK +301E..301F ; CL # Pe [2] DOUBLE PRIME QUOTATION MARK..LOW DOUBLE PRIME QUOTATION MARK +3020 ; ID # So POSTAL MARK FACE +3021..3029 ; ID # Nl [9] HANGZHOU NUMERAL ONE..HANGZHOU NUMERAL NINE +302A..302D ; CM # Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK +302E..302F ; CM # Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK +3030 ; ID # Pd WAVY DASH +3031..3034 ; ID # Lm [4] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT WITH VOICED SOUND MARK UPPER HALF +3035 ; CM # Lm VERTICAL KANA REPEAT MARK LOWER HALF +3036..3037 ; ID # So [2] CIRCLED POSTAL MARK..IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL +3038..303A ; ID # Nl [3] HANGZHOU NUMERAL TEN..HANGZHOU NUMERAL THIRTY +303B ; NS # Lm VERTICAL IDEOGRAPHIC ITERATION MARK +303C ; NS # Lo MASU MARK +303D ; ID # Po PART ALTERNATION MARK +303E..303F ; ID # So [2] IDEOGRAPHIC VARIATION INDICATOR..IDEOGRAPHIC HALF FILL SPACE +3041 ; CJ # Lo HIRAGANA LETTER SMALL A +3042 ; ID # Lo HIRAGANA LETTER A +3043 ; CJ # Lo HIRAGANA LETTER SMALL I +3044 ; ID # Lo HIRAGANA LETTER I +3045 ; CJ # Lo HIRAGANA LETTER SMALL U +3046 ; ID # Lo HIRAGANA LETTER U +3047 ; CJ # Lo HIRAGANA LETTER SMALL E +3048 ; ID # Lo HIRAGANA LETTER E +3049 ; CJ # Lo HIRAGANA LETTER SMALL O +304A..3062 ; ID # Lo [25] HIRAGANA LETTER O..HIRAGANA LETTER DI +3063 ; CJ # Lo HIRAGANA LETTER SMALL TU +3064..3082 ; ID # Lo [31] HIRAGANA LETTER TU..HIRAGANA LETTER MO +3083 ; CJ # Lo HIRAGANA LETTER SMALL YA +3084 ; ID # Lo HIRAGANA LETTER YA +3085 ; CJ # Lo HIRAGANA LETTER SMALL YU +3086 ; ID # Lo HIRAGANA LETTER YU +3087 ; CJ # Lo HIRAGANA LETTER SMALL YO +3088..308D ; ID # Lo [6] HIRAGANA LETTER YO..HIRAGANA LETTER RO +308E ; CJ # Lo HIRAGANA LETTER SMALL WA +308F..3094 ; ID # Lo [6] HIRAGANA LETTER WA..HIRAGANA LETTER VU +3095..3096 ; CJ # Lo [2] HIRAGANA LETTER SMALL KA..HIRAGANA LETTER SMALL KE +3099..309A ; CM # Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309B..309C ; NS # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +309D..309E ; NS # Lm [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK +309F ; ID # Lo HIRAGANA DIGRAPH YORI +30A0 ; NS # Pd KATAKANA-HIRAGANA DOUBLE HYPHEN +30A1 ; CJ # Lo KATAKANA LETTER SMALL A +30A2 ; ID # Lo KATAKANA LETTER A +30A3 ; CJ # Lo KATAKANA LETTER SMALL I +30A4 ; ID # Lo KATAKANA LETTER I +30A5 ; CJ # Lo KATAKANA LETTER SMALL U +30A6 ; ID # Lo KATAKANA LETTER U +30A7 ; CJ # Lo KATAKANA LETTER SMALL E +30A8 ; ID # Lo KATAKANA LETTER E +30A9 ; CJ # Lo KATAKANA LETTER SMALL O +30AA..30C2 ; ID # Lo [25] KATAKANA LETTER O..KATAKANA LETTER DI +30C3 ; CJ # Lo KATAKANA LETTER SMALL TU +30C4..30E2 ; ID # Lo [31] KATAKANA LETTER TU..KATAKANA LETTER MO +30E3 ; CJ # Lo KATAKANA LETTER SMALL YA +30E4 ; ID # Lo KATAKANA LETTER YA +30E5 ; CJ # Lo KATAKANA LETTER SMALL YU +30E6 ; ID # Lo KATAKANA LETTER YU +30E7 ; CJ # Lo KATAKANA LETTER SMALL YO +30E8..30ED ; ID # Lo [6] KATAKANA LETTER YO..KATAKANA LETTER RO +30EE ; CJ # Lo KATAKANA LETTER SMALL WA +30EF..30F4 ; ID # Lo [6] KATAKANA LETTER WA..KATAKANA LETTER VU +30F5..30F6 ; CJ # Lo [2] KATAKANA LETTER SMALL KA..KATAKANA LETTER SMALL KE +30F7..30FA ; ID # Lo [4] KATAKANA LETTER VA..KATAKANA LETTER VO +30FB ; NS # Po KATAKANA MIDDLE DOT +30FC ; CJ # Lm KATAKANA-HIRAGANA PROLONGED SOUND MARK +30FD..30FE ; NS # Lm [2] KATAKANA ITERATION MARK..KATAKANA VOICED ITERATION MARK +30FF ; ID # Lo KATAKANA DIGRAPH KOTO +3105..312F ; ID # Lo [43] BOPOMOFO LETTER B..BOPOMOFO LETTER NN +3131..318E ; ID # Lo [94] HANGUL LETTER KIYEOK..HANGUL LETTER ARAEAE +3190..3191 ; ID # So [2] IDEOGRAPHIC ANNOTATION LINKING MARK..IDEOGRAPHIC ANNOTATION REVERSE MARK +3192..3195 ; ID # No [4] IDEOGRAPHIC ANNOTATION ONE MARK..IDEOGRAPHIC ANNOTATION FOUR MARK +3196..319F ; ID # So [10] IDEOGRAPHIC ANNOTATION TOP MARK..IDEOGRAPHIC ANNOTATION MAN MARK +31A0..31BF ; ID # Lo [32] BOPOMOFO LETTER BU..BOPOMOFO LETTER AH +31C0..31E3 ; ID # So [36] CJK STROKE T..CJK STROKE Q +31EF ; ID # So IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION +31F0..31FF ; CJ # Lo [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO +3200..321E ; ID # So [31] PARENTHESIZED HANGUL KIYEOK..PARENTHESIZED KOREAN CHARACTER O HU +3220..3229 ; ID # No [10] PARENTHESIZED IDEOGRAPH ONE..PARENTHESIZED IDEOGRAPH TEN +322A..3247 ; ID # So [30] PARENTHESIZED IDEOGRAPH MOON..CIRCLED IDEOGRAPH KOTO +3248..324F ; AI # No [8] CIRCLED NUMBER TEN ON BLACK SQUARE..CIRCLED NUMBER EIGHTY ON BLACK SQUARE +3250 ; ID # So PARTNERSHIP SIGN +3251..325F ; ID # No [15] CIRCLED NUMBER TWENTY ONE..CIRCLED NUMBER THIRTY FIVE +3260..327F ; ID # So [32] CIRCLED HANGUL KIYEOK..KOREAN STANDARD SYMBOL +3280..3289 ; ID # No [10] CIRCLED IDEOGRAPH ONE..CIRCLED IDEOGRAPH TEN +328A..32B0 ; ID # So [39] CIRCLED IDEOGRAPH MOON..CIRCLED IDEOGRAPH NIGHT +32B1..32BF ; ID # No [15] CIRCLED NUMBER THIRTY SIX..CIRCLED NUMBER FIFTY +32C0..32FF ; ID # So [64] IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY..SQUARE ERA NAME REIWA +3300..33FF ; ID # So [256] SQUARE APAATO..SQUARE GAL +3400..4DBF ; ID # Lo [6592] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DBF +4DC0..4DFF ; AL # So [64] HEXAGRAM FOR THE CREATIVE HEAVEN..HEXAGRAM FOR BEFORE COMPLETION +4E00..9FFF ; ID # Lo [20992] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FFF +A000..A014 ; ID # Lo [21] YI SYLLABLE IT..YI SYLLABLE E +A015 ; NS # Lm YI SYLLABLE WU +A016..A48C ; ID # Lo [1143] YI SYLLABLE BIT..YI SYLLABLE YYR +A490..A4C6 ; ID # So [55] YI RADICAL QOT..YI RADICAL KE +A4D0..A4F7 ; AL # Lo [40] LISU LETTER BA..LISU LETTER OE +A4F8..A4FD ; AL # Lm [6] LISU LETTER TONE MYA TI..LISU LETTER TONE MYA JEU +A4FE..A4FF ; BA # Po [2] LISU PUNCTUATION COMMA..LISU PUNCTUATION FULL STOP +A500..A60B ; AL # Lo [268] VAI SYLLABLE EE..VAI SYLLABLE NG +A60C ; AL # Lm VAI SYLLABLE LENGTHENER +A60D ; BA # Po VAI COMMA +A60E ; EX # Po VAI FULL STOP +A60F ; BA # Po VAI QUESTION MARK +A610..A61F ; AL # Lo [16] VAI SYLLABLE NDOLE FA..VAI SYMBOL JONG +A620..A629 ; NU # Nd [10] VAI DIGIT ZERO..VAI DIGIT NINE +A62A..A62B ; AL # Lo [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO +A640..A66D ; AL # L& [46] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER DOUBLE MONOCULAR O +A66E ; AL # Lo CYRILLIC LETTER MULTIOCULAR O +A66F ; CM # Mn COMBINING CYRILLIC VZMET +A670..A672 ; CM # Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN +A673 ; AL # Po SLAVONIC ASTERISK +A674..A67D ; CM # Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK +A67E ; AL # Po CYRILLIC KAVYKA +A67F ; AL # Lm CYRILLIC PAYEROK +A680..A69B ; AL # L& [28] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER CROSSED O +A69C..A69D ; AL # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN +A69E..A69F ; CM # Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E +A6A0..A6E5 ; AL # Lo [70] BAMUM LETTER A..BAMUM LETTER KI +A6E6..A6EF ; AL # Nl [10] BAMUM LETTER MO..BAMUM LETTER KOGHOM +A6F0..A6F1 ; CM # Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS +A6F2 ; AL # Po BAMUM NJAEMLI +A6F3..A6F7 ; BA # Po [5] BAMUM FULL STOP..BAMUM QUESTION MARK +A700..A716 ; AL # Sk [23] MODIFIER LETTER CHINESE TONE YIN PING..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR +A717..A71F ; AL # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A720..A721 ; AL # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE +A722..A76F ; AL # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CON +A770 ; AL # Lm MODIFIER LETTER US +A771..A787 ; AL # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T +A788 ; AL # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT +A789..A78A ; AL # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +A78B..A78E ; AL # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT +A78F ; AL # Lo LATIN LETTER SINOLOGICAL DOT +A790..A7CA ; AL # L& [59] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D0..A7D1 ; AL # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; AL # Ll LATIN SMALL LETTER DOUBLE THORN +A7D5..A7D9 ; AL # L& [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S +A7F2..A7F4 ; AL # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F5..A7F6 ; AL # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H +A7F7 ; AL # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I +A7F8..A7F9 ; AL # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE +A7FA ; AL # Ll LATIN LETTER SMALL CAPITAL TURNED M +A7FB..A7FF ; AL # Lo [5] LATIN EPIGRAPHIC LETTER REVERSED F..LATIN EPIGRAPHIC LETTER ARCHAIC M +A800..A801 ; AL # Lo [2] SYLOTI NAGRI LETTER A..SYLOTI NAGRI LETTER I +A802 ; CM # Mn SYLOTI NAGRI SIGN DVISVARA +A803..A805 ; AL # Lo [3] SYLOTI NAGRI LETTER U..SYLOTI NAGRI LETTER O +A806 ; CM # Mn SYLOTI NAGRI SIGN HASANTA +A807..A80A ; AL # Lo [4] SYLOTI NAGRI LETTER KO..SYLOTI NAGRI LETTER GHO +A80B ; CM # Mn SYLOTI NAGRI SIGN ANUSVARA +A80C..A822 ; AL # Lo [23] SYLOTI NAGRI LETTER CO..SYLOTI NAGRI LETTER HO +A823..A824 ; CM # Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I +A825..A826 ; CM # Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E +A827 ; CM # Mc SYLOTI NAGRI VOWEL SIGN OO +A828..A82B ; AL # So [4] SYLOTI NAGRI POETRY MARK-1..SYLOTI NAGRI POETRY MARK-4 +A82C ; CM # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA +A830..A835 ; AL # No [6] NORTH INDIC FRACTION ONE QUARTER..NORTH INDIC FRACTION THREE SIXTEENTHS +A836..A837 ; AL # So [2] NORTH INDIC QUARTER MARK..NORTH INDIC PLACEHOLDER MARK +A838 ; PO # Sc NORTH INDIC RUPEE MARK +A839 ; AL # So NORTH INDIC QUANTITY MARK +A840..A873 ; AL # Lo [52] PHAGS-PA LETTER KA..PHAGS-PA LETTER CANDRABINDU +A874..A875 ; BB # Po [2] PHAGS-PA SINGLE HEAD MARK..PHAGS-PA DOUBLE HEAD MARK +A876..A877 ; EX # Po [2] PHAGS-PA MARK SHAD..PHAGS-PA MARK DOUBLE SHAD +A880..A881 ; CM # Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA +A882..A8B3 ; AL # Lo [50] SAURASHTRA LETTER A..SAURASHTRA LETTER LLA +A8B4..A8C3 ; CM # Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU +A8C4..A8C5 ; CM # Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU +A8CE..A8CF ; BA # Po [2] SAURASHTRA DANDA..SAURASHTRA DOUBLE DANDA +A8D0..A8D9 ; NU # Nd [10] SAURASHTRA DIGIT ZERO..SAURASHTRA DIGIT NINE +A8E0..A8F1 ; CM # Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA +A8F2..A8F7 ; AL # Lo [6] DEVANAGARI SIGN SPACING CANDRABINDU..DEVANAGARI SIGN CANDRABINDU AVAGRAHA +A8F8..A8FA ; AL # Po [3] DEVANAGARI SIGN PUSHPIKA..DEVANAGARI CARET +A8FB ; AL # Lo DEVANAGARI HEADSTROKE +A8FC ; BB # Po DEVANAGARI SIGN SIDDHAM +A8FD..A8FE ; AL # Lo [2] DEVANAGARI JAIN OM..DEVANAGARI LETTER AY +A8FF ; CM # Mn DEVANAGARI VOWEL SIGN AY +A900..A909 ; NU # Nd [10] KAYAH LI DIGIT ZERO..KAYAH LI DIGIT NINE +A90A..A925 ; AL # Lo [28] KAYAH LI LETTER KA..KAYAH LI LETTER OO +A926..A92D ; CM # Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU +A92E..A92F ; BA # Po [2] KAYAH LI SIGN CWI..KAYAH LI SIGN SHYA +A930..A946 ; AL # Lo [23] REJANG LETTER KA..REJANG LETTER A +A947..A951 ; CM # Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R +A952..A953 ; CM # Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA +A95F ; AL # Po REJANG SECTION MARK +A960..A97C ; JL # Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH +A980..A982 ; CM # Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR +A983 ; CM # Mc JAVANESE SIGN WIGNYAN +A984..A9B2 ; AK # Lo [47] JAVANESE LETTER A..JAVANESE LETTER HA +A9B3 ; CM # Mn JAVANESE SIGN CECAK TELU +A9B4..A9B5 ; CM # Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG +A9B6..A9B9 ; CM # Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT +A9BA..A9BB ; CM # Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE +A9BC..A9BD ; CM # Mn [2] JAVANESE VOWEL SIGN PEPET..JAVANESE CONSONANT SIGN KERET +A9BE..A9BF ; CM # Mc [2] JAVANESE CONSONANT SIGN PENGKAL..JAVANESE CONSONANT SIGN CAKRA +A9C0 ; VI # Mc JAVANESE PANGKON +A9C1..A9C6 ; ID # Po [6] JAVANESE LEFT RERENGGAN..JAVANESE PADA WINDU +A9C7..A9C9 ; BA # Po [3] JAVANESE PADA PANGKAT..JAVANESE PADA LUNGSI +A9CA..A9CD ; ID # Po [4] JAVANESE PADA ADEG..JAVANESE TURNED PADA PISELEH +A9CF ; BA # Lm JAVANESE PANGRANGKEP +A9D0..A9D9 ; ID # Nd [10] JAVANESE DIGIT ZERO..JAVANESE DIGIT NINE +A9DE..A9DF ; ID # Po [2] JAVANESE PADA TIRTA TUMETES..JAVANESE PADA ISEN-ISEN +A9E0..A9E4 ; SA # Lo [5] MYANMAR LETTER SHAN GHA..MYANMAR LETTER SHAN BHA +A9E5 ; SA # Mn MYANMAR SIGN SHAN SAW +A9E6 ; SA # Lm MYANMAR MODIFIER LETTER SHAN REDUPLICATION +A9E7..A9EF ; SA # Lo [9] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING NNA +A9F0..A9F9 ; NU # Nd [10] MYANMAR TAI LAING DIGIT ZERO..MYANMAR TAI LAING DIGIT NINE +A9FA..A9FE ; SA # Lo [5] MYANMAR LETTER TAI LAING LLA..MYANMAR LETTER TAI LAING BHA +AA00..AA28 ; AS # Lo [41] CHAM LETTER A..CHAM LETTER HA +AA29..AA2E ; CM # Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE +AA2F..AA30 ; CM # Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI +AA31..AA32 ; CM # Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE +AA33..AA34 ; CM # Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA +AA35..AA36 ; CM # Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA +AA40..AA42 ; BA # Lo [3] CHAM LETTER FINAL K..CHAM LETTER FINAL NG +AA43 ; CM # Mn CHAM CONSONANT SIGN FINAL NG +AA44..AA4B ; BA # Lo [8] CHAM LETTER FINAL CH..CHAM LETTER FINAL SS +AA4C ; CM # Mn CHAM CONSONANT SIGN FINAL M +AA4D ; CM # Mc CHAM CONSONANT SIGN FINAL H +AA50..AA59 ; ID # Nd [10] CHAM DIGIT ZERO..CHAM DIGIT NINE +AA5C ; ID # Po CHAM PUNCTUATION SPIRAL +AA5D..AA5F ; BA # Po [3] CHAM PUNCTUATION DANDA..CHAM PUNCTUATION TRIPLE DANDA +AA60..AA6F ; SA # Lo [16] MYANMAR LETTER KHAMTI GA..MYANMAR LETTER KHAMTI FA +AA70 ; SA # Lm MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION +AA71..AA76 ; SA # Lo [6] MYANMAR LETTER KHAMTI XA..MYANMAR LOGOGRAM KHAMTI HM +AA77..AA79 ; SA # So [3] MYANMAR SYMBOL AITON EXCLAMATION..MYANMAR SYMBOL AITON TWO +AA7A ; SA # Lo MYANMAR LETTER AITON RA +AA7B ; SA # Mc MYANMAR SIGN PAO KAREN TONE +AA7C ; SA # Mn MYANMAR SIGN TAI LAING TONE-2 +AA7D ; SA # Mc MYANMAR SIGN TAI LAING TONE-5 +AA7E..AA7F ; SA # Lo [2] MYANMAR LETTER SHWE PALAUNG CHA..MYANMAR LETTER SHWE PALAUNG SHA +AA80..AAAF ; SA # Lo [48] TAI VIET LETTER LOW KO..TAI VIET LETTER HIGH O +AAB0 ; SA # Mn TAI VIET MAI KANG +AAB1 ; SA # Lo TAI VIET VOWEL AA +AAB2..AAB4 ; SA # Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U +AAB5..AAB6 ; SA # Lo [2] TAI VIET VOWEL E..TAI VIET VOWEL O +AAB7..AAB8 ; SA # Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA +AAB9..AABD ; SA # Lo [5] TAI VIET VOWEL UEA..TAI VIET VOWEL AN +AABE..AABF ; SA # Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK +AAC0 ; SA # Lo TAI VIET TONE MAI NUENG +AAC1 ; SA # Mn TAI VIET TONE MAI THO +AAC2 ; SA # Lo TAI VIET TONE MAI SONG +AADB..AADC ; SA # Lo [2] TAI VIET SYMBOL KON..TAI VIET SYMBOL NUENG +AADD ; SA # Lm TAI VIET SYMBOL SAM +AADE..AADF ; SA # Po [2] TAI VIET SYMBOL HO HOI..TAI VIET SYMBOL KOI KOI +AAE0..AAEA ; AL # Lo [11] MEETEI MAYEK LETTER E..MEETEI MAYEK LETTER SSA +AAEB ; CM # Mc MEETEI MAYEK VOWEL SIGN II +AAEC..AAED ; CM # Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI +AAEE..AAEF ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU +AAF0..AAF1 ; BA # Po [2] MEETEI MAYEK CHEIKHAN..MEETEI MAYEK AHANG KHUDAM +AAF2 ; AL # Lo MEETEI MAYEK ANJI +AAF3..AAF4 ; AL # Lm [2] MEETEI MAYEK SYLLABLE REPETITION MARK..MEETEI MAYEK WORD REPETITION MARK +AAF5 ; CM # Mc MEETEI MAYEK VOWEL SIGN VISARGA +AAF6 ; CM # Mn MEETEI MAYEK VIRAMA +AB01..AB06 ; AL # Lo [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; AL # Lo [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; AL # Lo [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; AL # Lo [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; AL # Lo [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO +AB30..AB5A ; AL # Ll [43] LATIN SMALL LETTER BARRED ALPHA..LATIN SMALL LETTER Y WITH SHORT RIGHT LEG +AB5B ; AL # Sk MODIFIER BREVE WITH INVERTED BREVE +AB5C..AB5F ; AL # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK +AB60..AB68 ; AL # Ll [9] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE +AB69 ; AL # Lm MODIFIER LETTER SMALL TURNED W +AB6A..AB6B ; AL # Sk [2] MODIFIER LETTER LEFT TACK..MODIFIER LETTER RIGHT TACK +AB70..ABBF ; AL # Ll [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA +ABC0..ABE2 ; AL # Lo [35] MEETEI MAYEK LETTER KOK..MEETEI MAYEK LETTER I LONSUM +ABE3..ABE4 ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP +ABE5 ; CM # Mn MEETEI MAYEK VOWEL SIGN ANAP +ABE6..ABE7 ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP +ABE8 ; CM # Mn MEETEI MAYEK VOWEL SIGN UNAP +ABE9..ABEA ; CM # Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG +ABEB ; BA # Po MEETEI MAYEK CHEIKHEI +ABEC ; CM # Mc MEETEI MAYEK LUM IYEK +ABED ; CM # Mn MEETEI MAYEK APUN IYEK +ABF0..ABF9 ; NU # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DIGIT NINE +AC00 ; H2 # Lo HANGUL SYLLABLE GA +AC01..AC1B ; H3 # Lo [27] HANGUL SYLLABLE GAG..HANGUL SYLLABLE GAH +AC1C ; H2 # Lo HANGUL SYLLABLE GAE +AC1D..AC37 ; H3 # Lo [27] HANGUL SYLLABLE GAEG..HANGUL SYLLABLE GAEH +AC38 ; H2 # Lo HANGUL SYLLABLE GYA +AC39..AC53 ; H3 # Lo [27] HANGUL SYLLABLE GYAG..HANGUL SYLLABLE GYAH +AC54 ; H2 # Lo HANGUL SYLLABLE GYAE +AC55..AC6F ; H3 # Lo [27] HANGUL SYLLABLE GYAEG..HANGUL SYLLABLE GYAEH +AC70 ; H2 # Lo HANGUL SYLLABLE GEO +AC71..AC8B ; H3 # Lo [27] HANGUL SYLLABLE GEOG..HANGUL SYLLABLE GEOH +AC8C ; H2 # Lo HANGUL SYLLABLE GE +AC8D..ACA7 ; H3 # Lo [27] HANGUL SYLLABLE GEG..HANGUL SYLLABLE GEH +ACA8 ; H2 # Lo HANGUL SYLLABLE GYEO +ACA9..ACC3 ; H3 # Lo [27] HANGUL SYLLABLE GYEOG..HANGUL SYLLABLE GYEOH +ACC4 ; H2 # Lo HANGUL SYLLABLE GYE +ACC5..ACDF ; H3 # Lo [27] HANGUL SYLLABLE GYEG..HANGUL SYLLABLE GYEH +ACE0 ; H2 # Lo HANGUL SYLLABLE GO +ACE1..ACFB ; H3 # Lo [27] HANGUL SYLLABLE GOG..HANGUL SYLLABLE GOH +ACFC ; H2 # Lo HANGUL SYLLABLE GWA +ACFD..AD17 ; H3 # Lo [27] HANGUL SYLLABLE GWAG..HANGUL SYLLABLE GWAH +AD18 ; H2 # Lo HANGUL SYLLABLE GWAE +AD19..AD33 ; H3 # Lo [27] HANGUL SYLLABLE GWAEG..HANGUL SYLLABLE GWAEH +AD34 ; H2 # Lo HANGUL SYLLABLE GOE +AD35..AD4F ; H3 # Lo [27] HANGUL SYLLABLE GOEG..HANGUL SYLLABLE GOEH +AD50 ; H2 # Lo HANGUL SYLLABLE GYO +AD51..AD6B ; H3 # Lo [27] HANGUL SYLLABLE GYOG..HANGUL SYLLABLE GYOH +AD6C ; H2 # Lo HANGUL SYLLABLE GU +AD6D..AD87 ; H3 # Lo [27] HANGUL SYLLABLE GUG..HANGUL SYLLABLE GUH +AD88 ; H2 # Lo HANGUL SYLLABLE GWEO +AD89..ADA3 ; H3 # Lo [27] HANGUL SYLLABLE GWEOG..HANGUL SYLLABLE GWEOH +ADA4 ; H2 # Lo HANGUL SYLLABLE GWE +ADA5..ADBF ; H3 # Lo [27] HANGUL SYLLABLE GWEG..HANGUL SYLLABLE GWEH +ADC0 ; H2 # Lo HANGUL SYLLABLE GWI +ADC1..ADDB ; H3 # Lo [27] HANGUL SYLLABLE GWIG..HANGUL SYLLABLE GWIH +ADDC ; H2 # Lo HANGUL SYLLABLE GYU +ADDD..ADF7 ; H3 # Lo [27] HANGUL SYLLABLE GYUG..HANGUL SYLLABLE GYUH +ADF8 ; H2 # Lo HANGUL SYLLABLE GEU +ADF9..AE13 ; H3 # Lo [27] HANGUL SYLLABLE GEUG..HANGUL SYLLABLE GEUH +AE14 ; H2 # Lo HANGUL SYLLABLE GYI +AE15..AE2F ; H3 # Lo [27] HANGUL SYLLABLE GYIG..HANGUL SYLLABLE GYIH +AE30 ; H2 # Lo HANGUL SYLLABLE GI +AE31..AE4B ; H3 # Lo [27] HANGUL SYLLABLE GIG..HANGUL SYLLABLE GIH +AE4C ; H2 # Lo HANGUL SYLLABLE GGA +AE4D..AE67 ; H3 # Lo [27] HANGUL SYLLABLE GGAG..HANGUL SYLLABLE GGAH +AE68 ; H2 # Lo HANGUL SYLLABLE GGAE +AE69..AE83 ; H3 # Lo [27] HANGUL SYLLABLE GGAEG..HANGUL SYLLABLE GGAEH +AE84 ; H2 # Lo HANGUL SYLLABLE GGYA +AE85..AE9F ; H3 # Lo [27] HANGUL SYLLABLE GGYAG..HANGUL SYLLABLE GGYAH +AEA0 ; H2 # Lo HANGUL SYLLABLE GGYAE +AEA1..AEBB ; H3 # Lo [27] HANGUL SYLLABLE GGYAEG..HANGUL SYLLABLE GGYAEH +AEBC ; H2 # Lo HANGUL SYLLABLE GGEO +AEBD..AED7 ; H3 # Lo [27] HANGUL SYLLABLE GGEOG..HANGUL SYLLABLE GGEOH +AED8 ; H2 # Lo HANGUL SYLLABLE GGE +AED9..AEF3 ; H3 # Lo [27] HANGUL SYLLABLE GGEG..HANGUL SYLLABLE GGEH +AEF4 ; H2 # Lo HANGUL SYLLABLE GGYEO +AEF5..AF0F ; H3 # Lo [27] HANGUL SYLLABLE GGYEOG..HANGUL SYLLABLE GGYEOH +AF10 ; H2 # Lo HANGUL SYLLABLE GGYE +AF11..AF2B ; H3 # Lo [27] HANGUL SYLLABLE GGYEG..HANGUL SYLLABLE GGYEH +AF2C ; H2 # Lo HANGUL SYLLABLE GGO +AF2D..AF47 ; H3 # Lo [27] HANGUL SYLLABLE GGOG..HANGUL SYLLABLE GGOH +AF48 ; H2 # Lo HANGUL SYLLABLE GGWA +AF49..AF63 ; H3 # Lo [27] HANGUL SYLLABLE GGWAG..HANGUL SYLLABLE GGWAH +AF64 ; H2 # Lo HANGUL SYLLABLE GGWAE +AF65..AF7F ; H3 # Lo [27] HANGUL SYLLABLE GGWAEG..HANGUL SYLLABLE GGWAEH +AF80 ; H2 # Lo HANGUL SYLLABLE GGOE +AF81..AF9B ; H3 # Lo [27] HANGUL SYLLABLE GGOEG..HANGUL SYLLABLE GGOEH +AF9C ; H2 # Lo HANGUL SYLLABLE GGYO +AF9D..AFB7 ; H3 # Lo [27] HANGUL SYLLABLE GGYOG..HANGUL SYLLABLE GGYOH +AFB8 ; H2 # Lo HANGUL SYLLABLE GGU +AFB9..AFD3 ; H3 # Lo [27] HANGUL SYLLABLE GGUG..HANGUL SYLLABLE GGUH +AFD4 ; H2 # Lo HANGUL SYLLABLE GGWEO +AFD5..AFEF ; H3 # Lo [27] HANGUL SYLLABLE GGWEOG..HANGUL SYLLABLE GGWEOH +AFF0 ; H2 # Lo HANGUL SYLLABLE GGWE +AFF1..B00B ; H3 # Lo [27] HANGUL SYLLABLE GGWEG..HANGUL SYLLABLE GGWEH +B00C ; H2 # Lo HANGUL SYLLABLE GGWI +B00D..B027 ; H3 # Lo [27] HANGUL SYLLABLE GGWIG..HANGUL SYLLABLE GGWIH +B028 ; H2 # Lo HANGUL SYLLABLE GGYU +B029..B043 ; H3 # Lo [27] HANGUL SYLLABLE GGYUG..HANGUL SYLLABLE GGYUH +B044 ; H2 # Lo HANGUL SYLLABLE GGEU +B045..B05F ; H3 # Lo [27] HANGUL SYLLABLE GGEUG..HANGUL SYLLABLE GGEUH +B060 ; H2 # Lo HANGUL SYLLABLE GGYI +B061..B07B ; H3 # Lo [27] HANGUL SYLLABLE GGYIG..HANGUL SYLLABLE GGYIH +B07C ; H2 # Lo HANGUL SYLLABLE GGI +B07D..B097 ; H3 # Lo [27] HANGUL SYLLABLE GGIG..HANGUL SYLLABLE GGIH +B098 ; H2 # Lo HANGUL SYLLABLE NA +B099..B0B3 ; H3 # Lo [27] HANGUL SYLLABLE NAG..HANGUL SYLLABLE NAH +B0B4 ; H2 # Lo HANGUL SYLLABLE NAE +B0B5..B0CF ; H3 # Lo [27] HANGUL SYLLABLE NAEG..HANGUL SYLLABLE NAEH +B0D0 ; H2 # Lo HANGUL SYLLABLE NYA +B0D1..B0EB ; H3 # Lo [27] HANGUL SYLLABLE NYAG..HANGUL SYLLABLE NYAH +B0EC ; H2 # Lo HANGUL SYLLABLE NYAE +B0ED..B107 ; H3 # Lo [27] HANGUL SYLLABLE NYAEG..HANGUL SYLLABLE NYAEH +B108 ; H2 # Lo HANGUL SYLLABLE NEO +B109..B123 ; H3 # Lo [27] HANGUL SYLLABLE NEOG..HANGUL SYLLABLE NEOH +B124 ; H2 # Lo HANGUL SYLLABLE NE +B125..B13F ; H3 # Lo [27] HANGUL SYLLABLE NEG..HANGUL SYLLABLE NEH +B140 ; H2 # Lo HANGUL SYLLABLE NYEO +B141..B15B ; H3 # Lo [27] HANGUL SYLLABLE NYEOG..HANGUL SYLLABLE NYEOH +B15C ; H2 # Lo HANGUL SYLLABLE NYE +B15D..B177 ; H3 # Lo [27] HANGUL SYLLABLE NYEG..HANGUL SYLLABLE NYEH +B178 ; H2 # Lo HANGUL SYLLABLE NO +B179..B193 ; H3 # Lo [27] HANGUL SYLLABLE NOG..HANGUL SYLLABLE NOH +B194 ; H2 # Lo HANGUL SYLLABLE NWA +B195..B1AF ; H3 # Lo [27] HANGUL SYLLABLE NWAG..HANGUL SYLLABLE NWAH +B1B0 ; H2 # Lo HANGUL SYLLABLE NWAE +B1B1..B1CB ; H3 # Lo [27] HANGUL SYLLABLE NWAEG..HANGUL SYLLABLE NWAEH +B1CC ; H2 # Lo HANGUL SYLLABLE NOE +B1CD..B1E7 ; H3 # Lo [27] HANGUL SYLLABLE NOEG..HANGUL SYLLABLE NOEH +B1E8 ; H2 # Lo HANGUL SYLLABLE NYO +B1E9..B203 ; H3 # Lo [27] HANGUL SYLLABLE NYOG..HANGUL SYLLABLE NYOH +B204 ; H2 # Lo HANGUL SYLLABLE NU +B205..B21F ; H3 # Lo [27] HANGUL SYLLABLE NUG..HANGUL SYLLABLE NUH +B220 ; H2 # Lo HANGUL SYLLABLE NWEO +B221..B23B ; H3 # Lo [27] HANGUL SYLLABLE NWEOG..HANGUL SYLLABLE NWEOH +B23C ; H2 # Lo HANGUL SYLLABLE NWE +B23D..B257 ; H3 # Lo [27] HANGUL SYLLABLE NWEG..HANGUL SYLLABLE NWEH +B258 ; H2 # Lo HANGUL SYLLABLE NWI +B259..B273 ; H3 # Lo [27] HANGUL SYLLABLE NWIG..HANGUL SYLLABLE NWIH +B274 ; H2 # Lo HANGUL SYLLABLE NYU +B275..B28F ; H3 # Lo [27] HANGUL SYLLABLE NYUG..HANGUL SYLLABLE NYUH +B290 ; H2 # Lo HANGUL SYLLABLE NEU +B291..B2AB ; H3 # Lo [27] HANGUL SYLLABLE NEUG..HANGUL SYLLABLE NEUH +B2AC ; H2 # Lo HANGUL SYLLABLE NYI +B2AD..B2C7 ; H3 # Lo [27] HANGUL SYLLABLE NYIG..HANGUL SYLLABLE NYIH +B2C8 ; H2 # Lo HANGUL SYLLABLE NI +B2C9..B2E3 ; H3 # Lo [27] HANGUL SYLLABLE NIG..HANGUL SYLLABLE NIH +B2E4 ; H2 # Lo HANGUL SYLLABLE DA +B2E5..B2FF ; H3 # Lo [27] HANGUL SYLLABLE DAG..HANGUL SYLLABLE DAH +B300 ; H2 # Lo HANGUL SYLLABLE DAE +B301..B31B ; H3 # Lo [27] HANGUL SYLLABLE DAEG..HANGUL SYLLABLE DAEH +B31C ; H2 # Lo HANGUL SYLLABLE DYA +B31D..B337 ; H3 # Lo [27] HANGUL SYLLABLE DYAG..HANGUL SYLLABLE DYAH +B338 ; H2 # Lo HANGUL SYLLABLE DYAE +B339..B353 ; H3 # Lo [27] HANGUL SYLLABLE DYAEG..HANGUL SYLLABLE DYAEH +B354 ; H2 # Lo HANGUL SYLLABLE DEO +B355..B36F ; H3 # Lo [27] HANGUL SYLLABLE DEOG..HANGUL SYLLABLE DEOH +B370 ; H2 # Lo HANGUL SYLLABLE DE +B371..B38B ; H3 # Lo [27] HANGUL SYLLABLE DEG..HANGUL SYLLABLE DEH +B38C ; H2 # Lo HANGUL SYLLABLE DYEO +B38D..B3A7 ; H3 # Lo [27] HANGUL SYLLABLE DYEOG..HANGUL SYLLABLE DYEOH +B3A8 ; H2 # Lo HANGUL SYLLABLE DYE +B3A9..B3C3 ; H3 # Lo [27] HANGUL SYLLABLE DYEG..HANGUL SYLLABLE DYEH +B3C4 ; H2 # Lo HANGUL SYLLABLE DO +B3C5..B3DF ; H3 # Lo [27] HANGUL SYLLABLE DOG..HANGUL SYLLABLE DOH +B3E0 ; H2 # Lo HANGUL SYLLABLE DWA +B3E1..B3FB ; H3 # Lo [27] HANGUL SYLLABLE DWAG..HANGUL SYLLABLE DWAH +B3FC ; H2 # Lo HANGUL SYLLABLE DWAE +B3FD..B417 ; H3 # Lo [27] HANGUL SYLLABLE DWAEG..HANGUL SYLLABLE DWAEH +B418 ; H2 # Lo HANGUL SYLLABLE DOE +B419..B433 ; H3 # Lo [27] HANGUL SYLLABLE DOEG..HANGUL SYLLABLE DOEH +B434 ; H2 # Lo HANGUL SYLLABLE DYO +B435..B44F ; H3 # Lo [27] HANGUL SYLLABLE DYOG..HANGUL SYLLABLE DYOH +B450 ; H2 # Lo HANGUL SYLLABLE DU +B451..B46B ; H3 # Lo [27] HANGUL SYLLABLE DUG..HANGUL SYLLABLE DUH +B46C ; H2 # Lo HANGUL SYLLABLE DWEO +B46D..B487 ; H3 # Lo [27] HANGUL SYLLABLE DWEOG..HANGUL SYLLABLE DWEOH +B488 ; H2 # Lo HANGUL SYLLABLE DWE +B489..B4A3 ; H3 # Lo [27] HANGUL SYLLABLE DWEG..HANGUL SYLLABLE DWEH +B4A4 ; H2 # Lo HANGUL SYLLABLE DWI +B4A5..B4BF ; H3 # Lo [27] HANGUL SYLLABLE DWIG..HANGUL SYLLABLE DWIH +B4C0 ; H2 # Lo HANGUL SYLLABLE DYU +B4C1..B4DB ; H3 # Lo [27] HANGUL SYLLABLE DYUG..HANGUL SYLLABLE DYUH +B4DC ; H2 # Lo HANGUL SYLLABLE DEU +B4DD..B4F7 ; H3 # Lo [27] HANGUL SYLLABLE DEUG..HANGUL SYLLABLE DEUH +B4F8 ; H2 # Lo HANGUL SYLLABLE DYI +B4F9..B513 ; H3 # Lo [27] HANGUL SYLLABLE DYIG..HANGUL SYLLABLE DYIH +B514 ; H2 # Lo HANGUL SYLLABLE DI +B515..B52F ; H3 # Lo [27] HANGUL SYLLABLE DIG..HANGUL SYLLABLE DIH +B530 ; H2 # Lo HANGUL SYLLABLE DDA +B531..B54B ; H3 # Lo [27] HANGUL SYLLABLE DDAG..HANGUL SYLLABLE DDAH +B54C ; H2 # Lo HANGUL SYLLABLE DDAE +B54D..B567 ; H3 # Lo [27] HANGUL SYLLABLE DDAEG..HANGUL SYLLABLE DDAEH +B568 ; H2 # Lo HANGUL SYLLABLE DDYA +B569..B583 ; H3 # Lo [27] HANGUL SYLLABLE DDYAG..HANGUL SYLLABLE DDYAH +B584 ; H2 # Lo HANGUL SYLLABLE DDYAE +B585..B59F ; H3 # Lo [27] HANGUL SYLLABLE DDYAEG..HANGUL SYLLABLE DDYAEH +B5A0 ; H2 # Lo HANGUL SYLLABLE DDEO +B5A1..B5BB ; H3 # Lo [27] HANGUL SYLLABLE DDEOG..HANGUL SYLLABLE DDEOH +B5BC ; H2 # Lo HANGUL SYLLABLE DDE +B5BD..B5D7 ; H3 # Lo [27] HANGUL SYLLABLE DDEG..HANGUL SYLLABLE DDEH +B5D8 ; H2 # Lo HANGUL SYLLABLE DDYEO +B5D9..B5F3 ; H3 # Lo [27] HANGUL SYLLABLE DDYEOG..HANGUL SYLLABLE DDYEOH +B5F4 ; H2 # Lo HANGUL SYLLABLE DDYE +B5F5..B60F ; H3 # Lo [27] HANGUL SYLLABLE DDYEG..HANGUL SYLLABLE DDYEH +B610 ; H2 # Lo HANGUL SYLLABLE DDO +B611..B62B ; H3 # Lo [27] HANGUL SYLLABLE DDOG..HANGUL SYLLABLE DDOH +B62C ; H2 # Lo HANGUL SYLLABLE DDWA +B62D..B647 ; H3 # Lo [27] HANGUL SYLLABLE DDWAG..HANGUL SYLLABLE DDWAH +B648 ; H2 # Lo HANGUL SYLLABLE DDWAE +B649..B663 ; H3 # Lo [27] HANGUL SYLLABLE DDWAEG..HANGUL SYLLABLE DDWAEH +B664 ; H2 # Lo HANGUL SYLLABLE DDOE +B665..B67F ; H3 # Lo [27] HANGUL SYLLABLE DDOEG..HANGUL SYLLABLE DDOEH +B680 ; H2 # Lo HANGUL SYLLABLE DDYO +B681..B69B ; H3 # Lo [27] HANGUL SYLLABLE DDYOG..HANGUL SYLLABLE DDYOH +B69C ; H2 # Lo HANGUL SYLLABLE DDU +B69D..B6B7 ; H3 # Lo [27] HANGUL SYLLABLE DDUG..HANGUL SYLLABLE DDUH +B6B8 ; H2 # Lo HANGUL SYLLABLE DDWEO +B6B9..B6D3 ; H3 # Lo [27] HANGUL SYLLABLE DDWEOG..HANGUL SYLLABLE DDWEOH +B6D4 ; H2 # Lo HANGUL SYLLABLE DDWE +B6D5..B6EF ; H3 # Lo [27] HANGUL SYLLABLE DDWEG..HANGUL SYLLABLE DDWEH +B6F0 ; H2 # Lo HANGUL SYLLABLE DDWI +B6F1..B70B ; H3 # Lo [27] HANGUL SYLLABLE DDWIG..HANGUL SYLLABLE DDWIH +B70C ; H2 # Lo HANGUL SYLLABLE DDYU +B70D..B727 ; H3 # Lo [27] HANGUL SYLLABLE DDYUG..HANGUL SYLLABLE DDYUH +B728 ; H2 # Lo HANGUL SYLLABLE DDEU +B729..B743 ; H3 # Lo [27] HANGUL SYLLABLE DDEUG..HANGUL SYLLABLE DDEUH +B744 ; H2 # Lo HANGUL SYLLABLE DDYI +B745..B75F ; H3 # Lo [27] HANGUL SYLLABLE DDYIG..HANGUL SYLLABLE DDYIH +B760 ; H2 # Lo HANGUL SYLLABLE DDI +B761..B77B ; H3 # Lo [27] HANGUL SYLLABLE DDIG..HANGUL SYLLABLE DDIH +B77C ; H2 # Lo HANGUL SYLLABLE RA +B77D..B797 ; H3 # Lo [27] HANGUL SYLLABLE RAG..HANGUL SYLLABLE RAH +B798 ; H2 # Lo HANGUL SYLLABLE RAE +B799..B7B3 ; H3 # Lo [27] HANGUL SYLLABLE RAEG..HANGUL SYLLABLE RAEH +B7B4 ; H2 # Lo HANGUL SYLLABLE RYA +B7B5..B7CF ; H3 # Lo [27] HANGUL SYLLABLE RYAG..HANGUL SYLLABLE RYAH +B7D0 ; H2 # Lo HANGUL SYLLABLE RYAE +B7D1..B7EB ; H3 # Lo [27] HANGUL SYLLABLE RYAEG..HANGUL SYLLABLE RYAEH +B7EC ; H2 # Lo HANGUL SYLLABLE REO +B7ED..B807 ; H3 # Lo [27] HANGUL SYLLABLE REOG..HANGUL SYLLABLE REOH +B808 ; H2 # Lo HANGUL SYLLABLE RE +B809..B823 ; H3 # Lo [27] HANGUL SYLLABLE REG..HANGUL SYLLABLE REH +B824 ; H2 # Lo HANGUL SYLLABLE RYEO +B825..B83F ; H3 # Lo [27] HANGUL SYLLABLE RYEOG..HANGUL SYLLABLE RYEOH +B840 ; H2 # Lo HANGUL SYLLABLE RYE +B841..B85B ; H3 # Lo [27] HANGUL SYLLABLE RYEG..HANGUL SYLLABLE RYEH +B85C ; H2 # Lo HANGUL SYLLABLE RO +B85D..B877 ; H3 # Lo [27] HANGUL SYLLABLE ROG..HANGUL SYLLABLE ROH +B878 ; H2 # Lo HANGUL SYLLABLE RWA +B879..B893 ; H3 # Lo [27] HANGUL SYLLABLE RWAG..HANGUL SYLLABLE RWAH +B894 ; H2 # Lo HANGUL SYLLABLE RWAE +B895..B8AF ; H3 # Lo [27] HANGUL SYLLABLE RWAEG..HANGUL SYLLABLE RWAEH +B8B0 ; H2 # Lo HANGUL SYLLABLE ROE +B8B1..B8CB ; H3 # Lo [27] HANGUL SYLLABLE ROEG..HANGUL SYLLABLE ROEH +B8CC ; H2 # Lo HANGUL SYLLABLE RYO +B8CD..B8E7 ; H3 # Lo [27] HANGUL SYLLABLE RYOG..HANGUL SYLLABLE RYOH +B8E8 ; H2 # Lo HANGUL SYLLABLE RU +B8E9..B903 ; H3 # Lo [27] HANGUL SYLLABLE RUG..HANGUL SYLLABLE RUH +B904 ; H2 # Lo HANGUL SYLLABLE RWEO +B905..B91F ; H3 # Lo [27] HANGUL SYLLABLE RWEOG..HANGUL SYLLABLE RWEOH +B920 ; H2 # Lo HANGUL SYLLABLE RWE +B921..B93B ; H3 # Lo [27] HANGUL SYLLABLE RWEG..HANGUL SYLLABLE RWEH +B93C ; H2 # Lo HANGUL SYLLABLE RWI +B93D..B957 ; H3 # Lo [27] HANGUL SYLLABLE RWIG..HANGUL SYLLABLE RWIH +B958 ; H2 # Lo HANGUL SYLLABLE RYU +B959..B973 ; H3 # Lo [27] HANGUL SYLLABLE RYUG..HANGUL SYLLABLE RYUH +B974 ; H2 # Lo HANGUL SYLLABLE REU +B975..B98F ; H3 # Lo [27] HANGUL SYLLABLE REUG..HANGUL SYLLABLE REUH +B990 ; H2 # Lo HANGUL SYLLABLE RYI +B991..B9AB ; H3 # Lo [27] HANGUL SYLLABLE RYIG..HANGUL SYLLABLE RYIH +B9AC ; H2 # Lo HANGUL SYLLABLE RI +B9AD..B9C7 ; H3 # Lo [27] HANGUL SYLLABLE RIG..HANGUL SYLLABLE RIH +B9C8 ; H2 # Lo HANGUL SYLLABLE MA +B9C9..B9E3 ; H3 # Lo [27] HANGUL SYLLABLE MAG..HANGUL SYLLABLE MAH +B9E4 ; H2 # Lo HANGUL SYLLABLE MAE +B9E5..B9FF ; H3 # Lo [27] HANGUL SYLLABLE MAEG..HANGUL SYLLABLE MAEH +BA00 ; H2 # Lo HANGUL SYLLABLE MYA +BA01..BA1B ; H3 # Lo [27] HANGUL SYLLABLE MYAG..HANGUL SYLLABLE MYAH +BA1C ; H2 # Lo HANGUL SYLLABLE MYAE +BA1D..BA37 ; H3 # Lo [27] HANGUL SYLLABLE MYAEG..HANGUL SYLLABLE MYAEH +BA38 ; H2 # Lo HANGUL SYLLABLE MEO +BA39..BA53 ; H3 # Lo [27] HANGUL SYLLABLE MEOG..HANGUL SYLLABLE MEOH +BA54 ; H2 # Lo HANGUL SYLLABLE ME +BA55..BA6F ; H3 # Lo [27] HANGUL SYLLABLE MEG..HANGUL SYLLABLE MEH +BA70 ; H2 # Lo HANGUL SYLLABLE MYEO +BA71..BA8B ; H3 # Lo [27] HANGUL SYLLABLE MYEOG..HANGUL SYLLABLE MYEOH +BA8C ; H2 # Lo HANGUL SYLLABLE MYE +BA8D..BAA7 ; H3 # Lo [27] HANGUL SYLLABLE MYEG..HANGUL SYLLABLE MYEH +BAA8 ; H2 # Lo HANGUL SYLLABLE MO +BAA9..BAC3 ; H3 # Lo [27] HANGUL SYLLABLE MOG..HANGUL SYLLABLE MOH +BAC4 ; H2 # Lo HANGUL SYLLABLE MWA +BAC5..BADF ; H3 # Lo [27] HANGUL SYLLABLE MWAG..HANGUL SYLLABLE MWAH +BAE0 ; H2 # Lo HANGUL SYLLABLE MWAE +BAE1..BAFB ; H3 # Lo [27] HANGUL SYLLABLE MWAEG..HANGUL SYLLABLE MWAEH +BAFC ; H2 # Lo HANGUL SYLLABLE MOE +BAFD..BB17 ; H3 # Lo [27] HANGUL SYLLABLE MOEG..HANGUL SYLLABLE MOEH +BB18 ; H2 # Lo HANGUL SYLLABLE MYO +BB19..BB33 ; H3 # Lo [27] HANGUL SYLLABLE MYOG..HANGUL SYLLABLE MYOH +BB34 ; H2 # Lo HANGUL SYLLABLE MU +BB35..BB4F ; H3 # Lo [27] HANGUL SYLLABLE MUG..HANGUL SYLLABLE MUH +BB50 ; H2 # Lo HANGUL SYLLABLE MWEO +BB51..BB6B ; H3 # Lo [27] HANGUL SYLLABLE MWEOG..HANGUL SYLLABLE MWEOH +BB6C ; H2 # Lo HANGUL SYLLABLE MWE +BB6D..BB87 ; H3 # Lo [27] HANGUL SYLLABLE MWEG..HANGUL SYLLABLE MWEH +BB88 ; H2 # Lo HANGUL SYLLABLE MWI +BB89..BBA3 ; H3 # Lo [27] HANGUL SYLLABLE MWIG..HANGUL SYLLABLE MWIH +BBA4 ; H2 # Lo HANGUL SYLLABLE MYU +BBA5..BBBF ; H3 # Lo [27] HANGUL SYLLABLE MYUG..HANGUL SYLLABLE MYUH +BBC0 ; H2 # Lo HANGUL SYLLABLE MEU +BBC1..BBDB ; H3 # Lo [27] HANGUL SYLLABLE MEUG..HANGUL SYLLABLE MEUH +BBDC ; H2 # Lo HANGUL SYLLABLE MYI +BBDD..BBF7 ; H3 # Lo [27] HANGUL SYLLABLE MYIG..HANGUL SYLLABLE MYIH +BBF8 ; H2 # Lo HANGUL SYLLABLE MI +BBF9..BC13 ; H3 # Lo [27] HANGUL SYLLABLE MIG..HANGUL SYLLABLE MIH +BC14 ; H2 # Lo HANGUL SYLLABLE BA +BC15..BC2F ; H3 # Lo [27] HANGUL SYLLABLE BAG..HANGUL SYLLABLE BAH +BC30 ; H2 # Lo HANGUL SYLLABLE BAE +BC31..BC4B ; H3 # Lo [27] HANGUL SYLLABLE BAEG..HANGUL SYLLABLE BAEH +BC4C ; H2 # Lo HANGUL SYLLABLE BYA +BC4D..BC67 ; H3 # Lo [27] HANGUL SYLLABLE BYAG..HANGUL SYLLABLE BYAH +BC68 ; H2 # Lo HANGUL SYLLABLE BYAE +BC69..BC83 ; H3 # Lo [27] HANGUL SYLLABLE BYAEG..HANGUL SYLLABLE BYAEH +BC84 ; H2 # Lo HANGUL SYLLABLE BEO +BC85..BC9F ; H3 # Lo [27] HANGUL SYLLABLE BEOG..HANGUL SYLLABLE BEOH +BCA0 ; H2 # Lo HANGUL SYLLABLE BE +BCA1..BCBB ; H3 # Lo [27] HANGUL SYLLABLE BEG..HANGUL SYLLABLE BEH +BCBC ; H2 # Lo HANGUL SYLLABLE BYEO +BCBD..BCD7 ; H3 # Lo [27] HANGUL SYLLABLE BYEOG..HANGUL SYLLABLE BYEOH +BCD8 ; H2 # Lo HANGUL SYLLABLE BYE +BCD9..BCF3 ; H3 # Lo [27] HANGUL SYLLABLE BYEG..HANGUL SYLLABLE BYEH +BCF4 ; H2 # Lo HANGUL SYLLABLE BO +BCF5..BD0F ; H3 # Lo [27] HANGUL SYLLABLE BOG..HANGUL SYLLABLE BOH +BD10 ; H2 # Lo HANGUL SYLLABLE BWA +BD11..BD2B ; H3 # Lo [27] HANGUL SYLLABLE BWAG..HANGUL SYLLABLE BWAH +BD2C ; H2 # Lo HANGUL SYLLABLE BWAE +BD2D..BD47 ; H3 # Lo [27] HANGUL SYLLABLE BWAEG..HANGUL SYLLABLE BWAEH +BD48 ; H2 # Lo HANGUL SYLLABLE BOE +BD49..BD63 ; H3 # Lo [27] HANGUL SYLLABLE BOEG..HANGUL SYLLABLE BOEH +BD64 ; H2 # Lo HANGUL SYLLABLE BYO +BD65..BD7F ; H3 # Lo [27] HANGUL SYLLABLE BYOG..HANGUL SYLLABLE BYOH +BD80 ; H2 # Lo HANGUL SYLLABLE BU +BD81..BD9B ; H3 # Lo [27] HANGUL SYLLABLE BUG..HANGUL SYLLABLE BUH +BD9C ; H2 # Lo HANGUL SYLLABLE BWEO +BD9D..BDB7 ; H3 # Lo [27] HANGUL SYLLABLE BWEOG..HANGUL SYLLABLE BWEOH +BDB8 ; H2 # Lo HANGUL SYLLABLE BWE +BDB9..BDD3 ; H3 # Lo [27] HANGUL SYLLABLE BWEG..HANGUL SYLLABLE BWEH +BDD4 ; H2 # Lo HANGUL SYLLABLE BWI +BDD5..BDEF ; H3 # Lo [27] HANGUL SYLLABLE BWIG..HANGUL SYLLABLE BWIH +BDF0 ; H2 # Lo HANGUL SYLLABLE BYU +BDF1..BE0B ; H3 # Lo [27] HANGUL SYLLABLE BYUG..HANGUL SYLLABLE BYUH +BE0C ; H2 # Lo HANGUL SYLLABLE BEU +BE0D..BE27 ; H3 # Lo [27] HANGUL SYLLABLE BEUG..HANGUL SYLLABLE BEUH +BE28 ; H2 # Lo HANGUL SYLLABLE BYI +BE29..BE43 ; H3 # Lo [27] HANGUL SYLLABLE BYIG..HANGUL SYLLABLE BYIH +BE44 ; H2 # Lo HANGUL SYLLABLE BI +BE45..BE5F ; H3 # Lo [27] HANGUL SYLLABLE BIG..HANGUL SYLLABLE BIH +BE60 ; H2 # Lo HANGUL SYLLABLE BBA +BE61..BE7B ; H3 # Lo [27] HANGUL SYLLABLE BBAG..HANGUL SYLLABLE BBAH +BE7C ; H2 # Lo HANGUL SYLLABLE BBAE +BE7D..BE97 ; H3 # Lo [27] HANGUL SYLLABLE BBAEG..HANGUL SYLLABLE BBAEH +BE98 ; H2 # Lo HANGUL SYLLABLE BBYA +BE99..BEB3 ; H3 # Lo [27] HANGUL SYLLABLE BBYAG..HANGUL SYLLABLE BBYAH +BEB4 ; H2 # Lo HANGUL SYLLABLE BBYAE +BEB5..BECF ; H3 # Lo [27] HANGUL SYLLABLE BBYAEG..HANGUL SYLLABLE BBYAEH +BED0 ; H2 # Lo HANGUL SYLLABLE BBEO +BED1..BEEB ; H3 # Lo [27] HANGUL SYLLABLE BBEOG..HANGUL SYLLABLE BBEOH +BEEC ; H2 # Lo HANGUL SYLLABLE BBE +BEED..BF07 ; H3 # Lo [27] HANGUL SYLLABLE BBEG..HANGUL SYLLABLE BBEH +BF08 ; H2 # Lo HANGUL SYLLABLE BBYEO +BF09..BF23 ; H3 # Lo [27] HANGUL SYLLABLE BBYEOG..HANGUL SYLLABLE BBYEOH +BF24 ; H2 # Lo HANGUL SYLLABLE BBYE +BF25..BF3F ; H3 # Lo [27] HANGUL SYLLABLE BBYEG..HANGUL SYLLABLE BBYEH +BF40 ; H2 # Lo HANGUL SYLLABLE BBO +BF41..BF5B ; H3 # Lo [27] HANGUL SYLLABLE BBOG..HANGUL SYLLABLE BBOH +BF5C ; H2 # Lo HANGUL SYLLABLE BBWA +BF5D..BF77 ; H3 # Lo [27] HANGUL SYLLABLE BBWAG..HANGUL SYLLABLE BBWAH +BF78 ; H2 # Lo HANGUL SYLLABLE BBWAE +BF79..BF93 ; H3 # Lo [27] HANGUL SYLLABLE BBWAEG..HANGUL SYLLABLE BBWAEH +BF94 ; H2 # Lo HANGUL SYLLABLE BBOE +BF95..BFAF ; H3 # Lo [27] HANGUL SYLLABLE BBOEG..HANGUL SYLLABLE BBOEH +BFB0 ; H2 # Lo HANGUL SYLLABLE BBYO +BFB1..BFCB ; H3 # Lo [27] HANGUL SYLLABLE BBYOG..HANGUL SYLLABLE BBYOH +BFCC ; H2 # Lo HANGUL SYLLABLE BBU +BFCD..BFE7 ; H3 # Lo [27] HANGUL SYLLABLE BBUG..HANGUL SYLLABLE BBUH +BFE8 ; H2 # Lo HANGUL SYLLABLE BBWEO +BFE9..C003 ; H3 # Lo [27] HANGUL SYLLABLE BBWEOG..HANGUL SYLLABLE BBWEOH +C004 ; H2 # Lo HANGUL SYLLABLE BBWE +C005..C01F ; H3 # Lo [27] HANGUL SYLLABLE BBWEG..HANGUL SYLLABLE BBWEH +C020 ; H2 # Lo HANGUL SYLLABLE BBWI +C021..C03B ; H3 # Lo [27] HANGUL SYLLABLE BBWIG..HANGUL SYLLABLE BBWIH +C03C ; H2 # Lo HANGUL SYLLABLE BBYU +C03D..C057 ; H3 # Lo [27] HANGUL SYLLABLE BBYUG..HANGUL SYLLABLE BBYUH +C058 ; H2 # Lo HANGUL SYLLABLE BBEU +C059..C073 ; H3 # Lo [27] HANGUL SYLLABLE BBEUG..HANGUL SYLLABLE BBEUH +C074 ; H2 # Lo HANGUL SYLLABLE BBYI +C075..C08F ; H3 # Lo [27] HANGUL SYLLABLE BBYIG..HANGUL SYLLABLE BBYIH +C090 ; H2 # Lo HANGUL SYLLABLE BBI +C091..C0AB ; H3 # Lo [27] HANGUL SYLLABLE BBIG..HANGUL SYLLABLE BBIH +C0AC ; H2 # Lo HANGUL SYLLABLE SA +C0AD..C0C7 ; H3 # Lo [27] HANGUL SYLLABLE SAG..HANGUL SYLLABLE SAH +C0C8 ; H2 # Lo HANGUL SYLLABLE SAE +C0C9..C0E3 ; H3 # Lo [27] HANGUL SYLLABLE SAEG..HANGUL SYLLABLE SAEH +C0E4 ; H2 # Lo HANGUL SYLLABLE SYA +C0E5..C0FF ; H3 # Lo [27] HANGUL SYLLABLE SYAG..HANGUL SYLLABLE SYAH +C100 ; H2 # Lo HANGUL SYLLABLE SYAE +C101..C11B ; H3 # Lo [27] HANGUL SYLLABLE SYAEG..HANGUL SYLLABLE SYAEH +C11C ; H2 # Lo HANGUL SYLLABLE SEO +C11D..C137 ; H3 # Lo [27] HANGUL SYLLABLE SEOG..HANGUL SYLLABLE SEOH +C138 ; H2 # Lo HANGUL SYLLABLE SE +C139..C153 ; H3 # Lo [27] HANGUL SYLLABLE SEG..HANGUL SYLLABLE SEH +C154 ; H2 # Lo HANGUL SYLLABLE SYEO +C155..C16F ; H3 # Lo [27] HANGUL SYLLABLE SYEOG..HANGUL SYLLABLE SYEOH +C170 ; H2 # Lo HANGUL SYLLABLE SYE +C171..C18B ; H3 # Lo [27] HANGUL SYLLABLE SYEG..HANGUL SYLLABLE SYEH +C18C ; H2 # Lo HANGUL SYLLABLE SO +C18D..C1A7 ; H3 # Lo [27] HANGUL SYLLABLE SOG..HANGUL SYLLABLE SOH +C1A8 ; H2 # Lo HANGUL SYLLABLE SWA +C1A9..C1C3 ; H3 # Lo [27] HANGUL SYLLABLE SWAG..HANGUL SYLLABLE SWAH +C1C4 ; H2 # Lo HANGUL SYLLABLE SWAE +C1C5..C1DF ; H3 # Lo [27] HANGUL SYLLABLE SWAEG..HANGUL SYLLABLE SWAEH +C1E0 ; H2 # Lo HANGUL SYLLABLE SOE +C1E1..C1FB ; H3 # Lo [27] HANGUL SYLLABLE SOEG..HANGUL SYLLABLE SOEH +C1FC ; H2 # Lo HANGUL SYLLABLE SYO +C1FD..C217 ; H3 # Lo [27] HANGUL SYLLABLE SYOG..HANGUL SYLLABLE SYOH +C218 ; H2 # Lo HANGUL SYLLABLE SU +C219..C233 ; H3 # Lo [27] HANGUL SYLLABLE SUG..HANGUL SYLLABLE SUH +C234 ; H2 # Lo HANGUL SYLLABLE SWEO +C235..C24F ; H3 # Lo [27] HANGUL SYLLABLE SWEOG..HANGUL SYLLABLE SWEOH +C250 ; H2 # Lo HANGUL SYLLABLE SWE +C251..C26B ; H3 # Lo [27] HANGUL SYLLABLE SWEG..HANGUL SYLLABLE SWEH +C26C ; H2 # Lo HANGUL SYLLABLE SWI +C26D..C287 ; H3 # Lo [27] HANGUL SYLLABLE SWIG..HANGUL SYLLABLE SWIH +C288 ; H2 # Lo HANGUL SYLLABLE SYU +C289..C2A3 ; H3 # Lo [27] HANGUL SYLLABLE SYUG..HANGUL SYLLABLE SYUH +C2A4 ; H2 # Lo HANGUL SYLLABLE SEU +C2A5..C2BF ; H3 # Lo [27] HANGUL SYLLABLE SEUG..HANGUL SYLLABLE SEUH +C2C0 ; H2 # Lo HANGUL SYLLABLE SYI +C2C1..C2DB ; H3 # Lo [27] HANGUL SYLLABLE SYIG..HANGUL SYLLABLE SYIH +C2DC ; H2 # Lo HANGUL SYLLABLE SI +C2DD..C2F7 ; H3 # Lo [27] HANGUL SYLLABLE SIG..HANGUL SYLLABLE SIH +C2F8 ; H2 # Lo HANGUL SYLLABLE SSA +C2F9..C313 ; H3 # Lo [27] HANGUL SYLLABLE SSAG..HANGUL SYLLABLE SSAH +C314 ; H2 # Lo HANGUL SYLLABLE SSAE +C315..C32F ; H3 # Lo [27] HANGUL SYLLABLE SSAEG..HANGUL SYLLABLE SSAEH +C330 ; H2 # Lo HANGUL SYLLABLE SSYA +C331..C34B ; H3 # Lo [27] HANGUL SYLLABLE SSYAG..HANGUL SYLLABLE SSYAH +C34C ; H2 # Lo HANGUL SYLLABLE SSYAE +C34D..C367 ; H3 # Lo [27] HANGUL SYLLABLE SSYAEG..HANGUL SYLLABLE SSYAEH +C368 ; H2 # Lo HANGUL SYLLABLE SSEO +C369..C383 ; H3 # Lo [27] HANGUL SYLLABLE SSEOG..HANGUL SYLLABLE SSEOH +C384 ; H2 # Lo HANGUL SYLLABLE SSE +C385..C39F ; H3 # Lo [27] HANGUL SYLLABLE SSEG..HANGUL SYLLABLE SSEH +C3A0 ; H2 # Lo HANGUL SYLLABLE SSYEO +C3A1..C3BB ; H3 # Lo [27] HANGUL SYLLABLE SSYEOG..HANGUL SYLLABLE SSYEOH +C3BC ; H2 # Lo HANGUL SYLLABLE SSYE +C3BD..C3D7 ; H3 # Lo [27] HANGUL SYLLABLE SSYEG..HANGUL SYLLABLE SSYEH +C3D8 ; H2 # Lo HANGUL SYLLABLE SSO +C3D9..C3F3 ; H3 # Lo [27] HANGUL SYLLABLE SSOG..HANGUL SYLLABLE SSOH +C3F4 ; H2 # Lo HANGUL SYLLABLE SSWA +C3F5..C40F ; H3 # Lo [27] HANGUL SYLLABLE SSWAG..HANGUL SYLLABLE SSWAH +C410 ; H2 # Lo HANGUL SYLLABLE SSWAE +C411..C42B ; H3 # Lo [27] HANGUL SYLLABLE SSWAEG..HANGUL SYLLABLE SSWAEH +C42C ; H2 # Lo HANGUL SYLLABLE SSOE +C42D..C447 ; H3 # Lo [27] HANGUL SYLLABLE SSOEG..HANGUL SYLLABLE SSOEH +C448 ; H2 # Lo HANGUL SYLLABLE SSYO +C449..C463 ; H3 # Lo [27] HANGUL SYLLABLE SSYOG..HANGUL SYLLABLE SSYOH +C464 ; H2 # Lo HANGUL SYLLABLE SSU +C465..C47F ; H3 # Lo [27] HANGUL SYLLABLE SSUG..HANGUL SYLLABLE SSUH +C480 ; H2 # Lo HANGUL SYLLABLE SSWEO +C481..C49B ; H3 # Lo [27] HANGUL SYLLABLE SSWEOG..HANGUL SYLLABLE SSWEOH +C49C ; H2 # Lo HANGUL SYLLABLE SSWE +C49D..C4B7 ; H3 # Lo [27] HANGUL SYLLABLE SSWEG..HANGUL SYLLABLE SSWEH +C4B8 ; H2 # Lo HANGUL SYLLABLE SSWI +C4B9..C4D3 ; H3 # Lo [27] HANGUL SYLLABLE SSWIG..HANGUL SYLLABLE SSWIH +C4D4 ; H2 # Lo HANGUL SYLLABLE SSYU +C4D5..C4EF ; H3 # Lo [27] HANGUL SYLLABLE SSYUG..HANGUL SYLLABLE SSYUH +C4F0 ; H2 # Lo HANGUL SYLLABLE SSEU +C4F1..C50B ; H3 # Lo [27] HANGUL SYLLABLE SSEUG..HANGUL SYLLABLE SSEUH +C50C ; H2 # Lo HANGUL SYLLABLE SSYI +C50D..C527 ; H3 # Lo [27] HANGUL SYLLABLE SSYIG..HANGUL SYLLABLE SSYIH +C528 ; H2 # Lo HANGUL SYLLABLE SSI +C529..C543 ; H3 # Lo [27] HANGUL SYLLABLE SSIG..HANGUL SYLLABLE SSIH +C544 ; H2 # Lo HANGUL SYLLABLE A +C545..C55F ; H3 # Lo [27] HANGUL SYLLABLE AG..HANGUL SYLLABLE AH +C560 ; H2 # Lo HANGUL SYLLABLE AE +C561..C57B ; H3 # Lo [27] HANGUL SYLLABLE AEG..HANGUL SYLLABLE AEH +C57C ; H2 # Lo HANGUL SYLLABLE YA +C57D..C597 ; H3 # Lo [27] HANGUL SYLLABLE YAG..HANGUL SYLLABLE YAH +C598 ; H2 # Lo HANGUL SYLLABLE YAE +C599..C5B3 ; H3 # Lo [27] HANGUL SYLLABLE YAEG..HANGUL SYLLABLE YAEH +C5B4 ; H2 # Lo HANGUL SYLLABLE EO +C5B5..C5CF ; H3 # Lo [27] HANGUL SYLLABLE EOG..HANGUL SYLLABLE EOH +C5D0 ; H2 # Lo HANGUL SYLLABLE E +C5D1..C5EB ; H3 # Lo [27] HANGUL SYLLABLE EG..HANGUL SYLLABLE EH +C5EC ; H2 # Lo HANGUL SYLLABLE YEO +C5ED..C607 ; H3 # Lo [27] HANGUL SYLLABLE YEOG..HANGUL SYLLABLE YEOH +C608 ; H2 # Lo HANGUL SYLLABLE YE +C609..C623 ; H3 # Lo [27] HANGUL SYLLABLE YEG..HANGUL SYLLABLE YEH +C624 ; H2 # Lo HANGUL SYLLABLE O +C625..C63F ; H3 # Lo [27] HANGUL SYLLABLE OG..HANGUL SYLLABLE OH +C640 ; H2 # Lo HANGUL SYLLABLE WA +C641..C65B ; H3 # Lo [27] HANGUL SYLLABLE WAG..HANGUL SYLLABLE WAH +C65C ; H2 # Lo HANGUL SYLLABLE WAE +C65D..C677 ; H3 # Lo [27] HANGUL SYLLABLE WAEG..HANGUL SYLLABLE WAEH +C678 ; H2 # Lo HANGUL SYLLABLE OE +C679..C693 ; H3 # Lo [27] HANGUL SYLLABLE OEG..HANGUL SYLLABLE OEH +C694 ; H2 # Lo HANGUL SYLLABLE YO +C695..C6AF ; H3 # Lo [27] HANGUL SYLLABLE YOG..HANGUL SYLLABLE YOH +C6B0 ; H2 # Lo HANGUL SYLLABLE U +C6B1..C6CB ; H3 # Lo [27] HANGUL SYLLABLE UG..HANGUL SYLLABLE UH +C6CC ; H2 # Lo HANGUL SYLLABLE WEO +C6CD..C6E7 ; H3 # Lo [27] HANGUL SYLLABLE WEOG..HANGUL SYLLABLE WEOH +C6E8 ; H2 # Lo HANGUL SYLLABLE WE +C6E9..C703 ; H3 # Lo [27] HANGUL SYLLABLE WEG..HANGUL SYLLABLE WEH +C704 ; H2 # Lo HANGUL SYLLABLE WI +C705..C71F ; H3 # Lo [27] HANGUL SYLLABLE WIG..HANGUL SYLLABLE WIH +C720 ; H2 # Lo HANGUL SYLLABLE YU +C721..C73B ; H3 # Lo [27] HANGUL SYLLABLE YUG..HANGUL SYLLABLE YUH +C73C ; H2 # Lo HANGUL SYLLABLE EU +C73D..C757 ; H3 # Lo [27] HANGUL SYLLABLE EUG..HANGUL SYLLABLE EUH +C758 ; H2 # Lo HANGUL SYLLABLE YI +C759..C773 ; H3 # Lo [27] HANGUL SYLLABLE YIG..HANGUL SYLLABLE YIH +C774 ; H2 # Lo HANGUL SYLLABLE I +C775..C78F ; H3 # Lo [27] HANGUL SYLLABLE IG..HANGUL SYLLABLE IH +C790 ; H2 # Lo HANGUL SYLLABLE JA +C791..C7AB ; H3 # Lo [27] HANGUL SYLLABLE JAG..HANGUL SYLLABLE JAH +C7AC ; H2 # Lo HANGUL SYLLABLE JAE +C7AD..C7C7 ; H3 # Lo [27] HANGUL SYLLABLE JAEG..HANGUL SYLLABLE JAEH +C7C8 ; H2 # Lo HANGUL SYLLABLE JYA +C7C9..C7E3 ; H3 # Lo [27] HANGUL SYLLABLE JYAG..HANGUL SYLLABLE JYAH +C7E4 ; H2 # Lo HANGUL SYLLABLE JYAE +C7E5..C7FF ; H3 # Lo [27] HANGUL SYLLABLE JYAEG..HANGUL SYLLABLE JYAEH +C800 ; H2 # Lo HANGUL SYLLABLE JEO +C801..C81B ; H3 # Lo [27] HANGUL SYLLABLE JEOG..HANGUL SYLLABLE JEOH +C81C ; H2 # Lo HANGUL SYLLABLE JE +C81D..C837 ; H3 # Lo [27] HANGUL SYLLABLE JEG..HANGUL SYLLABLE JEH +C838 ; H2 # Lo HANGUL SYLLABLE JYEO +C839..C853 ; H3 # Lo [27] HANGUL SYLLABLE JYEOG..HANGUL SYLLABLE JYEOH +C854 ; H2 # Lo HANGUL SYLLABLE JYE +C855..C86F ; H3 # Lo [27] HANGUL SYLLABLE JYEG..HANGUL SYLLABLE JYEH +C870 ; H2 # Lo HANGUL SYLLABLE JO +C871..C88B ; H3 # Lo [27] HANGUL SYLLABLE JOG..HANGUL SYLLABLE JOH +C88C ; H2 # Lo HANGUL SYLLABLE JWA +C88D..C8A7 ; H3 # Lo [27] HANGUL SYLLABLE JWAG..HANGUL SYLLABLE JWAH +C8A8 ; H2 # Lo HANGUL SYLLABLE JWAE +C8A9..C8C3 ; H3 # Lo [27] HANGUL SYLLABLE JWAEG..HANGUL SYLLABLE JWAEH +C8C4 ; H2 # Lo HANGUL SYLLABLE JOE +C8C5..C8DF ; H3 # Lo [27] HANGUL SYLLABLE JOEG..HANGUL SYLLABLE JOEH +C8E0 ; H2 # Lo HANGUL SYLLABLE JYO +C8E1..C8FB ; H3 # Lo [27] HANGUL SYLLABLE JYOG..HANGUL SYLLABLE JYOH +C8FC ; H2 # Lo HANGUL SYLLABLE JU +C8FD..C917 ; H3 # Lo [27] HANGUL SYLLABLE JUG..HANGUL SYLLABLE JUH +C918 ; H2 # Lo HANGUL SYLLABLE JWEO +C919..C933 ; H3 # Lo [27] HANGUL SYLLABLE JWEOG..HANGUL SYLLABLE JWEOH +C934 ; H2 # Lo HANGUL SYLLABLE JWE +C935..C94F ; H3 # Lo [27] HANGUL SYLLABLE JWEG..HANGUL SYLLABLE JWEH +C950 ; H2 # Lo HANGUL SYLLABLE JWI +C951..C96B ; H3 # Lo [27] HANGUL SYLLABLE JWIG..HANGUL SYLLABLE JWIH +C96C ; H2 # Lo HANGUL SYLLABLE JYU +C96D..C987 ; H3 # Lo [27] HANGUL SYLLABLE JYUG..HANGUL SYLLABLE JYUH +C988 ; H2 # Lo HANGUL SYLLABLE JEU +C989..C9A3 ; H3 # Lo [27] HANGUL SYLLABLE JEUG..HANGUL SYLLABLE JEUH +C9A4 ; H2 # Lo HANGUL SYLLABLE JYI +C9A5..C9BF ; H3 # Lo [27] HANGUL SYLLABLE JYIG..HANGUL SYLLABLE JYIH +C9C0 ; H2 # Lo HANGUL SYLLABLE JI +C9C1..C9DB ; H3 # Lo [27] HANGUL SYLLABLE JIG..HANGUL SYLLABLE JIH +C9DC ; H2 # Lo HANGUL SYLLABLE JJA +C9DD..C9F7 ; H3 # Lo [27] HANGUL SYLLABLE JJAG..HANGUL SYLLABLE JJAH +C9F8 ; H2 # Lo HANGUL SYLLABLE JJAE +C9F9..CA13 ; H3 # Lo [27] HANGUL SYLLABLE JJAEG..HANGUL SYLLABLE JJAEH +CA14 ; H2 # Lo HANGUL SYLLABLE JJYA +CA15..CA2F ; H3 # Lo [27] HANGUL SYLLABLE JJYAG..HANGUL SYLLABLE JJYAH +CA30 ; H2 # Lo HANGUL SYLLABLE JJYAE +CA31..CA4B ; H3 # Lo [27] HANGUL SYLLABLE JJYAEG..HANGUL SYLLABLE JJYAEH +CA4C ; H2 # Lo HANGUL SYLLABLE JJEO +CA4D..CA67 ; H3 # Lo [27] HANGUL SYLLABLE JJEOG..HANGUL SYLLABLE JJEOH +CA68 ; H2 # Lo HANGUL SYLLABLE JJE +CA69..CA83 ; H3 # Lo [27] HANGUL SYLLABLE JJEG..HANGUL SYLLABLE JJEH +CA84 ; H2 # Lo HANGUL SYLLABLE JJYEO +CA85..CA9F ; H3 # Lo [27] HANGUL SYLLABLE JJYEOG..HANGUL SYLLABLE JJYEOH +CAA0 ; H2 # Lo HANGUL SYLLABLE JJYE +CAA1..CABB ; H3 # Lo [27] HANGUL SYLLABLE JJYEG..HANGUL SYLLABLE JJYEH +CABC ; H2 # Lo HANGUL SYLLABLE JJO +CABD..CAD7 ; H3 # Lo [27] HANGUL SYLLABLE JJOG..HANGUL SYLLABLE JJOH +CAD8 ; H2 # Lo HANGUL SYLLABLE JJWA +CAD9..CAF3 ; H3 # Lo [27] HANGUL SYLLABLE JJWAG..HANGUL SYLLABLE JJWAH +CAF4 ; H2 # Lo HANGUL SYLLABLE JJWAE +CAF5..CB0F ; H3 # Lo [27] HANGUL SYLLABLE JJWAEG..HANGUL SYLLABLE JJWAEH +CB10 ; H2 # Lo HANGUL SYLLABLE JJOE +CB11..CB2B ; H3 # Lo [27] HANGUL SYLLABLE JJOEG..HANGUL SYLLABLE JJOEH +CB2C ; H2 # Lo HANGUL SYLLABLE JJYO +CB2D..CB47 ; H3 # Lo [27] HANGUL SYLLABLE JJYOG..HANGUL SYLLABLE JJYOH +CB48 ; H2 # Lo HANGUL SYLLABLE JJU +CB49..CB63 ; H3 # Lo [27] HANGUL SYLLABLE JJUG..HANGUL SYLLABLE JJUH +CB64 ; H2 # Lo HANGUL SYLLABLE JJWEO +CB65..CB7F ; H3 # Lo [27] HANGUL SYLLABLE JJWEOG..HANGUL SYLLABLE JJWEOH +CB80 ; H2 # Lo HANGUL SYLLABLE JJWE +CB81..CB9B ; H3 # Lo [27] HANGUL SYLLABLE JJWEG..HANGUL SYLLABLE JJWEH +CB9C ; H2 # Lo HANGUL SYLLABLE JJWI +CB9D..CBB7 ; H3 # Lo [27] HANGUL SYLLABLE JJWIG..HANGUL SYLLABLE JJWIH +CBB8 ; H2 # Lo HANGUL SYLLABLE JJYU +CBB9..CBD3 ; H3 # Lo [27] HANGUL SYLLABLE JJYUG..HANGUL SYLLABLE JJYUH +CBD4 ; H2 # Lo HANGUL SYLLABLE JJEU +CBD5..CBEF ; H3 # Lo [27] HANGUL SYLLABLE JJEUG..HANGUL SYLLABLE JJEUH +CBF0 ; H2 # Lo HANGUL SYLLABLE JJYI +CBF1..CC0B ; H3 # Lo [27] HANGUL SYLLABLE JJYIG..HANGUL SYLLABLE JJYIH +CC0C ; H2 # Lo HANGUL SYLLABLE JJI +CC0D..CC27 ; H3 # Lo [27] HANGUL SYLLABLE JJIG..HANGUL SYLLABLE JJIH +CC28 ; H2 # Lo HANGUL SYLLABLE CA +CC29..CC43 ; H3 # Lo [27] HANGUL SYLLABLE CAG..HANGUL SYLLABLE CAH +CC44 ; H2 # Lo HANGUL SYLLABLE CAE +CC45..CC5F ; H3 # Lo [27] HANGUL SYLLABLE CAEG..HANGUL SYLLABLE CAEH +CC60 ; H2 # Lo HANGUL SYLLABLE CYA +CC61..CC7B ; H3 # Lo [27] HANGUL SYLLABLE CYAG..HANGUL SYLLABLE CYAH +CC7C ; H2 # Lo HANGUL SYLLABLE CYAE +CC7D..CC97 ; H3 # Lo [27] HANGUL SYLLABLE CYAEG..HANGUL SYLLABLE CYAEH +CC98 ; H2 # Lo HANGUL SYLLABLE CEO +CC99..CCB3 ; H3 # Lo [27] HANGUL SYLLABLE CEOG..HANGUL SYLLABLE CEOH +CCB4 ; H2 # Lo HANGUL SYLLABLE CE +CCB5..CCCF ; H3 # Lo [27] HANGUL SYLLABLE CEG..HANGUL SYLLABLE CEH +CCD0 ; H2 # Lo HANGUL SYLLABLE CYEO +CCD1..CCEB ; H3 # Lo [27] HANGUL SYLLABLE CYEOG..HANGUL SYLLABLE CYEOH +CCEC ; H2 # Lo HANGUL SYLLABLE CYE +CCED..CD07 ; H3 # Lo [27] HANGUL SYLLABLE CYEG..HANGUL SYLLABLE CYEH +CD08 ; H2 # Lo HANGUL SYLLABLE CO +CD09..CD23 ; H3 # Lo [27] HANGUL SYLLABLE COG..HANGUL SYLLABLE COH +CD24 ; H2 # Lo HANGUL SYLLABLE CWA +CD25..CD3F ; H3 # Lo [27] HANGUL SYLLABLE CWAG..HANGUL SYLLABLE CWAH +CD40 ; H2 # Lo HANGUL SYLLABLE CWAE +CD41..CD5B ; H3 # Lo [27] HANGUL SYLLABLE CWAEG..HANGUL SYLLABLE CWAEH +CD5C ; H2 # Lo HANGUL SYLLABLE COE +CD5D..CD77 ; H3 # Lo [27] HANGUL SYLLABLE COEG..HANGUL SYLLABLE COEH +CD78 ; H2 # Lo HANGUL SYLLABLE CYO +CD79..CD93 ; H3 # Lo [27] HANGUL SYLLABLE CYOG..HANGUL SYLLABLE CYOH +CD94 ; H2 # Lo HANGUL SYLLABLE CU +CD95..CDAF ; H3 # Lo [27] HANGUL SYLLABLE CUG..HANGUL SYLLABLE CUH +CDB0 ; H2 # Lo HANGUL SYLLABLE CWEO +CDB1..CDCB ; H3 # Lo [27] HANGUL SYLLABLE CWEOG..HANGUL SYLLABLE CWEOH +CDCC ; H2 # Lo HANGUL SYLLABLE CWE +CDCD..CDE7 ; H3 # Lo [27] HANGUL SYLLABLE CWEG..HANGUL SYLLABLE CWEH +CDE8 ; H2 # Lo HANGUL SYLLABLE CWI +CDE9..CE03 ; H3 # Lo [27] HANGUL SYLLABLE CWIG..HANGUL SYLLABLE CWIH +CE04 ; H2 # Lo HANGUL SYLLABLE CYU +CE05..CE1F ; H3 # Lo [27] HANGUL SYLLABLE CYUG..HANGUL SYLLABLE CYUH +CE20 ; H2 # Lo HANGUL SYLLABLE CEU +CE21..CE3B ; H3 # Lo [27] HANGUL SYLLABLE CEUG..HANGUL SYLLABLE CEUH +CE3C ; H2 # Lo HANGUL SYLLABLE CYI +CE3D..CE57 ; H3 # Lo [27] HANGUL SYLLABLE CYIG..HANGUL SYLLABLE CYIH +CE58 ; H2 # Lo HANGUL SYLLABLE CI +CE59..CE73 ; H3 # Lo [27] HANGUL SYLLABLE CIG..HANGUL SYLLABLE CIH +CE74 ; H2 # Lo HANGUL SYLLABLE KA +CE75..CE8F ; H3 # Lo [27] HANGUL SYLLABLE KAG..HANGUL SYLLABLE KAH +CE90 ; H2 # Lo HANGUL SYLLABLE KAE +CE91..CEAB ; H3 # Lo [27] HANGUL SYLLABLE KAEG..HANGUL SYLLABLE KAEH +CEAC ; H2 # Lo HANGUL SYLLABLE KYA +CEAD..CEC7 ; H3 # Lo [27] HANGUL SYLLABLE KYAG..HANGUL SYLLABLE KYAH +CEC8 ; H2 # Lo HANGUL SYLLABLE KYAE +CEC9..CEE3 ; H3 # Lo [27] HANGUL SYLLABLE KYAEG..HANGUL SYLLABLE KYAEH +CEE4 ; H2 # Lo HANGUL SYLLABLE KEO +CEE5..CEFF ; H3 # Lo [27] HANGUL SYLLABLE KEOG..HANGUL SYLLABLE KEOH +CF00 ; H2 # Lo HANGUL SYLLABLE KE +CF01..CF1B ; H3 # Lo [27] HANGUL SYLLABLE KEG..HANGUL SYLLABLE KEH +CF1C ; H2 # Lo HANGUL SYLLABLE KYEO +CF1D..CF37 ; H3 # Lo [27] HANGUL SYLLABLE KYEOG..HANGUL SYLLABLE KYEOH +CF38 ; H2 # Lo HANGUL SYLLABLE KYE +CF39..CF53 ; H3 # Lo [27] HANGUL SYLLABLE KYEG..HANGUL SYLLABLE KYEH +CF54 ; H2 # Lo HANGUL SYLLABLE KO +CF55..CF6F ; H3 # Lo [27] HANGUL SYLLABLE KOG..HANGUL SYLLABLE KOH +CF70 ; H2 # Lo HANGUL SYLLABLE KWA +CF71..CF8B ; H3 # Lo [27] HANGUL SYLLABLE KWAG..HANGUL SYLLABLE KWAH +CF8C ; H2 # Lo HANGUL SYLLABLE KWAE +CF8D..CFA7 ; H3 # Lo [27] HANGUL SYLLABLE KWAEG..HANGUL SYLLABLE KWAEH +CFA8 ; H2 # Lo HANGUL SYLLABLE KOE +CFA9..CFC3 ; H3 # Lo [27] HANGUL SYLLABLE KOEG..HANGUL SYLLABLE KOEH +CFC4 ; H2 # Lo HANGUL SYLLABLE KYO +CFC5..CFDF ; H3 # Lo [27] HANGUL SYLLABLE KYOG..HANGUL SYLLABLE KYOH +CFE0 ; H2 # Lo HANGUL SYLLABLE KU +CFE1..CFFB ; H3 # Lo [27] HANGUL SYLLABLE KUG..HANGUL SYLLABLE KUH +CFFC ; H2 # Lo HANGUL SYLLABLE KWEO +CFFD..D017 ; H3 # Lo [27] HANGUL SYLLABLE KWEOG..HANGUL SYLLABLE KWEOH +D018 ; H2 # Lo HANGUL SYLLABLE KWE +D019..D033 ; H3 # Lo [27] HANGUL SYLLABLE KWEG..HANGUL SYLLABLE KWEH +D034 ; H2 # Lo HANGUL SYLLABLE KWI +D035..D04F ; H3 # Lo [27] HANGUL SYLLABLE KWIG..HANGUL SYLLABLE KWIH +D050 ; H2 # Lo HANGUL SYLLABLE KYU +D051..D06B ; H3 # Lo [27] HANGUL SYLLABLE KYUG..HANGUL SYLLABLE KYUH +D06C ; H2 # Lo HANGUL SYLLABLE KEU +D06D..D087 ; H3 # Lo [27] HANGUL SYLLABLE KEUG..HANGUL SYLLABLE KEUH +D088 ; H2 # Lo HANGUL SYLLABLE KYI +D089..D0A3 ; H3 # Lo [27] HANGUL SYLLABLE KYIG..HANGUL SYLLABLE KYIH +D0A4 ; H2 # Lo HANGUL SYLLABLE KI +D0A5..D0BF ; H3 # Lo [27] HANGUL SYLLABLE KIG..HANGUL SYLLABLE KIH +D0C0 ; H2 # Lo HANGUL SYLLABLE TA +D0C1..D0DB ; H3 # Lo [27] HANGUL SYLLABLE TAG..HANGUL SYLLABLE TAH +D0DC ; H2 # Lo HANGUL SYLLABLE TAE +D0DD..D0F7 ; H3 # Lo [27] HANGUL SYLLABLE TAEG..HANGUL SYLLABLE TAEH +D0F8 ; H2 # Lo HANGUL SYLLABLE TYA +D0F9..D113 ; H3 # Lo [27] HANGUL SYLLABLE TYAG..HANGUL SYLLABLE TYAH +D114 ; H2 # Lo HANGUL SYLLABLE TYAE +D115..D12F ; H3 # Lo [27] HANGUL SYLLABLE TYAEG..HANGUL SYLLABLE TYAEH +D130 ; H2 # Lo HANGUL SYLLABLE TEO +D131..D14B ; H3 # Lo [27] HANGUL SYLLABLE TEOG..HANGUL SYLLABLE TEOH +D14C ; H2 # Lo HANGUL SYLLABLE TE +D14D..D167 ; H3 # Lo [27] HANGUL SYLLABLE TEG..HANGUL SYLLABLE TEH +D168 ; H2 # Lo HANGUL SYLLABLE TYEO +D169..D183 ; H3 # Lo [27] HANGUL SYLLABLE TYEOG..HANGUL SYLLABLE TYEOH +D184 ; H2 # Lo HANGUL SYLLABLE TYE +D185..D19F ; H3 # Lo [27] HANGUL SYLLABLE TYEG..HANGUL SYLLABLE TYEH +D1A0 ; H2 # Lo HANGUL SYLLABLE TO +D1A1..D1BB ; H3 # Lo [27] HANGUL SYLLABLE TOG..HANGUL SYLLABLE TOH +D1BC ; H2 # Lo HANGUL SYLLABLE TWA +D1BD..D1D7 ; H3 # Lo [27] HANGUL SYLLABLE TWAG..HANGUL SYLLABLE TWAH +D1D8 ; H2 # Lo HANGUL SYLLABLE TWAE +D1D9..D1F3 ; H3 # Lo [27] HANGUL SYLLABLE TWAEG..HANGUL SYLLABLE TWAEH +D1F4 ; H2 # Lo HANGUL SYLLABLE TOE +D1F5..D20F ; H3 # Lo [27] HANGUL SYLLABLE TOEG..HANGUL SYLLABLE TOEH +D210 ; H2 # Lo HANGUL SYLLABLE TYO +D211..D22B ; H3 # Lo [27] HANGUL SYLLABLE TYOG..HANGUL SYLLABLE TYOH +D22C ; H2 # Lo HANGUL SYLLABLE TU +D22D..D247 ; H3 # Lo [27] HANGUL SYLLABLE TUG..HANGUL SYLLABLE TUH +D248 ; H2 # Lo HANGUL SYLLABLE TWEO +D249..D263 ; H3 # Lo [27] HANGUL SYLLABLE TWEOG..HANGUL SYLLABLE TWEOH +D264 ; H2 # Lo HANGUL SYLLABLE TWE +D265..D27F ; H3 # Lo [27] HANGUL SYLLABLE TWEG..HANGUL SYLLABLE TWEH +D280 ; H2 # Lo HANGUL SYLLABLE TWI +D281..D29B ; H3 # Lo [27] HANGUL SYLLABLE TWIG..HANGUL SYLLABLE TWIH +D29C ; H2 # Lo HANGUL SYLLABLE TYU +D29D..D2B7 ; H3 # Lo [27] HANGUL SYLLABLE TYUG..HANGUL SYLLABLE TYUH +D2B8 ; H2 # Lo HANGUL SYLLABLE TEU +D2B9..D2D3 ; H3 # Lo [27] HANGUL SYLLABLE TEUG..HANGUL SYLLABLE TEUH +D2D4 ; H2 # Lo HANGUL SYLLABLE TYI +D2D5..D2EF ; H3 # Lo [27] HANGUL SYLLABLE TYIG..HANGUL SYLLABLE TYIH +D2F0 ; H2 # Lo HANGUL SYLLABLE TI +D2F1..D30B ; H3 # Lo [27] HANGUL SYLLABLE TIG..HANGUL SYLLABLE TIH +D30C ; H2 # Lo HANGUL SYLLABLE PA +D30D..D327 ; H3 # Lo [27] HANGUL SYLLABLE PAG..HANGUL SYLLABLE PAH +D328 ; H2 # Lo HANGUL SYLLABLE PAE +D329..D343 ; H3 # Lo [27] HANGUL SYLLABLE PAEG..HANGUL SYLLABLE PAEH +D344 ; H2 # Lo HANGUL SYLLABLE PYA +D345..D35F ; H3 # Lo [27] HANGUL SYLLABLE PYAG..HANGUL SYLLABLE PYAH +D360 ; H2 # Lo HANGUL SYLLABLE PYAE +D361..D37B ; H3 # Lo [27] HANGUL SYLLABLE PYAEG..HANGUL SYLLABLE PYAEH +D37C ; H2 # Lo HANGUL SYLLABLE PEO +D37D..D397 ; H3 # Lo [27] HANGUL SYLLABLE PEOG..HANGUL SYLLABLE PEOH +D398 ; H2 # Lo HANGUL SYLLABLE PE +D399..D3B3 ; H3 # Lo [27] HANGUL SYLLABLE PEG..HANGUL SYLLABLE PEH +D3B4 ; H2 # Lo HANGUL SYLLABLE PYEO +D3B5..D3CF ; H3 # Lo [27] HANGUL SYLLABLE PYEOG..HANGUL SYLLABLE PYEOH +D3D0 ; H2 # Lo HANGUL SYLLABLE PYE +D3D1..D3EB ; H3 # Lo [27] HANGUL SYLLABLE PYEG..HANGUL SYLLABLE PYEH +D3EC ; H2 # Lo HANGUL SYLLABLE PO +D3ED..D407 ; H3 # Lo [27] HANGUL SYLLABLE POG..HANGUL SYLLABLE POH +D408 ; H2 # Lo HANGUL SYLLABLE PWA +D409..D423 ; H3 # Lo [27] HANGUL SYLLABLE PWAG..HANGUL SYLLABLE PWAH +D424 ; H2 # Lo HANGUL SYLLABLE PWAE +D425..D43F ; H3 # Lo [27] HANGUL SYLLABLE PWAEG..HANGUL SYLLABLE PWAEH +D440 ; H2 # Lo HANGUL SYLLABLE POE +D441..D45B ; H3 # Lo [27] HANGUL SYLLABLE POEG..HANGUL SYLLABLE POEH +D45C ; H2 # Lo HANGUL SYLLABLE PYO +D45D..D477 ; H3 # Lo [27] HANGUL SYLLABLE PYOG..HANGUL SYLLABLE PYOH +D478 ; H2 # Lo HANGUL SYLLABLE PU +D479..D493 ; H3 # Lo [27] HANGUL SYLLABLE PUG..HANGUL SYLLABLE PUH +D494 ; H2 # Lo HANGUL SYLLABLE PWEO +D495..D4AF ; H3 # Lo [27] HANGUL SYLLABLE PWEOG..HANGUL SYLLABLE PWEOH +D4B0 ; H2 # Lo HANGUL SYLLABLE PWE +D4B1..D4CB ; H3 # Lo [27] HANGUL SYLLABLE PWEG..HANGUL SYLLABLE PWEH +D4CC ; H2 # Lo HANGUL SYLLABLE PWI +D4CD..D4E7 ; H3 # Lo [27] HANGUL SYLLABLE PWIG..HANGUL SYLLABLE PWIH +D4E8 ; H2 # Lo HANGUL SYLLABLE PYU +D4E9..D503 ; H3 # Lo [27] HANGUL SYLLABLE PYUG..HANGUL SYLLABLE PYUH +D504 ; H2 # Lo HANGUL SYLLABLE PEU +D505..D51F ; H3 # Lo [27] HANGUL SYLLABLE PEUG..HANGUL SYLLABLE PEUH +D520 ; H2 # Lo HANGUL SYLLABLE PYI +D521..D53B ; H3 # Lo [27] HANGUL SYLLABLE PYIG..HANGUL SYLLABLE PYIH +D53C ; H2 # Lo HANGUL SYLLABLE PI +D53D..D557 ; H3 # Lo [27] HANGUL SYLLABLE PIG..HANGUL SYLLABLE PIH +D558 ; H2 # Lo HANGUL SYLLABLE HA +D559..D573 ; H3 # Lo [27] HANGUL SYLLABLE HAG..HANGUL SYLLABLE HAH +D574 ; H2 # Lo HANGUL SYLLABLE HAE +D575..D58F ; H3 # Lo [27] HANGUL SYLLABLE HAEG..HANGUL SYLLABLE HAEH +D590 ; H2 # Lo HANGUL SYLLABLE HYA +D591..D5AB ; H3 # Lo [27] HANGUL SYLLABLE HYAG..HANGUL SYLLABLE HYAH +D5AC ; H2 # Lo HANGUL SYLLABLE HYAE +D5AD..D5C7 ; H3 # Lo [27] HANGUL SYLLABLE HYAEG..HANGUL SYLLABLE HYAEH +D5C8 ; H2 # Lo HANGUL SYLLABLE HEO +D5C9..D5E3 ; H3 # Lo [27] HANGUL SYLLABLE HEOG..HANGUL SYLLABLE HEOH +D5E4 ; H2 # Lo HANGUL SYLLABLE HE +D5E5..D5FF ; H3 # Lo [27] HANGUL SYLLABLE HEG..HANGUL SYLLABLE HEH +D600 ; H2 # Lo HANGUL SYLLABLE HYEO +D601..D61B ; H3 # Lo [27] HANGUL SYLLABLE HYEOG..HANGUL SYLLABLE HYEOH +D61C ; H2 # Lo HANGUL SYLLABLE HYE +D61D..D637 ; H3 # Lo [27] HANGUL SYLLABLE HYEG..HANGUL SYLLABLE HYEH +D638 ; H2 # Lo HANGUL SYLLABLE HO +D639..D653 ; H3 # Lo [27] HANGUL SYLLABLE HOG..HANGUL SYLLABLE HOH +D654 ; H2 # Lo HANGUL SYLLABLE HWA +D655..D66F ; H3 # Lo [27] HANGUL SYLLABLE HWAG..HANGUL SYLLABLE HWAH +D670 ; H2 # Lo HANGUL SYLLABLE HWAE +D671..D68B ; H3 # Lo [27] HANGUL SYLLABLE HWAEG..HANGUL SYLLABLE HWAEH +D68C ; H2 # Lo HANGUL SYLLABLE HOE +D68D..D6A7 ; H3 # Lo [27] HANGUL SYLLABLE HOEG..HANGUL SYLLABLE HOEH +D6A8 ; H2 # Lo HANGUL SYLLABLE HYO +D6A9..D6C3 ; H3 # Lo [27] HANGUL SYLLABLE HYOG..HANGUL SYLLABLE HYOH +D6C4 ; H2 # Lo HANGUL SYLLABLE HU +D6C5..D6DF ; H3 # Lo [27] HANGUL SYLLABLE HUG..HANGUL SYLLABLE HUH +D6E0 ; H2 # Lo HANGUL SYLLABLE HWEO +D6E1..D6FB ; H3 # Lo [27] HANGUL SYLLABLE HWEOG..HANGUL SYLLABLE HWEOH +D6FC ; H2 # Lo HANGUL SYLLABLE HWE +D6FD..D717 ; H3 # Lo [27] HANGUL SYLLABLE HWEG..HANGUL SYLLABLE HWEH +D718 ; H2 # Lo HANGUL SYLLABLE HWI +D719..D733 ; H3 # Lo [27] HANGUL SYLLABLE HWIG..HANGUL SYLLABLE HWIH +D734 ; H2 # Lo HANGUL SYLLABLE HYU +D735..D74F ; H3 # Lo [27] HANGUL SYLLABLE HYUG..HANGUL SYLLABLE HYUH +D750 ; H2 # Lo HANGUL SYLLABLE HEU +D751..D76B ; H3 # Lo [27] HANGUL SYLLABLE HEUG..HANGUL SYLLABLE HEUH +D76C ; H2 # Lo HANGUL SYLLABLE HYI +D76D..D787 ; H3 # Lo [27] HANGUL SYLLABLE HYIG..HANGUL SYLLABLE HYIH +D788 ; H2 # Lo HANGUL SYLLABLE HI +D789..D7A3 ; H3 # Lo [27] HANGUL SYLLABLE HIG..HANGUL SYLLABLE HIH +D7B0..D7C6 ; JV # Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E +D7CB..D7FB ; JT # Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH +D800..DB7F ; SG # Cs [896] .. +DB80..DBFF ; SG # Cs [128] .. +DC00..DFFF ; SG # Cs [1024] .. +E000..F8FF ; XX # Co [6400] .. +F900..FA6D ; ID # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D +FA6E..FA6F ; ID # Cn [2] .. +FA70..FAD9 ; ID # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 +FADA..FAFF ; ID # Cn [38] .. +FB00..FB06 ; AL # Ll [7] LATIN SMALL LIGATURE FF..LATIN SMALL LIGATURE ST +FB13..FB17 ; AL # Ll [5] ARMENIAN SMALL LIGATURE MEN NOW..ARMENIAN SMALL LIGATURE MEN XEH +FB1D ; HL # Lo HEBREW LETTER YOD WITH HIRIQ +FB1E ; CM # Mn HEBREW POINT JUDEO-SPANISH VARIKA +FB1F..FB28 ; HL # Lo [10] HEBREW LIGATURE YIDDISH YOD YOD PATAH..HEBREW LETTER WIDE TAV +FB29 ; AL # Sm HEBREW LETTER ALTERNATIVE PLUS SIGN +FB2A..FB36 ; HL # Lo [13] HEBREW LETTER SHIN WITH SHIN DOT..HEBREW LETTER ZAYIN WITH DAGESH +FB38..FB3C ; HL # Lo [5] HEBREW LETTER TET WITH DAGESH..HEBREW LETTER LAMED WITH DAGESH +FB3E ; HL # Lo HEBREW LETTER MEM WITH DAGESH +FB40..FB41 ; HL # Lo [2] HEBREW LETTER NUN WITH DAGESH..HEBREW LETTER SAMEKH WITH DAGESH +FB43..FB44 ; HL # Lo [2] HEBREW LETTER FINAL PE WITH DAGESH..HEBREW LETTER PE WITH DAGESH +FB46..FB4F ; HL # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATURE ALEF LAMED +FB50..FBB1 ; AL # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM +FBB2..FBC2 ; AL # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBD3..FD3D ; AL # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM +FD3E ; CL # Pe ORNATE LEFT PARENTHESIS +FD3F ; OP # Ps ORNATE RIGHT PARENTHESIS +FD40..FD4F ; AL # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FD50..FD8F ; AL # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD92..FDC7 ; AL # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM +FDCF ; AL # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDF0..FDFB ; AL # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU +FDFC ; PO # Sc RIAL SIGN +FDFD..FDFF ; AL # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL +FE00..FE0F ; CM # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 +FE10 ; IS # Po PRESENTATION FORM FOR VERTICAL COMMA +FE11..FE12 ; CL # Po [2] PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA..PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP +FE13..FE14 ; IS # Po [2] PRESENTATION FORM FOR VERTICAL COLON..PRESENTATION FORM FOR VERTICAL SEMICOLON +FE15..FE16 ; EX # Po [2] PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK..PRESENTATION FORM FOR VERTICAL QUESTION MARK +FE17 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET +FE18 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET +FE19 ; IN # Po PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS +FE20..FE2F ; CM # Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF +FE30 ; ID # Po PRESENTATION FORM FOR VERTICAL TWO DOT LEADER +FE31..FE32 ; ID # Pd [2] PRESENTATION FORM FOR VERTICAL EM DASH..PRESENTATION FORM FOR VERTICAL EN DASH +FE33..FE34 ; ID # Pc [2] PRESENTATION FORM FOR VERTICAL LOW LINE..PRESENTATION FORM FOR VERTICAL WAVY LOW LINE +FE35 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +FE36 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +FE37 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +FE38 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +FE39 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET +FE3A ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET +FE3B ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET +FE3C ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET +FE3D ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET +FE3E ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET +FE3F ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET +FE40 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET +FE41 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET +FE42 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET +FE43 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET +FE44 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET +FE45..FE46 ; ID # Po [2] SESAME DOT..WHITE SESAME DOT +FE47 ; OP # Ps PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET +FE48 ; CL # Pe PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET +FE49..FE4C ; ID # Po [4] DASHED OVERLINE..DOUBLE WAVY OVERLINE +FE4D..FE4F ; ID # Pc [3] DASHED LOW LINE..WAVY LOW LINE +FE50 ; CL # Po SMALL COMMA +FE51 ; ID # Po SMALL IDEOGRAPHIC COMMA +FE52 ; CL # Po SMALL FULL STOP +FE54..FE55 ; NS # Po [2] SMALL SEMICOLON..SMALL COLON +FE56..FE57 ; EX # Po [2] SMALL QUESTION MARK..SMALL EXCLAMATION MARK +FE58 ; ID # Pd SMALL EM DASH +FE59 ; OP # Ps SMALL LEFT PARENTHESIS +FE5A ; CL # Pe SMALL RIGHT PARENTHESIS +FE5B ; OP # Ps SMALL LEFT CURLY BRACKET +FE5C ; CL # Pe SMALL RIGHT CURLY BRACKET +FE5D ; OP # Ps SMALL LEFT TORTOISE SHELL BRACKET +FE5E ; CL # Pe SMALL RIGHT TORTOISE SHELL BRACKET +FE5F..FE61 ; ID # Po [3] SMALL NUMBER SIGN..SMALL ASTERISK +FE62 ; ID # Sm SMALL PLUS SIGN +FE63 ; ID # Pd SMALL HYPHEN-MINUS +FE64..FE66 ; ID # Sm [3] SMALL LESS-THAN SIGN..SMALL EQUALS SIGN +FE68 ; ID # Po SMALL REVERSE SOLIDUS +FE69 ; PR # Sc SMALL DOLLAR SIGN +FE6A ; PO # Po SMALL PERCENT SIGN +FE6B ; ID # Po SMALL COMMERCIAL AT +FE70..FE74 ; AL # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN ISOLATED FORM +FE76..FEFC ; AL # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM +FEFF ; WJ # Cf ZERO WIDTH NO-BREAK SPACE +FF01 ; EX # Po FULLWIDTH EXCLAMATION MARK +FF02..FF03 ; ID # Po [2] FULLWIDTH QUOTATION MARK..FULLWIDTH NUMBER SIGN +FF04 ; PR # Sc FULLWIDTH DOLLAR SIGN +FF05 ; PO # Po FULLWIDTH PERCENT SIGN +FF06..FF07 ; ID # Po [2] FULLWIDTH AMPERSAND..FULLWIDTH APOSTROPHE +FF08 ; OP # Ps FULLWIDTH LEFT PARENTHESIS +FF09 ; CL # Pe FULLWIDTH RIGHT PARENTHESIS +FF0A ; ID # Po FULLWIDTH ASTERISK +FF0B ; ID # Sm FULLWIDTH PLUS SIGN +FF0C ; CL # Po FULLWIDTH COMMA +FF0D ; ID # Pd FULLWIDTH HYPHEN-MINUS +FF0E ; CL # Po FULLWIDTH FULL STOP +FF0F ; ID # Po FULLWIDTH SOLIDUS +FF10..FF19 ; ID # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE +FF1A..FF1B ; NS # Po [2] FULLWIDTH COLON..FULLWIDTH SEMICOLON +FF1C..FF1E ; ID # Sm [3] FULLWIDTH LESS-THAN SIGN..FULLWIDTH GREATER-THAN SIGN +FF1F ; EX # Po FULLWIDTH QUESTION MARK +FF20 ; ID # Po FULLWIDTH COMMERCIAL AT +FF21..FF3A ; ID # Lu [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH LATIN CAPITAL LETTER Z +FF3B ; OP # Ps FULLWIDTH LEFT SQUARE BRACKET +FF3C ; ID # Po FULLWIDTH REVERSE SOLIDUS +FF3D ; CL # Pe FULLWIDTH RIGHT SQUARE BRACKET +FF3E ; ID # Sk FULLWIDTH CIRCUMFLEX ACCENT +FF3F ; ID # Pc FULLWIDTH LOW LINE +FF40 ; ID # Sk FULLWIDTH GRAVE ACCENT +FF41..FF5A ; ID # Ll [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN SMALL LETTER Z +FF5B ; OP # Ps FULLWIDTH LEFT CURLY BRACKET +FF5C ; ID # Sm FULLWIDTH VERTICAL LINE +FF5D ; CL # Pe FULLWIDTH RIGHT CURLY BRACKET +FF5E ; ID # Sm FULLWIDTH TILDE +FF5F ; OP # Ps FULLWIDTH LEFT WHITE PARENTHESIS +FF60 ; CL # Pe FULLWIDTH RIGHT WHITE PARENTHESIS +FF61 ; CL # Po HALFWIDTH IDEOGRAPHIC FULL STOP +FF62 ; OP # Ps HALFWIDTH LEFT CORNER BRACKET +FF63 ; CL # Pe HALFWIDTH RIGHT CORNER BRACKET +FF64 ; CL # Po HALFWIDTH IDEOGRAPHIC COMMA +FF65 ; NS # Po HALFWIDTH KATAKANA MIDDLE DOT +FF66 ; ID # Lo HALFWIDTH KATAKANA LETTER WO +FF67..FF6F ; CJ # Lo [9] HALFWIDTH KATAKANA LETTER SMALL A..HALFWIDTH KATAKANA LETTER SMALL TU +FF70 ; CJ # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK +FF71..FF9D ; ID # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAKANA LETTER N +FF9E..FF9F ; NS # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK +FFA0..FFBE ; ID # Lo [31] HALFWIDTH HANGUL FILLER..HALFWIDTH HANGUL LETTER HIEUH +FFC2..FFC7 ; ID # Lo [6] HALFWIDTH HANGUL LETTER A..HALFWIDTH HANGUL LETTER E +FFCA..FFCF ; ID # Lo [6] HALFWIDTH HANGUL LETTER YEO..HALFWIDTH HANGUL LETTER OE +FFD2..FFD7 ; ID # Lo [6] HALFWIDTH HANGUL LETTER YO..HALFWIDTH HANGUL LETTER YU +FFDA..FFDC ; ID # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL LETTER I +FFE0 ; PO # Sc FULLWIDTH CENT SIGN +FFE1 ; PR # Sc FULLWIDTH POUND SIGN +FFE2 ; ID # Sm FULLWIDTH NOT SIGN +FFE3 ; ID # Sk FULLWIDTH MACRON +FFE4 ; ID # So FULLWIDTH BROKEN BAR +FFE5..FFE6 ; PR # Sc [2] FULLWIDTH YEN SIGN..FULLWIDTH WON SIGN +FFE8 ; AL # So HALFWIDTH FORMS LIGHT VERTICAL +FFE9..FFEC ; AL # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW +FFED..FFEE ; AL # So [2] HALFWIDTH BLACK SQUARE..HALFWIDTH WHITE CIRCLE +FFF9..FFFB ; CM # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR +FFFC ; CB # So OBJECT REPLACEMENT CHARACTER +FFFD ; AI # So REPLACEMENT CHARACTER +10000..1000B ; AL # Lo [12] LINEAR B SYLLABLE B008 A..LINEAR B SYLLABLE B046 JE +1000D..10026 ; AL # Lo [26] LINEAR B SYLLABLE B036 JO..LINEAR B SYLLABLE B032 QO +10028..1003A ; AL # Lo [19] LINEAR B SYLLABLE B060 RA..LINEAR B SYLLABLE B042 WO +1003C..1003D ; AL # Lo [2] LINEAR B SYLLABLE B017 ZA..LINEAR B SYLLABLE B074 ZE +1003F..1004D ; AL # Lo [15] LINEAR B SYLLABLE B020 ZO..LINEAR B SYLLABLE B091 TWO +10050..1005D ; AL # Lo [14] LINEAR B SYMBOL B018..LINEAR B SYMBOL B089 +10080..100FA ; AL # Lo [123] LINEAR B IDEOGRAM B100 MAN..LINEAR B IDEOGRAM VESSEL B305 +10100..10102 ; BA # Po [3] AEGEAN WORD SEPARATOR LINE..AEGEAN CHECK MARK +10107..10133 ; AL # No [45] AEGEAN NUMBER ONE..AEGEAN NUMBER NINETY THOUSAND +10137..1013F ; AL # So [9] AEGEAN WEIGHT BASE UNIT..AEGEAN MEASURE THIRD SUBUNIT +10140..10174 ; AL # Nl [53] GREEK ACROPHONIC ATTIC ONE QUARTER..GREEK ACROPHONIC STRATIAN FIFTY MNAS +10175..10178 ; AL # No [4] GREEK ONE HALF SIGN..GREEK THREE QUARTERS SIGN +10179..10189 ; AL # So [17] GREEK YEAR SIGN..GREEK TRYBLION BASE SIGN +1018A..1018B ; AL # No [2] GREEK ZERO SIGN..GREEK ONE QUARTER SIGN +1018C..1018E ; AL # So [3] GREEK SINUSOID SIGN..NOMISMA SIGN +10190..1019C ; AL # So [13] ROMAN SEXTANS SIGN..ASCIA SYMBOL +101A0 ; AL # So GREEK SYMBOL TAU RHO +101D0..101FC ; AL # So [45] PHAISTOS DISC SIGN PEDESTRIAN..PHAISTOS DISC SIGN WAVY BAND +101FD ; CM # Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE +10280..1029C ; AL # Lo [29] LYCIAN LETTER A..LYCIAN LETTER X +102A0..102D0 ; AL # Lo [49] CARIAN LETTER A..CARIAN LETTER UUU3 +102E0 ; CM # Mn COPTIC EPACT THOUSANDS MARK +102E1..102FB ; AL # No [27] COPTIC EPACT DIGIT ONE..COPTIC EPACT NUMBER NINE HUNDRED +10300..1031F ; AL # Lo [32] OLD ITALIC LETTER A..OLD ITALIC LETTER ESS +10320..10323 ; AL # No [4] OLD ITALIC NUMERAL ONE..OLD ITALIC NUMERAL FIFTY +1032D..1032F ; AL # Lo [3] OLD ITALIC LETTER YE..OLD ITALIC LETTER SOUTHERN TSE +10330..10340 ; AL # Lo [17] GOTHIC LETTER AHSA..GOTHIC LETTER PAIRTHRA +10341 ; AL # Nl GOTHIC LETTER NINETY +10342..10349 ; AL # Lo [8] GOTHIC LETTER RAIDA..GOTHIC LETTER OTHAL +1034A ; AL # Nl GOTHIC LETTER NINE HUNDRED +10350..10375 ; AL # Lo [38] OLD PERMIC LETTER AN..OLD PERMIC LETTER IA +10376..1037A ; CM # Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII +10380..1039D ; AL # Lo [30] UGARITIC LETTER ALPA..UGARITIC LETTER SSU +1039F ; BA # Po UGARITIC WORD DIVIDER +103A0..103C3 ; AL # Lo [36] OLD PERSIAN SIGN A..OLD PERSIAN SIGN HA +103C8..103CF ; AL # Lo [8] OLD PERSIAN SIGN AURAMAZDAA..OLD PERSIAN SIGN BUUMISH +103D0 ; BA # Po OLD PERSIAN WORD DIVIDER +103D1..103D5 ; AL # Nl [5] OLD PERSIAN NUMBER ONE..OLD PERSIAN NUMBER HUNDRED +10400..1044F ; AL # L& [80] DESERET CAPITAL LETTER LONG I..DESERET SMALL LETTER EW +10450..1047F ; AL # Lo [48] SHAVIAN LETTER PEEP..SHAVIAN LETTER YEW +10480..1049D ; AL # Lo [30] OSMANYA LETTER ALEF..OSMANYA LETTER OO +104A0..104A9 ; NU # Nd [10] OSMANYA DIGIT ZERO..OSMANYA DIGIT NINE +104B0..104D3 ; AL # Lu [36] OSAGE CAPITAL LETTER A..OSAGE CAPITAL LETTER ZHA +104D8..104FB ; AL # Ll [36] OSAGE SMALL LETTER A..OSAGE SMALL LETTER ZHA +10500..10527 ; AL # Lo [40] ELBASAN LETTER A..ELBASAN LETTER KHE +10530..10563 ; AL # Lo [52] CAUCASIAN ALBANIAN LETTER ALT..CAUCASIAN ALBANIAN LETTER KIW +1056F ; AL # Po CAUCASIAN ALBANIAN CITATION MARK +10570..1057A ; AL # Lu [11] VITHKUQI CAPITAL LETTER A..VITHKUQI CAPITAL LETTER GA +1057C..1058A ; AL # Lu [15] VITHKUQI CAPITAL LETTER HA..VITHKUQI CAPITAL LETTER RE +1058C..10592 ; AL # Lu [7] VITHKUQI CAPITAL LETTER SE..VITHKUQI CAPITAL LETTER XE +10594..10595 ; AL # Lu [2] VITHKUQI CAPITAL LETTER Y..VITHKUQI CAPITAL LETTER ZE +10597..105A1 ; AL # Ll [11] VITHKUQI SMALL LETTER A..VITHKUQI SMALL LETTER GA +105A3..105B1 ; AL # Ll [15] VITHKUQI SMALL LETTER HA..VITHKUQI SMALL LETTER RE +105B3..105B9 ; AL # Ll [7] VITHKUQI SMALL LETTER SE..VITHKUQI SMALL LETTER XE +105BB..105BC ; AL # Ll [2] VITHKUQI SMALL LETTER Y..VITHKUQI SMALL LETTER ZE +10600..10736 ; AL # Lo [311] LINEAR A SIGN AB001..LINEAR A SIGN A664 +10740..10755 ; AL # Lo [22] LINEAR A SIGN A701 A..LINEAR A SIGN A732 JE +10760..10767 ; AL # Lo [8] LINEAR A SIGN A800..LINEAR A SIGN A807 +10780..10785 ; AL # Lm [6] MODIFIER LETTER SMALL CAPITAL AA..MODIFIER LETTER SMALL B WITH HOOK +10787..107B0 ; AL # Lm [42] MODIFIER LETTER SMALL DZ DIGRAPH..MODIFIER LETTER SMALL V WITH RIGHT HOOK +107B2..107BA ; AL # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL +10800..10805 ; AL # Lo [6] CYPRIOT SYLLABLE A..CYPRIOT SYLLABLE JA +10808 ; AL # Lo CYPRIOT SYLLABLE JO +1080A..10835 ; AL # Lo [44] CYPRIOT SYLLABLE KA..CYPRIOT SYLLABLE WO +10837..10838 ; AL # Lo [2] CYPRIOT SYLLABLE XA..CYPRIOT SYLLABLE XE +1083C ; AL # Lo CYPRIOT SYLLABLE ZA +1083F ; AL # Lo CYPRIOT SYLLABLE ZO +10840..10855 ; AL # Lo [22] IMPERIAL ARAMAIC LETTER ALEPH..IMPERIAL ARAMAIC LETTER TAW +10857 ; BA # Po IMPERIAL ARAMAIC SECTION SIGN +10858..1085F ; AL # No [8] IMPERIAL ARAMAIC NUMBER ONE..IMPERIAL ARAMAIC NUMBER TEN THOUSAND +10860..10876 ; AL # Lo [23] PALMYRENE LETTER ALEPH..PALMYRENE LETTER TAW +10877..10878 ; AL # So [2] PALMYRENE LEFT-POINTING FLEURON..PALMYRENE RIGHT-POINTING FLEURON +10879..1087F ; AL # No [7] PALMYRENE NUMBER ONE..PALMYRENE NUMBER TWENTY +10880..1089E ; AL # Lo [31] NABATAEAN LETTER FINAL ALEPH..NABATAEAN LETTER TAW +108A7..108AF ; AL # No [9] NABATAEAN NUMBER ONE..NABATAEAN NUMBER ONE HUNDRED +108E0..108F2 ; AL # Lo [19] HATRAN LETTER ALEPH..HATRAN LETTER QOPH +108F4..108F5 ; AL # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW +108FB..108FF ; AL # No [5] HATRAN NUMBER ONE..HATRAN NUMBER ONE HUNDRED +10900..10915 ; AL # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU +10916..1091B ; AL # No [6] PHOENICIAN NUMBER ONE..PHOENICIAN NUMBER THREE +1091F ; BA # Po PHOENICIAN WORD SEPARATOR +10920..10939 ; AL # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +1093F ; AL # Po LYDIAN TRIANGULAR MARK +10980..1099F ; AL # Lo [32] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2 +109A0..109B7 ; AL # Lo [24] MEROITIC CURSIVE LETTER A..MEROITIC CURSIVE LETTER DA +109BC..109BD ; AL # No [2] MEROITIC CURSIVE FRACTION ELEVEN TWELFTHS..MEROITIC CURSIVE FRACTION ONE HALF +109BE..109BF ; AL # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN +109C0..109CF ; AL # No [16] MEROITIC CURSIVE NUMBER ONE..MEROITIC CURSIVE NUMBER SEVENTY +109D2..109FF ; AL # No [46] MEROITIC CURSIVE NUMBER ONE HUNDRED..MEROITIC CURSIVE FRACTION TEN TWELFTHS +10A00 ; AL # Lo KHAROSHTHI LETTER A +10A01..10A03 ; CM # Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R +10A05..10A06 ; CM # Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O +10A0C..10A0F ; CM # Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA +10A10..10A13 ; AL # Lo [4] KHAROSHTHI LETTER KA..KHAROSHTHI LETTER GHA +10A15..10A17 ; AL # Lo [3] KHAROSHTHI LETTER CA..KHAROSHTHI LETTER JA +10A19..10A35 ; AL # Lo [29] KHAROSHTHI LETTER NYA..KHAROSHTHI LETTER VHA +10A38..10A3A ; CM # Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW +10A3F ; CM # Mn KHAROSHTHI VIRAMA +10A40..10A48 ; AL # No [9] KHAROSHTHI DIGIT ONE..KHAROSHTHI FRACTION ONE HALF +10A50..10A57 ; BA # Po [8] KHAROSHTHI PUNCTUATION DOT..KHAROSHTHI PUNCTUATION DOUBLE DANDA +10A58 ; AL # Po KHAROSHTHI PUNCTUATION LINES +10A60..10A7C ; AL # Lo [29] OLD SOUTH ARABIAN LETTER HE..OLD SOUTH ARABIAN LETTER THETH +10A7D..10A7E ; AL # No [2] OLD SOUTH ARABIAN NUMBER ONE..OLD SOUTH ARABIAN NUMBER FIFTY +10A7F ; AL # Po OLD SOUTH ARABIAN NUMERIC INDICATOR +10A80..10A9C ; AL # Lo [29] OLD NORTH ARABIAN LETTER HEH..OLD NORTH ARABIAN LETTER ZAH +10A9D..10A9F ; AL # No [3] OLD NORTH ARABIAN NUMBER ONE..OLD NORTH ARABIAN NUMBER TWENTY +10AC0..10AC7 ; AL # Lo [8] MANICHAEAN LETTER ALEPH..MANICHAEAN LETTER WAW +10AC8 ; AL # So MANICHAEAN SIGN UD +10AC9..10AE4 ; AL # Lo [28] MANICHAEAN LETTER ZAYIN..MANICHAEAN LETTER TAW +10AE5..10AE6 ; CM # Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW +10AEB..10AEF ; AL # No [5] MANICHAEAN NUMBER ONE..MANICHAEAN NUMBER ONE HUNDRED +10AF0..10AF5 ; BA # Po [6] MANICHAEAN PUNCTUATION STAR..MANICHAEAN PUNCTUATION TWO DOTS +10AF6 ; IN # Po MANICHAEAN PUNCTUATION LINE FILLER +10B00..10B35 ; AL # Lo [54] AVESTAN LETTER A..AVESTAN LETTER HE +10B39..10B3F ; BA # Po [7] AVESTAN ABBREVIATION MARK..LARGE ONE RING OVER TWO RINGS PUNCTUATION +10B40..10B55 ; AL # Lo [22] INSCRIPTIONAL PARTHIAN LETTER ALEPH..INSCRIPTIONAL PARTHIAN LETTER TAW +10B58..10B5F ; AL # No [8] INSCRIPTIONAL PARTHIAN NUMBER ONE..INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND +10B60..10B72 ; AL # Lo [19] INSCRIPTIONAL PAHLAVI LETTER ALEPH..INSCRIPTIONAL PAHLAVI LETTER TAW +10B78..10B7F ; AL # No [8] INSCRIPTIONAL PAHLAVI NUMBER ONE..INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND +10B80..10B91 ; AL # Lo [18] PSALTER PAHLAVI LETTER ALEPH..PSALTER PAHLAVI LETTER TAW +10B99..10B9C ; AL # Po [4] PSALTER PAHLAVI SECTION MARK..PSALTER PAHLAVI FOUR DOTS WITH DOT +10BA9..10BAF ; AL # No [7] PSALTER PAHLAVI NUMBER ONE..PSALTER PAHLAVI NUMBER ONE HUNDRED +10C00..10C48 ; AL # Lo [73] OLD TURKIC LETTER ORKHON A..OLD TURKIC LETTER ORKHON BASH +10C80..10CB2 ; AL # Lu [51] OLD HUNGARIAN CAPITAL LETTER A..OLD HUNGARIAN CAPITAL LETTER US +10CC0..10CF2 ; AL # Ll [51] OLD HUNGARIAN SMALL LETTER A..OLD HUNGARIAN SMALL LETTER US +10CFA..10CFF ; AL # No [6] OLD HUNGARIAN NUMBER ONE..OLD HUNGARIAN NUMBER ONE THOUSAND +10D00..10D23 ; AL # Lo [36] HANIFI ROHINGYA LETTER A..HANIFI ROHINGYA MARK NA KHONNA +10D24..10D27 ; CM # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI +10D30..10D39 ; NU # Nd [10] HANIFI ROHINGYA DIGIT ZERO..HANIFI ROHINGYA DIGIT NINE +10E60..10E7E ; AL # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10E80..10EA9 ; AL # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET +10EAB..10EAC ; CM # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK +10EAD ; BA # Pd YEZIDI HYPHENATION MARK +10EB0..10EB1 ; AL # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10EFD..10EFF ; CM # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +10F00..10F1C ; AL # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL +10F1D..10F26 ; AL # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF +10F27 ; AL # Lo OLD SOGDIAN LIGATURE AYIN-DALETH +10F30..10F45 ; AL # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN +10F46..10F50 ; CM # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW +10F51..10F54 ; AL # No [4] SOGDIAN NUMBER ONE..SOGDIAN NUMBER ONE HUNDRED +10F55..10F59 ; AL # Po [5] SOGDIAN PUNCTUATION TWO VERTICAL BARS..SOGDIAN PUNCTUATION HALF CIRCLE WITH DOT +10F70..10F81 ; AL # Lo [18] OLD UYGHUR LETTER ALEPH..OLD UYGHUR LETTER LESH +10F82..10F85 ; CM # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW +10F86..10F89 ; AL # Po [4] OLD UYGHUR PUNCTUATION BAR..OLD UYGHUR PUNCTUATION FOUR DOTS +10FB0..10FC4 ; AL # Lo [21] CHORASMIAN LETTER ALEPH..CHORASMIAN LETTER TAW +10FC5..10FCB ; AL # No [7] CHORASMIAN NUMBER ONE..CHORASMIAN NUMBER ONE HUNDRED +10FE0..10FF6 ; AL # Lo [23] ELYMAIC LETTER ALEPH..ELYMAIC LIGATURE ZAYIN-YODH +11000 ; CM # Mc BRAHMI SIGN CANDRABINDU +11001 ; CM # Mn BRAHMI SIGN ANUSVARA +11002 ; CM # Mc BRAHMI SIGN VISARGA +11003..11004 ; AP # Lo [2] BRAHMI SIGN JIHVAMULIYA..BRAHMI SIGN UPADHMANIYA +11005..11037 ; AK # Lo [51] BRAHMI LETTER A..BRAHMI LETTER OLD TAMIL NNNA +11038..11045 ; CM # Mn [14] BRAHMI VOWEL SIGN AA..BRAHMI VOWEL SIGN AU +11046 ; VI # Mn BRAHMI VIRAMA +11047..11048 ; BA # Po [2] BRAHMI DANDA..BRAHMI DOUBLE DANDA +11049..1104D ; ID # Po [5] BRAHMI PUNCTUATION DOT..BRAHMI PUNCTUATION LOTUS +11052..11065 ; ID # No [20] BRAHMI NUMBER ONE..BRAHMI NUMBER ONE THOUSAND +11066..1106F ; AS # Nd [10] BRAHMI DIGIT ZERO..BRAHMI DIGIT NINE +11070 ; CM # Mn BRAHMI SIGN OLD TAMIL VIRAMA +11071..11072 ; AK # Lo [2] BRAHMI LETTER OLD TAMIL SHORT E..BRAHMI LETTER OLD TAMIL SHORT O +11073..11074 ; CM # Mn [2] BRAHMI VOWEL SIGN OLD TAMIL SHORT E..BRAHMI VOWEL SIGN OLD TAMIL SHORT O +11075 ; AK # Lo BRAHMI LETTER OLD TAMIL LLA +1107F ; GL # Mn BRAHMI NUMBER JOINER +11080..11081 ; CM # Mn [2] KAITHI SIGN CANDRABINDU..KAITHI SIGN ANUSVARA +11082 ; CM # Mc KAITHI SIGN VISARGA +11083..110AF ; AL # Lo [45] KAITHI LETTER A..KAITHI LETTER HA +110B0..110B2 ; CM # Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II +110B3..110B6 ; CM # Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI +110B7..110B8 ; CM # Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU +110B9..110BA ; CM # Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA +110BB..110BC ; AL # Po [2] KAITHI ABBREVIATION SIGN..KAITHI ENUMERATION SIGN +110BD ; NU # Cf KAITHI NUMBER SIGN +110BE..110C1 ; BA # Po [4] KAITHI SECTION MARK..KAITHI DOUBLE DANDA +110C2 ; CM # Mn KAITHI VOWEL SIGN VOCALIC R +110CD ; NU # Cf KAITHI NUMBER SIGN ABOVE +110D0..110E8 ; AL # Lo [25] SORA SOMPENG LETTER SAH..SORA SOMPENG LETTER MAE +110F0..110F9 ; NU # Nd [10] SORA SOMPENG DIGIT ZERO..SORA SOMPENG DIGIT NINE +11100..11102 ; CM # Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA +11103..11126 ; AL # Lo [36] CHAKMA LETTER AA..CHAKMA LETTER HAA +11127..1112B ; CM # Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU +1112C ; CM # Mc CHAKMA VOWEL SIGN E +1112D..11134 ; CM # Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA +11136..1113F ; NU # Nd [10] CHAKMA DIGIT ZERO..CHAKMA DIGIT NINE +11140..11143 ; BA # Po [4] CHAKMA SECTION MARK..CHAKMA QUESTION MARK +11144 ; AL # Lo CHAKMA LETTER LHAA +11145..11146 ; CM # Mc [2] CHAKMA VOWEL SIGN AA..CHAKMA VOWEL SIGN EI +11147 ; AL # Lo CHAKMA LETTER VAA +11150..11172 ; AL # Lo [35] MAHAJANI LETTER A..MAHAJANI LETTER RRA +11173 ; CM # Mn MAHAJANI SIGN NUKTA +11174 ; AL # Po MAHAJANI ABBREVIATION SIGN +11175 ; BB # Po MAHAJANI SECTION MARK +11176 ; AL # Lo MAHAJANI LIGATURE SHRI +11180..11181 ; CM # Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA +11182 ; CM # Mc SHARADA SIGN VISARGA +11183..111B2 ; AL # Lo [48] SHARADA LETTER A..SHARADA LETTER HA +111B3..111B5 ; CM # Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II +111B6..111BE ; CM # Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O +111BF..111C0 ; CM # Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA +111C1..111C4 ; AL # Lo [4] SHARADA SIGN AVAGRAHA..SHARADA OM +111C5..111C6 ; BA # Po [2] SHARADA DANDA..SHARADA DOUBLE DANDA +111C7 ; AL # Po SHARADA ABBREVIATION SIGN +111C8 ; BA # Po SHARADA SEPARATOR +111C9..111CC ; CM # Mn [4] SHARADA SANDHI MARK..SHARADA EXTRA SHORT VOWEL MARK +111CD ; AL # Po SHARADA SUTRA MARK +111CE ; CM # Mc SHARADA VOWEL SIGN PRISHTHAMATRA E +111CF ; CM # Mn SHARADA SIGN INVERTED CANDRABINDU +111D0..111D9 ; NU # Nd [10] SHARADA DIGIT ZERO..SHARADA DIGIT NINE +111DA ; AL # Lo SHARADA EKAM +111DB ; BB # Po SHARADA SIGN SIDDHAM +111DC ; AL # Lo SHARADA HEADSTROKE +111DD..111DF ; BA # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +111E1..111F4 ; AL # No [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND +11200..11211 ; AL # Lo [18] KHOJKI LETTER A..KHOJKI LETTER JJA +11213..1122B ; AL # Lo [25] KHOJKI LETTER NYA..KHOJKI LETTER LLA +1122C..1122E ; CM # Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II +1122F..11231 ; CM # Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI +11232..11233 ; CM # Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU +11234 ; CM # Mn KHOJKI SIGN ANUSVARA +11235 ; CM # Mc KHOJKI SIGN VIRAMA +11236..11237 ; CM # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA +11238..11239 ; BA # Po [2] KHOJKI DANDA..KHOJKI DOUBLE DANDA +1123A ; AL # Po KHOJKI WORD SEPARATOR +1123B..1123C ; BA # Po [2] KHOJKI SECTION MARK..KHOJKI DOUBLE SECTION MARK +1123D ; AL # Po KHOJKI ABBREVIATION SIGN +1123E ; CM # Mn KHOJKI SIGN SUKUN +1123F..11240 ; AL # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11241 ; CM # Mn KHOJKI VOWEL SIGN VOCALIC R +11280..11286 ; AL # Lo [7] MULTANI LETTER A..MULTANI LETTER GA +11288 ; AL # Lo MULTANI LETTER GHA +1128A..1128D ; AL # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA +1128F..1129D ; AL # Lo [15] MULTANI LETTER NYA..MULTANI LETTER BA +1129F..112A8 ; AL # Lo [10] MULTANI LETTER BHA..MULTANI LETTER RHA +112A9 ; BA # Po MULTANI SECTION MARK +112B0..112DE ; AL # Lo [47] KHUDAWADI LETTER A..KHUDAWADI LETTER HA +112DF ; CM # Mn KHUDAWADI SIGN ANUSVARA +112E0..112E2 ; CM # Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II +112E3..112EA ; CM # Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA +112F0..112F9 ; NU # Nd [10] KHUDAWADI DIGIT ZERO..KHUDAWADI DIGIT NINE +11300..11301 ; CM # Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU +11302..11303 ; CM # Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA +11305..1130C ; AK # Lo [8] GRANTHA LETTER A..GRANTHA LETTER VOCALIC L +1130F..11310 ; AK # Lo [2] GRANTHA LETTER EE..GRANTHA LETTER AI +11313..11328 ; AK # Lo [22] GRANTHA LETTER OO..GRANTHA LETTER NA +1132A..11330 ; AK # Lo [7] GRANTHA LETTER PA..GRANTHA LETTER RA +11332..11333 ; AK # Lo [2] GRANTHA LETTER LA..GRANTHA LETTER LLA +11335..11339 ; AK # Lo [5] GRANTHA LETTER VA..GRANTHA LETTER HA +1133B..1133C ; CM # Mn [2] COMBINING BINDU BELOW..GRANTHA SIGN NUKTA +1133D ; BA # Lo GRANTHA SIGN AVAGRAHA +1133E..1133F ; CM # Mc [2] GRANTHA VOWEL SIGN AA..GRANTHA VOWEL SIGN I +11340 ; CM # Mn GRANTHA VOWEL SIGN II +11341..11344 ; CM # Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR +11347..11348 ; CM # Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI +1134B..1134C ; CM # Mc [2] GRANTHA VOWEL SIGN OO..GRANTHA VOWEL SIGN AU +1134D ; VI # Mc GRANTHA SIGN VIRAMA +11350 ; AS # Lo GRANTHA OM +11357 ; CM # Mc GRANTHA AU LENGTH MARK +1135D ; BA # Lo GRANTHA SIGN PLUTA +1135E..1135F ; AS # Lo [2] GRANTHA LETTER VEDIC ANUSVARA..GRANTHA LETTER VEDIC DOUBLE ANUSVARA +11360..11361 ; AK # Lo [2] GRANTHA LETTER VOCALIC RR..GRANTHA LETTER VOCALIC LL +11362..11363 ; CM # Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL +11366..1136C ; CM # Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX +11370..11374 ; CM # Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA +11400..11434 ; AL # Lo [53] NEWA LETTER A..NEWA LETTER HA +11435..11437 ; CM # Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II +11438..1143F ; CM # Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI +11440..11441 ; CM # Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU +11442..11444 ; CM # Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA +11445 ; CM # Mc NEWA SIGN VISARGA +11446 ; CM # Mn NEWA SIGN NUKTA +11447..1144A ; AL # Lo [4] NEWA SIGN AVAGRAHA..NEWA SIDDHI +1144B..1144E ; BA # Po [4] NEWA DANDA..NEWA GAP FILLER +1144F ; AL # Po NEWA ABBREVIATION SIGN +11450..11459 ; NU # Nd [10] NEWA DIGIT ZERO..NEWA DIGIT NINE +1145A..1145B ; BA # Po [2] NEWA DOUBLE COMMA..NEWA PLACEHOLDER MARK +1145D ; AL # Po NEWA INSERTION SIGN +1145E ; CM # Mn NEWA SANDHI MARK +1145F..11461 ; AL # Lo [3] NEWA LETTER VEDIC ANUSVARA..NEWA SIGN UPADHMANIYA +11480..114AF ; AL # Lo [48] TIRHUTA ANJI..TIRHUTA LETTER HA +114B0..114B2 ; CM # Mc [3] TIRHUTA VOWEL SIGN AA..TIRHUTA VOWEL SIGN II +114B3..114B8 ; CM # Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL +114B9 ; CM # Mc TIRHUTA VOWEL SIGN E +114BA ; CM # Mn TIRHUTA VOWEL SIGN SHORT E +114BB..114BE ; CM # Mc [4] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN AU +114BF..114C0 ; CM # Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA +114C1 ; CM # Mc TIRHUTA SIGN VISARGA +114C2..114C3 ; CM # Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA +114C4..114C5 ; AL # Lo [2] TIRHUTA SIGN AVAGRAHA..TIRHUTA GVANG +114C6 ; AL # Po TIRHUTA ABBREVIATION SIGN +114C7 ; AL # Lo TIRHUTA OM +114D0..114D9 ; NU # Nd [10] TIRHUTA DIGIT ZERO..TIRHUTA DIGIT NINE +11580..115AE ; AL # Lo [47] SIDDHAM LETTER A..SIDDHAM LETTER HA +115AF..115B1 ; CM # Mc [3] SIDDHAM VOWEL SIGN AA..SIDDHAM VOWEL SIGN II +115B2..115B5 ; CM # Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR +115B8..115BB ; CM # Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU +115BC..115BD ; CM # Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA +115BE ; CM # Mc SIDDHAM SIGN VISARGA +115BF..115C0 ; CM # Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA +115C1 ; BB # Po SIDDHAM SIGN SIDDHAM +115C2..115C3 ; BA # Po [2] SIDDHAM DANDA..SIDDHAM DOUBLE DANDA +115C4..115C5 ; EX # Po [2] SIDDHAM SEPARATOR DOT..SIDDHAM SEPARATOR BAR +115C6..115C8 ; AL # Po [3] SIDDHAM REPETITION MARK-1..SIDDHAM REPETITION MARK-3 +115C9..115D7 ; BA # Po [15] SIDDHAM END OF TEXT MARK..SIDDHAM SECTION MARK WITH CIRCLES AND FOUR ENCLOSURES +115D8..115DB ; AL # Lo [4] SIDDHAM LETTER THREE-CIRCLE ALTERNATE I..SIDDHAM LETTER ALTERNATE U +115DC..115DD ; CM # Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU +11600..1162F ; AL # Lo [48] MODI LETTER A..MODI LETTER LLA +11630..11632 ; CM # Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II +11633..1163A ; CM # Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI +1163B..1163C ; CM # Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU +1163D ; CM # Mn MODI SIGN ANUSVARA +1163E ; CM # Mc MODI SIGN VISARGA +1163F..11640 ; CM # Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA +11641..11642 ; BA # Po [2] MODI DANDA..MODI DOUBLE DANDA +11643 ; AL # Po MODI ABBREVIATION SIGN +11644 ; AL # Lo MODI SIGN HUVA +11650..11659 ; NU # Nd [10] MODI DIGIT ZERO..MODI DIGIT NINE +11660..1166C ; BB # Po [13] MONGOLIAN BIRGA WITH ORNAMENT..MONGOLIAN TURNED SWIRL BIRGA WITH DOUBLE ORNAMENT +11680..116AA ; AL # Lo [43] TAKRI LETTER A..TAKRI LETTER RRA +116AB ; CM # Mn TAKRI SIGN ANUSVARA +116AC ; CM # Mc TAKRI SIGN VISARGA +116AD ; CM # Mn TAKRI VOWEL SIGN AA +116AE..116AF ; CM # Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II +116B0..116B5 ; CM # Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU +116B6 ; CM # Mc TAKRI SIGN VIRAMA +116B7 ; CM # Mn TAKRI SIGN NUKTA +116B8 ; AL # Lo TAKRI LETTER ARCHAIC KHA +116B9 ; AL # Po TAKRI ABBREVIATION SIGN +116C0..116C9 ; NU # Nd [10] TAKRI DIGIT ZERO..TAKRI DIGIT NINE +11700..1171A ; SA # Lo [27] AHOM LETTER KA..AHOM LETTER ALTERNATE BA +1171D..1171F ; SA # Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA +11720..11721 ; SA # Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA +11722..11725 ; SA # Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU +11726 ; SA # Mc AHOM VOWEL SIGN E +11727..1172B ; SA # Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER +11730..11739 ; NU # Nd [10] AHOM DIGIT ZERO..AHOM DIGIT NINE +1173A..1173B ; SA # No [2] AHOM NUMBER TEN..AHOM NUMBER TWENTY +1173C..1173E ; BA # Po [3] AHOM SIGN SMALL SECTION..AHOM SIGN RULAI +1173F ; SA # So AHOM SYMBOL VI +11740..11746 ; SA # Lo [7] AHOM LETTER CA..AHOM LETTER LLA +11800..1182B ; AL # Lo [44] DOGRA LETTER A..DOGRA LETTER RRA +1182C..1182E ; CM # Mc [3] DOGRA VOWEL SIGN AA..DOGRA VOWEL SIGN II +1182F..11837 ; CM # Mn [9] DOGRA VOWEL SIGN U..DOGRA SIGN ANUSVARA +11838 ; CM # Mc DOGRA SIGN VISARGA +11839..1183A ; CM # Mn [2] DOGRA SIGN VIRAMA..DOGRA SIGN NUKTA +1183B ; AL # Po DOGRA ABBREVIATION SIGN +118A0..118DF ; AL # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO +118E0..118E9 ; NU # Nd [10] WARANG CITI DIGIT ZERO..WARANG CITI DIGIT NINE +118EA..118F2 ; AL # No [9] WARANG CITI NUMBER TEN..WARANG CITI NUMBER NINETY +118FF ; AL # Lo WARANG CITI OM +11900..11906 ; AK # Lo [7] DIVES AKURU LETTER A..DIVES AKURU LETTER E +11909 ; AK # Lo DIVES AKURU LETTER O +1190C..11913 ; AK # Lo [8] DIVES AKURU LETTER KA..DIVES AKURU LETTER JA +11915..11916 ; AK # Lo [2] DIVES AKURU LETTER NYA..DIVES AKURU LETTER TTA +11918..1192F ; AK # Lo [24] DIVES AKURU LETTER DDA..DIVES AKURU LETTER ZA +11930..11935 ; CM # Mc [6] DIVES AKURU VOWEL SIGN AA..DIVES AKURU VOWEL SIGN E +11937..11938 ; CM # Mc [2] DIVES AKURU VOWEL SIGN AI..DIVES AKURU VOWEL SIGN O +1193B..1193C ; CM # Mn [2] DIVES AKURU SIGN ANUSVARA..DIVES AKURU SIGN CANDRABINDU +1193D ; CM # Mc DIVES AKURU SIGN HALANTA +1193E ; VI # Mn DIVES AKURU VIRAMA +1193F ; AP # Lo DIVES AKURU PREFIXED NASAL SIGN +11940 ; CM # Mc DIVES AKURU MEDIAL YA +11941 ; AP # Lo DIVES AKURU INITIAL RA +11942 ; CM # Mc DIVES AKURU MEDIAL RA +11943 ; CM # Mn DIVES AKURU SIGN NUKTA +11944..11946 ; BA # Po [3] DIVES AKURU DOUBLE DANDA..DIVES AKURU END OF TEXT MARK +11950..11959 ; ID # Nd [10] DIVES AKURU DIGIT ZERO..DIVES AKURU DIGIT NINE +119A0..119A7 ; AL # Lo [8] NANDINAGARI LETTER A..NANDINAGARI LETTER VOCALIC RR +119AA..119D0 ; AL # Lo [39] NANDINAGARI LETTER E..NANDINAGARI LETTER RRA +119D1..119D3 ; CM # Mc [3] NANDINAGARI VOWEL SIGN AA..NANDINAGARI VOWEL SIGN II +119D4..119D7 ; CM # Mn [4] NANDINAGARI VOWEL SIGN U..NANDINAGARI VOWEL SIGN VOCALIC RR +119DA..119DB ; CM # Mn [2] NANDINAGARI VOWEL SIGN E..NANDINAGARI VOWEL SIGN AI +119DC..119DF ; CM # Mc [4] NANDINAGARI VOWEL SIGN O..NANDINAGARI SIGN VISARGA +119E0 ; CM # Mn NANDINAGARI SIGN VIRAMA +119E1 ; AL # Lo NANDINAGARI SIGN AVAGRAHA +119E2 ; BB # Po NANDINAGARI SIGN SIDDHAM +119E3 ; AL # Lo NANDINAGARI HEADSTROKE +119E4 ; CM # Mc NANDINAGARI VOWEL SIGN PRISHTHAMATRA E +11A00 ; AL # Lo ZANABAZAR SQUARE LETTER A +11A01..11A0A ; CM # Mn [10] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL LENGTH MARK +11A0B..11A32 ; AL # Lo [40] ZANABAZAR SQUARE LETTER KA..ZANABAZAR SQUARE LETTER KSSA +11A33..11A38 ; CM # Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA +11A39 ; CM # Mc ZANABAZAR SQUARE SIGN VISARGA +11A3A ; AL # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA +11A3B..11A3E ; CM # Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA +11A3F ; BB # Po ZANABAZAR SQUARE INITIAL HEAD MARK +11A40 ; AL # Po ZANABAZAR SQUARE CLOSING HEAD MARK +11A41..11A44 ; BA # Po [4] ZANABAZAR SQUARE MARK TSHEG..ZANABAZAR SQUARE MARK LONG TSHEG +11A45 ; BB # Po ZANABAZAR SQUARE INITIAL DOUBLE-LINED HEAD MARK +11A46 ; AL # Po ZANABAZAR SQUARE CLOSING DOUBLE-LINED HEAD MARK +11A47 ; CM # Mn ZANABAZAR SQUARE SUBJOINER +11A50 ; AL # Lo SOYOMBO LETTER A +11A51..11A56 ; CM # Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE +11A57..11A58 ; CM # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU +11A59..11A5B ; CM # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK +11A5C..11A89 ; AL # Lo [46] SOYOMBO LETTER KA..SOYOMBO CLUSTER-INITIAL LETTER SA +11A8A..11A96 ; CM # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA +11A97 ; CM # Mc SOYOMBO SIGN VISARGA +11A98..11A99 ; CM # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11A9A..11A9C ; BA # Po [3] SOYOMBO MARK TSHEG..SOYOMBO MARK DOUBLE SHAD +11A9D ; AL # Lo SOYOMBO MARK PLUTA +11A9E..11AA0 ; BB # Po [3] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO HEAD MARK WITH MOON AND SUN +11AA1..11AA2 ; BA # Po [2] SOYOMBO TERMINAL MARK-1..SOYOMBO TERMINAL MARK-2 +11AB0..11ABF ; AL # Lo [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA +11AC0..11AF8 ; AL # Lo [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL +11B00..11B09 ; BB # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU +11C00..11C08 ; AL # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L +11C0A..11C2E ; AL # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA +11C2F ; CM # Mc BHAIKSUKI VOWEL SIGN AA +11C30..11C36 ; CM # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L +11C38..11C3D ; CM # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA +11C3E ; CM # Mc BHAIKSUKI SIGN VISARGA +11C3F ; CM # Mn BHAIKSUKI SIGN VIRAMA +11C40 ; AL # Lo BHAIKSUKI SIGN AVAGRAHA +11C41..11C45 ; BA # Po [5] BHAIKSUKI DANDA..BHAIKSUKI GAP FILLER-2 +11C50..11C59 ; NU # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE +11C5A..11C6C ; AL # No [19] BHAIKSUKI NUMBER ONE..BHAIKSUKI HUNDREDS UNIT MARK +11C70 ; BB # Po MARCHEN HEAD MARK +11C71 ; EX # Po MARCHEN MARK SHAD +11C72..11C8F ; AL # Lo [30] MARCHEN LETTER KA..MARCHEN LETTER A +11C92..11CA7 ; CM # Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA +11CA9 ; CM # Mc MARCHEN SUBJOINED LETTER YA +11CAA..11CB0 ; CM # Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA +11CB1 ; CM # Mc MARCHEN VOWEL SIGN I +11CB2..11CB3 ; CM # Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E +11CB4 ; CM # Mc MARCHEN VOWEL SIGN O +11CB5..11CB6 ; CM # Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU +11D00..11D06 ; AL # Lo [7] MASARAM GONDI LETTER A..MASARAM GONDI LETTER E +11D08..11D09 ; AL # Lo [2] MASARAM GONDI LETTER AI..MASARAM GONDI LETTER O +11D0B..11D30 ; AL # Lo [38] MASARAM GONDI LETTER AU..MASARAM GONDI LETTER TRA +11D31..11D36 ; CM # Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R +11D3A ; CM # Mn MASARAM GONDI VOWEL SIGN E +11D3C..11D3D ; CM # Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O +11D3F..11D45 ; CM # Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA +11D46 ; AL # Lo MASARAM GONDI REPHA +11D47 ; CM # Mn MASARAM GONDI RA-KARA +11D50..11D59 ; NU # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11D60..11D65 ; AL # Lo [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; AL # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D89 ; AL # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA +11D8A..11D8E ; CM # Mc [5] GUNJALA GONDI VOWEL SIGN AA..GUNJALA GONDI VOWEL SIGN UU +11D90..11D91 ; CM # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D93..11D94 ; CM # Mc [2] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI VOWEL SIGN AU +11D95 ; CM # Mn GUNJALA GONDI SIGN ANUSVARA +11D96 ; CM # Mc GUNJALA GONDI SIGN VISARGA +11D97 ; CM # Mn GUNJALA GONDI VIRAMA +11D98 ; AL # Lo GUNJALA GONDI OM +11DA0..11DA9 ; NU # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11EE0..11EF1 ; AS # Lo [18] MAKASAR LETTER KA..MAKASAR LETTER A +11EF2 ; BA # Lo MAKASAR ANGKA +11EF3..11EF4 ; CM # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U +11EF5..11EF6 ; CM # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O +11EF7..11EF8 ; BA # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F00..11F01 ; CM # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F02 ; AP # Lo KAWI SIGN REPHA +11F03 ; CM # Mc KAWI SIGN VISARGA +11F04..11F10 ; AK # Lo [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33 ; AK # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA +11F34..11F35 ; CM # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F36..11F3A ; CM # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F3E..11F3F ; CM # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F40 ; CM # Mn KAWI VOWEL SIGN EU +11F41 ; CM # Mc KAWI SIGN KILLER +11F42 ; VI # Mn KAWI CONJOINER +11F43..11F44 ; BA # Po [2] KAWI DANDA..KAWI DOUBLE DANDA +11F45..11F4F ; ID # Po [11] KAWI PUNCTUATION SECTION MARKER..KAWI PUNCTUATION CLOSING SPIRAL +11F50..11F59 ; AS # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE +11FB0 ; AL # Lo LISU LETTER YHA +11FC0..11FD4 ; AL # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH +11FD5..11FDC ; AL # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI +11FDD..11FE0 ; PO # Sc [4] TAMIL SIGN KAACU..TAMIL SIGN VARAAKAN +11FE1..11FF1 ; AL # So [17] TAMIL SIGN PAARAM..TAMIL SIGN VAKAIYARAA +11FFF ; BA # Po TAMIL PUNCTUATION END OF TEXT +12000..12399 ; AL # Lo [922] CUNEIFORM SIGN A..CUNEIFORM SIGN U U +12400..1246E ; AL # Nl [111] CUNEIFORM NUMERIC SIGN TWO ASH..CUNEIFORM NUMERIC SIGN NINE U VARIANT FORM +12470..12474 ; BA # Po [5] CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER..CUNEIFORM PUNCTUATION SIGN DIAGONAL QUADCOLON +12480..12543 ; AL # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU +12F90..12FF0 ; AL # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 +12FF1..12FF2 ; AL # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 +13000..13257 ; AL # Lo [600] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH O006 +13258..1325A ; OP # Lo [3] EGYPTIAN HIEROGLYPH O006A..EGYPTIAN HIEROGLYPH O006C +1325B..1325D ; CL # Lo [3] EGYPTIAN HIEROGLYPH O006D..EGYPTIAN HIEROGLYPH O006F +1325E..13281 ; AL # Lo [36] EGYPTIAN HIEROGLYPH O007..EGYPTIAN HIEROGLYPH O033 +13282 ; CL # Lo EGYPTIAN HIEROGLYPH O033A +13283..13285 ; AL # Lo [3] EGYPTIAN HIEROGLYPH O034..EGYPTIAN HIEROGLYPH O036 +13286 ; OP # Lo EGYPTIAN HIEROGLYPH O036A +13287 ; CL # Lo EGYPTIAN HIEROGLYPH O036B +13288 ; OP # Lo EGYPTIAN HIEROGLYPH O036C +13289 ; CL # Lo EGYPTIAN HIEROGLYPH O036D +1328A..13378 ; AL # Lo [239] EGYPTIAN HIEROGLYPH O037..EGYPTIAN HIEROGLYPH V011 +13379 ; OP # Lo EGYPTIAN HIEROGLYPH V011A +1337A..1337B ; CL # Lo [2] EGYPTIAN HIEROGLYPH V011B..EGYPTIAN HIEROGLYPH V011C +1337C..1342E ; AL # Lo [179] EGYPTIAN HIEROGLYPH V012..EGYPTIAN HIEROGLYPH AA032 +1342F ; OP # Lo EGYPTIAN HIEROGLYPH V011D +13430..13436 ; GL # Cf [7] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH OVERLAY MIDDLE +13437 ; OP # Cf EGYPTIAN HIEROGLYPH BEGIN SEGMENT +13438 ; CL # Cf EGYPTIAN HIEROGLYPH END SEGMENT +13439..1343B ; GL # Cf [3] EGYPTIAN HIEROGLYPH INSERT AT MIDDLE..EGYPTIAN HIEROGLYPH INSERT AT BOTTOM +1343C ; OP # Cf EGYPTIAN HIEROGLYPH BEGIN ENCLOSURE +1343D ; CL # Cf EGYPTIAN HIEROGLYPH END ENCLOSURE +1343E ; OP # Cf EGYPTIAN HIEROGLYPH BEGIN WALLED ENCLOSURE +1343F ; CL # Cf EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +13440 ; CM # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13441..13446 ; AL # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +13447..13455 ; CM # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED +14400..145CD ; AL # Lo [462] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A409 +145CE ; OP # Lo ANATOLIAN HIEROGLYPH A410 BEGIN LOGOGRAM MARK +145CF ; CL # Lo ANATOLIAN HIEROGLYPH A410A END LOGOGRAM MARK +145D0..14646 ; AL # Lo [119] ANATOLIAN HIEROGLYPH A411..ANATOLIAN HIEROGLYPH A530 +16800..16A38 ; AL # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ +16A40..16A5E ; AL # Lo [31] MRO LETTER TA..MRO LETTER TEK +16A60..16A69 ; NU # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE +16A6E..16A6F ; BA # Po [2] MRO DANDA..MRO DOUBLE DANDA +16A70..16ABE ; AL # Lo [79] TANGSA LETTER OZ..TANGSA LETTER ZA +16AC0..16AC9 ; NU # Nd [10] TANGSA DIGIT ZERO..TANGSA DIGIT NINE +16AD0..16AED ; AL # Lo [30] BASSA VAH LETTER ENNI..BASSA VAH LETTER I +16AF0..16AF4 ; CM # Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE +16AF5 ; BA # Po BASSA VAH FULL STOP +16B00..16B2F ; AL # Lo [48] PAHAWH HMONG VOWEL KEEB..PAHAWH HMONG CONSONANT CAU +16B30..16B36 ; CM # Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM +16B37..16B39 ; BA # Po [3] PAHAWH HMONG SIGN VOS THOM..PAHAWH HMONG SIGN CIM CHEEM +16B3A..16B3B ; AL # Po [2] PAHAWH HMONG SIGN VOS THIAB..PAHAWH HMONG SIGN VOS FEEM +16B3C..16B3F ; AL # So [4] PAHAWH HMONG SIGN XYEEM NTXIV..PAHAWH HMONG SIGN XYEEM FAIB +16B40..16B43 ; AL # Lm [4] PAHAWH HMONG SIGN VOS SEEV..PAHAWH HMONG SIGN IB YAM +16B44 ; BA # Po PAHAWH HMONG SIGN XAUS +16B45 ; AL # So PAHAWH HMONG SIGN CIM TSOV ROG +16B50..16B59 ; NU # Nd [10] PAHAWH HMONG DIGIT ZERO..PAHAWH HMONG DIGIT NINE +16B5B..16B61 ; AL # No [7] PAHAWH HMONG NUMBER TENS..PAHAWH HMONG NUMBER TRILLIONS +16B63..16B77 ; AL # Lo [21] PAHAWH HMONG SIGN VOS LUB..PAHAWH HMONG SIGN CIM NRES TOS +16B7D..16B8F ; AL # Lo [19] PAHAWH HMONG CLAN SIGN TSHEEJ..PAHAWH HMONG CLAN SIGN VWJ +16E40..16E7F ; AL # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16E80..16E96 ; AL # No [23] MEDEFAIDRIN DIGIT ZERO..MEDEFAIDRIN DIGIT THREE ALTERNATE FORM +16E97..16E98 ; BA # Po [2] MEDEFAIDRIN COMMA..MEDEFAIDRIN FULL STOP +16E99..16E9A ; AL # Po [2] MEDEFAIDRIN SYMBOL AIVA..MEDEFAIDRIN EXCLAMATION OH +16F00..16F4A ; AL # Lo [75] MIAO LETTER PA..MIAO LETTER RTE +16F4F ; CM # Mn MIAO SIGN CONSONANT MODIFIER BAR +16F50 ; AL # Lo MIAO LETTER NASALIZATION +16F51..16F87 ; CM # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI +16F8F..16F92 ; CM # Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW +16F93..16F9F ; AL # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 +16FE0..16FE1 ; NS # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK +16FE2 ; NS # Po OLD CHINESE HOOK MARK +16FE3 ; NS # Lm OLD CHINESE ITERATION MARK +16FE4 ; GL # Mn KHITAN SMALL SCRIPT FILLER +16FF0..16FF1 ; CM # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +17000..187F7 ; ID # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 +18800..18AFF ; ID # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 +18B00..18CD5 ; AL # Lo [470] KHITAN SMALL SCRIPT CHARACTER-18B00..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18D00..18D08 ; ID # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +1AFF0..1AFF3 ; AL # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 +1AFF5..1AFFB ; AL # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 +1AFFD..1AFFE ; AL # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +1B000..1B0FF ; ID # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2 +1B100..1B122 ; ID # Lo [35] HENTAIGANA LETTER RE-3..KATAKANA LETTER ARCHAIC WU +1B132 ; CJ # Lo HIRAGANA LETTER SMALL KO +1B150..1B152 ; CJ # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; CJ # Lo KATAKANA LETTER SMALL KO +1B164..1B167 ; CJ # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1B170..1B2FB ; ID # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB +1BC00..1BC6A ; AL # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M +1BC70..1BC7C ; AL # Lo [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK +1BC80..1BC88 ; AL # Lo [9] DUPLOYAN AFFIX HIGH ACUTE..DUPLOYAN AFFIX HIGH VERTICAL +1BC90..1BC99 ; AL # Lo [10] DUPLOYAN AFFIX LOW ACUTE..DUPLOYAN AFFIX LOW ARROW +1BC9C ; AL # So DUPLOYAN SIGN O WITH CROSS +1BC9D..1BC9E ; CM # Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK +1BC9F ; BA # Po DUPLOYAN PUNCTUATION CHINOOK FULL STOP +1BCA0..1BCA3 ; CM # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP +1CF00..1CF2D ; CM # Mn [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT +1CF30..1CF46 ; CM # Mn [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG +1CF50..1CFC3 ; AL # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK +1D000..1D0F5 ; AL # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO +1D100..1D126 ; AL # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 +1D129..1D164 ; AL # So [60] MUSICAL SYMBOL MULTIPLE MEASURE REST..MUSICAL SYMBOL ONE HUNDRED TWENTY-EIGHTH NOTE +1D165..1D166 ; CM # Mc [2] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING SPRECHGESANG STEM +1D167..1D169 ; CM # Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3 +1D16A..1D16C ; AL # So [3] MUSICAL SYMBOL FINGERED TREMOLO-1..MUSICAL SYMBOL FINGERED TREMOLO-3 +1D16D..1D172 ; CM # Mc [6] MUSICAL SYMBOL COMBINING AUGMENTATION DOT..MUSICAL SYMBOL COMBINING FLAG-5 +1D173..1D17A ; CM # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE +1D17B..1D182 ; CM # Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE +1D183..1D184 ; AL # So [2] MUSICAL SYMBOL ARPEGGIATO UP..MUSICAL SYMBOL ARPEGGIATO DOWN +1D185..1D18B ; CM # Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE +1D18C..1D1A9 ; AL # So [30] MUSICAL SYMBOL RINFORZANDO..MUSICAL SYMBOL DEGREE SLASH +1D1AA..1D1AD ; CM # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1D1AE..1D1EA ; AL # So [61] MUSICAL SYMBOL PEDAL MARK..MUSICAL SYMBOL KORON +1D200..1D241 ; AL # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 +1D242..1D244 ; CM # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME +1D245 ; AL # So GREEK MUSICAL LEIMMA +1D2C0..1D2D3 ; AL # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN +1D2E0..1D2F3 ; AL # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN +1D300..1D356 ; AL # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING +1D360..1D378 ; AL # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE +1D400..1D454 ; AL # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G +1D456..1D49C ; AL # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A +1D49E..1D49F ; AL # Lu [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D +1D4A2 ; AL # Lu MATHEMATICAL SCRIPT CAPITAL G +1D4A5..1D4A6 ; AL # Lu [2] MATHEMATICAL SCRIPT CAPITAL J..MATHEMATICAL SCRIPT CAPITAL K +1D4A9..1D4AC ; AL # Lu [4] MATHEMATICAL SCRIPT CAPITAL N..MATHEMATICAL SCRIPT CAPITAL Q +1D4AE..1D4B9 ; AL # L& [12] MATHEMATICAL SCRIPT CAPITAL S..MATHEMATICAL SCRIPT SMALL D +1D4BB ; AL # Ll MATHEMATICAL SCRIPT SMALL F +1D4BD..1D4C3 ; AL # Ll [7] MATHEMATICAL SCRIPT SMALL H..MATHEMATICAL SCRIPT SMALL N +1D4C5..1D505 ; AL # L& [65] MATHEMATICAL SCRIPT SMALL P..MATHEMATICAL FRAKTUR CAPITAL B +1D507..1D50A ; AL # Lu [4] MATHEMATICAL FRAKTUR CAPITAL D..MATHEMATICAL FRAKTUR CAPITAL G +1D50D..1D514 ; AL # Lu [8] MATHEMATICAL FRAKTUR CAPITAL J..MATHEMATICAL FRAKTUR CAPITAL Q +1D516..1D51C ; AL # Lu [7] MATHEMATICAL FRAKTUR CAPITAL S..MATHEMATICAL FRAKTUR CAPITAL Y +1D51E..1D539 ; AL # L& [28] MATHEMATICAL FRAKTUR SMALL A..MATHEMATICAL DOUBLE-STRUCK CAPITAL B +1D53B..1D53E ; AL # Lu [4] MATHEMATICAL DOUBLE-STRUCK CAPITAL D..MATHEMATICAL DOUBLE-STRUCK CAPITAL G +1D540..1D544 ; AL # Lu [5] MATHEMATICAL DOUBLE-STRUCK CAPITAL I..MATHEMATICAL DOUBLE-STRUCK CAPITAL M +1D546 ; AL # Lu MATHEMATICAL DOUBLE-STRUCK CAPITAL O +1D54A..1D550 ; AL # Lu [7] MATHEMATICAL DOUBLE-STRUCK CAPITAL S..MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +1D552..1D6A5 ; AL # L& [340] MATHEMATICAL DOUBLE-STRUCK SMALL A..MATHEMATICAL ITALIC SMALL DOTLESS J +1D6A8..1D6C0 ; AL # Lu [25] MATHEMATICAL BOLD CAPITAL ALPHA..MATHEMATICAL BOLD CAPITAL OMEGA +1D6C1 ; AL # Sm MATHEMATICAL BOLD NABLA +1D6C2..1D6DA ; AL # Ll [25] MATHEMATICAL BOLD SMALL ALPHA..MATHEMATICAL BOLD SMALL OMEGA +1D6DB ; AL # Sm MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +1D6DC..1D6FA ; AL # L& [31] MATHEMATICAL BOLD EPSILON SYMBOL..MATHEMATICAL ITALIC CAPITAL OMEGA +1D6FB ; AL # Sm MATHEMATICAL ITALIC NABLA +1D6FC..1D714 ; AL # Ll [25] MATHEMATICAL ITALIC SMALL ALPHA..MATHEMATICAL ITALIC SMALL OMEGA +1D715 ; AL # Sm MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +1D716..1D734 ; AL # L& [31] MATHEMATICAL ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD ITALIC CAPITAL OMEGA +1D735 ; AL # Sm MATHEMATICAL BOLD ITALIC NABLA +1D736..1D74E ; AL # Ll [25] MATHEMATICAL BOLD ITALIC SMALL ALPHA..MATHEMATICAL BOLD ITALIC SMALL OMEGA +1D74F ; AL # Sm MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +1D750..1D76E ; AL # L& [31] MATHEMATICAL BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA +1D76F ; AL # Sm MATHEMATICAL SANS-SERIF BOLD NABLA +1D770..1D788 ; AL # Ll [25] MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA +1D789 ; AL # Sm MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +1D78A..1D7A8 ; AL # L& [31] MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL..MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA +1D7A9 ; AL # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA +1D7AA..1D7C2 ; AL # Ll [25] MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA..MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA +1D7C3 ; AL # Sm MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL +1D7C4..1D7CB ; AL # L& [8] MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL..MATHEMATICAL BOLD SMALL DIGAMMA +1D7CE..1D7FF ; NU # Nd [50] MATHEMATICAL BOLD DIGIT ZERO..MATHEMATICAL MONOSPACE DIGIT NINE +1D800..1D9FF ; AL # So [512] SIGNWRITING HAND-FIST INDEX..SIGNWRITING HEAD +1DA00..1DA36 ; CM # Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN +1DA37..1DA3A ; AL # So [4] SIGNWRITING AIR BLOW SMALL ROTATIONS..SIGNWRITING BREATH EXHALE +1DA3B..1DA6C ; CM # Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT +1DA6D..1DA74 ; AL # So [8] SIGNWRITING SHOULDER HIP SPINE..SIGNWRITING TORSO-FLOORPLANE TWISTING +1DA75 ; CM # Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS +1DA76..1DA83 ; AL # So [14] SIGNWRITING LIMB COMBINATION..SIGNWRITING LOCATION DEPTH +1DA84 ; CM # Mn SIGNWRITING LOCATION HEAD NECK +1DA85..1DA86 ; AL # So [2] SIGNWRITING LOCATION TORSO..SIGNWRITING LOCATION LIMBS DIGITS +1DA87..1DA8A ; BA # Po [4] SIGNWRITING COMMA..SIGNWRITING COLON +1DA8B ; AL # Po SIGNWRITING PARENTHESIS +1DA9B..1DA9F ; CM # Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6 +1DAA1..1DAAF ; CM # Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16 +1DF00..1DF09 ; AL # Ll [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK +1DF0A ; AL # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK +1DF0B..1DF1E ; AL # Ll [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; AL # Ll [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK +1E000..1E006 ; CM # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE +1E008..1E018 ; CM # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU +1E01B..1E021 ; CM # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI +1E023..1E024 ; CM # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS +1E026..1E02A ; CM # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E030..1E06D ; AL # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E08F ; CM # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E100..1E12C ; AL # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W +1E130..1E136 ; CM # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D +1E137..1E13D ; AL # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER +1E140..1E149 ; NU # Nd [10] NYIAKENG PUACHUE HMONG DIGIT ZERO..NYIAKENG PUACHUE HMONG DIGIT NINE +1E14E ; AL # Lo NYIAKENG PUACHUE HMONG LOGOGRAM NYAJ +1E14F ; AL # So NYIAKENG PUACHUE HMONG CIRCLED CA +1E290..1E2AD ; AL # Lo [30] TOTO LETTER PA..TOTO LETTER A +1E2AE ; CM # Mn TOTO SIGN RISING TONE +1E2C0..1E2EB ; AL # Lo [44] WANCHO LETTER AA..WANCHO LETTER YIH +1E2EC..1E2EF ; CM # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI +1E2F0..1E2F9 ; NU # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE +1E2FF ; PR # Sc WANCHO NGUN SIGN +1E4D0..1E4EA ; AL # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E4EB ; AL # Lm NAG MUNDARI SIGN OJOD +1E4EC..1E4EF ; CM # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E4F0..1E4F9 ; NU # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE +1E7E0..1E7E6 ; AL # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO +1E7E8..1E7EB ; AL # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE +1E7ED..1E7EE ; AL # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE +1E7F0..1E7FE ; AL # Lo [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE +1E800..1E8C4 ; AL # Lo [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON +1E8C7..1E8CF ; AL # No [9] MENDE KIKAKUI DIGIT ONE..MENDE KIKAKUI DIGIT NINE +1E8D0..1E8D6 ; CM # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS +1E900..1E943 ; AL # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA +1E944..1E94A ; CM # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA +1E94B ; AL # Lm ADLAM NASALIZATION MARK +1E950..1E959 ; NU # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE +1E95E..1E95F ; OP # Po [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK +1EC71..1ECAB ; AL # No [59] INDIC SIYAQ NUMBER ONE..INDIC SIYAQ NUMBER PREFIXED NINE +1ECAC ; PO # So INDIC SIYAQ PLACEHOLDER +1ECAD..1ECAF ; AL # No [3] INDIC SIYAQ FRACTION ONE QUARTER..INDIC SIYAQ FRACTION THREE QUARTERS +1ECB0 ; PO # Sc INDIC SIYAQ RUPEE MARK +1ECB1..1ECB4 ; AL # No [4] INDIC SIYAQ NUMBER ALTERNATE ONE..INDIC SIYAQ ALTERNATE LAKH MARK +1ED01..1ED2D ; AL # No [45] OTTOMAN SIYAQ NUMBER ONE..OTTOMAN SIYAQ NUMBER NINETY THOUSAND +1ED2E ; AL # So OTTOMAN SIYAQ MARRATAN +1ED2F..1ED3D ; AL # No [15] OTTOMAN SIYAQ ALTERNATE NUMBER TWO..OTTOMAN SIYAQ FRACTION ONE SIXTH +1EE00..1EE03 ; AL # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL +1EE05..1EE1F ; AL # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF +1EE21..1EE22 ; AL # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM +1EE24 ; AL # Lo ARABIC MATHEMATICAL INITIAL HEH +1EE27 ; AL # Lo ARABIC MATHEMATICAL INITIAL HAH +1EE29..1EE32 ; AL # Lo [10] ARABIC MATHEMATICAL INITIAL YEH..ARABIC MATHEMATICAL INITIAL QAF +1EE34..1EE37 ; AL # Lo [4] ARABIC MATHEMATICAL INITIAL SHEEN..ARABIC MATHEMATICAL INITIAL KHAH +1EE39 ; AL # Lo ARABIC MATHEMATICAL INITIAL DAD +1EE3B ; AL # Lo ARABIC MATHEMATICAL INITIAL GHAIN +1EE42 ; AL # Lo ARABIC MATHEMATICAL TAILED JEEM +1EE47 ; AL # Lo ARABIC MATHEMATICAL TAILED HAH +1EE49 ; AL # Lo ARABIC MATHEMATICAL TAILED YEH +1EE4B ; AL # Lo ARABIC MATHEMATICAL TAILED LAM +1EE4D..1EE4F ; AL # Lo [3] ARABIC MATHEMATICAL TAILED NOON..ARABIC MATHEMATICAL TAILED AIN +1EE51..1EE52 ; AL # Lo [2] ARABIC MATHEMATICAL TAILED SAD..ARABIC MATHEMATICAL TAILED QAF +1EE54 ; AL # Lo ARABIC MATHEMATICAL TAILED SHEEN +1EE57 ; AL # Lo ARABIC MATHEMATICAL TAILED KHAH +1EE59 ; AL # Lo ARABIC MATHEMATICAL TAILED DAD +1EE5B ; AL # Lo ARABIC MATHEMATICAL TAILED GHAIN +1EE5D ; AL # Lo ARABIC MATHEMATICAL TAILED DOTLESS NOON +1EE5F ; AL # Lo ARABIC MATHEMATICAL TAILED DOTLESS QAF +1EE61..1EE62 ; AL # Lo [2] ARABIC MATHEMATICAL STRETCHED BEH..ARABIC MATHEMATICAL STRETCHED JEEM +1EE64 ; AL # Lo ARABIC MATHEMATICAL STRETCHED HEH +1EE67..1EE6A ; AL # Lo [4] ARABIC MATHEMATICAL STRETCHED HAH..ARABIC MATHEMATICAL STRETCHED KAF +1EE6C..1EE72 ; AL # Lo [7] ARABIC MATHEMATICAL STRETCHED MEEM..ARABIC MATHEMATICAL STRETCHED QAF +1EE74..1EE77 ; AL # Lo [4] ARABIC MATHEMATICAL STRETCHED SHEEN..ARABIC MATHEMATICAL STRETCHED KHAH +1EE79..1EE7C ; AL # Lo [4] ARABIC MATHEMATICAL STRETCHED DAD..ARABIC MATHEMATICAL STRETCHED DOTLESS BEH +1EE7E ; AL # Lo ARABIC MATHEMATICAL STRETCHED DOTLESS FEH +1EE80..1EE89 ; AL # Lo [10] ARABIC MATHEMATICAL LOOPED ALEF..ARABIC MATHEMATICAL LOOPED YEH +1EE8B..1EE9B ; AL # Lo [17] ARABIC MATHEMATICAL LOOPED LAM..ARABIC MATHEMATICAL LOOPED GHAIN +1EEA1..1EEA3 ; AL # Lo [3] ARABIC MATHEMATICAL DOUBLE-STRUCK BEH..ARABIC MATHEMATICAL DOUBLE-STRUCK DAL +1EEA5..1EEA9 ; AL # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH +1EEAB..1EEBB ; AL # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN +1EEF0..1EEF1 ; AL # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1F000..1F02B ; ID # So [44] MAHJONG TILE EAST WIND..MAHJONG TILE BACK +1F02C..1F02F ; ID # Cn [4] .. +1F030..1F093 ; ID # So [100] DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 +1F094..1F09F ; ID # Cn [12] .. +1F0A0..1F0AE ; ID # So [15] PLAYING CARD BACK..PLAYING CARD KING OF SPADES +1F0AF..1F0B0 ; ID # Cn [2] .. +1F0B1..1F0BF ; ID # So [15] PLAYING CARD ACE OF HEARTS..PLAYING CARD RED JOKER +1F0C0 ; ID # Cn +1F0C1..1F0CF ; ID # So [15] PLAYING CARD ACE OF DIAMONDS..PLAYING CARD BLACK JOKER +1F0D0 ; ID # Cn +1F0D1..1F0F5 ; ID # So [37] PLAYING CARD ACE OF CLUBS..PLAYING CARD TRUMP-21 +1F0F6..1F0FF ; ID # Cn [10] .. +1F100..1F10C ; AI # No [13] DIGIT ZERO FULL STOP..DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ZERO +1F10D..1F10F ; ID # So [3] CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F110..1F12D ; AI # So [30] PARENTHESIZED LATIN CAPITAL LETTER A..CIRCLED CD +1F12E..1F12F ; AL # So [2] CIRCLED WZ..COPYLEFT SYMBOL +1F130..1F169 ; AI # So [58] SQUARED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z +1F16A..1F16C ; AL # So [3] RAISED MC SIGN..RAISED MR SIGN +1F16D..1F16F ; ID # So [3] CIRCLED CC..CIRCLED HUMAN FIGURE +1F170..1F1AC ; AI # So [61] NEGATIVE SQUARED LATIN CAPITAL LETTER A..SQUARED VOD +1F1AD ; ID # So MASK WORK SYMBOL +1F1AE..1F1E5 ; ID # Cn [56] .. +1F1E6..1F1FF ; RI # So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z +1F200..1F202 ; ID # So [3] SQUARE HIRAGANA HOKA..SQUARED KATAKANA SA +1F203..1F20F ; ID # Cn [13] .. +1F210..1F23B ; ID # So [44] SQUARED CJK UNIFIED IDEOGRAPH-624B..SQUARED CJK UNIFIED IDEOGRAPH-914D +1F23C..1F23F ; ID # Cn [4] .. +1F240..1F248 ; ID # So [9] TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C..TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 +1F249..1F24F ; ID # Cn [7] .. +1F250..1F251 ; ID # So [2] CIRCLED IDEOGRAPH ADVANTAGE..CIRCLED IDEOGRAPH ACCEPT +1F252..1F25F ; ID # Cn [14] .. +1F260..1F265 ; ID # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI +1F266..1F2FF ; ID # Cn [154] .. +1F300..1F384 ; ID # So [133] CYCLONE..CHRISTMAS TREE +1F385 ; EB # So FATHER CHRISTMAS +1F386..1F39B ; ID # So [22] FIREWORKS..CONTROL KNOBS +1F39C..1F39D ; AL # So [2] BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES +1F39E..1F3B4 ; ID # So [23] FILM FRAMES..FLOWER PLAYING CARDS +1F3B5..1F3B6 ; AL # So [2] MUSICAL NOTE..MULTIPLE MUSICAL NOTES +1F3B7..1F3BB ; ID # So [5] SAXOPHONE..VIOLIN +1F3BC ; AL # So MUSICAL SCORE +1F3BD..1F3C1 ; ID # So [5] RUNNING SHIRT WITH SASH..CHEQUERED FLAG +1F3C2..1F3C4 ; EB # So [3] SNOWBOARDER..SURFER +1F3C5..1F3C6 ; ID # So [2] SPORTS MEDAL..TROPHY +1F3C7 ; EB # So HORSE RACING +1F3C8..1F3C9 ; ID # So [2] AMERICAN FOOTBALL..RUGBY FOOTBALL +1F3CA..1F3CC ; EB # So [3] SWIMMER..GOLFER +1F3CD..1F3FA ; ID # So [46] RACING MOTORCYCLE..AMPHORA +1F3FB..1F3FF ; EM # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 +1F400..1F441 ; ID # So [66] RAT..EYE +1F442..1F443 ; EB # So [2] EAR..NOSE +1F444..1F445 ; ID # So [2] MOUTH..TONGUE +1F446..1F450 ; EB # So [11] WHITE UP POINTING BACKHAND INDEX..OPEN HANDS SIGN +1F451..1F465 ; ID # So [21] CROWN..BUSTS IN SILHOUETTE +1F466..1F478 ; EB # So [19] BOY..PRINCESS +1F479..1F47B ; ID # So [3] JAPANESE OGRE..GHOST +1F47C ; EB # So BABY ANGEL +1F47D..1F480 ; ID # So [4] EXTRATERRESTRIAL ALIEN..SKULL +1F481..1F483 ; EB # So [3] INFORMATION DESK PERSON..DANCER +1F484 ; ID # So LIPSTICK +1F485..1F487 ; EB # So [3] NAIL POLISH..HAIRCUT +1F488..1F48E ; ID # So [7] BARBER POLE..GEM STONE +1F48F ; EB # So KISS +1F490 ; ID # So BOUQUET +1F491 ; EB # So COUPLE WITH HEART +1F492..1F49F ; ID # So [14] WEDDING..HEART DECORATION +1F4A0 ; AL # So DIAMOND SHAPE WITH A DOT INSIDE +1F4A1 ; ID # So ELECTRIC LIGHT BULB +1F4A2 ; AL # So ANGER SYMBOL +1F4A3 ; ID # So BOMB +1F4A4 ; AL # So SLEEPING SYMBOL +1F4A5..1F4A9 ; ID # So [5] COLLISION SYMBOL..PILE OF POO +1F4AA ; EB # So FLEXED BICEPS +1F4AB..1F4AE ; ID # So [4] DIZZY SYMBOL..WHITE FLOWER +1F4AF ; AL # So HUNDRED POINTS SYMBOL +1F4B0 ; ID # So MONEY BAG +1F4B1..1F4B2 ; AL # So [2] CURRENCY EXCHANGE..HEAVY DOLLAR SIGN +1F4B3..1F4FF ; ID # So [77] CREDIT CARD..PRAYER BEADS +1F500..1F506 ; AL # So [7] TWISTED RIGHTWARDS ARROWS..HIGH BRIGHTNESS SYMBOL +1F507..1F516 ; ID # So [16] SPEAKER WITH CANCELLATION STROKE..BOOKMARK +1F517..1F524 ; AL # So [14] LINK SYMBOL..INPUT SYMBOL FOR LATIN LETTERS +1F525..1F531 ; ID # So [13] FIRE..TRIDENT EMBLEM +1F532..1F549 ; AL # So [24] BLACK SQUARE BUTTON..OM SYMBOL +1F54A..1F573 ; ID # So [42] DOVE OF PEACE..HOLE +1F574..1F575 ; EB # So [2] MAN IN BUSINESS SUIT LEVITATING..SLEUTH OR SPY +1F576..1F579 ; ID # So [4] DARK SUNGLASSES..JOYSTICK +1F57A ; EB # So MAN DANCING +1F57B..1F58F ; ID # So [21] LEFT HAND TELEPHONE RECEIVER..TURNED OK HAND SIGN +1F590 ; EB # So RAISED HAND WITH FINGERS SPLAYED +1F591..1F594 ; ID # So [4] REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND +1F595..1F596 ; EB # So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS +1F597..1F5D3 ; ID # So [61] WHITE DOWN POINTING LEFT HAND INDEX..SPIRAL CALENDAR PAD +1F5D4..1F5DB ; AL # So [8] DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL +1F5DC..1F5F3 ; ID # So [24] COMPRESSION..BALLOT BOX WITH BALLOT +1F5F4..1F5F9 ; AL # So [6] BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK +1F5FA..1F5FF ; ID # So [6] WORLD MAP..MOYAI +1F600..1F644 ; ID # So [69] GRINNING FACE..FACE WITH ROLLING EYES +1F645..1F647 ; EB # So [3] FACE WITH NO GOOD GESTURE..PERSON BOWING DEEPLY +1F648..1F64A ; ID # So [3] SEE-NO-EVIL MONKEY..SPEAK-NO-EVIL MONKEY +1F64B..1F64F ; EB # So [5] HAPPY PERSON RAISING ONE HAND..PERSON WITH FOLDED HANDS +1F650..1F675 ; AL # So [38] NORTH WEST POINTING LEAF..SWASH AMPERSAND ORNAMENT +1F676..1F678 ; QU # So [3] SANS-SERIF HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT..SANS-SERIF HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT +1F679..1F67B ; NS # So [3] HEAVY INTERROBANG ORNAMENT..HEAVY SANS-SERIF INTERROBANG ORNAMENT +1F67C..1F67F ; AL # So [4] VERY HEAVY SOLIDUS..REVERSE CHECKER BOARD +1F680..1F6A2 ; ID # So [35] ROCKET..SHIP +1F6A3 ; EB # So ROWBOAT +1F6A4..1F6B3 ; ID # So [16] SPEEDBOAT..NO BICYCLES +1F6B4..1F6B6 ; EB # So [3] BICYCLIST..PEDESTRIAN +1F6B7..1F6BF ; ID # So [9] NO PEDESTRIANS..SHOWER +1F6C0 ; EB # So BATH +1F6C1..1F6CB ; ID # So [11] BATHTUB..COUCH AND LAMP +1F6CC ; EB # So SLEEPING ACCOMMODATION +1F6CD..1F6D7 ; ID # So [11] SHOPPING BAGS..ELEVATOR +1F6D8..1F6DB ; ID # Cn [4] .. +1F6DC..1F6EC ; ID # So [17] WIRELESS..AIRPLANE ARRIVING +1F6ED..1F6EF ; ID # Cn [3] .. +1F6F0..1F6FC ; ID # So [13] SATELLITE..ROLLER SKATE +1F6FD..1F6FF ; ID # Cn [3] .. +1F700..1F773 ; AL # So [116] ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE +1F774..1F776 ; ID # So [3] LOT OF FORTUNE..LUNAR ECLIPSE +1F777..1F77A ; ID # Cn [4] .. +1F77B..1F77F ; ID # So [5] HAUMEA..ORCUS +1F780..1F7D4 ; AL # So [85] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..HEAVY TWELVE POINTED PINWHEEL STAR +1F7D5..1F7D9 ; ID # So [5] CIRCLED TRIANGLE..NINE POINTED WHITE STAR +1F7DA..1F7DF ; ID # Cn [6] .. +1F7E0..1F7EB ; ID # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE +1F7EC..1F7EF ; ID # Cn [4] .. +1F7F0 ; ID # So HEAVY EQUALS SIGN +1F7F1..1F7FF ; ID # Cn [15] .. +1F800..1F80B ; AL # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD +1F80C..1F80F ; ID # Cn [4] .. +1F810..1F847 ; AL # So [56] LEFTWARDS ARROW WITH SMALL EQUILATERAL ARROWHEAD..DOWNWARDS HEAVY ARROW +1F848..1F84F ; ID # Cn [8] .. +1F850..1F859 ; AL # So [10] LEFTWARDS SANS-SERIF ARROW..UP DOWN SANS-SERIF ARROW +1F85A..1F85F ; ID # Cn [6] .. +1F860..1F887 ; AL # So [40] WIDE-HEADED LEFTWARDS LIGHT BARB ARROW..WIDE-HEADED SOUTH WEST VERY HEAVY BARB ARROW +1F888..1F88F ; ID # Cn [8] .. +1F890..1F8AD ; AL # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS +1F8AE..1F8AF ; ID # Cn [2] .. +1F8B0..1F8B1 ; ID # So [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST +1F8B2..1F8FF ; ID # Cn [78] .. +1F900..1F90B ; AL # So [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT +1F90C ; EB # So PINCHED FINGERS +1F90D..1F90E ; ID # So [2] WHITE HEART..BROWN HEART +1F90F ; EB # So PINCHING HAND +1F910..1F917 ; ID # So [8] ZIPPER-MOUTH FACE..HUGGING FACE +1F918..1F91F ; EB # So [8] SIGN OF THE HORNS..I LOVE YOU HAND SIGN +1F920..1F925 ; ID # So [6] FACE WITH COWBOY HAT..LYING FACE +1F926 ; EB # So FACE PALM +1F927..1F92F ; ID # So [9] SNEEZING FACE..SHOCKED FACE WITH EXPLODING HEAD +1F930..1F939 ; EB # So [10] PREGNANT WOMAN..JUGGLING +1F93A..1F93B ; ID # So [2] FENCER..MODERN PENTATHLON +1F93C..1F93E ; EB # So [3] WRESTLERS..HANDBALL +1F93F..1F976 ; ID # So [56] DIVING MASK..FREEZING FACE +1F977 ; EB # So NINJA +1F978..1F9B4 ; ID # So [61] DISGUISED FACE..BONE +1F9B5..1F9B6 ; EB # So [2] LEG..FOOT +1F9B7 ; ID # So TOOTH +1F9B8..1F9B9 ; EB # So [2] SUPERHERO..SUPERVILLAIN +1F9BA ; ID # So SAFETY VEST +1F9BB ; EB # So EAR WITH HEARING AID +1F9BC..1F9CC ; ID # So [17] MOTORIZED WHEELCHAIR..TROLL +1F9CD..1F9CF ; EB # So [3] STANDING PERSON..DEAF PERSON +1F9D0 ; ID # So FACE WITH MONOCLE +1F9D1..1F9DD ; EB # So [13] ADULT..ELF +1F9DE..1F9FF ; ID # So [34] GENIE..NAZAR AMULET +1FA00..1FA53 ; AL # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP +1FA54..1FA5F ; ID # Cn [12] .. +1FA60..1FA6D ; ID # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER +1FA6E..1FA6F ; ID # Cn [2] .. +1FA70..1FA7C ; ID # So [13] BALLET SHOES..CRUTCH +1FA7D..1FA7F ; ID # Cn [3] .. +1FA80..1FA88 ; ID # So [9] YO-YO..FLUTE +1FA89..1FA8F ; ID # Cn [7] .. +1FA90..1FABD ; ID # So [46] RINGED PLANET..WING +1FABE ; ID # Cn +1FABF..1FAC2 ; ID # So [4] GOOSE..PEOPLE HUGGING +1FAC3..1FAC5 ; EB # So [3] PREGNANT MAN..PERSON WITH CROWN +1FAC6..1FACD ; ID # Cn [8] .. +1FACE..1FADB ; ID # So [14] MOOSE..PEA POD +1FADC..1FADF ; ID # Cn [4] .. +1FAE0..1FAE8 ; ID # So [9] MELTING FACE..SHAKING FACE +1FAE9..1FAEF ; ID # Cn [7] .. +1FAF0..1FAF8 ; EB # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FAF9..1FAFF ; ID # Cn [7] .. +1FB00..1FB92 ; AL # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK +1FB94..1FBCA ; AL # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON +1FBF0..1FBF9 ; NU # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +1FC00..1FFFD ; ID # Cn [1022] .. +20000..2A6DF ; ID # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF +2A6E0..2A6FF ; ID # Cn [32] .. +2A700..2B739 ; ID # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B73A..2B73F ; ID # Cn [6] .. +2B740..2B81D ; ID # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B81E..2B81F ; ID # Cn [2] .. +2B820..2CEA1 ; ID # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEA2..2CEAF ; ID # Cn [14] .. +2CEB0..2EBE0 ; ID # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBE1..2EBEF ; ID # Cn [15] .. +2EBF0..2EE5D ; ID # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +2EE5E..2F7FF ; ID # Cn [2466] .. +2F800..2FA1D ; ID # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D +2FA1E..2FA1F ; ID # Cn [2] .. +2FA20..2FFFD ; ID # Cn [1502] .. +30000..3134A ; ID # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +3134B..3134F ; ID # Cn [5] .. +31350..323AF ; ID # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +323B0..3FFFD ; ID # Cn [56398] .. +E0001 ; CM # Cf LANGUAGE TAG +E0020..E007F ; CM # Cf [96] TAG SPACE..CANCEL TAG +E0100..E01EF ; CM # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 +F0000..FFFFD ; XX # Co [65534] .. +100000..10FFFD ; XX # Co [65534] .. + +# EOF diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs new file mode 100644 index 000000000..49e2298b0 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs @@ -0,0 +1,366 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeEastAsianWidthClass; +using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeGeneralCategory; +using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeLineBreakClass; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +/// Enumerates line break offsets. +internal ref struct LineBreakEnumerator +{ + private readonly UtfEnumeratorFlags enumeratorFlags; + private readonly int dataLength; + + private UtfEnumerator enumerator; + private Entry class1; + private Entry class2; + + private Entry space1; + private Entry space2; + private bool spaceStreak; + + private int consecutiveRegionalIndicators; + + private bool finished; + + /// Initializes a new instance of the struct. + /// UTF-N byte sequence. + /// Flags to pass to sub-enumerator. + public LineBreakEnumerator( + ReadOnlySpan data, + UtfEnumeratorFlags enumeratorFlags = UtfEnumeratorFlags.Default) + { + this.enumerator = UtfEnumerator.From(data, enumeratorFlags); + this.enumeratorFlags = enumeratorFlags; + this.dataLength = data.Length; + } + + private LineBreakEnumerator( + int dataLength, + UtfEnumerator enumerator, + UtfEnumeratorFlags enumeratorFlags) + { + this.dataLength = dataLength; + this.enumerator = enumerator; + this.enumeratorFlags = enumeratorFlags; + } + + private enum LineBreakMode : byte + { + Prohibited, + Mandatory, + Optional, + } + + /// + public (int ByteOffset, bool Mandatory) Current { get; private set; } + + /// + [SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement", Justification = "No")] + public bool MoveNext() + { + if (this.finished) + return false; + + while (this.enumerator.MoveNext()) + { + var effectiveInt = + this.enumerator.Current.IsSeStringPayload + ? UtfEnumerator.RepresentativeCharFor(this.enumerator.Current.MacroCode) + : this.enumerator.Current.EffectiveInt; + if (effectiveInt == -1) + continue; + + switch (this.HandleCharacter(effectiveInt)) + { + case LineBreakMode.Mandatory: + this.Current = (this.enumerator.Current.ByteOffset, true); + return true; + case LineBreakMode.Optional: + this.Current = (this.enumerator.Current.ByteOffset, false); + return true; + case LineBreakMode.Prohibited: + default: + continue; + } + } + + // Start and end of text: + // LB3 Always break at the end of text. + this.Current = (this.dataLength, true); + this.finished = true; + return true; + } + + /// + public LineBreakEnumerator GetEnumerator() => + new(this.dataLength, this.enumerator.GetEnumerator(), this.enumeratorFlags); + + private LineBreakMode HandleCharacter(int c) + { + // https://unicode.org/reports/tr14/#Algorithm + + // 6.1 Non-tailorable Line Breaking Rules + // Resolve line breaking classes: + // LB1 Assign a line breaking class to each code point of the input. + // => done inside Entry ctor + var curr = new Entry(c); + var (prev1, prev2) = (this.class1, this.class2); + (this.class2, this.class1) = (this.class1, curr); + + if (curr.Class == RI) + this.consecutiveRegionalIndicators++; + else + this.consecutiveRegionalIndicators = 0; + + var (prevSpaceStreak, prevSpace1, prevSpace2) = (this.spaceStreak, this.space1, this.space2); + this.spaceStreak = curr.Class == SP; + if (this.spaceStreak && !prevSpaceStreak) + (this.space1, this.space2) = (prev1, prev2); + + if (!prevSpaceStreak) + (prevSpace1, prevSpace2) = (prev1, prev2); + + // Start and end of text: + // LB2 Never break at the start of text. + if (prev1.Class is sot) + return LineBreakMode.Prohibited; + + // Mandatory breaks: + // LB4 Always break after hard line breaks. + if (prev1.Class is BK) + return LineBreakMode.Mandatory; + + // LB5 Treat CR followed by LF, as well as CR, LF, and NL as hard line breaks. + if (prev2.Class is CR && prev1.Class is LF) + return LineBreakMode.Mandatory; + if (prev1.Class is CR && curr.Class is LF) + return LineBreakMode.Prohibited; + if (prev1.Class is CR or LF or NL) + return LineBreakMode.Mandatory; + + // LB6 Do not break before hard line breaks. + if (curr.Class is BK or CR or LF or NL) + return LineBreakMode.Prohibited; + + // Explicit breaks and non-breaks: + // LB7 Do not break before spaces or zero width space. + if (curr.Class is SP or ZW) + return LineBreakMode.Prohibited; + + // LB8 Break before any character following a zero-width space, even if one or more spaces intervene. + if (prev1.Class is ZW) + return LineBreakMode.Optional; + if (prevSpaceStreak && prevSpace1.Class is ZW) + return LineBreakMode.Optional; + + // LB8a Do not break after a zero width joiner. + if (prev1.Class is ZWJ) + return LineBreakMode.Prohibited; + + // Combining marks: + // LB9 Do not break a combining character sequence; treat it as if it has the line breaking class of the base character in all of the following rules. Treat ZWJ as if it were CM. + // ? + + // LB10 Treat any remaining combining mark or ZWJ as AL. + if (curr.Class is CM or ZWJ) + this.class1 = curr = new('A'); + + // Word joiner: + // LB11 Do not break before or after Word joiner and related characters. + if (prev1.Class is WJ || curr.Class is WJ) + return LineBreakMode.Prohibited; + + // Non-breaking characters: + // LB12 Do not break after NBSP and related characters. + if (prev1.Class is GL) + return LineBreakMode.Prohibited; + + // 6.2 Tailorable Line Breaking Rules + // Non-breaking characters: + // LB12a Do not break before NBSP and related characters, except after spaces and hyphens. + if (prev1.Class is not SP and not BA and not HY && + curr.Class is GL) + return LineBreakMode.Prohibited; + + // Opening and closing: + // LB13 Do not break before ‘]’ or ‘!’ or ‘;’ or ‘/’, even after spaces. + if (curr.Class is CL or CP or EX or IS or SY) + return LineBreakMode.Prohibited; + + // LB14 Do not break after ‘[’, even after spaces. + if (prevSpace1.Class is OP) + return LineBreakMode.Prohibited; + + // LB15a Do not break after an unresolved initial punctuation that lies at the start of the line, after a space, after opening punctuation, or after an unresolved quotation mark, even after spaces. + if (prevSpace2.Class is sot or BK or CR or LF or NL or OP or QU or GL or SP or ZW && + prevSpace1.Class is QU && + prevSpace1.GeneralCategory is Pi) + return LineBreakMode.Prohibited; + + var next = this.enumerator.TryPeekNext(out var nextSubsequence, out _) + ? new Entry(nextSubsequence.EffectiveChar) + : new(eot); + + // LB15b Do not break before an unresolved final punctuation that lies at the end of the line, before a space, before a prohibited break, or before an unresolved quotation mark, even after spaces. + if (curr.Class is QU && curr.GeneralCategory is Pf && + next.Class is SP or GL or WJ or CL or QU or CP or EX or IS or SY or BK or CR or LF or NL or ZW or eot) + return LineBreakMode.Prohibited; + + // LB16 Do not break between closing punctuation and a nonstarter (lb=NS), even with intervening spaces. + if (prevSpace1.Class is CL or CP && next.Class is NS) + return LineBreakMode.Prohibited; + + // LB17 Do not break within ‘——’, even with intervening spaces. + if (prevSpace1.Class is B2 && next.Class is B2) + return LineBreakMode.Prohibited; + + // Spaces: + // LB18 Break after spaces. + if (prev1.Class is SP) + return LineBreakMode.Optional; + + // Special case rules: + // LB19 Do not break before or after quotation marks, such as ‘ ” ’. + if (prev1.Class is QU || curr.Class is QU) + return LineBreakMode.Prohibited; + + // LB20 Break before and after unresolved CB. + if (prev1.Class is CB || curr.Class is CB) + return LineBreakMode.Optional; + + // LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small kana, and other non-starters, or after acute accents. + if (curr.Class is BA or HY or NS || prev1.Class is BB) + return LineBreakMode.Prohibited; + + // LB21a Don't break after Hebrew + Hyphen. + if (prev2.Class is HL && prev1.Class is HY or BA) + return LineBreakMode.Prohibited; + + // LB21b Don’t break between Solidus and Hebrew letters. + if (prev1.Class is SY && curr.Class is HL) + return LineBreakMode.Prohibited; + + // LB22 Do not break before ellipses. + if (curr.Class is IN) + return LineBreakMode.Prohibited; + + // Numbers: + // LB23 Do not break between digits and letters. + if (prev1.Class is AL or HL && curr.Class is NU) + return LineBreakMode.Prohibited; + if (prev1.Class is NU && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + + // LB23a Do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes. + if (prev1.Class is PR && curr.Class is ID or EB or EM) + return LineBreakMode.Prohibited; + if (prev1.Class is ID or EB or EM && curr.Class is PR) + return LineBreakMode.Prohibited; + + // LB24 Do not break between numeric prefix/postfix and letters, or between letters and prefix/postfix. + if (prev1.Class is PR or PO && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + if (prev1.Class is AL or HL && curr.Class is PR or PO) + return LineBreakMode.Prohibited; + + // LB25 Do not break between the following pairs of classes relevant to numbers: + if ((prev1.Class, curr.Class) is (CL, PO) or (CP, PO) or (CL, PR) or (CP, PR) or (NU, PO) or (NU, PR) + or (PO, OP) or (PO, NU) or (PR, OP) or (PR, NU) or (HY, NU) or (IS, NU) or (NU, NU) or (SY, NU)) + return LineBreakMode.Prohibited; + + // Korean syllable blocks: + // LB26 Do not break a Korean syllable. + if (prev1.Class is JL && curr.Class is JL or JV or H2 or H3) + return LineBreakMode.Prohibited; + if (prev1.Class is JV or H2 && curr.Class is JV or JT) + return LineBreakMode.Prohibited; + + // LB27 Treat a Korean Syllable Block the same as ID. + if (prev1.Class is JL or JV or JT or H2 or H3 && curr.Class is PO) + return LineBreakMode.Prohibited; + if (prev1.Class is PR && curr.Class is JL or JV or JT or H2 or H3) + return LineBreakMode.Prohibited; + + // Finally, join alphabetic letters into words and break everything else. + // LB28 Do not break between alphabetics (“at”). + if (prev1.Class is AL or HL && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + + // LB28a Do not break inside the orthographic syllables of Brahmic scripts. + // TODO: what's "◌"? + if (prev1.Class is AP && curr.Class is AK or AS) + return LineBreakMode.Prohibited; + if (prev1.Class is AK or AS && curr.Class is VF or VI) + return LineBreakMode.Prohibited; + if (prev2.Class is AK or AS && prev1.Class is VI && curr.Class is AK) + return LineBreakMode.Prohibited; + if (prev1.Class is AK or AS && curr.Class is AK or AS && next.Class is VF) + return LineBreakMode.Prohibited; + + // LB29 Do not break between numeric punctuation and alphabetics (“e.g.”). + if (prev1.Class is IS && curr.Class is AL or HL) + return LineBreakMode.Prohibited; + + // LB30 Do not break between letters, numbers, or ordinary symbols and opening or closing parentheses. + if (prev1.Class is AL or HL or NU && + curr.Class is OP && curr.EastAsianWidth is not F and not W and not H) + return LineBreakMode.Prohibited; + if (prev1.Class is CP && prev1.EastAsianWidth is not F and not W and not H && + curr.Class is AL or HL or NU) + return LineBreakMode.Prohibited; + + // LB30a Break between two regional indicator symbols if and only if there are an even number of regional indicators preceding the position of the break. + if (this.consecutiveRegionalIndicators % 2 == 0) + return LineBreakMode.Optional; + + // LB30b Do not break between an emoji base (or potential emoji) and an emoji modifier. + if (prev1.Class is EB && curr.Class is EM) + return LineBreakMode.Prohibited; + if (prev1.GeneralCategory is Cn && + (prev1.EmojiProperty & UnicodeEmojiProperty.Extended_Pictographic) != 0 && + curr.Class is EM) + return LineBreakMode.Prohibited; + + // LB31 Break everywhere else. + return LineBreakMode.Optional; + } + + private readonly struct Entry + { + public readonly UnicodeLineBreakClass Class; + public readonly UnicodeGeneralCategory GeneralCategory; + public readonly UnicodeEastAsianWidthClass EastAsianWidth; + public readonly UnicodeEmojiProperty EmojiProperty; + + public Entry(int c) + { + this.Class = UnicodeData.LineBreak[c] switch + { + AI or SG or XX => AL, + SA when UnicodeData.GeneralCategory[c] is Mn or Mc => CM, + SA => AL, + CJ => NS, + var x => x, + }; + this.GeneralCategory = UnicodeData.GeneralCategory[c]; + this.EastAsianWidth = UnicodeData.EastAsianWidth[c]; + this.EmojiProperty = UnicodeData.EmojiProperty[c]; + } + + public Entry( + UnicodeLineBreakClass lineBreakClass, + UnicodeGeneralCategory generalCategory = Cn, + UnicodeEastAsianWidthClass eastAsianWidth = N, + UnicodeEmojiProperty emojiProperty = 0) + { + this.Class = lineBreakClass; + this.GeneralCategory = generalCategory; + this.EastAsianWidth = eastAsianWidth; + this.EmojiProperty = emojiProperty; + } + } +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs new file mode 100644 index 000000000..a97fb95a4 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs @@ -0,0 +1,118 @@ +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +/// Stores unicode data. +internal static class UnicodeData +{ + /// Line break classes. + public static readonly UnicodeLineBreakClass[] LineBreak; + + /// East asian width classes. + public static readonly UnicodeEastAsianWidthClass[] EastAsianWidth; + + /// General categories. + public static readonly UnicodeGeneralCategory[] GeneralCategory; + + /// Emoji properties. + public static readonly UnicodeEmojiProperty[] EmojiProperty; + + static UnicodeData() + { + // File is from https://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt + LineBreak = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("LineBreak.txt")!, + UnicodeLineBreakClass.XX); + + // https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt + EastAsianWidth = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("EastAsianWidth.txt")!, + UnicodeEastAsianWidthClass.N); + + // https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt + GeneralCategory = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("DerivedGeneralCategory.txt")!, + UnicodeGeneralCategory.Cn); + + // https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt + EmojiProperty = + Parse( + typeof(UnicodeData).Assembly.GetManifestResourceStream("emoji-data.txt")!, + UnicodeEmojiProperty.Emoji); + } + + private static T[] Parse(Stream stream, T defaultValue) + where T : unmanaged, Enum + { + if (Unsafe.SizeOf() != 1) + throw new InvalidOperationException("Enum must be of size 1 byte"); + + var isFlag = typeof(T).GetCustomAttribute() is not null; + using var sr = new StreamReader(stream); + var res = new T[0x110000]; + res.AsSpan().Fill(defaultValue); + for (string? line; (line = sr.ReadLine()) != null;) + { + var span = line.AsSpan(); + + // strip comment + var i = span.IndexOf('#'); + if (i != -1) + span = span[..i]; + + span = span.Trim(); + if (span.IsEmpty) + continue; + + // find delimiter + i = span.IndexOf(';'); + if (i == -1) + throw new InvalidDataException(); + + var range = span[..i].Trim(); + var entry = Enum.Parse(span[(i + 1)..].Trim()); + + i = range.IndexOf(".."); + int from, to; + if (i == -1) + { + from = int.Parse(range, NumberStyles.HexNumber); + to = from + 1; + } + else + { + from = int.Parse(range[..i], NumberStyles.HexNumber); + to = int.Parse(range[(i + 2)..], NumberStyles.HexNumber) + 1; + } + + if (from > char.MaxValue) + continue; + + from = Math.Min(from, char.MaxValue); + to = Math.Min(to, char.MaxValue); + if (isFlag) + { + foreach (ref var v in res.AsSpan()[from..to]) + { + unsafe + { + fixed (void* p = &v) + *(byte*)p |= *(byte*)&entry; + } + } + } + else + { + res.AsSpan()[from..to].Fill(entry); + } + } + + return res; + } +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs new file mode 100644 index 000000000..e6d1cbd5f --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs @@ -0,0 +1,22 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1600:Elements should be documented", + Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1602:Enumeration items should be documented", + Justification = "Unicode Data")] +internal enum UnicodeEastAsianWidthClass : byte +{ + A = 0, + F = 1, + H = 2, + N = 3, + Na = 4, + W = 5, +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs new file mode 100644 index 000000000..8847e08fe --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1600:Elements should be documented", + Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1602:Enumeration items should be documented", + Justification = "Unicode Data")] +[Flags] +internal enum UnicodeEmojiProperty : byte +{ + Emoji = 1 << 0, + Emoji_Presentation = 1 << 1, + Emoji_Modifier_Base = 1 << 2, + Emoji_Modifier = 1 << 3, + Emoji_Component = 1 << 4, + Extended_Pictographic = 1 << 5, +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs new file mode 100644 index 000000000..0c0614a5f --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1600:Elements should be documented", + Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1602:Enumeration items should be documented", + Justification = "Unicode Data")] +internal enum UnicodeGeneralCategory : byte +{ + Cn = 0, + Lu = 1, + Ll = 2, + Lt = 3, + Lm = 4, + Lo = 5, + Mn = 6, + Me = 7, + Mc = 8, + Nd = 9, + Nl = 10, + No = 11, + Zs = 12, + Zl = 13, + Zp = 14, + Cc = 15, + Cf = 16, + Co = 17, + Cs = 18, + Pd = 19, + Ps = 20, + Pe = 21, + Pc = 22, + Po = 23, + Sm = 24, + Sc = 25, + Sk = 26, + So = 27, + Pi = 28, + Pf = 29, +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs new file mode 100644 index 000000000..b33eb9dc7 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs @@ -0,0 +1,169 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1300:Element should begin with an uppercase letter", + Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1600:Elements should be documented", + Justification = "Unicode Data")] +[SuppressMessage( + "StyleCop.CSharp.DocumentationRules", + "SA1602:Enumeration items should be documented", + Justification = "Unicode Data")] +internal enum UnicodeLineBreakClass : byte +{ + /// Start of text. + sot, + + /// End of text. + eot, + + /// Mandatory Break; NL, PARAGRAPH SEPARATOR; Cause a line break (after). + BK, + + /// Carriage Return; CR; Cause a line break (after), except between CR and LF. + CR, + + /// Line Feed; LF; Cause a line break (after). + LF, + + /// Combining Mark; Combining marks, control codes; Prohibit a line break between the character and the preceding character. + CM, + + /// Next Line; NEL; Cause a line break (after). + NL, + + /// Surrogate; Surrogates; Do not occur in well-formed text. + SG, + + /// Word Joiner; WJ; Prohibit line breaks before and after. + WJ, + + /// Zero Width Space; ZWSP; Provide a break opportunity. + ZW, + + /// Non-breaking (“Glue”); CGJ, NBSP, ZWNBSP; Prohibit line breaks before and after. + GL, + + /// Space; SPACE; Enable indirect line breaks. + SP, + + /// Zero Width Joiner; Zero Width Joiner; Prohibit line breaks within joiner sequences. + ZWJ, + + /// Break Opportunity Before and After; Em dash; Provide a line break opportunity before and after the character. + B2, + + /// Break After; Spaces, hyphens; Generally provide a line break opportunity after the character. + BA, + + /// Break Before; Punctuation used in dictionaries; Generally provide a line break opportunity before the character. + BB, + + /// Hyphen; HYPHEN-MINUS; Provide a line break opportunity after the character, except in numeric context. + HY, + + /// Contingent Break Opportunity; Inline objects; Provide a line break opportunity contingent on additional information. + CB, + + /// Close Punctuation; “}”, “❳”, “⟫” etc.; Prohibit line breaks before. + CL, + + /// Close Parenthesis; “)”, “]”; Prohibit line breaks before. + CP, + + /// Exclamation/Interrogation; “!”, “?”, etc.; Prohibit line breaks before. + EX, + + /// Inseparable; Leaders; Allow only indirect line breaks between pairs. + IN, + + /// Nonstarter; “‼”, “‽”, “⁇”, “⁉”, etc.; Allow only indirect line breaks before. + NS, + + /// Open Punctuation; “(“, “[“, “{“, etc.; Prohibit line breaks after. + OP, + + /// Quotation; Quotation marks; Act like they are both opening and closing. + QU, + + /// Infix Numeric Separator; . ,; Prevent breaks after any and before numeric. + IS, + + /// Numeric; Digits; Form numeric expressions for line breaking purposes. + NU, + + /// Postfix Numeric; %, ¢; Do not break following a numeric expression. + PO, + + /// Prefix Numeric; $, £, ¥, etc.; Do not break in front of a numeric expression. + PR, + + /// Symbols Allowing Break After; /; Prevent a break before, and allow a break after. + SY, + + /// Ambiguous (Alphabetic or Ideographic); Characters with Ambiguous East Asian Width; Act like AL when the resolved EAW is N; otherwise, act as ID. + AI, + + /// Aksara; Consonants; Form orthographic syllables in Brahmic scripts. + AK, + + /// Alphabetic; Alphabets and regular symbols; Are alphabetic characters or symbols that are used with alphabetic characters. + AL, + + /// Aksara Pre-Base; Pre-base repha; Form orthographic syllables in Brahmic scripts. + AP, + + /// Aksara Start; Independent vowels; Form orthographic syllables in Brahmic scripts. + AS, + + /// Conditional Japanese Starter; Small kana; Treat as NS or ID for strict or normal breaking. + CJ, + + /// Emoji Base; All emoji allowing modifiers; Do not break from following Emoji Modifier. + EB, + + /// Emoji Modifier; Skin tone modifiers; Do not break from preceding Emoji Base. + EM, + + /// Hangul LV Syllable; Hangul; Form Korean syllable blocks. + H2, + + /// Hangul LVT Syllable; Hangul; Form Korean syllable blocks. + H3, + + /// Hebrew Letter; Hebrew; Do not break around a following hyphen; otherwise act as Alphabetic. + HL, + + /// Ideographic; Ideographs; Break before or after, except in some numeric context. + ID, + + /// Hangul L Jamo; Conjoining jamo; Form Korean syllable blocks. + JL, + + /// Hangul V Jamo; Conjoining jamo; Form Korean syllable blocks. + JV, + + /// Hangul T Jamo; Conjoining jamo; Form Korean syllable blocks. + JT, + + /// Regional Indicator; REGIONAL INDICATOR SYMBOL LETTER A .. Z; Keep pairs together. For pairs, break before and after other classes. + RI, + + /// Complex Context Dependent (South East Asian); South East Asian: Thai, Lao, Khmer; Provide a line break opportunity contingent on additional, language-specific context analysis. + SA, + + /// Virama Final; Viramas for final consonants; Form orthographic syllables in Brahmic scripts. + VF, + + /// Virama; Conjoining viramas; Form orthographic syllables in Brahmic scripts. + VI, + + /// Unknown; Most unassigned, private-use; Have as yet unknown line breaking behavior or unassigned code positions. + XX, +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs new file mode 100644 index 000000000..6d319ed92 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs @@ -0,0 +1,313 @@ +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.Internal.ImGuiSeStringRenderer.TextProcessing; + +/// Enumerates a UTF-N byte sequence by codepoint. +[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")] +internal ref struct UtfEnumerator +{ + private readonly ReadOnlySpan data; + private readonly UtfEnumeratorFlags flags; + private readonly byte numBytesPerUnit; + private bool isBigEndian; + + /// Initializes a new instance of the struct. + /// UTF-N byte sequence. + /// Enumeration flags. + public UtfEnumerator(ReadOnlySpan data, UtfEnumeratorFlags flags) + { + this.data = data; + this.flags = flags; + this.numBytesPerUnit = (this.flags & UtfEnumeratorFlags.UtfMask) switch + { + UtfEnumeratorFlags.Utf8 or UtfEnumeratorFlags.Utf8SeString => 1, + UtfEnumeratorFlags.Utf16 => 2, + UtfEnumeratorFlags.Utf32 => 4, + _ => throw new ArgumentOutOfRangeException(nameof(this.flags), this.flags, "Multiple UTF flag specified."), + }; + this.isBigEndian = (flags & UtfEnumeratorFlags.EndiannessMask) switch + { + UtfEnumeratorFlags.NativeEndian => !BitConverter.IsLittleEndian, + UtfEnumeratorFlags.LittleEndian => false, + UtfEnumeratorFlags.BigEndian => true, + _ => throw new ArgumentOutOfRangeException(nameof(flags), flags, "Multiple endianness flag specified."), + }; + } + + /// + public Subsequence Current { get; private set; } = default; + + /// Creates a new instance of the struct. + /// UTF-N byte sequence. + /// Enumeration flags. + /// A new enumerator. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => new(data, flags); + + /// Creates a new instance of the struct. + /// UTF-N byte sequence. + /// Enumeration flags. + /// A new enumerator. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => + new(MemoryMarshal.Cast(data), flags); + + /// Gets the representative char for a given SeString macro code. + /// The macro code. + /// Representative char, or if none. + public static char RepresentativeCharFor(MacroCode macroCode) => macroCode switch + { + MacroCode.NewLine => '\u0085', + MacroCode.SoftHyphen => '\u00AD', + MacroCode.NonBreakingSpace => '\u00A0', + MacroCode.Hyphen => '-', + MacroCode.Icon or MacroCode.Icon2 => '\uFFFC', + _ => char.MaxValue, + }; + + /// Attempts to peek the next item. + /// Retrieved next item. + /// Whether it still should be parsed in big endian. + /// true if anything is retrieved. + /// The sequence is not a fully valid Unicode sequence, and + /// is set. + public readonly bool TryPeekNext(out Subsequence nextSubsequence, out bool isStillBigEndian) + { + var offset = this.Current.ByteOffset + this.Current.ByteLength; + isStillBigEndian = this.isBigEndian; + while (true) + { + var subspan = this.data[offset..]; + + if (subspan.IsEmpty) + { + nextSubsequence = default; + return false; + } + + UtfValue value; + int length; + var isBroken = + this.numBytesPerUnit switch + { + 1 => !UtfValue.TryDecode8(subspan, out value, out length), + 2 => !UtfValue.TryDecode16(subspan, isStillBigEndian, out value, out length), + 4 => !UtfValue.TryDecode32(subspan, isStillBigEndian, out value, out length), + _ => throw new InvalidOperationException(), + }; + if (!isBroken && value.IntValue == 0xFFFE) + { + if ((this.flags & UtfEnumeratorFlags.DisrespectByteOrderMask) == 0) + { + isStillBigEndian = !isStillBigEndian; + value = 0xFEFF; + } + + if ((this.flags & UtfEnumeratorFlags.YieldByteOrderMask) == 0) + { + offset += length; + continue; + } + } + + if (isBroken || !Rune.IsValid(value)) + { + switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) + { + case UtfEnumeratorFlags.ReplaceErrors: + break; + + case UtfEnumeratorFlags.IgnoreErrors: + offset = Math.Min(offset + this.numBytesPerUnit, this.data.Length); + continue; + + case UtfEnumeratorFlags.ThrowOnFirstError: + if (isBroken) + throw new EncoderFallbackException($"0x{subspan[0]:X02} is not a valid sequence."); + throw new EncoderFallbackException( + $"U+{value.UIntValue:X08} is not a valid unicode codepoint."); + + case UtfEnumeratorFlags.TerminateOnFirstError: + default: + nextSubsequence = default; + return false; + } + } + + if (isBroken) + value = subspan[0]; + + if (value == SeString.StartByte && (this.flags & UtfEnumeratorFlags.Utf8SeString) != 0) + { + var e = new ReadOnlySeStringSpan(subspan).GetEnumerator(); + e.MoveNext(); + switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) + { + case var _ when e.Current.Type is ReadOnlySePayloadType.Macro: + nextSubsequence = Subsequence.FromPayload( + e.Current.MacroCode, + offset, + e.Current.EnvelopeByteLength); + return true; + + case UtfEnumeratorFlags.ReplaceErrors: + value = '\uFFFE'; + length = e.Current.EnvelopeByteLength; + isBroken = true; + break; + + case UtfEnumeratorFlags.IgnoreErrors: + offset = Math.Min(offset + e.Current.EnvelopeByteLength, this.data.Length); + continue; + + case UtfEnumeratorFlags.ThrowOnFirstError: + throw new EncoderFallbackException("Invalid SeString payload."); + + case UtfEnumeratorFlags.TerminateOnFirstError: + default: + nextSubsequence = default; + return false; + } + } + + nextSubsequence = Subsequence.FromUnicode(value, offset, length, isBroken); + return true; + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + if (!this.TryPeekNext(out var next, out var isStillBigEndian)) + return false; + + this.Current = next; + this.isBigEndian = isStillBigEndian; + return true; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UtfEnumerator GetEnumerator() => new(this.data, this.flags); + + /// A part of a UTF-N sequence containing one codepoint. + [StructLayout(LayoutKind.Explicit, Size = 16)] + [DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")] + public readonly struct Subsequence : IEquatable + { + /// The codepoint. Valid if is false. + [FieldOffset(0)] + public readonly UtfValue Value; + + /// The macro code. Valid if is true. + [FieldOffset(0)] + public readonly MacroCode MacroCode; + + /// The offset of this part of a UTF-8 sequence. + [FieldOffset(4)] + public readonly int ByteOffset; + + /// The length of this part of a UTF-8 sequence. + /// This may not match , if is true. + /// + [FieldOffset(8)] + public readonly int ByteLength; + + /// Whether this part of the UTF-8 sequence is broken. + [FieldOffset(12)] + public readonly bool BrokenSequence; + + /// Whether this part of the SeString sequence is a payload. + [FieldOffset(13)] + public readonly bool IsSeStringPayload; + + /// Storage at byte offset 0, for fast implementation. + [FieldOffset(0)] + private readonly ulong storage0; + + /// Storage at byte offset 8, for fast implementation. + [FieldOffset(8)] + private readonly ulong storage1; + + /// Initializes a new instance of the struct. + /// The value. + /// The byte offset of this part of a UTF-N sequence. + /// The byte length of this part of a UTF-N sequence. + /// Whether this part of the UTF-N sequence is broken. + /// Whether this part of the SeString sequence is a payload. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Subsequence(uint value, int byteOffset, int byteLength, bool brokenSequence, bool isSeStringPayload) + { + this.Value = new(value); + this.ByteOffset = byteOffset; + this.ByteLength = byteLength; + this.BrokenSequence = brokenSequence; + this.IsSeStringPayload = isSeStringPayload; + } + + /// Gets the effective char value, with invalid or non-representable codepoints replaced. + /// + /// if the character should not be displayed at all. + public char EffectiveChar => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue; + + /// Gets the effective int value, with invalid codepoints replaced. + /// if the character should not be displayed at all. + public int EffectiveInt => + this.IsSeStringPayload + ? RepresentativeCharFor(this.MacroCode) + : this.BrokenSequence || !this.Value.TryGetRune(out var rune) + ? 0xFFFD + : rune.Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Subsequence left, Subsequence right) => !left.Equals(right); + + /// Creates a new instance of the struct from a Unicode value. + /// The codepoint. + /// The byte offset of this part of a UTF-N sequence. + /// The byte length of this part of a UTF-N sequence. + /// Whether this part of the UTF-N sequence is broken. + /// A new instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) => + new(codepoint, byteOffset, byteLength, brokenSequence, false); + + /// Creates a new instance of the struct from a SeString payload. + /// The macro code. + /// The byte offset of this part of a UTF-N sequence. + /// The byte length of this part of a UTF-N sequence. + /// A new instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) => + new((uint)macroCode, byteOffset, byteLength, false, true); + + /// Tests whether this subsequence contains a valid Unicode codepoint. + /// true if this subsequence contains a valid Unicode codepoint. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1); + } +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs new file mode 100644 index 000000000..7d07049da --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs @@ -0,0 +1,58 @@ +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +/// Flags on enumerating a unicode sequence. +[Flags] +internal enum UtfEnumeratorFlags +{ + /// Use the default configuration of and . + Default = default, + + /// Enumerate as UTF-8 (the default.) + Utf8 = Default, + + /// Enumerate as UTF-8 in a SeString. + Utf8SeString = 1 << 1, + + /// Enumerate as UTF-16. + Utf16 = 1 << 2, + + /// Enumerate as UTF-32. + Utf32 = 1 << 3, + + /// Bitmask for specifying the encoding. + UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32, + + /// On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.) + ReplaceErrors = Default, + + /// On error, drop the invalid byte. + IgnoreErrors = 1 << 4, + + /// On error, stop the handling. + TerminateOnFirstError = 1 << 5, + + /// On error, throw an exception. + ThrowOnFirstError = 1 << 6, + + /// Bitmask for specifying the error handling mode. + ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError, + + /// Use the current system native endianness from + /// (the default.) + NativeEndian = Default, + + /// Use little endianness. + LittleEndian = 1 << 7, + + /// Use big endianness. + BigEndian = 1 << 8, + + /// Bitmask for specifying endianness. + EndiannessMask = NativeEndian | LittleEndian | BigEndian, + + /// Disrespect byte order mask. + DisrespectByteOrderMask = 1 << 9, + + /// Yield byte order masks, if it shows up. + YieldByteOrderMask = 1 << 10, +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs new file mode 100644 index 000000000..c35b117a2 --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs @@ -0,0 +1,665 @@ +using System.Buffers.Binary; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; + +/// Represents a single value to be used in a UTF-N byte sequence. +[StructLayout(LayoutKind.Explicit, Size = 4)] +[DebuggerDisplay("0x{IntValue,h} ({CharValue})")] +internal readonly struct UtfValue : IEquatable, IComparable +{ + /// The unicode codepoint in int, that may not be in a valid range. + [FieldOffset(0)] + public readonly int IntValue; + + /// The unicode codepoint in uint, that may not be in a valid range. + [FieldOffset(0)] + public readonly uint UIntValue; + + /// The high UInt16 value in char, that may have been cut off if outside BMP. + [FieldOffset(0)] + public readonly char CharValue; + + /// Initializes a new instance of the struct. + /// The raw codepoint value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UtfValue(uint value) => this.UIntValue = value; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UtfValue(int value) => this.IntValue = value; + + /// Gets the length of this codepoint, encoded in UTF-8. + public int Length8 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetEncodedLength8(this); + } + + /// Gets the length of this codepoint, encoded in UTF-16. + public int Length16 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetEncodedLength16(this); + } + + /// Gets the short name, if supported. + /// The buffer containing the short name, or empty if unsupported. + public ReadOnlySpan ShortName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetShortName(this); + } + + public static implicit operator uint(UtfValue c) => c.UIntValue; + + public static implicit operator int(UtfValue c) => c.IntValue; + + public static implicit operator UtfValue(byte c) => new(c); + + public static implicit operator UtfValue(sbyte c) => new(c); + + public static implicit operator UtfValue(ushort c) => new(c); + + public static implicit operator UtfValue(short c) => new(c); + + public static implicit operator UtfValue(uint c) => new(c); + + public static implicit operator UtfValue(int c) => new(c); + + public static implicit operator UtfValue(char c) => new(c); + + public static implicit operator UtfValue(Rune c) => new(c.Value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(UtfValue left, UtfValue right) => left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(UtfValue left, UtfValue right) => !left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator <(UtfValue left, UtfValue right) => left.CompareTo(right) < 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator >(UtfValue left, UtfValue right) => left.CompareTo(right) > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator <=(UtfValue left, UtfValue right) => left.CompareTo(right) <= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator >=(UtfValue left, UtfValue right) => left.CompareTo(right) >= 0; + + /// Gets the short name of the codepoint, for some select codepoints. + /// The codepoint. + /// The value. + public static ReadOnlySpan GetShortName(int codepoint) => + codepoint switch + { + 0x00 => "NUL", + 0x01 => "SOH", + 0x02 => "STX", + 0x03 => "ETX", + 0x04 => "EOT", + 0x05 => "ENQ", + 0x06 => "ACK", + 0x07 => "BEL", + 0x08 => "BS", + 0x09 => "HT", + 0x0a => "LF", + 0x0b => "VT", + 0x0c => "FF", + 0x0d => "CR", + 0x0e => "SO", + 0x0f => "SI", + + 0x10 => "DLE", + 0x11 => "DC1", + 0x12 => "DC2", + 0x13 => "DC3", + 0x14 => "DC4", + 0x15 => "NAK", + 0x16 => "SYN", + 0x17 => "SOH", + 0x18 => "CAN", + 0x19 => "EOM", + 0x1a => "SUB", + 0x1b => "ESC", + 0x1c => "FS", + 0x1d => "GS", + 0x1e => "RS", + 0x1f => "US", + + 0x80 => "PAD", + 0x81 => "HOP", + 0x82 => "BPH", + 0x83 => "NBH", + 0x84 => "IND", + 0x85 => "NEL", + 0x86 => "SSA", + 0x87 => "ESA", + 0x88 => "HTS", + 0x89 => "HTJ", + 0x8a => "VTS", + 0x8b => "PLD", + 0x8c => "PLU", + 0x8d => "RI", + 0x8e => "SS2", + 0x8f => "SS3", + + 0x90 => "DCS", + 0x91 => "PU1", + 0x92 => "PU2", + 0x93 => "STS", + 0x94 => "CCH", + 0x95 => "MW", + 0x96 => "SPA", + 0x97 => "EPA", + 0x98 => "SOS", + 0x99 => "SGC", + 0x9a => "SCI", + 0x9b => "CSI", + 0x9c => "ST", + 0x9d => "OSC", + 0x9e => "PM", + 0x9f => "APC", + + 0xa0 => "NBSP", + 0xad => "SHY", + + _ => default, + }; + + /// Gets the length of the codepoint, when encoded in UTF-8. + /// The codepoint to encode. + /// The length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetEncodedLength8(int codepoint) => (uint)codepoint switch + { + < 1u << 7 => 1, + < 1u << 11 => 2, + < 1u << 16 => 3, + < 1u << 21 => 4, + // Not a valid Unicode codepoint anymore below. + < 1u << 26 => 5, + < 1u << 31 => 6, + _ => 7, + }; + + /// Gets the length of the codepoint, when encoded in UTF-16. + /// The codepoint to encode. + /// The length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetEncodedLength16(int codepoint) => (uint)codepoint switch + { + < 0x10000 => 2, + < 0x10000 + (1 << 20) => 4, + // Not a valid Unicode codepoint anymore below. + < 0x10000 + (1 << 30) => 6, + _ => 8, + }; + + /// + /// Trims at beginning by . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryDecode8(ref ReadOnlySpan source, out UtfValue value, out int length) + { + var v = TryDecode8(source, out value, out length); + source = source[length..]; + return v; + } + + /// Attempts to decode a value from a UTF-8 byte sequence. + /// The span to decode from. + /// The decoded value. + /// The length of the consumed bytes. 1 if sequence is broken. + /// true if is successfully decoded. + /// Codepoints that results in false from can still be returned, + /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only + /// indicating whether the sequence could be decoded into a number, without being too short. + public static unsafe bool TryDecode8(ReadOnlySpan source, out UtfValue value, out int length) + { + if (source.IsEmpty) + { + value = default; + length = 0; + return false; + } + + fixed (byte* ptr = source) + { + if ((ptr[0] & 0x80) == 0) + { + length = 1; + value = ptr[0]; + } + else if ((ptr[0] & 0b11100000) == 0b11000000 && source.Length >= 2 + && ((uint)ptr[1] & 0b11000000) == 0b10000000) + { + length = 2; + value = (((uint)ptr[0] & 0x1F) << 6) | + (((uint)ptr[1] & 0x3F) << 0); + } + else if (((uint)ptr[0] & 0b11110000) == 0b11100000 && source.Length >= 3 + && ((uint)ptr[1] & 0b11000000) == 0b10000000 + && ((uint)ptr[2] & 0b11000000) == 0b10000000) + { + length = 3; + value = (((uint)ptr[0] & 0x0F) << 12) | + (((uint)ptr[1] & 0x3F) << 6) | + (((uint)ptr[2] & 0x3F) << 0); + } + else if (((uint)ptr[0] & 0b11111000) == 0b11110000 && source.Length >= 4 + && ((uint)ptr[1] & 0b11000000) == 0b10000000 + && ((uint)ptr[2] & 0b11000000) == 0b10000000 + && ((uint)ptr[3] & 0b11000000) == 0b10000000) + { + length = 4; + value = (((uint)ptr[0] & 0x07) << 18) | + (((uint)ptr[1] & 0x3F) << 12) | + (((uint)ptr[2] & 0x3F) << 6) | + (((uint)ptr[3] & 0x3F) << 0); + } + else if (((uint)ptr[0] & 0b11111100) == 0b11111000 && source.Length >= 5 + && ((uint)ptr[1] & 0b11000000) == 0b10000000 + && ((uint)ptr[2] & 0b11000000) == 0b10000000 + && ((uint)ptr[3] & 0b11000000) == 0b10000000 + && ((uint)ptr[4] & 0b11000000) == 0b10000000) + { + length = 5; + value = (((uint)ptr[0] & 0x03) << 24) | + (((uint)ptr[1] & 0x3F) << 18) | + (((uint)ptr[2] & 0x3F) << 12) | + (((uint)ptr[3] & 0x3F) << 6) | + (((uint)ptr[4] & 0x3F) << 0); + } + else if (((uint)ptr[0] & 0b11111110) == 0b11111100 && source.Length >= 6 + && ((uint)ptr[1] & 0b11000000) == 0b10000000 + && ((uint)ptr[2] & 0b11000000) == 0b10000000 + && ((uint)ptr[3] & 0b11000000) == 0b10000000 + && ((uint)ptr[4] & 0b11000000) == 0b10000000 + && ((uint)ptr[5] & 0b11000000) == 0b10000000) + { + length = 6; + value = (((uint)ptr[0] & 0x01) << 30) | + (((uint)ptr[1] & 0x3F) << 24) | + (((uint)ptr[2] & 0x3F) << 18) | + (((uint)ptr[3] & 0x3F) << 12) | + (((uint)ptr[4] & 0x3F) << 6) | + (((uint)ptr[5] & 0x3F) << 0); + } + else if (((uint)ptr[0] & 0b11111111) == 0b11111110 && source.Length >= 7 + && ((uint)ptr[1] & 0b11111100) == 0b10000000 + && ((uint)ptr[2] & 0b11000000) == 0b10000000 + && ((uint)ptr[3] & 0b11000000) == 0b10000000 + && ((uint)ptr[4] & 0b11000000) == 0b10000000 + && ((uint)ptr[5] & 0b11000000) == 0b10000000 + && ((uint)ptr[6] & 0b11000000) == 0b10000000) + { + length = 7; + value = (((uint)ptr[1] & 0x03) << 30) | + (((uint)ptr[2] & 0x3F) << 24) | + (((uint)ptr[3] & 0x3F) << 18) | + (((uint)ptr[4] & 0x3F) << 12) | + (((uint)ptr[5] & 0x3F) << 6) | + (((uint)ptr[6] & 0x3F) << 0); + } + else + { + length = 1; + value = default; + return false; + } + + return true; + } + } + + /// + /// Trims at beginning by . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryDecode16(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) + { + var v = TryDecode16(source, be, out value, out length); + source = source[length..]; + return v; + } + + /// Attempts to decode a value from a UTF-16 byte sequence. + /// The span to decode from. + /// Whether to use big endian. + /// The decoded value. + /// The length of the consumed bytes. 1 if cut short. + /// 2 if sequence is broken. + /// true if is successfully decoded. + /// Codepoints that results in false from can still be returned, + /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only + /// indicating whether the sequence could be decoded into a number, without being too short. + public static unsafe bool TryDecode16(ReadOnlySpan source, bool be, out UtfValue value, out int length) + { + if (source.Length < 2) + { + value = default; + length = source.Length; + return false; + } + + fixed (byte* ptr = source) + { + var p16 = (ushort*)ptr; + var val = be == BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(*p16) : *p16; + if (char.IsHighSurrogate((char)val)) + { + var lookahead1 = source.Length >= 4 ? p16[1] : 0; + var lookahead2 = source.Length >= 6 ? p16[2] : 0; + var lookahead3 = source.Length >= 8 ? p16[3] : 0; + if (char.IsLowSurrogate((char)lookahead1)) + { + // Not a valid Unicode codepoint anymore inside the block below. + if (char.IsLowSurrogate((char)lookahead2)) + { + if (char.IsLowSurrogate((char)lookahead3)) + { + value = 0x10000 + + (((val & 0x3) << 30) | + ((lookahead1 & 0x3FF) << 20) | + ((lookahead2 & 0x3FF) << 10) | + ((lookahead3 & 0x3FF) << 0)); + length = 8; + return true; + } + + value = 0x10000 + + (((val & 0x3FF) << 20) | + ((lookahead1 & 0x3FF) << 10) | + ((lookahead2 & 0x3FF) << 0)); + length = 6; + return true; + } + + value = 0x10000 + + (((val & 0x3FF) << 10) | + ((lookahead1 & 0x3FF) << 0)); + length = 4; + return true; + } + } + + // Calls are supposed to handle unpaired surrogates. + value = val; + length = 2; + return true; + } + } + + /// + /// Trims at beginning by . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryDecode32(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) + { + var v = TryDecode32(source, be, out value, out length); + source = source[length..]; + return v; + } + + /// Attempts to decode a value from a UTF-32 byte sequence. + /// The span to decode from. + /// Whether to use big endian. + /// The decoded value. + /// The length of the consumed bytes. 1 to 3 if cut short. + /// 4 if sequence is broken. + /// true if is successfully decoded. + /// Codepoints that results in false from can still be returned, + /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only + /// indicating whether the sequence could be decoded into a number, without being too short. + public static bool TryDecode32(ReadOnlySpan source, bool be, out UtfValue value, out int length) + { + if (source.Length < 4) + { + value = default; + length = source.Length; + return false; + } + + length = 4; + if ((be && BinaryPrimitives.TryReadInt32BigEndian(source, out var i32)) + || (!be && BinaryPrimitives.TryReadInt32LittleEndian(source, out i32))) + { + value = i32; + return true; + } + + value = default; + return false; + } + + /// Encodes the codepoint to the target in UTF-8. + /// The target stream. + /// The codepoint to encode. + /// The length of the encoded data. + /// Trims at beginning by the length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Encode8(Stream target, int codepoint) + { + Span buf = stackalloc byte[7]; + Encode8(buf, codepoint, out var length); + target.Write(buf[..length]); + return length; + } + + /// Encodes the codepoint to the target in UTF-8. + /// The target byte span. + /// The codepoint to encode. + /// The length of the encoded data. + /// Trims at beginning by the length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Encode8(ref Span target, int codepoint) + { + target = Encode8(target, codepoint, out var length); + return length; + } + + /// Encodes the codepoint to the target in UTF-8. + /// The optional target byte span. + /// The codepoint to encode. + /// The length of the encoded data. + /// The remaning region of . + public static Span Encode8(Span target, int codepoint, out int length) + { + var value = (uint)codepoint; + length = GetEncodedLength8(codepoint); + if (target.IsEmpty) + return target; + + switch (length) + { + case 1: + target[0] = (byte)value; + return target[1..]; + case 2: + target[0] = (byte)(0xC0 | ((value >> 6) & 0x1F)); + target[1] = (byte)(0x80 | ((value >> 0) & 0x3F)); + return target[2..]; + case 3: + target[0] = (byte)(0xE0 | ((value >> 12) & 0x0F)); + target[1] = (byte)(0x80 | ((value >> 6) & 0x3F)); + target[2] = (byte)(0x80 | ((value >> 0) & 0x3F)); + return target[3..]; + case 4: + target[0] = (byte)(0xF0 | ((value >> 18) & 0x07)); + target[1] = (byte)(0x80 | ((value >> 12) & 0x3F)); + target[2] = (byte)(0x80 | ((value >> 6) & 0x3F)); + target[3] = (byte)(0x80 | ((value >> 0) & 0x3F)); + return target[4..]; + case 5: + target[0] = (byte)(0xF8 | ((value >> 24) & 0x03)); + target[1] = (byte)(0x80 | ((value >> 18) & 0x3F)); + target[2] = (byte)(0x80 | ((value >> 12) & 0x3F)); + target[3] = (byte)(0x80 | ((value >> 6) & 0x3F)); + target[4] = (byte)(0x80 | ((value >> 0) & 0x3F)); + return target[5..]; + case 6: + target[0] = (byte)(0xFC | ((value >> 30) & 0x01)); + target[1] = (byte)(0x80 | ((value >> 24) & 0x3F)); + target[2] = (byte)(0x80 | ((value >> 18) & 0x3F)); + target[3] = (byte)(0x80 | ((value >> 12) & 0x3F)); + target[4] = (byte)(0x80 | ((value >> 6) & 0x3F)); + target[5] = (byte)(0x80 | ((value >> 0) & 0x3F)); + return target[6..]; + case 7: + target[0] = 0xFE; + target[1] = (byte)(0x80 | ((value >> 30) & 0x03)); + target[2] = (byte)(0x80 | ((value >> 24) & 0x3F)); + target[3] = (byte)(0x80 | ((value >> 18) & 0x3F)); + target[4] = (byte)(0x80 | ((value >> 12) & 0x3F)); + target[5] = (byte)(0x80 | ((value >> 6) & 0x3F)); + target[6] = (byte)(0x80 | ((value >> 0) & 0x3F)); + return target[7..]; + default: + Debug.Assert(false, $"{nameof(Length8)} property should have produced all possible cases."); + return target; + } + } + + /// Encodes the codepoint to the target in UTF-16. + /// The target stream. + /// The codepoint to encode. + /// Whether to use big endian. + /// The length of the encoded data. + /// Trims at beginning by the length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Encode16(Stream target, int codepoint, bool be) + { + Span buf = stackalloc byte[8]; + Encode16(buf, codepoint, be, out var length); + target.Write(buf[..length]); + return length; + } + + /// Encodes the codepoint to the target in UTF-16. + /// The target byte span. + /// The codepoint to encode. + /// Whether to use big endian. + /// The length of the encoded data. + /// Trims at beginning by the length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Encode16(ref Span target, int codepoint, bool be) + { + target = Encode16(target, codepoint, be, out var length); + return length; + } + + /// Encodes the codepoint to the target in UTF-16. + /// The optional target byte span. + /// The codepoint to encode. + /// Whether to use big endian. + /// The length of the encoded data. + /// The remaning region of . + public static Span Encode16(Span target, int codepoint, bool be, out int length) + { + var value = (uint)codepoint; + length = GetEncodedLength16(codepoint); + if (target.IsEmpty) + return target; + + if (be) + { + switch (length) + { + case 2: + BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)value); + return target[2..]; + case 4: + value -= 0x10000; + BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); + BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); + return target[4..]; + case 6: + value -= 0x10000; + BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); + BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); + BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); + return target[6..]; + case 8: + value -= 0x10000; + BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); + BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); + BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); + BinaryPrimitives.WriteUInt16BigEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); + return target[8..]; + default: + Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); + return target; + } + } + + switch (length) + { + case 2: + BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)value); + return target[2..]; + case 4: + value -= 0x10000; + BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); + BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); + return target[4..]; + case 6: + value -= 0x10000; + BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); + BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); + BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); + return target[6..]; + case 8: + value -= 0x10000; + BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); + BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); + BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); + BinaryPrimitives.WriteUInt16LittleEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); + return target[8..]; + default: + Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); + return target; + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(UtfValue other) => this.IntValue == other.IntValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => this.IntValue; + + /// Attempts to get the corresponding rune. + /// The retrieved rune. + /// true if retrieved. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetRune(out Rune rune) + { + if (Rune.IsValid(this.IntValue)) + { + rune = new(this.IntValue); + return true; + } + + rune = default; + return false; + } + + /// Encodes the codepoint to the target. + /// The target byte span. + /// The remaning region of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Encode8(Span target) => Encode8(target, this, out _); +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/emoji-data.txt b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/emoji-data.txt new file mode 100644 index 000000000..0ba10e9ce --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/emoji-data.txt @@ -0,0 +1,1320 @@ +# emoji-data.txt +# Date: 2023-02-01, 02:22:54 GMT +# © 2023 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see https://www.unicode.org/terms_of_use.html +# +# Emoji Data for UTS #51 +# Used with Emoji Version 15.1 and subsequent minor revisions (if any) +# +# For documentation and usage, see https://www.unicode.org/reports/tr51 +# +# Format: +# ; # +# Note: there is no guarantee as to the structure of whitespace or comments +# +# Characters and sequences are listed in code point order. Users should be shown a more natural order. +# See the CLDR collation order for Emoji. + + +# ================================================ + +# All omitted code points have Emoji=No + +0023 ; Emoji # E0.0 [1] (#️) hash sign +002A ; Emoji # E0.0 [1] (*️) asterisk +0030..0039 ; Emoji # E0.0 [10] (0️..9️) digit zero..digit nine +00A9 ; Emoji # E0.6 [1] (©️) copyright +00AE ; Emoji # E0.6 [1] (®️) registered +203C ; Emoji # E0.6 [1] (‼️) double exclamation mark +2049 ; Emoji # E0.6 [1] (⁉️) exclamation question mark +2122 ; Emoji # E0.6 [1] (™️) trade mark +2139 ; Emoji # E0.6 [1] (ℹ️) information +2194..2199 ; Emoji # E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Emoji # E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Emoji # E0.6 [2] (⌚..⌛) watch..hourglass done +2328 ; Emoji # E1.0 [1] (⌨️) keyboard +23CF ; Emoji # E1.0 [1] (⏏️) eject button +23E9..23EC ; Emoji # E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23ED..23EE ; Emoji # E0.7 [2] (⏭️..⏮️) next track button..last track button +23EF ; Emoji # E1.0 [1] (⏯️) play or pause button +23F0 ; Emoji # E0.6 [1] (⏰) alarm clock +23F1..23F2 ; Emoji # E1.0 [2] (⏱️..⏲️) stopwatch..timer clock +23F3 ; Emoji # E0.6 [1] (⏳) hourglass not done +23F8..23FA ; Emoji # E0.7 [3] (⏸️..⏺️) pause button..record button +24C2 ; Emoji # E0.6 [1] (Ⓜ️) circled M +25AA..25AB ; Emoji # E0.6 [2] (▪️..▫️) black small square..white small square +25B6 ; Emoji # E0.6 [1] (▶️) play button +25C0 ; Emoji # E0.6 [1] (◀️) reverse button +25FB..25FE ; Emoji # E0.6 [4] (◻️..◾) white medium square..black medium-small square +2600..2601 ; Emoji # E0.6 [2] (☀️..☁️) sun..cloud +2602..2603 ; Emoji # E0.7 [2] (☂️..☃️) umbrella..snowman +2604 ; Emoji # E1.0 [1] (☄️) comet +260E ; Emoji # E0.6 [1] (☎️) telephone +2611 ; Emoji # E0.6 [1] (☑️) check box with check +2614..2615 ; Emoji # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2618 ; Emoji # E1.0 [1] (☘️) shamrock +261D ; Emoji # E0.6 [1] (☝️) index pointing up +2620 ; Emoji # E1.0 [1] (☠️) skull and crossbones +2622..2623 ; Emoji # E1.0 [2] (☢️..☣️) radioactive..biohazard +2626 ; Emoji # E1.0 [1] (☦️) orthodox cross +262A ; Emoji # E0.7 [1] (☪️) star and crescent +262E ; Emoji # E1.0 [1] (☮️) peace symbol +262F ; Emoji # E0.7 [1] (☯️) yin yang +2638..2639 ; Emoji # E0.7 [2] (☸️..☹️) wheel of dharma..frowning face +263A ; Emoji # E0.6 [1] (☺️) smiling face +2640 ; Emoji # E4.0 [1] (♀️) female sign +2642 ; Emoji # E4.0 [1] (♂️) male sign +2648..2653 ; Emoji # E0.6 [12] (♈..♓) Aries..Pisces +265F ; Emoji # E11.0 [1] (♟️) chess pawn +2660 ; Emoji # E0.6 [1] (♠️) spade suit +2663 ; Emoji # E0.6 [1] (♣️) club suit +2665..2666 ; Emoji # E0.6 [2] (♥️..♦️) heart suit..diamond suit +2668 ; Emoji # E0.6 [1] (♨️) hot springs +267B ; Emoji # E0.6 [1] (♻️) recycling symbol +267E ; Emoji # E11.0 [1] (♾️) infinity +267F ; Emoji # E0.6 [1] (♿) wheelchair symbol +2692 ; Emoji # E1.0 [1] (⚒️) hammer and pick +2693 ; Emoji # E0.6 [1] (⚓) anchor +2694 ; Emoji # E1.0 [1] (⚔️) crossed swords +2695 ; Emoji # E4.0 [1] (⚕️) medical symbol +2696..2697 ; Emoji # E1.0 [2] (⚖️..⚗️) balance scale..alembic +2699 ; Emoji # E1.0 [1] (⚙️) gear +269B..269C ; Emoji # E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +26A0..26A1 ; Emoji # E0.6 [2] (⚠️..⚡) warning..high voltage +26A7 ; Emoji # E13.0 [1] (⚧️) transgender symbol +26AA..26AB ; Emoji # E0.6 [2] (⚪..⚫) white circle..black circle +26B0..26B1 ; Emoji # E1.0 [2] (⚰️..⚱️) coffin..funeral urn +26BD..26BE ; Emoji # E0.6 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C8 ; Emoji # E0.7 [1] (⛈️) cloud with lightning and rain +26CE ; Emoji # E0.6 [1] (⛎) Ophiuchus +26CF ; Emoji # E0.7 [1] (⛏️) pick +26D1 ; Emoji # E0.7 [1] (⛑️) rescue worker’s helmet +26D3 ; Emoji # E0.7 [1] (⛓️) chains +26D4 ; Emoji # E0.6 [1] (⛔) no entry +26E9 ; Emoji # E0.7 [1] (⛩️) shinto shrine +26EA ; Emoji # E0.6 [1] (⛪) church +26F0..26F1 ; Emoji # E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground +26F2..26F3 ; Emoji # E0.6 [2] (⛲..⛳) fountain..flag in hole +26F4 ; Emoji # E0.7 [1] (⛴️) ferry +26F5 ; Emoji # E0.6 [1] (⛵) sailboat +26F7..26F9 ; Emoji # E0.7 [3] (⛷️..⛹️) skier..person bouncing ball +26FA ; Emoji # E0.6 [1] (⛺) tent +26FD ; Emoji # E0.6 [1] (⛽) fuel pump +2702 ; Emoji # E0.6 [1] (✂️) scissors +2705 ; Emoji # E0.6 [1] (✅) check mark button +2708..270C ; Emoji # E0.6 [5] (✈️..✌️) airplane..victory hand +270D ; Emoji # E0.7 [1] (✍️) writing hand +270F ; Emoji # E0.6 [1] (✏️) pencil +2712 ; Emoji # E0.6 [1] (✒️) black nib +2714 ; Emoji # E0.6 [1] (✔️) check mark +2716 ; Emoji # E0.6 [1] (✖️) multiply +271D ; Emoji # E0.7 [1] (✝️) latin cross +2721 ; Emoji # E0.7 [1] (✡️) star of David +2728 ; Emoji # E0.6 [1] (✨) sparkles +2733..2734 ; Emoji # E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Emoji # E0.6 [1] (❄️) snowflake +2747 ; Emoji # E0.6 [1] (❇️) sparkle +274C ; Emoji # E0.6 [1] (❌) cross mark +274E ; Emoji # E0.6 [1] (❎) cross mark button +2753..2755 ; Emoji # E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Emoji # E0.6 [1] (❗) red exclamation mark +2763 ; Emoji # E1.0 [1] (❣️) heart exclamation +2764 ; Emoji # E0.6 [1] (❤️) red heart +2795..2797 ; Emoji # E0.6 [3] (➕..➗) plus..divide +27A1 ; Emoji # E0.6 [1] (➡️) right arrow +27B0 ; Emoji # E0.6 [1] (➰) curly loop +27BF ; Emoji # E1.0 [1] (➿) double curly loop +2934..2935 ; Emoji # E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Emoji # E0.6 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Emoji # E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji # E0.6 [1] (⭐) star +2B55 ; Emoji # E0.6 [1] (⭕) hollow red circle +3030 ; Emoji # E0.6 [1] (〰️) wavy dash +303D ; Emoji # E0.6 [1] (〽️) part alternation mark +3297 ; Emoji # E0.6 [1] (㊗️) Japanese “congratulations” button +3299 ; Emoji # E0.6 [1] (㊙️) Japanese “secret” button +1F004 ; Emoji # E0.6 [1] (🀄) mahjong red dragon +1F0CF ; Emoji # E0.6 [1] (🃏) joker +1F170..1F171 ; Emoji # E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E..1F17F ; Emoji # E0.6 [2] (🅾️..🅿️) O button (blood type)..P button +1F18E ; Emoji # E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji # E0.6 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201..1F202 ; Emoji # E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F21A ; Emoji # E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji # E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Emoji # E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F250..1F251 ; Emoji # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F30C ; Emoji # E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Emoji # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji # E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Emoji # E1.0 [1] (🌐) globe with meridians +1F311 ; Emoji # E0.6 [1] (🌑) new moon +1F312 ; Emoji # E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Emoji # E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Emoji # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Emoji # E0.6 [1] (🌙) crescent moon +1F31A ; Emoji # E1.0 [1] (🌚) new moon face +1F31B ; Emoji # E0.6 [1] (🌛) first quarter moon face +1F31C ; Emoji # E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Emoji # E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Emoji # E0.6 [2] (🌟..🌠) glowing star..shooting star +1F321 ; Emoji # E0.7 [1] (🌡️) thermometer +1F324..1F32C ; Emoji # E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Emoji # E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Emoji # E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Emoji # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Emoji # E0.6 [2] (🌴..🌵) palm tree..cactus +1F336 ; Emoji # E0.7 [1] (🌶️) hot pepper +1F337..1F34A ; Emoji # E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Emoji # E1.0 [1] (🍋) lemon +1F34C..1F34F ; Emoji # E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Emoji # E1.0 [1] (🍐) pear +1F351..1F37B ; Emoji # E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Emoji # E1.0 [1] (🍼) baby bottle +1F37D ; Emoji # E0.7 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Emoji # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji # E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F396..1F397 ; Emoji # E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon +1F399..1F39B ; Emoji # E0.7 [3] (🎙️..🎛️) studio microphone..control knobs +1F39E..1F39F ; Emoji # E0.7 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Emoji # E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji # E1.0 [1] (🏅) sports medal +1F3C6 ; Emoji # E0.6 [1] (🏆) trophy +1F3C7 ; Emoji # E1.0 [1] (🏇) horse racing +1F3C8 ; Emoji # E0.6 [1] (🏈) american football +1F3C9 ; Emoji # E1.0 [1] (🏉) rugby football +1F3CA ; Emoji # E0.6 [1] (🏊) person swimming +1F3CB..1F3CE ; Emoji # E0.7 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Emoji # E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Emoji # E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3E3 ; Emoji # E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Emoji # E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Emoji # E0.6 [12] (🏥..🏰) hospital..castle +1F3F3 ; Emoji # E0.7 [1] (🏳️) white flag +1F3F4 ; Emoji # E1.0 [1] (🏴) black flag +1F3F5 ; Emoji # E0.7 [1] (🏵️) rosette +1F3F7 ; Emoji # E0.7 [1] (🏷️) label +1F3F8..1F407 ; Emoji # E1.0 [16] (🏸..🐇) badminton..rabbit +1F408 ; Emoji # E0.7 [1] (🐈) cat +1F409..1F40B ; Emoji # E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Emoji # E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Emoji # E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Emoji # E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Emoji # E1.0 [1] (🐓) rooster +1F414 ; Emoji # E0.6 [1] (🐔) chicken +1F415 ; Emoji # E0.7 [1] (🐕) dog +1F416 ; Emoji # E1.0 [1] (🐖) pig +1F417..1F429 ; Emoji # E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Emoji # E1.0 [1] (🐪) camel +1F42B..1F43E ; Emoji # E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F43F ; Emoji # E0.7 [1] (🐿️) chipmunk +1F440 ; Emoji # E0.6 [1] (👀) eyes +1F441 ; Emoji # E0.7 [1] (👁️) eye +1F442..1F464 ; Emoji # E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Emoji # E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Emoji # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Emoji # E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Emoji # E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Emoji # E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji # E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji # E0.6 [1] (📮) postbox +1F4EF ; Emoji # E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Emoji # E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Emoji # E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Emoji # E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Emoji # E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji # E0.6 [4] (📹..📼) video camera..videocassette +1F4FD ; Emoji # E0.7 [1] (📽️) film projector +1F4FF..1F502 ; Emoji # E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Emoji # E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Emoji # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Emoji # E0.7 [1] (🔈) speaker low volume +1F509 ; Emoji # E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Emoji # E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Emoji # E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Emoji # E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Emoji # E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Emoji # E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F549..1F54A ; Emoji # E0.7 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Emoji # E1.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F55B ; Emoji # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Emoji # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F56F..1F570 ; Emoji # E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock +1F573..1F579 ; Emoji # E0.7 [7] (🕳️..🕹️) hole..joystick +1F57A ; Emoji # E3.0 [1] (🕺) man dancing +1F587 ; Emoji # E0.7 [1] (🖇️) linked paperclips +1F58A..1F58D ; Emoji # E0.7 [4] (🖊️..🖍️) pen..crayon +1F590 ; Emoji # E0.7 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji # E3.0 [1] (🖤) black heart +1F5A5 ; Emoji # E0.7 [1] (🖥️) desktop computer +1F5A8 ; Emoji # E0.7 [1] (🖨️) printer +1F5B1..1F5B2 ; Emoji # E0.7 [2] (🖱️..🖲️) computer mouse..trackball +1F5BC ; Emoji # E0.7 [1] (🖼️) framed picture +1F5C2..1F5C4 ; Emoji # E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5D1..1F5D3 ; Emoji # E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5DC..1F5DE ; Emoji # E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5E1 ; Emoji # E0.7 [1] (🗡️) dagger +1F5E3 ; Emoji # E0.7 [1] (🗣️) speaking head +1F5E8 ; Emoji # E2.0 [1] (🗨️) left speech bubble +1F5EF ; Emoji # E0.7 [1] (🗯️) right anger bubble +1F5F3 ; Emoji # E0.7 [1] (🗳️) ballot box with ballot +1F5FA ; Emoji # E0.7 [1] (🗺️) world map +1F5FB..1F5FF ; Emoji # E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji # E1.0 [1] (😀) grinning face +1F601..1F606 ; Emoji # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Emoji # E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Emoji # E0.6 [1] (😏) smirking face +1F610 ; Emoji # E0.7 [1] (😐) neutral face +1F611 ; Emoji # E1.0 [1] (😑) expressionless face +1F612..1F614 ; Emoji # E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji # E1.0 [1] (😕) confused face +1F616 ; Emoji # E0.6 [1] (😖) confounded face +1F617 ; Emoji # E1.0 [1] (😗) kissing face +1F618 ; Emoji # E0.6 [1] (😘) face blowing a kiss +1F619 ; Emoji # E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji # E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Emoji # E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Emoji # E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji # E1.0 [1] (😟) worried face +1F620..1F625 ; Emoji # E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji # E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji # E1.0 [1] (😬) grimacing face +1F62D ; Emoji # E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji # E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji # E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji # E1.0 [1] (😴) sleeping face +1F635 ; Emoji # E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Emoji # E1.0 [1] (😶) face without mouth +1F637..1F640 ; Emoji # E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Emoji # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Emoji # E0.6 [1] (🚀) rocket +1F681..1F682 ; Emoji # E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Emoji # E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Emoji # E1.0 [1] (🚆) train +1F687 ; Emoji # E0.6 [1] (🚇) metro +1F688 ; Emoji # E1.0 [1] (🚈) light rail +1F689 ; Emoji # E0.6 [1] (🚉) station +1F68A..1F68B ; Emoji # E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Emoji # E0.6 [1] (🚌) bus +1F68D ; Emoji # E0.7 [1] (🚍) oncoming bus +1F68E ; Emoji # E1.0 [1] (🚎) trolleybus +1F68F ; Emoji # E0.6 [1] (🚏) bus stop +1F690 ; Emoji # E1.0 [1] (🚐) minibus +1F691..1F693 ; Emoji # E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Emoji # E0.7 [1] (🚔) oncoming police car +1F695 ; Emoji # E0.6 [1] (🚕) taxi +1F696 ; Emoji # E1.0 [1] (🚖) oncoming taxi +1F697 ; Emoji # E0.6 [1] (🚗) automobile +1F698 ; Emoji # E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Emoji # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Emoji # E0.6 [1] (🚢) ship +1F6A3 ; Emoji # E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Emoji # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Emoji # E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Emoji # E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Emoji # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Emoji # E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Emoji # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Emoji # E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Emoji # E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji # E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Emoji # E1.0 [1] (🚿) shower +1F6C0 ; Emoji # E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Emoji # E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6CB ; Emoji # E0.7 [1] (🛋️) couch and lamp +1F6CC ; Emoji # E1.0 [1] (🛌) person in bed +1F6CD..1F6CF ; Emoji # E0.7 [3] (🛍️..🛏️) shopping bags..bed +1F6D0 ; Emoji # E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji # E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E9 ; Emoji # E0.7 [1] (🛩️) small airplane +1F6EB..1F6EC ; Emoji # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F0 ; Emoji # E0.7 [1] (🛰️) satellite +1F6F3 ; Emoji # E0.7 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Emoji # E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji # E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Emoji # E11.0 [1] (🛹) skateboard +1F6FA ; Emoji # E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Emoji # E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F7E0..1F7EB ; Emoji # E12.0 [12] (🟠..🟫) orange circle..brown square +1F7F0 ; Emoji # E14.0 [1] (🟰) heavy equals sign +1F90C ; Emoji # E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Emoji # E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Emoji # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji # E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji # E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Emoji # E12.0 [1] (🤿) diving mask +1F940..1F945 ; Emoji # E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji # E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Emoji # E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Emoji # E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji # E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Emoji # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Emoji # E12.0 [1] (🥱) yawning face +1F972 ; Emoji # E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Emoji # E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Emoji # E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Emoji # E14.0 [1] (🥹) face holding back tears +1F97A ; Emoji # E11.0 [1] (🥺) pleading face +1F97B ; Emoji # E12.0 [1] (🥻) sari +1F97C..1F97F ; Emoji # E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Emoji # E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Emoji # E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji # E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Emoji # E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Emoji # E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Emoji # E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Emoji # E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Emoji # E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Emoji # E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Emoji # E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Emoji # E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Emoji # E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Emoji # E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Emoji # E13.0 [1] (🧋) bubble tea +1F9CC ; Emoji # E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Emoji # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Emoji # E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Emoji # E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA70..1FA73 ; Emoji # E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Emoji # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji # E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Emoji # E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Emoji # E14.0 [2] (🩻..🩼) x-ray..crutch +1FA80..1FA82 ; Emoji # E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute +1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Emoji # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji # E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Emoji # E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Emoji # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji # E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji # E15.0 [2] (🫚..🫛) ginger root..pea pod +1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face +1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 1424 + +# ================================================ + +# All omitted code points have Emoji_Presentation=No + +231A..231B ; Emoji_Presentation # E0.6 [2] (⌚..⌛) watch..hourglass done +23E9..23EC ; Emoji_Presentation # E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23F0 ; Emoji_Presentation # E0.6 [1] (⏰) alarm clock +23F3 ; Emoji_Presentation # E0.6 [1] (⏳) hourglass not done +25FD..25FE ; Emoji_Presentation # E0.6 [2] (◽..◾) white medium-small square..black medium-small square +2614..2615 ; Emoji_Presentation # E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2648..2653 ; Emoji_Presentation # E0.6 [12] (♈..♓) Aries..Pisces +267F ; Emoji_Presentation # E0.6 [1] (♿) wheelchair symbol +2693 ; Emoji_Presentation # E0.6 [1] (⚓) anchor +26A1 ; Emoji_Presentation # E0.6 [1] (⚡) high voltage +26AA..26AB ; Emoji_Presentation # E0.6 [2] (⚪..⚫) white circle..black circle +26BD..26BE ; Emoji_Presentation # E0.6 [2] (⚽..⚾) soccer ball..baseball +26C4..26C5 ; Emoji_Presentation # E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26CE ; Emoji_Presentation # E0.6 [1] (⛎) Ophiuchus +26D4 ; Emoji_Presentation # E0.6 [1] (⛔) no entry +26EA ; Emoji_Presentation # E0.6 [1] (⛪) church +26F2..26F3 ; Emoji_Presentation # E0.6 [2] (⛲..⛳) fountain..flag in hole +26F5 ; Emoji_Presentation # E0.6 [1] (⛵) sailboat +26FA ; Emoji_Presentation # E0.6 [1] (⛺) tent +26FD ; Emoji_Presentation # E0.6 [1] (⛽) fuel pump +2705 ; Emoji_Presentation # E0.6 [1] (✅) check mark button +270A..270B ; Emoji_Presentation # E0.6 [2] (✊..✋) raised fist..raised hand +2728 ; Emoji_Presentation # E0.6 [1] (✨) sparkles +274C ; Emoji_Presentation # E0.6 [1] (❌) cross mark +274E ; Emoji_Presentation # E0.6 [1] (❎) cross mark button +2753..2755 ; Emoji_Presentation # E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Emoji_Presentation # E0.6 [1] (❗) red exclamation mark +2795..2797 ; Emoji_Presentation # E0.6 [3] (➕..➗) plus..divide +27B0 ; Emoji_Presentation # E0.6 [1] (➰) curly loop +27BF ; Emoji_Presentation # E1.0 [1] (➿) double curly loop +2B1B..2B1C ; Emoji_Presentation # E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Emoji_Presentation # E0.6 [1] (⭐) star +2B55 ; Emoji_Presentation # E0.6 [1] (⭕) hollow red circle +1F004 ; Emoji_Presentation # E0.6 [1] (🀄) mahjong red dragon +1F0CF ; Emoji_Presentation # E0.6 [1] (🃏) joker +1F18E ; Emoji_Presentation # E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Emoji_Presentation # E0.6 [10] (🆑..🆚) CL button..VS button +1F1E6..1F1FF ; Emoji_Presentation # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F201 ; Emoji_Presentation # E0.6 [1] (🈁) Japanese “here” button +1F21A ; Emoji_Presentation # E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Emoji_Presentation # E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F236 ; Emoji_Presentation # E0.6 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button +1F238..1F23A ; Emoji_Presentation # E0.6 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button +1F250..1F251 ; Emoji_Presentation # E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F300..1F30C ; Emoji_Presentation # E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Emoji_Presentation # E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Emoji_Presentation # E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Emoji_Presentation # E1.0 [1] (🌐) globe with meridians +1F311 ; Emoji_Presentation # E0.6 [1] (🌑) new moon +1F312 ; Emoji_Presentation # E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Emoji_Presentation # E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Emoji_Presentation # E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Emoji_Presentation # E0.6 [1] (🌙) crescent moon +1F31A ; Emoji_Presentation # E1.0 [1] (🌚) new moon face +1F31B ; Emoji_Presentation # E0.6 [1] (🌛) first quarter moon face +1F31C ; Emoji_Presentation # E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Emoji_Presentation # E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Emoji_Presentation # E0.6 [2] (🌟..🌠) glowing star..shooting star +1F32D..1F32F ; Emoji_Presentation # E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Emoji_Presentation # E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Emoji_Presentation # E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Emoji_Presentation # E0.6 [2] (🌴..🌵) palm tree..cactus +1F337..1F34A ; Emoji_Presentation # E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Emoji_Presentation # E1.0 [1] (🍋) lemon +1F34C..1F34F ; Emoji_Presentation # E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Emoji_Presentation # E1.0 [1] (🍐) pear +1F351..1F37B ; Emoji_Presentation # E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Emoji_Presentation # E1.0 [1] (🍼) baby bottle +1F37E..1F37F ; Emoji_Presentation # E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Emoji_Presentation # E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F3A0..1F3C4 ; Emoji_Presentation # E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Emoji_Presentation # E1.0 [1] (🏅) sports medal +1F3C6 ; Emoji_Presentation # E0.6 [1] (🏆) trophy +1F3C7 ; Emoji_Presentation # E1.0 [1] (🏇) horse racing +1F3C8 ; Emoji_Presentation # E0.6 [1] (🏈) american football +1F3C9 ; Emoji_Presentation # E1.0 [1] (🏉) rugby football +1F3CA ; Emoji_Presentation # E0.6 [1] (🏊) person swimming +1F3CF..1F3D3 ; Emoji_Presentation # E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3E0..1F3E3 ; Emoji_Presentation # E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Emoji_Presentation # E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Emoji_Presentation # E0.6 [12] (🏥..🏰) hospital..castle +1F3F4 ; Emoji_Presentation # E1.0 [1] (🏴) black flag +1F3F8..1F407 ; Emoji_Presentation # E1.0 [16] (🏸..🐇) badminton..rabbit +1F408 ; Emoji_Presentation # E0.7 [1] (🐈) cat +1F409..1F40B ; Emoji_Presentation # E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Emoji_Presentation # E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Emoji_Presentation # E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Emoji_Presentation # E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Emoji_Presentation # E1.0 [1] (🐓) rooster +1F414 ; Emoji_Presentation # E0.6 [1] (🐔) chicken +1F415 ; Emoji_Presentation # E0.7 [1] (🐕) dog +1F416 ; Emoji_Presentation # E1.0 [1] (🐖) pig +1F417..1F429 ; Emoji_Presentation # E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Emoji_Presentation # E1.0 [1] (🐪) camel +1F42B..1F43E ; Emoji_Presentation # E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F440 ; Emoji_Presentation # E0.6 [1] (👀) eyes +1F442..1F464 ; Emoji_Presentation # E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Emoji_Presentation # E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Emoji_Presentation # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Presentation # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Emoji_Presentation # E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Emoji_Presentation # E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Emoji_Presentation # E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Emoji_Presentation # E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Emoji_Presentation # E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Emoji_Presentation # E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Emoji_Presentation # E0.6 [1] (📮) postbox +1F4EF ; Emoji_Presentation # E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Emoji_Presentation # E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Emoji_Presentation # E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Emoji_Presentation # E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Emoji_Presentation # E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Emoji_Presentation # E0.6 [4] (📹..📼) video camera..videocassette +1F4FF..1F502 ; Emoji_Presentation # E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Emoji_Presentation # E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Emoji_Presentation # E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Emoji_Presentation # E0.7 [1] (🔈) speaker low volume +1F509 ; Emoji_Presentation # E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Emoji_Presentation # E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Emoji_Presentation # E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Emoji_Presentation # E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Emoji_Presentation # E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Emoji_Presentation # E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F54B..1F54E ; Emoji_Presentation # E1.0 [4] (🕋..🕎) kaaba..menorah +1F550..1F55B ; Emoji_Presentation # E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Emoji_Presentation # E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F57A ; Emoji_Presentation # E3.0 [1] (🕺) man dancing +1F595..1F596 ; Emoji_Presentation # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F5A4 ; Emoji_Presentation # E3.0 [1] (🖤) black heart +1F5FB..1F5FF ; Emoji_Presentation # E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Emoji_Presentation # E1.0 [1] (😀) grinning face +1F601..1F606 ; Emoji_Presentation # E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Emoji_Presentation # E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Emoji_Presentation # E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Emoji_Presentation # E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Emoji_Presentation # E0.6 [1] (😏) smirking face +1F610 ; Emoji_Presentation # E0.7 [1] (😐) neutral face +1F611 ; Emoji_Presentation # E1.0 [1] (😑) expressionless face +1F612..1F614 ; Emoji_Presentation # E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Emoji_Presentation # E1.0 [1] (😕) confused face +1F616 ; Emoji_Presentation # E0.6 [1] (😖) confounded face +1F617 ; Emoji_Presentation # E1.0 [1] (😗) kissing face +1F618 ; Emoji_Presentation # E0.6 [1] (😘) face blowing a kiss +1F619 ; Emoji_Presentation # E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Emoji_Presentation # E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Emoji_Presentation # E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Emoji_Presentation # E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Emoji_Presentation # E1.0 [1] (😟) worried face +1F620..1F625 ; Emoji_Presentation # E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Emoji_Presentation # E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Emoji_Presentation # E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Emoji_Presentation # E1.0 [1] (😬) grimacing face +1F62D ; Emoji_Presentation # E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Emoji_Presentation # E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Emoji_Presentation # E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Emoji_Presentation # E1.0 [1] (😴) sleeping face +1F635 ; Emoji_Presentation # E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Emoji_Presentation # E1.0 [1] (😶) face without mouth +1F637..1F640 ; Emoji_Presentation # E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Emoji_Presentation # E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Emoji_Presentation # E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Emoji_Presentation # E0.6 [1] (🚀) rocket +1F681..1F682 ; Emoji_Presentation # E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Emoji_Presentation # E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Emoji_Presentation # E1.0 [1] (🚆) train +1F687 ; Emoji_Presentation # E0.6 [1] (🚇) metro +1F688 ; Emoji_Presentation # E1.0 [1] (🚈) light rail +1F689 ; Emoji_Presentation # E0.6 [1] (🚉) station +1F68A..1F68B ; Emoji_Presentation # E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Emoji_Presentation # E0.6 [1] (🚌) bus +1F68D ; Emoji_Presentation # E0.7 [1] (🚍) oncoming bus +1F68E ; Emoji_Presentation # E1.0 [1] (🚎) trolleybus +1F68F ; Emoji_Presentation # E0.6 [1] (🚏) bus stop +1F690 ; Emoji_Presentation # E1.0 [1] (🚐) minibus +1F691..1F693 ; Emoji_Presentation # E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Emoji_Presentation # E0.7 [1] (🚔) oncoming police car +1F695 ; Emoji_Presentation # E0.6 [1] (🚕) taxi +1F696 ; Emoji_Presentation # E1.0 [1] (🚖) oncoming taxi +1F697 ; Emoji_Presentation # E0.6 [1] (🚗) automobile +1F698 ; Emoji_Presentation # E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Emoji_Presentation # E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Emoji_Presentation # E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Emoji_Presentation # E0.6 [1] (🚢) ship +1F6A3 ; Emoji_Presentation # E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Emoji_Presentation # E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Emoji_Presentation # E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Emoji_Presentation # E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Emoji_Presentation # E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Emoji_Presentation # E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Emoji_Presentation # E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Emoji_Presentation # E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Emoji_Presentation # E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Emoji_Presentation # E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Emoji_Presentation # E1.0 [1] (🚿) shower +1F6C0 ; Emoji_Presentation # E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Emoji_Presentation # E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6CC ; Emoji_Presentation # E1.0 [1] (🛌) person in bed +1F6D0 ; Emoji_Presentation # E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Emoji_Presentation # E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Emoji_Presentation # E11.0 [1] (🛹) skateboard +1F6FA ; Emoji_Presentation # E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Emoji_Presentation # E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F7E0..1F7EB ; Emoji_Presentation # E12.0 [12] (🟠..🟫) orange circle..brown square +1F7F0 ; Emoji_Presentation # E14.0 [1] (🟰) heavy equals sign +1F90C ; Emoji_Presentation # E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Emoji_Presentation # E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Emoji_Presentation # E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Emoji_Presentation # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Presentation # E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Emoji_Presentation # E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Emoji_Presentation # E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Emoji_Presentation # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Presentation # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Emoji_Presentation # E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Emoji_Presentation # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Emoji_Presentation # E12.0 [1] (🤿) diving mask +1F940..1F945 ; Emoji_Presentation # E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Emoji_Presentation # E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Emoji_Presentation # E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Emoji_Presentation # E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Emoji_Presentation # E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Emoji_Presentation # E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Emoji_Presentation # E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Emoji_Presentation # E12.0 [1] (🥱) yawning face +1F972 ; Emoji_Presentation # E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Emoji_Presentation # E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Emoji_Presentation # E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Emoji_Presentation # E14.0 [1] (🥹) face holding back tears +1F97A ; Emoji_Presentation # E11.0 [1] (🥺) pleading face +1F97B ; Emoji_Presentation # E12.0 [1] (🥻) sari +1F97C..1F97F ; Emoji_Presentation # E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Emoji_Presentation # E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Emoji_Presentation # E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Emoji_Presentation # E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Emoji_Presentation # E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Emoji_Presentation # E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Emoji_Presentation # E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Emoji_Presentation # E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Emoji_Presentation # E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Emoji_Presentation # E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Emoji_Presentation # E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Emoji_Presentation # E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Emoji_Presentation # E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Emoji_Presentation # E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Emoji_Presentation # E13.0 [1] (🧋) bubble tea +1F9CC ; Emoji_Presentation # E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Emoji_Presentation # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Emoji_Presentation # E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Emoji_Presentation # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji_Presentation # E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Emoji_Presentation # E14.0 [2] (🩻..🩼) x-ray..crutch +1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute +1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Emoji_Presentation # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji_Presentation # E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Emoji_Presentation # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji_Presentation # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji_Presentation # E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji_Presentation # E15.0 [2] (🫚..🫛) ginger root..pea pod +1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face +1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 1205 + +# ================================================ + +# All omitted code points have Emoji_Modifier=No + +1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone + +# Total elements: 5 + +# ================================================ + +# All omitted code points have Emoji_Modifier_Base=No + +261D ; Emoji_Modifier_Base # E0.6 [1] (☝️) index pointing up +26F9 ; Emoji_Modifier_Base # E0.7 [1] (⛹️) person bouncing ball +270A..270C ; Emoji_Modifier_Base # E0.6 [3] (✊..✌️) raised fist..victory hand +270D ; Emoji_Modifier_Base # E0.7 [1] (✍️) writing hand +1F385 ; Emoji_Modifier_Base # E0.6 [1] (🎅) Santa Claus +1F3C2..1F3C4 ; Emoji_Modifier_Base # E0.6 [3] (🏂..🏄) snowboarder..person surfing +1F3C7 ; Emoji_Modifier_Base # E1.0 [1] (🏇) horse racing +1F3CA ; Emoji_Modifier_Base # E0.6 [1] (🏊) person swimming +1F3CB..1F3CC ; Emoji_Modifier_Base # E0.7 [2] (🏋️..🏌️) person lifting weights..person golfing +1F442..1F443 ; Emoji_Modifier_Base # E0.6 [2] (👂..👃) ear..nose +1F446..1F450 ; Emoji_Modifier_Base # E0.6 [11] (👆..👐) backhand index pointing up..open hands +1F466..1F46B ; Emoji_Modifier_Base # E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Emoji_Modifier_Base # E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F478 ; Emoji_Modifier_Base # E0.6 [11] (👮..👸) police officer..princess +1F47C ; Emoji_Modifier_Base # E0.6 [1] (👼) baby angel +1F481..1F483 ; Emoji_Modifier_Base # E0.6 [3] (💁..💃) person tipping hand..woman dancing +1F485..1F487 ; Emoji_Modifier_Base # E0.6 [3] (💅..💇) nail polish..person getting haircut +1F48F ; Emoji_Modifier_Base # E0.6 [1] (💏) kiss +1F491 ; Emoji_Modifier_Base # E0.6 [1] (💑) couple with heart +1F4AA ; Emoji_Modifier_Base # E0.6 [1] (💪) flexed biceps +1F574..1F575 ; Emoji_Modifier_Base # E0.7 [2] (🕴️..🕵️) person in suit levitating..detective +1F57A ; Emoji_Modifier_Base # E3.0 [1] (🕺) man dancing +1F590 ; Emoji_Modifier_Base # E0.7 [1] (🖐️) hand with fingers splayed +1F595..1F596 ; Emoji_Modifier_Base # E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F645..1F647 ; Emoji_Modifier_Base # E0.6 [3] (🙅..🙇) person gesturing NO..person bowing +1F64B..1F64F ; Emoji_Modifier_Base # E0.6 [5] (🙋..🙏) person raising hand..folded hands +1F6A3 ; Emoji_Modifier_Base # E1.0 [1] (🚣) person rowing boat +1F6B4..1F6B5 ; Emoji_Modifier_Base # E1.0 [2] (🚴..🚵) person biking..person mountain biking +1F6B6 ; Emoji_Modifier_Base # E0.6 [1] (🚶) person walking +1F6C0 ; Emoji_Modifier_Base # E0.6 [1] (🛀) person taking bath +1F6CC ; Emoji_Modifier_Base # E1.0 [1] (🛌) person in bed +1F90C ; Emoji_Modifier_Base # E13.0 [1] (🤌) pinched fingers +1F90F ; Emoji_Modifier_Base # E12.0 [1] (🤏) pinching hand +1F918 ; Emoji_Modifier_Base # E1.0 [1] (🤘) sign of the horns +1F919..1F91E ; Emoji_Modifier_Base # E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Emoji_Modifier_Base # E5.0 [1] (🤟) love-you gesture +1F926 ; Emoji_Modifier_Base # E3.0 [1] (🤦) person facepalming +1F930 ; Emoji_Modifier_Base # E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Emoji_Modifier_Base # E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F939 ; Emoji_Modifier_Base # E3.0 [7] (🤳..🤹) selfie..person juggling +1F93C..1F93E ; Emoji_Modifier_Base # E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F977 ; Emoji_Modifier_Base # E13.0 [1] (🥷) ninja +1F9B5..1F9B6 ; Emoji_Modifier_Base # E11.0 [2] (🦵..🦶) leg..foot +1F9B8..1F9B9 ; Emoji_Modifier_Base # E11.0 [2] (🦸..🦹) superhero..supervillain +1F9BB ; Emoji_Modifier_Base # E12.0 [1] (🦻) ear with hearing aid +1F9CD..1F9CF ; Emoji_Modifier_Base # E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (🧑..🧝) person..elf +1FAC3..1FAC5 ; Emoji_Modifier_Base # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FAF0..1FAF6 ; Emoji_Modifier_Base # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Modifier_Base # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand + +# Total elements: 134 + +# ================================================ + +# All omitted code points have Emoji_Component=No + +0023 ; Emoji_Component # E0.0 [1] (#️) hash sign +002A ; Emoji_Component # E0.0 [1] (*️) asterisk +0030..0039 ; Emoji_Component # E0.0 [10] (0️..9️) digit zero..digit nine +200D ; Emoji_Component # E0.0 [1] (‍) zero width joiner +20E3 ; Emoji_Component # E0.0 [1] (⃣) combining enclosing keycap +FE0F ; Emoji_Component # E0.0 [1] () VARIATION SELECTOR-16 +1F1E6..1F1FF ; Emoji_Component # E0.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z +1F3FB..1F3FF ; Emoji_Component # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone +1F9B0..1F9B3 ; Emoji_Component # E11.0 [4] (🦰..🦳) red hair..white hair +E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..cancel tag + +# Total elements: 146 + +# ================================================ + +# All omitted code points have Extended_Pictographic=No + +00A9 ; Extended_Pictographic# E0.6 [1] (©️) copyright +00AE ; Extended_Pictographic# E0.6 [1] (®️) registered +203C ; Extended_Pictographic# E0.6 [1] (‼️) double exclamation mark +2049 ; Extended_Pictographic# E0.6 [1] (⁉️) exclamation question mark +2122 ; Extended_Pictographic# E0.6 [1] (™️) trade mark +2139 ; Extended_Pictographic# E0.6 [1] (ℹ️) information +2194..2199 ; Extended_Pictographic# E0.6 [6] (↔️..↙️) left-right arrow..down-left arrow +21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right +231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done +2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard +2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL +23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button +23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button +23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button +23EF ; Extended_Pictographic# E1.0 [1] (⏯️) play or pause button +23F0 ; Extended_Pictographic# E0.6 [1] (⏰) alarm clock +23F1..23F2 ; Extended_Pictographic# E1.0 [2] (⏱️..⏲️) stopwatch..timer clock +23F3 ; Extended_Pictographic# E0.6 [1] (⏳) hourglass not done +23F8..23FA ; Extended_Pictographic# E0.7 [3] (⏸️..⏺️) pause button..record button +24C2 ; Extended_Pictographic# E0.6 [1] (Ⓜ️) circled M +25AA..25AB ; Extended_Pictographic# E0.6 [2] (▪️..▫️) black small square..white small square +25B6 ; Extended_Pictographic# E0.6 [1] (▶️) play button +25C0 ; Extended_Pictographic# E0.6 [1] (◀️) reverse button +25FB..25FE ; Extended_Pictographic# E0.6 [4] (◻️..◾) white medium square..black medium-small square +2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud +2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman +2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet +2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR +2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION +260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone +260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX +2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check +2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X +2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage +2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE +2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock +2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX +261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up +261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX +2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones +2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN +2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard +2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH +2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross +2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM +262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent +262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE +262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol +262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang +2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH +2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face +263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face +263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY +2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign +2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH +2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign +2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO +2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces +2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT +265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn +2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit +2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT +2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit +2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT +2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit +2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT +2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs +2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS +267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol +267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL +267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity +267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol +2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 +2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG +2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick +2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor +2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords +2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol +2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic +2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER +2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear +269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES +269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis +269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT +26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage +26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN +26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol +26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN +26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle +26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL +26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn +26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE +26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball +26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING +26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud +26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN +26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain +26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR +26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus +26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick +26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING +26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet +26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES +26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains +26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry +26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD +26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine +26EA ; Extended_Pictographic# E0.6 [1] (⛪) church +26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE +26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground +26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole +26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry +26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat +26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS +26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball +26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent +26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL +26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump +26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS +2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors +2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS +2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button +2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand +270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand +270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL +270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil +2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB +2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib +2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark +2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply +271D ; Extended_Pictographic# E0.7 [1] (✝️) latin cross +2721 ; Extended_Pictographic# E0.7 [1] (✡️) star of David +2728 ; Extended_Pictographic# E0.6 [1] (✨) sparkles +2733..2734 ; Extended_Pictographic# E0.6 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star +2744 ; Extended_Pictographic# E0.6 [1] (❄️) snowflake +2747 ; Extended_Pictographic# E0.6 [1] (❇️) sparkle +274C ; Extended_Pictographic# E0.6 [1] (❌) cross mark +274E ; Extended_Pictographic# E0.6 [1] (❎) cross mark button +2753..2755 ; Extended_Pictographic# E0.6 [3] (❓..❕) red question mark..white exclamation mark +2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark +2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation +2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart +2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET +2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide +27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow +27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop +27BF ; Extended_Pictographic# E1.0 [1] (➿) double curly loop +2934..2935 ; Extended_Pictographic# E0.6 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down +2B05..2B07 ; Extended_Pictographic# E0.6 [3] (⬅️..⬇️) left arrow..down arrow +2B1B..2B1C ; Extended_Pictographic# E0.6 [2] (⬛..⬜) black large square..white large square +2B50 ; Extended_Pictographic# E0.6 [1] (⭐) star +2B55 ; Extended_Pictographic# E0.6 [1] (⭕) hollow red circle +3030 ; Extended_Pictographic# E0.6 [1] (〰️) wavy dash +303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark +3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button +3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button +1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND +1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon +1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker +1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. +1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH +1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL +1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) +1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button +1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) +1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button +1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button +1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. +1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button +1F22F ; Extended_Pictographic# E0.6 [1] (🈯) Japanese “reserved” button +1F232..1F23A ; Extended_Pictographic# E0.6 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button +1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. +1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. +1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button +1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way +1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas +1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia +1F310 ; Extended_Pictographic# E1.0 [1] (🌐) globe with meridians +1F311 ; Extended_Pictographic# E0.6 [1] (🌑) new moon +1F312 ; Extended_Pictographic# E1.0 [1] (🌒) waxing crescent moon +1F313..1F315 ; Extended_Pictographic# E0.6 [3] (🌓..🌕) first quarter moon..full moon +1F316..1F318 ; Extended_Pictographic# E1.0 [3] (🌖..🌘) waning gibbous moon..waning crescent moon +1F319 ; Extended_Pictographic# E0.6 [1] (🌙) crescent moon +1F31A ; Extended_Pictographic# E1.0 [1] (🌚) new moon face +1F31B ; Extended_Pictographic# E0.6 [1] (🌛) first quarter moon face +1F31C ; Extended_Pictographic# E0.7 [1] (🌜) last quarter moon face +1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face +1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star +1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer +1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN +1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face +1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito +1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling +1F332..1F333 ; Extended_Pictographic# E1.0 [2] (🌲..🌳) evergreen tree..deciduous tree +1F334..1F335 ; Extended_Pictographic# E0.6 [2] (🌴..🌵) palm tree..cactus +1F336 ; Extended_Pictographic# E0.7 [1] (🌶️) hot pepper +1F337..1F34A ; Extended_Pictographic# E0.6 [20] (🌷..🍊) tulip..tangerine +1F34B ; Extended_Pictographic# E1.0 [1] (🍋) lemon +1F34C..1F34F ; Extended_Pictographic# E0.6 [4] (🍌..🍏) banana..green apple +1F350 ; Extended_Pictographic# E1.0 [1] (🍐) pear +1F351..1F37B ; Extended_Pictographic# E0.6 [43] (🍑..🍻) peach..clinking beer mugs +1F37C ; Extended_Pictographic# E1.0 [1] (🍼) baby bottle +1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate +1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn +1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap +1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS +1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon +1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS +1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs +1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES +1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets +1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing +1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal +1F3C6 ; Extended_Pictographic# E0.6 [1] (🏆) trophy +1F3C7 ; Extended_Pictographic# E1.0 [1] (🏇) horse racing +1F3C8 ; Extended_Pictographic# E0.6 [1] (🏈) american football +1F3C9 ; Extended_Pictographic# E1.0 [1] (🏉) rugby football +1F3CA ; Extended_Pictographic# E0.6 [1] (🏊) person swimming +1F3CB..1F3CE ; Extended_Pictographic# E0.7 [4] (🏋️..🏎️) person lifting weights..racing car +1F3CF..1F3D3 ; Extended_Pictographic# E1.0 [5] (🏏..🏓) cricket game..ping pong +1F3D4..1F3DF ; Extended_Pictographic# E0.7 [12] (🏔️..🏟️) snow-capped mountain..stadium +1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office +1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office +1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle +1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT +1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag +1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag +1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette +1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE +1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label +1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora +1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit +1F408 ; Extended_Pictographic# E0.7 [1] (🐈) cat +1F409..1F40B ; Extended_Pictographic# E1.0 [3] (🐉..🐋) dragon..whale +1F40C..1F40E ; Extended_Pictographic# E0.6 [3] (🐌..🐎) snail..horse +1F40F..1F410 ; Extended_Pictographic# E1.0 [2] (🐏..🐐) ram..goat +1F411..1F412 ; Extended_Pictographic# E0.6 [2] (🐑..🐒) ewe..monkey +1F413 ; Extended_Pictographic# E1.0 [1] (🐓) rooster +1F414 ; Extended_Pictographic# E0.6 [1] (🐔) chicken +1F415 ; Extended_Pictographic# E0.7 [1] (🐕) dog +1F416 ; Extended_Pictographic# E1.0 [1] (🐖) pig +1F417..1F429 ; Extended_Pictographic# E0.6 [19] (🐗..🐩) boar..poodle +1F42A ; Extended_Pictographic# E1.0 [1] (🐪) camel +1F42B..1F43E ; Extended_Pictographic# E0.6 [20] (🐫..🐾) two-hump camel..paw prints +1F43F ; Extended_Pictographic# E0.7 [1] (🐿️) chipmunk +1F440 ; Extended_Pictographic# E0.6 [1] (👀) eyes +1F441 ; Extended_Pictographic# E0.7 [1] (👁️) eye +1F442..1F464 ; Extended_Pictographic# E0.6 [35] (👂..👤) ear..bust in silhouette +1F465 ; Extended_Pictographic# E1.0 [1] (👥) busts in silhouette +1F466..1F46B ; Extended_Pictographic# E0.6 [6] (👦..👫) boy..woman and man holding hands +1F46C..1F46D ; Extended_Pictographic# E1.0 [2] (👬..👭) men holding hands..women holding hands +1F46E..1F4AC ; Extended_Pictographic# E0.6 [63] (👮..💬) police officer..speech balloon +1F4AD ; Extended_Pictographic# E1.0 [1] (💭) thought balloon +1F4AE..1F4B5 ; Extended_Pictographic# E0.6 [8] (💮..💵) white flower..dollar banknote +1F4B6..1F4B7 ; Extended_Pictographic# E1.0 [2] (💶..💷) euro banknote..pound banknote +1F4B8..1F4EB ; Extended_Pictographic# E0.6 [52] (💸..📫) money with wings..closed mailbox with raised flag +1F4EC..1F4ED ; Extended_Pictographic# E0.7 [2] (📬..📭) open mailbox with raised flag..open mailbox with lowered flag +1F4EE ; Extended_Pictographic# E0.6 [1] (📮) postbox +1F4EF ; Extended_Pictographic# E1.0 [1] (📯) postal horn +1F4F0..1F4F4 ; Extended_Pictographic# E0.6 [5] (📰..📴) newspaper..mobile phone off +1F4F5 ; Extended_Pictographic# E1.0 [1] (📵) no mobile phones +1F4F6..1F4F7 ; Extended_Pictographic# E0.6 [2] (📶..📷) antenna bars..camera +1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash +1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette +1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector +1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO +1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button +1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows +1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker +1F508 ; Extended_Pictographic# E0.7 [1] (🔈) speaker low volume +1F509 ; Extended_Pictographic# E1.0 [1] (🔉) speaker medium volume +1F50A..1F514 ; Extended_Pictographic# E0.6 [11] (🔊..🔔) speaker high volume..bell +1F515 ; Extended_Pictographic# E1.0 [1] (🔕) bell with slash +1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol +1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope +1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button +1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS +1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove +1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah +1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA +1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock +1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty +1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK +1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock +1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY +1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick +1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing +1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE +1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips +1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL +1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon +1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN +1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed +1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND +1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute +1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX +1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart +1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer +1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS +1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer +1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE +1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball +1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE +1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture +1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER +1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet +1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES +1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar +1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL +1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper +1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART +1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger +1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS +1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head +1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT +1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble +1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE +1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble +1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD +1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot +1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK +1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map +1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai +1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face +1F601..1F606 ; Extended_Pictographic# E0.6 [6] (😁..😆) beaming face with smiling eyes..grinning squinting face +1F607..1F608 ; Extended_Pictographic# E1.0 [2] (😇..😈) smiling face with halo..smiling face with horns +1F609..1F60D ; Extended_Pictographic# E0.6 [5] (😉..😍) winking face..smiling face with heart-eyes +1F60E ; Extended_Pictographic# E1.0 [1] (😎) smiling face with sunglasses +1F60F ; Extended_Pictographic# E0.6 [1] (😏) smirking face +1F610 ; Extended_Pictographic# E0.7 [1] (😐) neutral face +1F611 ; Extended_Pictographic# E1.0 [1] (😑) expressionless face +1F612..1F614 ; Extended_Pictographic# E0.6 [3] (😒..😔) unamused face..pensive face +1F615 ; Extended_Pictographic# E1.0 [1] (😕) confused face +1F616 ; Extended_Pictographic# E0.6 [1] (😖) confounded face +1F617 ; Extended_Pictographic# E1.0 [1] (😗) kissing face +1F618 ; Extended_Pictographic# E0.6 [1] (😘) face blowing a kiss +1F619 ; Extended_Pictographic# E1.0 [1] (😙) kissing face with smiling eyes +1F61A ; Extended_Pictographic# E0.6 [1] (😚) kissing face with closed eyes +1F61B ; Extended_Pictographic# E1.0 [1] (😛) face with tongue +1F61C..1F61E ; Extended_Pictographic# E0.6 [3] (😜..😞) winking face with tongue..disappointed face +1F61F ; Extended_Pictographic# E1.0 [1] (😟) worried face +1F620..1F625 ; Extended_Pictographic# E0.6 [6] (😠..😥) angry face..sad but relieved face +1F626..1F627 ; Extended_Pictographic# E1.0 [2] (😦..😧) frowning face with open mouth..anguished face +1F628..1F62B ; Extended_Pictographic# E0.6 [4] (😨..😫) fearful face..tired face +1F62C ; Extended_Pictographic# E1.0 [1] (😬) grimacing face +1F62D ; Extended_Pictographic# E0.6 [1] (😭) loudly crying face +1F62E..1F62F ; Extended_Pictographic# E1.0 [2] (😮..😯) face with open mouth..hushed face +1F630..1F633 ; Extended_Pictographic# E0.6 [4] (😰..😳) anxious face with sweat..flushed face +1F634 ; Extended_Pictographic# E1.0 [1] (😴) sleeping face +1F635 ; Extended_Pictographic# E0.6 [1] (😵) face with crossed-out eyes +1F636 ; Extended_Pictographic# E1.0 [1] (😶) face without mouth +1F637..1F640 ; Extended_Pictographic# E0.6 [10] (😷..🙀) face with medical mask..weary cat +1F641..1F644 ; Extended_Pictographic# E1.0 [4] (🙁..🙄) slightly frowning face..face with rolling eyes +1F645..1F64F ; Extended_Pictographic# E0.6 [11] (🙅..🙏) person gesturing NO..folded hands +1F680 ; Extended_Pictographic# E0.6 [1] (🚀) rocket +1F681..1F682 ; Extended_Pictographic# E1.0 [2] (🚁..🚂) helicopter..locomotive +1F683..1F685 ; Extended_Pictographic# E0.6 [3] (🚃..🚅) railway car..bullet train +1F686 ; Extended_Pictographic# E1.0 [1] (🚆) train +1F687 ; Extended_Pictographic# E0.6 [1] (🚇) metro +1F688 ; Extended_Pictographic# E1.0 [1] (🚈) light rail +1F689 ; Extended_Pictographic# E0.6 [1] (🚉) station +1F68A..1F68B ; Extended_Pictographic# E1.0 [2] (🚊..🚋) tram..tram car +1F68C ; Extended_Pictographic# E0.6 [1] (🚌) bus +1F68D ; Extended_Pictographic# E0.7 [1] (🚍) oncoming bus +1F68E ; Extended_Pictographic# E1.0 [1] (🚎) trolleybus +1F68F ; Extended_Pictographic# E0.6 [1] (🚏) bus stop +1F690 ; Extended_Pictographic# E1.0 [1] (🚐) minibus +1F691..1F693 ; Extended_Pictographic# E0.6 [3] (🚑..🚓) ambulance..police car +1F694 ; Extended_Pictographic# E0.7 [1] (🚔) oncoming police car +1F695 ; Extended_Pictographic# E0.6 [1] (🚕) taxi +1F696 ; Extended_Pictographic# E1.0 [1] (🚖) oncoming taxi +1F697 ; Extended_Pictographic# E0.6 [1] (🚗) automobile +1F698 ; Extended_Pictographic# E0.7 [1] (🚘) oncoming automobile +1F699..1F69A ; Extended_Pictographic# E0.6 [2] (🚙..🚚) sport utility vehicle..delivery truck +1F69B..1F6A1 ; Extended_Pictographic# E1.0 [7] (🚛..🚡) articulated lorry..aerial tramway +1F6A2 ; Extended_Pictographic# E0.6 [1] (🚢) ship +1F6A3 ; Extended_Pictographic# E1.0 [1] (🚣) person rowing boat +1F6A4..1F6A5 ; Extended_Pictographic# E0.6 [2] (🚤..🚥) speedboat..horizontal traffic light +1F6A6 ; Extended_Pictographic# E1.0 [1] (🚦) vertical traffic light +1F6A7..1F6AD ; Extended_Pictographic# E0.6 [7] (🚧..🚭) construction..no smoking +1F6AE..1F6B1 ; Extended_Pictographic# E1.0 [4] (🚮..🚱) litter in bin sign..non-potable water +1F6B2 ; Extended_Pictographic# E0.6 [1] (🚲) bicycle +1F6B3..1F6B5 ; Extended_Pictographic# E1.0 [3] (🚳..🚵) no bicycles..person mountain biking +1F6B6 ; Extended_Pictographic# E0.6 [1] (🚶) person walking +1F6B7..1F6B8 ; Extended_Pictographic# E1.0 [2] (🚷..🚸) no pedestrians..children crossing +1F6B9..1F6BE ; Extended_Pictographic# E0.6 [6] (🚹..🚾) men’s room..water closet +1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower +1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath +1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage +1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL +1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp +1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed +1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed +1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship +1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart +1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA +1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple +1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator +1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless +1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy +1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat +1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE +1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane +1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE +1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival +1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. +1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite +1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE +1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship +1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe +1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer +1F6F9 ; Extended_Pictographic# E11.0 [1] (🛹) skateboard +1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw +1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate +1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. +1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS +1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square +1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. +1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign +1F7F1..1F7FF ; Extended_Pictographic# E0.0 [15] (🟱..🟿) .. +1F80C..1F80F ; Extended_Pictographic# E0.0 [4] (🠌..🠏) .. +1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. +1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. +1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. +1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers +1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand +1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns +1F919..1F91E ; Extended_Pictographic# E3.0 [6] (🤙..🤞) call me hand..crossed fingers +1F91F ; Extended_Pictographic# E5.0 [1] (🤟) love-you gesture +1F920..1F927 ; Extended_Pictographic# E3.0 [8] (🤠..🤧) cowboy hat face..sneezing face +1F928..1F92F ; Extended_Pictographic# E5.0 [8] (🤨..🤯) face with raised eyebrow..exploding head +1F930 ; Extended_Pictographic# E3.0 [1] (🤰) pregnant woman +1F931..1F932 ; Extended_Pictographic# E5.0 [2] (🤱..🤲) breast-feeding..palms up together +1F933..1F93A ; Extended_Pictographic# E3.0 [8] (🤳..🤺) selfie..person fencing +1F93C..1F93E ; Extended_Pictographic# E3.0 [3] (🤼..🤾) people wrestling..person playing handball +1F93F ; Extended_Pictographic# E12.0 [1] (🤿) diving mask +1F940..1F945 ; Extended_Pictographic# E3.0 [6] (🥀..🥅) wilted flower..goal net +1F947..1F94B ; Extended_Pictographic# E3.0 [5] (🥇..🥋) 1st place medal..martial arts uniform +1F94C ; Extended_Pictographic# E5.0 [1] (🥌) curling stone +1F94D..1F94F ; Extended_Pictographic# E11.0 [3] (🥍..🥏) lacrosse..flying disc +1F950..1F95E ; Extended_Pictographic# E3.0 [15] (🥐..🥞) croissant..pancakes +1F95F..1F96B ; Extended_Pictographic# E5.0 [13] (🥟..🥫) dumpling..canned food +1F96C..1F970 ; Extended_Pictographic# E11.0 [5] (🥬..🥰) leafy green..smiling face with hearts +1F971 ; Extended_Pictographic# E12.0 [1] (🥱) yawning face +1F972 ; Extended_Pictographic# E13.0 [1] (🥲) smiling face with tear +1F973..1F976 ; Extended_Pictographic# E11.0 [4] (🥳..🥶) partying face..cold face +1F977..1F978 ; Extended_Pictographic# E13.0 [2] (🥷..🥸) ninja..disguised face +1F979 ; Extended_Pictographic# E14.0 [1] (🥹) face holding back tears +1F97A ; Extended_Pictographic# E11.0 [1] (🥺) pleading face +1F97B ; Extended_Pictographic# E12.0 [1] (🥻) sari +1F97C..1F97F ; Extended_Pictographic# E11.0 [4] (🥼..🥿) lab coat..flat shoe +1F980..1F984 ; Extended_Pictographic# E1.0 [5] (🦀..🦄) crab..unicorn +1F985..1F991 ; Extended_Pictographic# E3.0 [13] (🦅..🦑) eagle..squid +1F992..1F997 ; Extended_Pictographic# E5.0 [6] (🦒..🦗) giraffe..cricket +1F998..1F9A2 ; Extended_Pictographic# E11.0 [11] (🦘..🦢) kangaroo..swan +1F9A3..1F9A4 ; Extended_Pictographic# E13.0 [2] (🦣..🦤) mammoth..dodo +1F9A5..1F9AA ; Extended_Pictographic# E12.0 [6] (🦥..🦪) sloth..oyster +1F9AB..1F9AD ; Extended_Pictographic# E13.0 [3] (🦫..🦭) beaver..seal +1F9AE..1F9AF ; Extended_Pictographic# E12.0 [2] (🦮..🦯) guide dog..white cane +1F9B0..1F9B9 ; Extended_Pictographic# E11.0 [10] (🦰..🦹) red hair..supervillain +1F9BA..1F9BF ; Extended_Pictographic# E12.0 [6] (🦺..🦿) safety vest..mechanical leg +1F9C0 ; Extended_Pictographic# E1.0 [1] (🧀) cheese wedge +1F9C1..1F9C2 ; Extended_Pictographic# E11.0 [2] (🧁..🧂) cupcake..salt +1F9C3..1F9CA ; Extended_Pictographic# E12.0 [8] (🧃..🧊) beverage box..ice +1F9CB ; Extended_Pictographic# E13.0 [1] (🧋) bubble tea +1F9CC ; Extended_Pictographic# E14.0 [1] (🧌) troll +1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person +1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks +1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet +1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts +1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart +1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (🩸..🩺) drop of blood..stethoscope +1FA7B..1FA7C ; Extended_Pictographic# E14.0 [2] (🩻..🩼) x-ray..crutch +1FA7D..1FA7F ; Extended_Pictographic# E0.0 [3] (🩽..🩿) .. +1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (🪀..🪂) yo-yo..parachute +1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute +1FA89..1FA8F ; Extended_Pictographic# E0.0 [7] (🪉..🪏) .. +1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo +1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock +1FAA9..1FAAC ; Extended_Pictographic# E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Extended_Pictographic# E15.0 [3] (🪭..🪯) folding hand fan..khanda +1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (🪰..🪶) fly..feather +1FAB7..1FABA ; Extended_Pictographic# E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Extended_Pictographic# E15.0 [3] (🪻..🪽) hyacinth..wing +1FABE ; Extended_Pictographic# E0.0 [1] (🪾) +1FABF ; Extended_Pictographic# E15.0 [1] (🪿) goose +1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging +1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FAC6..1FACD ; Extended_Pictographic# E0.0 [8] (🫆..🫍) .. +1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey +1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot +1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Extended_Pictographic# E15.0 [2] (🫚..🫛) ginger root..pea pod +1FADC..1FADF ; Extended_Pictographic# E0.0 [4] (🫜..🫟) .. +1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face +1FAE9..1FAEF ; Extended_Pictographic# E0.0 [7] (🫩..🫯) .. +1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand +1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. +1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. + +# Total elements: 3537 + +#EOF diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 97eec1ee1..5db6d393a 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -1,9 +1,8 @@ using System.Numerics; -using System.Runtime.CompilerServices; using Dalamud.Game.Gui; +using Dalamud.Interface.Internal.ImGuiSeStringRenderer; using Dalamud.Interface.Utility; -using Dalamud.Memory; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -203,7 +202,9 @@ internal unsafe class UiDebug { case NodeType.Text: var textNode = (AtkTextNode*)node; - ImGui.Text($"text: {MemoryHelper.ReadSeStringAsString(out _, (nint)textNode->NodeText.StringPtr)}"); + ImGui.Text("text: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textNode->NodeText); ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); @@ -230,7 +231,9 @@ internal unsafe class UiDebug break; case NodeType.Counter: var counterNode = (AtkCounterNode*)node; - ImGui.Text($"text: {MemoryHelper.ReadSeStringAsString(out _, (nint)counterNode->NodeText.StringPtr)}"); + ImGui.Text("text: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(counterNode->NodeText); break; case NodeType.Image: var imageNode = (AtkImageNode*)node; @@ -272,13 +275,8 @@ internal unsafe class UiDebug $"texture type: {texType} part_id={partId} part_id_count={partsList->PartCount}"); if (texType == TextureType.Resource) { - var texFileNameStdString = - &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; - var texString = texFileNameStdString->Length < 16 - ? MemoryHelper.ReadSeStringAsString(out _, (nint)texFileNameStdString->Buffer) - : MemoryHelper.ReadSeStringAsString(out _, (nint)texFileNameStdString->BufferPtr); - - ImGui.Text($"texture path: {texString}"); + ImGui.Text( + $"texture path: {textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName}"); var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) @@ -372,13 +370,33 @@ internal unsafe class UiDebug { case ComponentType.TextInput: var textInputComponent = (AtkComponentTextInput*)compNode->Component; - ImGui.Text($"InputBase Text1: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); - ImGui.Text($"InputBase Text2: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); - ImGui.Text($"Text1: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText01.StringPtr))}"); - ImGui.Text($"Text2: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText02.StringPtr))}"); - ImGui.Text($"Text3: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText03.StringPtr))}"); - ImGui.Text($"Text4: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText04.StringPtr))}"); - ImGui.Text($"Text5: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText05.StringPtr))}"); + ImGui.Text("InputBase Text1: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->AtkComponentInputBase.UnkText1); + + ImGui.Text("InputBase Text2: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->AtkComponentInputBase.UnkText2); + + ImGui.Text("Text1: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->UnkText01); + + ImGui.Text("Text2: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->UnkText02); + + ImGui.Text("Text3: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->UnkText03); + + ImGui.Text("Text4: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->UnkText04); + + ImGui.Text("Text5: "); + ImGui.SameLine(); + Service.Get().DrawWrapped(textInputComponent->UnkText05); break; } diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 8ac49aef4..9e1464105 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -50,6 +50,7 @@ internal class DataWindow : Window, IDisposable new PluginIpcWidget(), new SeFontTestWidget(), new ServicesWidget(), + new SeStringRendererTestWidget(), new StartInfoWidget(), new TargetWidget(), new TaskSchedulerWidget(), diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs new file mode 100644 index 000000000..e2182aba4 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -0,0 +1,67 @@ +using System.Text; + +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying Addon Data. +/// +internal unsafe class SeStringRendererTestWidget : IDataWindowWidget +{ + private ImVectorWrapper testStringBuffer; + private string testString = string.Empty; + + /// + public string DisplayName { get; init; } = "SeStringRenderer Test"; + + /// + public string[]? CommandShortcuts { get; init; } + + /// + public bool Ready { get; set; } + + /// + public void Load() => this.Ready = true; + + /// + public void Draw() + { + if (ImGui.Button("Reset Text") || this.testStringBuffer.IsDisposed) + { + this.testStringBuffer.Dispose(); + this.testStringBuffer = ImVectorWrapper.CreateFromSpan( + "
Lorem ipsum dolor sit amet, conse<->ctetur adipi<->scing elit. Maece<->nas digni<->ssim sem at inter<->dum ferme<->ntum. Praes<->ent ferme<->ntum conva<->llis velit sit amet hendr<->erit. Sed eu nibh magna. Integ<->er nec lacus in velit porta euism<->od sed et lacus. Sed non mauri<->s venen<->atis, matti<->s metus in, aliqu<->et dolor. Aliqu<->am erat volut<->pat. Nulla venen<->atis velit ac susci<->pit euism<->od. suspe<->ndisse maxim<->us viver<->ra dui id dapib<->us. Nam torto<->r dolor, eleme<->ntum quis orci id, pulvi<->nar fring<->illa quam. Pelle<->ntesque laore<->et viver<->ra torto<->r eget matti<->s. Vesti<->bulum eget porta ante, a molli<->s nulla. Curab<->itur a ligul<->a leo. Aliqu<->am volut<->pat sagit<->tis dapib<->us.\n\nFusce iacul<->is aliqu<->am mi, eget portt<->itor arcu solli<->citudin conse<->ctetur. suspe<->ndisse aliqu<->am commo<->do tinci<->dunt. Duis sed posue<->re tellu<->s. Sed phare<->tra ex vel torto<->r pelle<->ntesque, inter<->dum porta sapie<->n digni<->ssim. Queue Dun Scait<->h. Cras aliqu<->et at nulla quis moles<->tie. Vesti<->bulum eu ligul<->a sapie<->n. Curab<->itur digni<->ssim feugi<->at volut<->pat.\n\nVesti<->bulum condi<->mentum laore<->et rhonc<->us. Vivam<->us et accum<->san purus. Curab<->itur inter<->dum vel ligul<->a ac euism<->od. Donec sed nisl digni<->ssim est tinci<->dunt iacul<->is. Praes<->ent hendr<->erit pelle<->ntesque nisl, quis lacin<->ia arcu dictu<->m sit amet. Aliqu<->am variu<->s lectu<->s vel mauri<->s imper<->diet posue<->re. Ut gravi<->da non sapie<->n sed hendr<->erit.\n\nProin quis dapib<->us odio. Cras sagit<->tis non sem sed porta. Donec iacul<->is est ligul<->a, digni<->ssim aliqu<->et augue matti<->s vitae. Duis ullam<->corper tempu<->s odio, non vesti<->bulum est biben<->dum quis. In purus elit, vehic<->ula tinci<->dunt dictu<->m in, aucto<->r nec enim. Curab<->itur a nisi in leo matti<->s pelle<->ntesque id nec sem. Nunc vel ultri<->ces nisl. Nam congu<->e vulpu<->tate males<->uada. Aenea<->n vesti<->bulum mauri<->s leo, sit amet iacul<->is est imper<->diet ut. Phase<->llus nec lobor<->tis lacus, sit amet scele<->risque purus. Nam id lacin<->ia velit, euism<->od feugi<->at dui. Nulla sodal<->es odio ligul<->a, et hendr<->erit torto<->r maxim<->us eu. Donec et sem eu magna volut<->pat accum<->san non ut lectu<->s.\n\nVivam<->us susci<->pit ferme<->ntum gravi<->da. Cras nec conse<->ctetur magna. Vivam<->us ante massa, accum<->san sit amet felis et, tempu<->s iacul<->is ipsum. Pelle<->ntesque vitae nisi accum<->san, venen<->atis lectu<->s aucto<->r, aliqu<->et liber<->o. Nam nec imper<->diet justo. Vivam<->us ut vehic<->ula turpi<->s. Nunc lobor<->tis pelle<->ntesque urna, sit amet solli<->citudin nibh fauci<->bus in. Curab<->itur eu lobor<->tis lacus. Donec eu hendr<->erit diam, vitae cursu<->s odio. Cras eget scele<->risque mi.

"u8, + minCapacity: 65536); + this.testString = Encoding.UTF8.GetString(this.testStringBuffer.DataSpan); + } + + fixed (byte* labelPtr = "Test Input"u8) + { + if (ImGuiNative.igInputTextMultiline( + labelPtr, + this.testStringBuffer.Data, + (uint)this.testStringBuffer.Capacity, + new(ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight() * 3), + 0, + null, + null) != 0) + { + var len = this.testStringBuffer.StorageSpan.IndexOf((byte)0); + this.testString = Encoding.UTF8.GetString(this.testStringBuffer.StorageSpan[..len]); + if (len + 4 >= this.testStringBuffer.Capacity) + this.testStringBuffer.EnsureCapacityExponential(len + 4); + if (len < this.testStringBuffer.Capacity) + { + this.testStringBuffer.LengthUnsafe = len; + this.testStringBuffer.StorageSpan[len] = default; + } + } + } + + ImGui.Separator(); + ImGuiHelpers.CompileSeStringWrapped(this.testString); + } +} diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 885f73067..d979259ea 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -16,6 +16,10 @@ using Dalamud.Interface.Utility.Raii; using ImGuiNET; using ImGuiScene; +using Lumina.Text.ReadOnly; + +using SeStringRenderer = Dalamud.Interface.Internal.ImGuiSeStringRenderer.SeStringRenderer; + namespace Dalamud.Interface.Utility; /// @@ -177,6 +181,14 @@ public static class ImGuiHelpers if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); } + /// + public static void SeStringWrapped(ReadOnlySpan sss, float wrapWidth = 0) => + Service.Get().DrawWrapped(sss, wrapWidth); + + /// + public static void CompileSeStringWrapped(string text, float wrapWidth = 0) => + Service.Get().CompileAndDrawWrapped(text, wrapWidth); + /// /// Write unformatted text wrapped. /// diff --git a/Dalamud/Plugin/Services/IGameConfig.cs b/Dalamud/Plugin/Services/IGameConfig.cs index ed70b5753..5d8378659 100644 --- a/Dalamud/Plugin/Services/IGameConfig.cs +++ b/Dalamud/Plugin/Services/IGameConfig.cs @@ -101,7 +101,15 @@ public interface IGameConfig /// Details of the option: Minimum, Maximum, and Default values. /// A value representing the success. public bool TryGet(SystemConfigOption option, out FloatConfigProperties? properties); - + + /// + /// Attempts to get a string config value as a gamepad button enum value from the UiConfig section. + /// + /// Option to get the value of. + /// The returned value of the config option. + /// A value representing the success. + public bool TryGet(SystemConfigOption option, out PadButtonValue value); + /// /// Attempts to get the properties of a String option from the System section. /// From 3e86764748899ba44093b616857f1f2b7e0ef43d Mon Sep 17 00:00:00 2001 From: srkizer Date: Mon, 29 Jul 2024 00:29:25 +0900 Subject: [PATCH 005/375] Add xmldoc for TextProcessing (#1980) --- .../Internal/ImGuiSeStringRenderer/GfdFile.cs | 2 +- .../TextProcessing/UnicodeData.cs | 6 +- .../UnicodeEastAsianWidthClass.cs | 32 +++-- .../TextProcessing/UnicodeEmojiProperty.cs | 25 ++-- .../TextProcessing/UnicodeGeneralCategory.cs | 129 ++++++++++++------ .../TextProcessing/UnicodeLineBreakClass.cs | 9 +- 6 files changed, 129 insertions(+), 74 deletions(-) diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs index 2083d9cd9..48f9ad3a9 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs @@ -6,7 +6,7 @@ using Lumina.Data; namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer; -/// Reference member view of a .gfd file data. +/// Game font data file. internal sealed unsafe class GfdFile : FileResource { /// Gets or sets the file header. diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs index a97fb95a4..ffbd92cc5 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs @@ -44,7 +44,7 @@ internal static class UnicodeData EmojiProperty = Parse( typeof(UnicodeData).Assembly.GetManifestResourceStream("emoji-data.txt")!, - UnicodeEmojiProperty.Emoji); + default(UnicodeEmojiProperty)); } private static T[] Parse(Stream stream, T defaultValue) @@ -94,8 +94,8 @@ internal static class UnicodeData if (from > char.MaxValue) continue; - from = Math.Min(from, char.MaxValue); - to = Math.Min(to, char.MaxValue); + from = Math.Min(from, res.Length); + to = Math.Min(to, res.Length); if (isFlag) { foreach (ref var v in res.AsSpan()[from..to]) diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs index e6d1cbd5f..0335b29a0 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs @@ -2,21 +2,25 @@ using System.Diagnostics.CodeAnalysis; namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +/// Unicode east asian width. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1600:Elements should be documented", - Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1602:Enumeration items should be documented", - Justification = "Unicode Data")] internal enum UnicodeEastAsianWidthClass : byte { - A = 0, - F = 1, - H = 2, - N = 3, - Na = 4, - W = 5, + /// East Asian Ambiguous. + A, + + /// East Asian Fullwidth. + F, + + /// East Asian Halfwidth. + H, + + /// Neutral. + N, + + /// East Asian Narrow. + Na, + + /// East Asian Wide. + W, } diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs index 8847e08fe..3788d9d99 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs @@ -2,22 +2,27 @@ using System.Diagnostics.CodeAnalysis; namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +/// Unicode emoji property. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1600:Elements should be documented", - Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1602:Enumeration items should be documented", - Justification = "Unicode Data")] [Flags] internal enum UnicodeEmojiProperty : byte { + /// Characters that are emoji. Emoji = 1 << 0, + + /// Characters that have emoji presentation by default. Emoji_Presentation = 1 << 1, - Emoji_Modifier_Base = 1 << 2, - Emoji_Modifier = 1 << 3, + + /// Characters that are emoji modifiers. + Emoji_Modifier = 1 << 2, + + /// Characters that can serve as a base for emoji modifiers. + Emoji_Modifier_Base = 1 << 3, + + /// Characters used in emoji sequences that normally do not appear on emoji keyboards as separate choices, + /// such as keycap base characters or Regional_Indicator characters. Emoji_Component = 1 << 4, + + /// Characters that are used to future-proof segmentation. Extended_Pictographic = 1 << 5, } diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs index 0c0614a5f..007666031 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs @@ -2,45 +2,98 @@ using System.Diagnostics.CodeAnalysis; namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +/// Unicode general category.. +/// [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1600:Elements should be documented", - Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1602:Enumeration items should be documented", - Justification = "Unicode Data")] internal enum UnicodeGeneralCategory : byte { - Cn = 0, - Lu = 1, - Ll = 2, - Lt = 3, - Lm = 4, - Lo = 5, - Mn = 6, - Me = 7, - Mc = 8, - Nd = 9, - Nl = 10, - No = 11, - Zs = 12, - Zl = 13, - Zp = 14, - Cc = 15, - Cf = 16, - Co = 17, - Cs = 18, - Pd = 19, - Ps = 20, - Pe = 21, - Pc = 22, - Po = 23, - Sm = 24, - Sc = 25, - Sk = 26, - So = 27, - Pi = 28, - Pf = 29, + /// Uppercase_Letter; an uppercase letter. + Lu, + + /// Lowercase_Letter; a lowercase letter. + Ll, + + /// Titlecase_Letter; a digraph encoded as a single character, with first part uppercase. + Lt, + + /// Modifier_Letter; a modifier letter. + Lm, + + /// Other_Letter; other letters, including syllables and ideographs. + Lo, + + /// Nonspacing_Mark; a nonspacing combining mark (zero advance width). + Mn, + + /// Spacing_Mark; a spacing combining mark (positive advance width). + Mc, + + /// Enclosing_Mark; an enclosing combining mark. + Me, + + /// Decimal_Number; a decimal digit. + Nd, + + /// Letter_Number; a letterlike numeric character. + Nl, + + /// Other_Number; a numeric character of other type. + No, + + /// Connector_Punctuation; a connecting punctuation mark, like a tie. + Pc, + + /// Dash_Punctuation; a dash or hyphen punctuation mark. + Pd, + + /// Open_Punctuation; an opening punctuation mark (of a pair). + Ps, + + /// Close_Punctuation; a closing punctuation mark (of a pair). + Pe, + + /// Initial_Punctuation; an initial quotation mark. + Pi, + + /// Final_Punctuation; a final quotation mark. + Pf, + + /// Other_Punctuation; a punctuation mark of other type. + Po, + + /// Math_Symbol; a symbol of mathematical use. + Sm, + + /// Currency_Symbol; a currency sign. + Sc, + + /// Modifier_Symbol; a non-letterlike modifier symbol. + Sk, + + /// Other_Symbol; a symbol of other type. + So, + + /// Space_Separator; a space character (of various non-zero widths). + Zs, + + /// Line_Separator; U+2028 LINE SEPARATOR only. + Zl, + + /// Paragraph_Separator; U+2029 PARAGRAPH SEPARATOR only. + Zp, + + /// Control; a C0 or C1 control code. + Cc, + + /// Format; a format control character. + Cf, + + /// Surrogate; a surrogate code point. + Cs, + + /// Private_Use; a private-use character. + Co, + + /// Unassigned; a reserved unassigned code point or a noncharacter. + Cn, } diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs index b33eb9dc7..0ee5a50a3 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs +++ b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs @@ -2,19 +2,12 @@ using System.Diagnostics.CodeAnalysis; namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +/// Unicode line break class. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1300:Element should begin with an uppercase letter", Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1600:Elements should be documented", - Justification = "Unicode Data")] -[SuppressMessage( - "StyleCop.CSharp.DocumentationRules", - "SA1602:Enumeration items should be documented", - Justification = "Unicode Data")] internal enum UnicodeLineBreakClass : byte { /// Start of text. From b13e1eef86a068e2b6dd96f2d96479578e090213 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 28 Jul 2024 20:30:20 +0200 Subject: [PATCH 006/375] build: 10.0.0.9 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 5ad58e559..4ba608922 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.8 + 10.0.0.9 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 73e50c823bddff75dbd4cf109b696c6cfdec089d Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 28 Jul 2024 22:06:53 +0200 Subject: [PATCH 007/375] interface: only warn about signed reshade if we are actually using addon mode --- .../Interface/Internal/InterfaceManager.cs | 3 ++- .../Settings/Tabs/SettingsTabExperimental.cs | 22 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index aeff893a3..cb25bd9b5 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -817,7 +817,8 @@ internal partial class InterfaceManager : IInternalDisposableService 0, this.SetCursorDetour); - if (ReShadeAddonInterface.ReShadeIsSignedByReShade) + if (ReShadeAddonInterface.ReShadeIsSignedByReShade && + this.dalamudConfiguration.ReShadeHandlingMode is ReShadeHandlingMode.ReShadeAddonPresent or ReShadeHandlingMode.ReShadeAddonReShadeOverlay) { Log.Warning("Signed ReShade binary detected"); Service diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs index d3298f61a..aef674ac4 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs @@ -81,10 +81,24 @@ public class SettingsTabExperimental : SettingsTab (v, c) => c.ReShadeHandlingMode = v, fallbackValue: ReShadeHandlingMode.Default, warning: static rshm => - rshm is ReShadeHandlingMode.UnwrapReShade or ReShadeHandlingMode.None || - Service.Get().SwapChainHookMode == SwapChainHelper.HookMode.ByteCode - ? null - : "Current option will be ignored and no special ReShade handling will be done, because SwapChain vtable hook mode is set.") + { + var warning = string.Empty; + warning += rshm is ReShadeHandlingMode.UnwrapReShade or ReShadeHandlingMode.None || + Service.Get().SwapChainHookMode == SwapChainHelper.HookMode.ByteCode + ? string.Empty + : "Current option will be ignored and no special ReShade handling will be done, because SwapChain vtable hook mode is set."; + + if (ReShadeAddonInterface.ReShadeIsSignedByReShade) + { + warning += warning.Length > 0 ? "\n" : string.Empty; + warning += Loc.Localize( + "ReShadeNoAddonSupportNotificationContent", + "Your installation of ReShade does not have full addon support, and may not work with Dalamud and/or the game.\n" + + "Download and install ReShade with full addon-support."); + } + + return warning.Length > 0 ? warning : null; + }) { FriendlyEnumNameGetter = x => x switch { From 736987878905051f53edbcb0b508a203c83d8cd1 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 28 Jul 2024 22:22:11 +0200 Subject: [PATCH 008/375] build: 10.0.0.10 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 4ba608922..1d0ee79a4 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.9 + 10.0.0.10 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 9bb9b3acf44e13d29292f1af75c4bd28a968921c Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 30 Jul 2024 02:17:07 +0200 Subject: [PATCH 009/375] Add events for ClassJob and Level change (#1985) * Add events for ClassJob and Level change * Correctly handle uiModuleHandlePacketHook * Update Dalamud/Plugin/Services/IClientState.cs Co-authored-by: KazWolfe --- Dalamud/Game/ClientState/ClientState.cs | 126 +++++++++++++++++++----- Dalamud/Plugin/Services/IClientState.cs | 24 +++++ 2 files changed, 124 insertions(+), 26 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index b31bcf780..edf5e7ff9 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -13,7 +13,10 @@ using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Event; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using Lumina.Excel.GeneratedSheets; @@ -29,10 +32,11 @@ namespace Dalamud.Game.ClientState; internal sealed class ClientState : IInternalDisposableService, IClientState { private static readonly ModuleLog Log = new("ClientState"); - + private readonly GameLifecycle lifecycle; private readonly ClientStateAddressResolver address; private readonly Hook setupTerritoryTypeHook; + private readonly Hook uiModuleHandlePacketHook; [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -44,7 +48,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private bool lastFramePvP; [ServiceManager.ServiceConstructor] - private ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) + private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) { this.lifecycle = lifecycle; this.address = new ClientStateAddressResolver(); @@ -57,20 +61,28 @@ internal sealed class ClientState : IInternalDisposableService, IClientState Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}"); this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); + this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); this.framework.Update += this.FrameworkOnOnUpdateEvent; this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; this.setupTerritoryTypeHook.Enable(); + this.uiModuleHandlePacketHook.Enable(); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); + private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType); /// public event Action? TerritoryChanged; + /// + public event IClientState.ClassJobChangeDelegate? ClassJobChanged; + + /// + public event IClientState.LevelChangeDelegate? LevelChanged; + /// public event Action? Login; @@ -124,25 +136,25 @@ internal sealed class ClientState : IInternalDisposableService, IClientState /// Gets client state address resolver. /// internal ClientStateAddressResolver AddressResolver => this.address; - + /// public bool IsClientIdle(out ConditionFlag blockingFlag) { blockingFlag = 0; if (this.LocalPlayer is null) return true; - + var condition = Service.GetNullable(); - + var blockingConditions = condition.AsReadOnlySet().Except([ - ConditionFlag.NormalConditions, - ConditionFlag.Jumping, - ConditionFlag.Mounted, + ConditionFlag.NormalConditions, + ConditionFlag.Jumping, + ConditionFlag.Mounted, ConditionFlag.UsingParasol]); blockingFlag = blockingConditions.FirstOrDefault(); return blockingFlag == 0; } - + /// public bool IsClientIdle() => this.IsClientIdle(out _); @@ -152,18 +164,66 @@ internal sealed class ClientState : IInternalDisposableService, IClientState void IInternalDisposableService.DisposeService() { this.setupTerritoryTypeHook.Dispose(); + this.uiModuleHandlePacketHook.Dispose(); this.framework.Update -= this.FrameworkOnOnUpdateEvent; this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; } - private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) + private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType) { - this.TerritoryType = terriType; - this.TerritoryChanged?.InvokeSafely(terriType); + this.TerritoryType = territoryType; + this.TerritoryChanged?.InvokeSafely(territoryType); - Log.Debug("TerritoryType changed: {0}", terriType); + Log.Debug("TerritoryType changed: {0}", territoryType); - return this.setupTerritoryTypeHook.Original(manager, terriType); + this.setupTerritoryTypeHook.Original(eventFramework, territoryType); + } + + private unsafe void UIModuleHandlePacketDetour(UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet) + { + this.uiModuleHandlePacketHook!.Original(thisPtr, type, uintParam, packet); + + switch (type) + { + case UIModulePacketType.ClassJobChange: + { + var classJobId = uintParam; + + foreach (var action in this.ClassJobChanged.GetInvocationList().Cast()) + { + try + { + action(classJobId); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + + break; + } + + case UIModulePacketType.LevelChange: + { + var classJobId = *(uint*)packet; + var level = *(ushort*)((nint)packet + 4); + + foreach (var action in this.LevelChanged.GetInvocationList().Cast()) + { + try + { + action(classJobId, level); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + + break; + } + } } private void NetworkHandlersOnCfPop(ContentFinderCondition e) @@ -240,28 +300,36 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat internal ClientStatePluginScoped() { this.clientStateService.TerritoryChanged += this.TerritoryChangedForward; + this.clientStateService.ClassJobChanged += this.ClassJobChangedForward; + this.clientStateService.LevelChanged += this.LevelChangedForward; this.clientStateService.Login += this.LoginForward; this.clientStateService.Logout += this.LogoutForward; this.clientStateService.EnterPvP += this.EnterPvPForward; this.clientStateService.LeavePvP += this.ExitPvPForward; this.clientStateService.CfPop += this.ContentFinderPopForward; } - + /// public event Action? TerritoryChanged; - + + /// + public event IClientState.ClassJobChangeDelegate? ClassJobChanged; + + /// + public event IClientState.LevelChangeDelegate? LevelChanged; + /// public event Action? Login; - + /// public event Action? Logout; - + /// public event Action? EnterPvP; - + /// public event Action? LeavePvP; - + /// public event Action? CfPop; @@ -270,7 +338,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat /// public ushort TerritoryType => this.clientStateService.TerritoryType; - + /// public uint MapId => this.clientStateService.MapId; @@ -302,6 +370,8 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat void IInternalDisposableService.DisposeService() { this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward; + this.clientStateService.ClassJobChanged -= this.ClassJobChangedForward; + this.clientStateService.LevelChanged -= this.LevelChangedForward; this.clientStateService.Login -= this.LoginForward; this.clientStateService.Logout -= this.LogoutForward; this.clientStateService.EnterPvP -= this.EnterPvPForward; @@ -317,13 +387,17 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat } private void TerritoryChangedForward(ushort territoryId) => this.TerritoryChanged?.Invoke(territoryId); - + + private void ClassJobChangedForward(uint classJobId) => this.ClassJobChanged?.Invoke(classJobId); + + private void LevelChangedForward(uint classJobId, uint level) => this.LevelChanged?.Invoke(classJobId, level); + private void LoginForward() => this.Login?.Invoke(); - + private void LogoutForward() => this.Logout?.Invoke(); - + private void EnterPvPForward() => this.EnterPvP?.Invoke(); - + private void ExitPvPForward() => this.LeavePvP?.Invoke(); private void ContentFinderPopForward(ContentFinderCondition cfc) => this.CfPop?.Invoke(cfc); diff --git a/Dalamud/Plugin/Services/IClientState.cs b/Dalamud/Plugin/Services/IClientState.cs index db4903178..6b0555f23 100644 --- a/Dalamud/Plugin/Services/IClientState.cs +++ b/Dalamud/Plugin/Services/IClientState.cs @@ -9,11 +9,35 @@ namespace Dalamud.Plugin.Services; ///
public interface IClientState { + /// + /// A delegate type used for the event. + /// + /// The new ClassJob id. + public delegate void ClassJobChangeDelegate(uint classJobId); + + /// + /// A delegate type used for the event. + /// + /// The ClassJob id. + /// The level of the corresponding ClassJob. + public delegate void LevelChangeDelegate(uint classJobId, uint level); + /// /// Event that gets fired when the current Territory changes. /// public event Action TerritoryChanged; + /// + /// Event that fires when a characters ClassJob changed. + /// + public event ClassJobChangeDelegate? ClassJobChanged; + + /// + /// Event that fires when any character level changes, including levels + /// for a not-currently-active ClassJob (e.g. PvP matches, DoH/DoL). + /// + public event LevelChangeDelegate? LevelChanged; + /// /// Event that fires when a character is logging in, and the local character object is available. /// From a79b27db10712fc53c3af0359970568f401a7caa Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:05:42 +0200 Subject: [PATCH 010/375] Update ClientStructs (#1982) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index f1df136b2..b5003ced2 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit f1df136b28b1bf5319ddffc17c4563e575865de8 +Subproject commit b5003ced20e7e1e5fe61564ac6e3114469d34f90 From a5fa109afa6bad6c1a707af0d16b369a2b0ffbc6 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 30 Jul 2024 18:19:07 +0200 Subject: [PATCH 011/375] Rework AtkArrayDataBrowserWidget (#1986) Co-authored-by: KazWolfe --- .../Data/Widgets/AtkArrayDataBrowserWidget.cs | 478 +++++++++++++----- 1 file changed, 351 insertions(+), 127 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs index ee79764de..4f7e02062 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs @@ -1,8 +1,14 @@ -using System.Numerics; +using System.Numerics; + +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; -using Dalamud.Memory; using ImGuiNET; +using Lumina.Text.ReadOnly; + namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// @@ -10,6 +16,18 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget { + private readonly Type numberType = typeof(NumberArrayType); + private readonly Type stringType = typeof(StringArrayType); + private readonly Type extendType = typeof(ExtendArrayType); + + private int selectedNumberArray; + private int selectedStringArray; + private int selectedExtendArray; + + private string searchTerm = string.Empty; + private bool hideUnsetStringArrayEntries = false; + private bool hideUnsetExtendArrayEntries = false; + /// public bool Ready { get; set; } @@ -17,7 +35,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget public string[]? CommandShortcuts { get; init; } = { "atkarray" }; /// - public string DisplayName { get; init; } = "Atk Array Data"; + public string DisplayName { get; init; } = "Atk Array Data"; /// public void Load() @@ -28,148 +46,354 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget /// public void Draw() { - var fontWidth = ImGui.CalcTextSize("A").X; - var fontHeight = ImGui.GetTextLineHeightWithSpacing(); - var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUIModule(); + using var tabs = ImRaii.TabBar("AtkArrayDataTabs"); + if (!tabs) return; - if (uiModule == null) + this.DrawNumberArrayTab(); + this.DrawStringArrayTab(); + this.DrawExtendArrayTab(); + } + + private static void DrawCopyableText(string text, string tooltipText) + { + ImGui.TextUnformatted(text); + + if (ImGui.IsItemHovered()) { - ImGui.Text("UIModule unavailable."); - return; + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltipText); + ImGui.EndTooltip(); } - var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; - - if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar")) + if (ImGui.IsItemClicked()) { - if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]")) + ImGui.SetClipboardText(text); + } + } + + private void DrawArrayList(Type? arrayType, int arrayCount, short* arrayKeys, AtkArrayData** arrays, ref int selectedIndex) + { + using var table = ImRaii.Table("ArkArrayTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Borders, new Vector2(300, -1)); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 30); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupScrollFreeze(3, 1); + ImGui.TableHeadersRow(); + + var hasSearchTerm = !string.IsNullOrEmpty(this.searchTerm); + + for (var arrayIndex = 0; arrayIndex < arrayCount; arrayIndex++) + { + var inUse = arrayKeys[arrayIndex] != -1; + + var rowsFound = 0; + + if (hasSearchTerm && arrayType == typeof(StringArrayType)) { - if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + if (!inUse) + continue; + + var stringArrayData = (StringArrayData*)arrays[arrayIndex]; + for (var rowIndex = 0; rowIndex < arrays[arrayIndex]->Size; rowIndex++) { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; - if (numberArrayData != null) - { - ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9); - ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); - ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); - ImGui.TableHeadersRow(); - for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberIndex}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]}"); - ImGui.TableNextColumn(); - ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}"); - } + var isNull = (nint)stringArrayData->StringArray[rowIndex] == 0; + if (isNull) + continue; - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); + if (new ReadOnlySeStringSpan(stringArrayData->StringArray[rowIndex]).ExtractText().Contains(this.searchTerm, StringComparison.InvariantCultureIgnoreCase)) + rowsFound++; } - ImGui.EndTabItem(); + if (rowsFound == 0) + continue; } - if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]")) + using var disabled = ImRaii.Disabled(!inUse); + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); // Index + if (ImGui.Selectable($"#{arrayIndex}", selectedIndex == arrayIndex, ImGuiSelectableFlags.SpanAllColumns)) + selectedIndex = arrayIndex; + + ImGui.TableNextColumn(); // Type + if (arrayType != null && Enum.IsDefined(arrayType, arrayIndex)) { - if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + ImGui.TextUnformatted(Enum.GetName(arrayType, arrayIndex)); + } + else if (inUse && arrays[arrayIndex]->SubscribedAddonsCount > 0) + { + var raptureAtkUnitManager = RaptureAtkUnitManager.Instance(); + + for (var j = 0; j < arrays[arrayIndex]->SubscribedAddonsCount; j++) { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex]; - if (stringArrayData != null) - { - ImGui.Text($"{stringArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringIndex}"); - ImGui.TableNextColumn(); - if (stringArrayData->StringArray[stringIndex] != null) - { - ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}"); - } - else - { - ImGui.TextDisabled("--"); - } - } + if (arrays[arrayIndex]->SubscribedAddons[j] == 0) + continue; - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); + using (ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF)) + ImGui.TextUnformatted(raptureAtkUnitManager->GetAddonById(arrays[arrayIndex]->SubscribedAddons[j])->NameString); + break; } - - ImGui.EndTabItem(); } - ImGui.EndTabBar(); + ImGui.TableNextColumn(); // Size + if (inUse) + ImGui.TextUnformatted((rowsFound > 0 ? rowsFound : arrays[arrayIndex]->Size).ToString()); + } + } + + private void DrawArrayHeader(Type? arrayType, string type, int index, AtkArrayData* array) + { + ImGui.TextUnformatted($"{type} Array #{index}"); + + if (arrayType != null && Enum.IsDefined(arrayType, index)) + { + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" ({Enum.GetName(arrayType, index)})"); + } + + ImGui.SameLine(); + ImGui.TextUnformatted("–"); + ImGui.SameLine(); + ImGui.TextUnformatted("Address: "); + ImGui.SameLine(0, 0); + DrawCopyableText($"0x{(nint)array:X}", "Copy address"); + + if (array->SubscribedAddonsCount > 0) + { + ImGui.SameLine(); + ImGui.TextUnformatted("–"); + ImGui.SameLine(); + using (ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF)) + ImGui.TextUnformatted($"{array->SubscribedAddonsCount} Subscribed Addon" + (array->SubscribedAddonsCount > 1 ? 's' : string.Empty)); + + if (ImGui.IsItemHovered()) + { + using var tooltip = ImRaii.Tooltip(); + if (tooltip) + { + var raptureAtkUnitManager = RaptureAtkUnitManager.Instance(); + + for (var j = 0; j < array->SubscribedAddonsCount; j++) + { + if (array->SubscribedAddons[j] == 0) + continue; + + ImGui.TextUnformatted(raptureAtkUnitManager->GetAddonById(array->SubscribedAddons[j])->NameString); + } + } + } + } + } + + private void DrawNumberArrayTab() + { + var atkArrayDataHolder = RaptureAtkModule.Instance()->AtkArrayDataHolder; + + using var tab = ImRaii.TabItem("Number Arrays"); + if (!tab) return; + + this.DrawArrayList( + this.numberType, + atkArrayDataHolder.NumberArrayCount, + atkArrayDataHolder.NumberArrayKeys, + (AtkArrayData**)atkArrayDataHolder.NumberArrays, + ref this.selectedNumberArray); + + if (this.selectedNumberArray >= atkArrayDataHolder.NumberArrayCount || atkArrayDataHolder.NumberArrayKeys[this.selectedNumberArray] == -1) + this.selectedNumberArray = 0; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + using var child = ImRaii.Child("AtkArrayContent", new Vector2(-1), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings); + if (!child) return; + + var array = atkArrayDataHolder.NumberArrays[this.selectedNumberArray]; + this.DrawArrayHeader(this.numberType, "Number", this.selectedNumberArray, (AtkArrayData*)array); + + using var table = ImRaii.Table("NumberArrayDataTable", 7, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Entry Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Short", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Byte", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupScrollFreeze(7, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < array->Size; i++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); + + var ptr = &array->IntArray[i]; + + ImGui.TableNextColumn(); // Address + DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address"); + + ImGui.TableNextColumn(); // Integer + DrawCopyableText((*ptr).ToString(), "Copy value"); + + ImGui.TableNextColumn(); // Short + DrawCopyableText((*(short*)ptr).ToString(), "Copy as short"); + + ImGui.TableNextColumn(); // Byte + DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte"); + + ImGui.TableNextColumn(); // Float + DrawCopyableText((*(float*)ptr).ToString(), "Copy as float"); + + ImGui.TableNextColumn(); // Hex + DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex"); + } + } + + private void DrawStringArrayTab() + { + using var tab = ImRaii.TabItem("String Arrays"); + if (!tab) return; + + var atkArrayDataHolder = RaptureAtkModule.Instance()->AtkArrayDataHolder; + + using (var sidebarchild = ImRaii.Child("StringArraySidebar", new Vector2(300, -1), false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings)) + { + if (sidebarchild) + { + ImGui.SetNextItemWidth(-1); + ImGui.InputTextWithHint("##TextSearch", "Search...", ref this.searchTerm, 256, ImGuiInputTextFlags.AutoSelectAll); + + this.DrawArrayList( + this.stringType, + atkArrayDataHolder.StringArrayCount, + atkArrayDataHolder.StringArrayKeys, + (AtkArrayData**)atkArrayDataHolder.StringArrays, + ref this.selectedStringArray); + } + } + + if (this.selectedStringArray >= atkArrayDataHolder.StringArrayCount || atkArrayDataHolder.StringArrayKeys[this.selectedStringArray] == -1) + this.selectedStringArray = 0; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + using var child = ImRaii.Child("AtkArrayContent", new Vector2(-1), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings); + if (!child) return; + + var array = atkArrayDataHolder.StringArrays[this.selectedStringArray]; + this.DrawArrayHeader(this.stringType, "String", this.selectedStringArray, (AtkArrayData*)array); + ImGui.Checkbox("Hide unset entries##HideUnsetStringArrayEntriesCheckbox", ref this.hideUnsetStringArrayEntries); + + using var table = ImRaii.Table("StringArrayDataTable", 4, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn(ImGui.IsKeyDown(ImGuiKey.LeftShift) ? "Entry Address" : "Text Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Managed", ImGuiTableColumnFlags.WidthFixed, 60); + ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(4, 1); + ImGui.TableHeadersRow(); + + var hasSearchTerm = !string.IsNullOrEmpty(this.searchTerm); + + for (var i = 0; i < array->Size; i++) + { + var isNull = (nint)array->StringArray[i] == 0; + if (isNull && this.hideUnsetStringArrayEntries) + continue; + + if (hasSearchTerm) + { + if (isNull) + continue; + + if (!new ReadOnlySeStringSpan(array->StringArray[i]).ExtractText().Contains(this.searchTerm, StringComparison.InvariantCultureIgnoreCase)) + continue; + } + + using var disabledColor = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), isNull); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); + + ImGui.TableNextColumn(); // Address + if (ImGui.IsKeyDown(ImGuiKey.LeftShift)) + { + DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address"); + } + else if (!isNull) + { + DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address"); + } + + ImGui.TableNextColumn(); // Managed + if (!isNull) + ImGui.TextUnformatted(((nint)array->StringArray[i] != 0 && array->ManagedStringArray[i] == array->StringArray[i]).ToString()); + + ImGui.TableNextColumn(); // Text + if (!isNull) + DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text"); + } + } + + private void DrawExtendArrayTab() + { + using var tab = ImRaii.TabItem("Extend Arrays"); + if (!tab) return; + + var atkArrayDataHolder = RaptureAtkModule.Instance()->AtkArrayDataHolder; + + this.DrawArrayList( + this.extendType, + atkArrayDataHolder.ExtendArrayCount, + atkArrayDataHolder.ExtendArrayKeys, + (AtkArrayData**)atkArrayDataHolder.ExtendArrays, + ref this.selectedExtendArray); + + if (this.selectedExtendArray >= atkArrayDataHolder.ExtendArrayCount || atkArrayDataHolder.ExtendArrayKeys[this.selectedExtendArray] == -1) + this.selectedExtendArray = 0; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + using var child = ImRaii.Child("AtkArrayContent", new Vector2(-1), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoSavedSettings); + + var array = atkArrayDataHolder.ExtendArrays[this.selectedExtendArray]; + this.DrawArrayHeader(null, "Extend", this.selectedExtendArray, (AtkArrayData*)array); + ImGui.Checkbox("Hide unset entries##HideUnsetExtendArrayEntriesCheckbox", ref this.hideUnsetExtendArrayEntries); + + using var table = ImRaii.Table("ExtendArrayDataTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Entry Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(3, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < array->Size; i++) + { + var isNull = (nint)array->DataArray[i] == 0; + if (isNull && this.hideUnsetExtendArrayEntries) + continue; + + using var disabledColor = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), isNull); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); + + ImGui.TableNextColumn(); // Address + DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address"); + + ImGui.TableNextColumn(); // Pointer + if (!isNull) + DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address"); } } } From 731c0b7c6d8c528e01bc1335926bbb445a606dce Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 1 Aug 2024 19:32:12 +0200 Subject: [PATCH 012/375] Rework FateTableWidget (#1988) * Rework FateTableWidget * Maybe better FateTable column order * Add Icon, MapIcon and Radius column --- .../Windows/Data/Widgets/FateTableWidget.cs | 163 +++++++++++++++--- 1 file changed, 137 insertions(+), 26 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs index b36a7836c..77c05cb46 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs @@ -1,4 +1,7 @@ -using Dalamud.Game.ClientState.Fates; +using Dalamud.Game.ClientState.Fates; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using ImGuiNET; @@ -9,8 +12,6 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; ///
internal class FateTableWidget : IDataWindowWidget { - private bool resolveGameData; - /// public string[]? CommandShortcuts { get; init; } = { "fate", "fatetable" }; @@ -29,44 +30,154 @@ internal class FateTableWidget : IDataWindowWidget /// public void Draw() { - ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - var fateTable = Service.Get(); + var textureManager = Service.Get(); - var stateString = string.Empty; if (fateTable.Length == 0) { ImGui.TextUnformatted("No fates or data not ready."); + return; } - else + + using var table = ImRaii.Table("FateTable", 13, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("FateId", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("Level", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthFixed, 30); + ImGui.TableSetupColumn("MapIcon", ImGuiTableColumnFlags.WidthFixed, 30); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Progress", ImGuiTableColumnFlags.WidthFixed, 55); + ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("Bonus", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Position", ImGuiTableColumnFlags.WidthFixed, 240); + ImGui.TableSetupColumn("Radius", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupScrollFreeze(7, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < fateTable.Length; i++) { - stateString += $"FateTableLen: {fateTable.Length}\n"; + var fate = fateTable[i]; + if (fate == null) + continue; - ImGui.TextUnformatted(stateString); + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Index + ImGui.TextUnformatted($"#{i}"); - for (var i = 0; i < fateTable.Length; i++) + ImGui.TableNextColumn(); // Address + DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address"); + + ImGui.TableNextColumn(); // FateId + DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)"); + + ImGui.TableNextColumn(); // State + ImGui.TextUnformatted(fate.State.ToString()); + + ImGui.TableNextColumn(); // Level + + if (fate.Level == fate.MaxLevel) { - var fate = fateTable[i]; - if (fate == null) - continue; + ImGui.TextUnformatted($"{fate.Level}"); + } + else + { + ImGui.TextUnformatted($"{fate.Level}-{fate.MaxLevel}"); + } - var fateString = $"{fate.Address.ToInt64():X}:[{i}]" + - $" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" + - $" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" + - $" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n"; + ImGui.TableNextColumn(); // Icon - fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" + - $" - Duration: {fate.Duration}" + - $" - State: {fate.State}" + - $" - GameData name: {(this.resolveGameData ? (fate.GameData.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}"; - - ImGui.TextUnformatted(fateString); - ImGui.SameLine(); - if (ImGui.Button($"C##{fate.Address.ToInt64():X}")) + if (fate.IconId != 0) + { + if (textureManager.Shared.GetFromGameIcon(fate.IconId).TryGetWrap(out var texture, out _)) { - ImGui.SetClipboardText(fate.Address.ToString("X")); + ImGui.Image(texture.ImGuiHandle, new(ImGui.GetTextLineHeight())); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy IconId"); + ImGui.TextUnformatted($"ID: {fate.IconId} – Size: {texture.Width}x{texture.Height}"); + ImGui.Image(texture.ImGuiHandle, new(texture.Width, texture.Height)); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(fate.IconId.ToString()); + } } } + + ImGui.TableNextColumn(); // MapIconId + + if (fate.MapIconId != 0) + { + if (textureManager.Shared.GetFromGameIcon(fate.MapIconId).TryGetWrap(out var texture, out _)) + { + ImGui.Image(texture.ImGuiHandle, new(ImGui.GetTextLineHeight())); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy MapIconId"); + ImGui.TextUnformatted($"ID: {fate.MapIconId} – Size: {texture.Width}x{texture.Height}"); + ImGui.Image(texture.ImGuiHandle, new(texture.Width, texture.Height)); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(fate.MapIconId.ToString()); + } + } + } + + ImGui.TableNextColumn(); // Name + + DrawCopyableText(fate.Name.ToString(), "Click to copy Name"); + + ImGui.TableNextColumn(); // Progress + ImGui.TextUnformatted($"{fate.Progress}%"); + + ImGui.TableNextColumn(); // TimeRemaining + + if (fate.State == FateState.Running) + { + ImGui.TextUnformatted($"{TimeSpan.FromSeconds(fate.TimeRemaining):mm\\:ss} / {TimeSpan.FromSeconds(fate.Duration):mm\\:ss}"); + } + + ImGui.TableNextColumn(); // HasExpBonus + ImGui.TextUnformatted(fate.HasExpBonus.ToString()); + + ImGui.TableNextColumn(); // Position + DrawCopyableText(fate.Position.ToString(), "Click to copy Position"); + + ImGui.TableNextColumn(); // Radius + DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius"); + } + } + + private static void DrawCopyableText(string text, string tooltipText) + { + ImGuiHelpers.SafeTextWrapped(text); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltipText); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(text); } } } From 5fdd88b48892220c7d159c886330fa7c75193be4 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 1 Aug 2024 19:32:41 +0200 Subject: [PATCH 013/375] Update AtkArrayDataBrowserWidget (#1987) --- .../Data/Widgets/AtkArrayDataBrowserWidget.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs index 4f7e02062..791dc5310 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs @@ -1,5 +1,6 @@ using System.Numerics; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.UI; @@ -27,6 +28,8 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget private string searchTerm = string.Empty; private bool hideUnsetStringArrayEntries = false; private bool hideUnsetExtendArrayEntries = false; + private bool showTextAddress = false; + private bool showMacroString = false; /// public bool Ready { get; set; } @@ -56,7 +59,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget private static void DrawCopyableText(string text, string tooltipText) { - ImGui.TextUnformatted(text); + ImGuiHelpers.SafeTextWrapped(text); if (ImGui.IsItemHovered()) { @@ -288,12 +291,16 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget var array = atkArrayDataHolder.StringArrays[this.selectedStringArray]; this.DrawArrayHeader(this.stringType, "String", this.selectedStringArray, (AtkArrayData*)array); ImGui.Checkbox("Hide unset entries##HideUnsetStringArrayEntriesCheckbox", ref this.hideUnsetStringArrayEntries); + ImGui.SameLine(); + ImGui.Checkbox("Show text address##WordWrapCheckbox", ref this.showTextAddress); + ImGui.SameLine(); + ImGui.Checkbox("Show macro string##RenderStringsCheckbox", ref this.showMacroString); using var table = ImRaii.Table("StringArrayDataTable", 4, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders); if (!table) return; ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, 40); - ImGui.TableSetupColumn(ImGui.IsKeyDown(ImGuiKey.LeftShift) ? "Entry Address" : "Text Address", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn(this.showTextAddress ? "Text Address" : "Entry Address", ImGuiTableColumnFlags.WidthFixed, 120); ImGui.TableSetupColumn("Managed", ImGuiTableColumnFlags.WidthFixed, 60); ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupScrollFreeze(4, 1); @@ -323,22 +330,34 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget ImGui.TextUnformatted($"#{i}"); ImGui.TableNextColumn(); // Address - if (ImGui.IsKeyDown(ImGuiKey.LeftShift)) + if (this.showTextAddress) + { + if (!isNull) + DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address"); + } + else { DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address"); } - else if (!isNull) - { - DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address"); - } ImGui.TableNextColumn(); // Managed if (!isNull) + { ImGui.TextUnformatted(((nint)array->StringArray[i] != 0 && array->ManagedStringArray[i] == array->StringArray[i]).ToString()); + } ImGui.TableNextColumn(); // Text if (!isNull) - DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text"); + { + if (this.showMacroString) + { + DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text"); + } + else + { + ImGuiHelpers.SeStringWrapped(new ReadOnlySeStringSpan(array->StringArray[i])); + } + } } } From b6eb18d5507c444f613b52479a04b34efe53c083 Mon Sep 17 00:00:00 2001 From: srkizer Date: Fri, 2 Aug 2024 02:36:11 +0900 Subject: [PATCH 014/375] SeString renderer: fix colors, add link support (#1983) * Add coloring options * Add link support * simplify * fixes * Prevent EncodeString from causing crashes * Fix link range application and add link example * Fix test widget * Make DalamudLinkPayload backward compatible * make it better to use * make it better to use * Mark SeString rendering functions experimental via comments * rename * Simplify * Make sestring draw functions take in draw params * Improvements --- Dalamud/Dalamud.csproj | 8 +- .../Payloads/DalamudLinkPayload.cs | 96 +- .../Internal}/GfdFile.cs | 2 +- .../Internal/SeStringRenderer.cs | 1149 +++++++++++++++++ .../TextProcessing/DerivedGeneralCategory.txt | 0 .../TextProcessing/EastAsianWidth.txt | 0 .../Internal}/TextProcessing/LineBreak.txt | 0 .../TextProcessing/LineBreakEnumerator.cs | 8 +- .../Internal}/TextProcessing/UnicodeData.cs | 2 +- .../UnicodeEastAsianWidthClass.cs | 2 +- .../TextProcessing/UnicodeEmojiProperty.cs | 2 +- .../TextProcessing/UnicodeGeneralCategory.cs | 2 +- .../TextProcessing/UnicodeLineBreakClass.cs | 2 +- .../Internal}/TextProcessing/UtfEnumerator.cs | 16 +- .../TextProcessing/UtfEnumeratorFlags.cs | 2 +- .../Internal}/TextProcessing/UtfValue.cs | 2 +- .../Internal}/TextProcessing/emoji-data.txt | 0 .../SeStringDrawParams.cs | 168 +++ .../SeStringDrawResult.cs | 31 + .../ImGuiSeStringRenderer/SeStringRenderer.cs | 693 ---------- Dalamud/Interface/Internal/UiDebug.cs | 37 +- .../Widgets/SeStringRendererTestWidget.cs | 260 +++- Dalamud/Interface/Utility/ImGuiHelpers.cs | 59 +- Dalamud/Interface/Utility/ImGuiId.cs | 176 +++ Dalamud/Utility/SeStringExtensions.cs | 58 +- 25 files changed, 2009 insertions(+), 766 deletions(-) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/GfdFile.cs (98%) create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/DerivedGeneralCategory.txt (100%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/EastAsianWidth.txt (100%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/LineBreak.txt (100%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/LineBreakEnumerator.cs (98%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UnicodeData.cs (98%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UnicodeEastAsianWidthClass.cs (90%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UnicodeEmojiProperty.cs (94%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UnicodeGeneralCategory.cs (97%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UnicodeLineBreakClass.cs (98%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UtfEnumerator.cs (95%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UtfEnumeratorFlags.cs (96%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/UtfValue.cs (99%) rename Dalamud/Interface/{Internal/ImGuiSeStringRenderer => ImGuiSeStringRenderer/Internal}/TextProcessing/emoji-data.txt (100%) create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs delete mode 100644 Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs create mode 100644 Dalamud/Interface/Utility/ImGuiId.cs diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 1d0ee79a4..5aa87d963 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -115,10 +115,10 @@ - - - - + + + + diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index d069be30d..cbe416625 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -3,6 +3,8 @@ using System.IO; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; +using Newtonsoft.Json; + namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// @@ -13,32 +15,39 @@ public class DalamudLinkPayload : Payload /// public override PayloadType Type => PayloadType.DalamudLink; - /// - /// Gets the plugin command ID to be linked. - /// - public uint CommandId { get; internal set; } = 0; + /// Gets the plugin command ID to be linked. + public uint CommandId { get; internal set; } - /// - /// Gets the plugin name to be linked. - /// + /// Gets an optional extra integer value 1. + public int Extra1 { get; internal set; } + + /// Gets an optional extra integer value 2. + public int Extra2 { get; internal set; } + + /// Gets the plugin name to be linked. public string Plugin { get; internal set; } = string.Empty; + /// Gets an optional extra string. + public string ExtraString { get; internal set; } = string.Empty; + /// - public override string ToString() - { - return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; - } + public override string ToString() => + $"{this.Type} - {this.Plugin} ({this.CommandId}/{this.Extra1}/{this.Extra2}/{this.ExtraString})"; /// protected override byte[] EncodeImpl() { return new Lumina.Text.SeStringBuilder() - .BeginMacro(MacroCode.Link) - .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) - .AppendStringExpression(this.Plugin) - .AppendUIntExpression(this.CommandId) - .EndMacro() - .ToArray(); + .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(); } /// @@ -49,16 +58,53 @@ public class DalamudLinkPayload : Payload var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan()); - if (!rosps.TryGetExpression(out var pluginExpression, out var commandIdExpression)) - return; + if (!rosps.TryGetExpression( + out var commandIdExpression, + out var extra1Expression, + out var extra2Expression, + out var compositeExpression)) + { + if (!rosps.TryGetExpression(out var pluginExpression, out commandIdExpression)) + return; - if (!pluginExpression.TryGetString(out var pluginString)) - return; + if (!pluginExpression.TryGetString(out var pluginString)) + return; - if (!commandIdExpression.TryGetUInt(out var commandId)) - return; + if (!commandIdExpression.TryGetUInt(out var commandId)) + return; - this.Plugin = pluginString.ExtractText(); - this.CommandId = commandId; + this.Plugin = pluginString.ExtractText(); + this.CommandId = commandId; + } + else + { + if (!commandIdExpression.TryGetUInt(out var commandId)) + return; + + if (!extra1Expression.TryGetInt(out var extra1)) + return; + + if (!extra2Expression.TryGetInt(out var extra2)) + return; + + if (!compositeExpression.TryGetString(out var compositeString)) + return; + + string[] extraData; + try + { + extraData = JsonConvert.DeserializeObject(compositeString.ExtractText()); + } + catch + { + return; + } + + this.CommandId = commandId; + this.Extra1 = extra1; + this.Extra2 = extra2; + this.Plugin = extraData[0]; + this.ExtraString = extraData[1]; + } } } diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs similarity index 98% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs index 48f9ad3a9..194d71957 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/GfdFile.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using Lumina.Data; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; /// Game font data file. internal sealed unsafe class GfdFile : FileResource diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs new file mode 100644 index 000000000..33fcb4496 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -0,0 +1,1149 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +using BitFaster.Caching.Lru; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.Config; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; +using Dalamud.Interface.Utility; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using ImGuiNET; + +using Lumina.Excel.GeneratedSheets2; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon; + +using SeStringBuilder = Lumina.Text.SeStringBuilder; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; + +/// Draws SeString. +[ServiceManager.EarlyLoadedService] +internal unsafe class SeStringRenderer : IInternalDisposableService +{ + private const int ChannelLinkBackground = 0; + private const int ChannelShadow = 1; + private const int ChannelLinkUnderline = 2; + private const int ChannelEdge = 3; + private const int ChannelFore = 4; + private const int ChannelCount = 5; + + private const int ImGuiContextCurrentWindowOffset = 0x3FF0; + private const int ImGuiWindowDcOffset = 0x118; + private const int ImGuiWindowTempDataCurrLineTextBaseOffset = 0x38; + + /// Soft hyphen character, which signifies that a word can be broken here, and will display a standard + /// hyphen when it is broken there. + private const int SoftHyphen = '\u00AD'; + + /// Object replacement character, which signifies that there should be something else displayed in place + /// of this placeholder. On its own, usually displayed like [OBJ]. + private const int ObjectReplacementCharacter = '\uFFFC'; + + /// SeString to return instead, if macro encoder has failed and could not provide us the reason. + private static readonly ReadOnlySeString MacroEncoderEncodeStringError = + new SeStringBuilder() + .BeginMacro(MacroCode.ColorType).AppendIntExpression(508).EndMacro() + .BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(509).EndMacro() + .Append( + ""u8) + .BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(0).EndMacro() + .BeginMacro(MacroCode.ColorType).AppendIntExpression(0).EndMacro() + .ToReadOnlySeString(); + + [ServiceManager.ServiceDependency] + private readonly GameConfig gameConfig = Service.Get(); + + /// Cache of compiled SeStrings from . + private readonly ConcurrentLru cache = new(1024); + + /// Sets the global invalid parameter handler. Used to suppress vsprintf_s from raising. + /// There exists a thread local version of this, but as the game-provided implementation is what + /// effectively is a screaming tool that the game has a bug, it should be safe to fail in any means. + private readonly delegate* unmanaged< + delegate* unmanaged, + delegate* unmanaged> setInvalidParameterHandler; + + /// Parsed gfdata.gfd file, containing bitmap font icon lookup table. + private readonly GfdFile gfd; + + /// Parsed , containing colors to use with + /// . + private readonly uint[] colorTypes; + + /// Parsed , containing colors to use with + /// . + private readonly uint[] edgeColorTypes; + + /// Parsed text fragments from a SeString. + /// Touched only from the main thread. + private readonly List fragments = []; + + /// Foreground color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List colorStack = []; + + /// Edge/border color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List edgeColorStack = []; + + /// Shadow color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List shadowColorStack = []; + + /// Splits a draw list so that different layers of a single glyph can be drawn out of order. + private ImDrawListSplitter* splitter = ImGuiNative.ImDrawListSplitter_ImDrawListSplitter(); + + [ServiceManager.ServiceConstructor] + private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) + { + var uiColor = dm.Excel.GetSheet()!; + var maxId = 0; + foreach (var row in uiColor) + maxId = (int)Math.Max(row.RowId, maxId); + + this.colorTypes = new uint[maxId + 1]; + this.edgeColorTypes = new uint[maxId + 1]; + foreach (var row in uiColor) + { + // Contains ABGR. + this.colorTypes[row.RowId] = row.UIForeground; + this.edgeColorTypes[row.RowId] = row.UIGlow; + } + + if (BitConverter.IsLittleEndian) + { + // ImGui wants RGBA in LE. + foreach (ref var r in this.colorTypes.AsSpan()) + r = BinaryPrimitives.ReverseEndianness(r); + foreach (ref var r in this.edgeColorTypes.AsSpan()) + r = BinaryPrimitives.ReverseEndianness(r); + } + + this.gfd = dm.GetFile("common/font/gfdata.gfd")!; + + // SetUnhandledExceptionFilter(who cares); + // _set_purecall_handler(() => *(int*)0 = 0xff14); + // _set_invalid_parameter_handler(() => *(int*)0 = 0xff14); + var f = sigScanner.ScanText( + "ff 15 ff 0e e3 01 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ??") + 26; + fixed (void* p = &this.setInvalidParameterHandler) + *(nint*)p = *(int*)f + f + 4; + } + + /// Finalizes an instance of the class. + ~SeStringRenderer() => this.ReleaseUnmanagedResources(); + + /// + void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); + + /// Compiles a SeString from a text macro representation. + /// SeString text macro representation. + /// Compiled SeString. + public ReadOnlySeString Compile(ReadOnlySpan text) + { + // MacroEncoder looks stateful; disallowing calls from off main threads for now. + ThreadSafety.AssertMainThread(); + + var prev = this.setInvalidParameterHandler(&MsvcrtInvalidParameterHandlerDetour); + try + { + using var tmp = new Utf8String(); + RaptureTextModule.Instance()->MacroEncoder.EncodeString(&tmp, text); + return new(tmp.AsSpan().ToArray()); + } + catch (Exception) + { + return MacroEncoderEncodeStringError; + } + finally + { + this.setInvalidParameterHandler(prev); + } + + [UnmanagedCallersOnly] + static void MsvcrtInvalidParameterHandlerDetour(char* a, char* b, char* c, int d, nuint e) => + throw new InvalidOperationException(); + } + + /// Compiles a SeString from a text macro representation. + /// SeString text macro representation. + /// Compiled SeString. + public ReadOnlySeString Compile(ReadOnlySpan text) + { + var len = Encoding.UTF8.GetByteCount(text); + if (len >= 1024) + { + var buf = ArrayPool.Shared.Rent(len + 1); + buf[Encoding.UTF8.GetBytes(text, buf)] = 0; + var res = this.Compile(buf); + ArrayPool.Shared.Return(buf); + return res; + } + else + { + Span buf = stackalloc byte[len + 1]; + buf[Encoding.UTF8.GetBytes(text, buf)] = 0; + return this.Compile(buf); + } + } + + /// Compiles and caches a SeString from a text macro representation. + /// SeString text macro representation. + /// Newline characters will be normalized to newline payloads. + /// Compiled SeString. + public ReadOnlySeString CompileAndCache(string text) + { + // MacroEncoder looks stateful; disallowing calls from off main threads for now. + // Note that this is replicated in context.Compile. Only access cache from the main thread. + ThreadSafety.AssertMainThread(); + + return this.cache.GetOrAdd( + text, + static (text, context) => context.Compile(text.ReplaceLineEndings("
")), + this); + } + + /// Compiles and caches a SeString from a text macro representation, and then draws it. + /// SeString text macro representation. + /// Newline characters will be normalized to newline payloads. + /// Parameters for drawing. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public SeStringDrawResult CompileAndDrawWrapped( + string text, + in SeStringDrawParams drawParams = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + this.Draw(this.CompileAndCache(text).AsSpan(), drawParams, imGuiId, buttonFlags); + + /// Draws a SeString. + /// SeString to draw. + /// Parameters for drawing. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public SeStringDrawResult Draw( + in Utf8String utf8String, + in SeStringDrawParams drawParams = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + this.Draw(utf8String.AsSpan(), drawParams, imGuiId, buttonFlags); + + /// Draws a SeString. + /// SeString to draw. + /// Parameters for drawing. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + public SeStringDrawResult Draw( + ReadOnlySeStringSpan sss, + in SeStringDrawParams drawParams = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) + { + // Drawing is only valid if done from the main thread anyway, especially with interactivity. + ThreadSafety.AssertMainThread(); + + if (drawParams.TargetDrawList is not null && imGuiId) + throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId)); + + // This also does argument validation for drawParams. Do it here. + var state = new DrawState(sss, new(drawParams), this.splitter); + + // Reset and initialize the state. + this.fragments.Clear(); + this.colorStack.Clear(); + this.edgeColorStack.Clear(); + this.shadowColorStack.Clear(); + this.colorStack.Add(state.Params.Color); + this.edgeColorStack.Add(state.Params.EdgeColor); + this.shadowColorStack.Add(state.Params.ShadowColor); + state.Params.Color = ApplyOpacityValue(state.Params.Color, state.Params.Opacity); + state.Params.EdgeColor = ApplyOpacityValue(state.Params.EdgeColor, state.Params.EdgeOpacity); + state.Params.ShadowColor = ApplyOpacityValue(state.Params.ShadowColor, state.Params.Opacity); + + // Handle cases where ImGui.AlignTextToFramePadding has been called. + var pCurrentWindow = *(nint*)(ImGui.GetCurrentContext() + ImGuiContextCurrentWindowOffset); + var pWindowDc = pCurrentWindow + ImGuiWindowDcOffset; + var currLineTextBaseOffset = *(float*)(pWindowDc + ImGuiWindowTempDataCurrLineTextBaseOffset); + + // Analyze the provided SeString and break it up to text fragments. + this.CreateTextFragments(ref state, currLineTextBaseOffset); + var fragmentSpan = CollectionsMarshal.AsSpan(this.fragments); + + // Calculate size. + var size = Vector2.Zero; + foreach (ref var fragment in fragmentSpan) + size = Vector2.Max(size, fragment.Offset + new Vector2(fragment.VisibleWidth, state.Params.LineHeight)); + + // If we're not drawing at all, stop further processing. + if (state.Params.DrawList is null) + return new() { Size = size }; + + ImGuiNative.ImDrawListSplitter_Split(state.Splitter, state.Params.DrawList, ChannelCount); + + // Draw all text fragments. + var lastRune = default(Rune); + foreach (ref var f in fragmentSpan) + { + var data = state.Raw.Data[f.From..f.To]; + this.DrawTextFragment(ref state, f.Offset, f.IsSoftHyphenVisible, data, lastRune, f.Link); + lastRune = f.LastRune; + } + + // Create an ImGui item, if a target draw list is not manually set. + if (drawParams.TargetDrawList is null) + ImGui.Dummy(size); + + // Handle link interactions. + var clicked = false; + var hoveredLinkOffset = -1; + var activeLinkOffset = -1; + if (imGuiId.PushId()) + { + var invisibleButtonDrawn = false; + foreach (ref readonly var f in fragmentSpan) + { + if (f.Link == -1) + continue; + + var pos = ImGui.GetMousePos() - state.ScreenOffset - f.Offset; + var sz = new Vector2(f.AdvanceWidth, state.Params.LineHeight); + if (pos is { X: >= 0, Y: >= 0 } && pos.X <= sz.X && pos.Y <= sz.Y) + { + invisibleButtonDrawn = true; + + var cursorPosBackup = ImGui.GetCursorScreenPos(); + ImGui.SetCursorScreenPos(state.ScreenOffset + f.Offset); + clicked = ImGui.InvisibleButton("##link", sz, buttonFlags); + if (ImGui.IsItemHovered()) + hoveredLinkOffset = f.Link; + if (ImGui.IsItemActive()) + activeLinkOffset = f.Link; + ImGui.SetCursorScreenPos(cursorPosBackup); + + break; + } + } + + // If no link was hovered and thus no invisible button is put, treat the whole area as the button. + if (!invisibleButtonDrawn) + { + ImGui.SetCursorScreenPos(state.ScreenOffset); + clicked = ImGui.InvisibleButton("##text", size, buttonFlags); + } + + ImGui.PopID(); + } + + // If any link is being interacted, draw rectangles behind the relevant text fragments. + if (hoveredLinkOffset != -1 || activeLinkOffset != -1) + { + state.SetCurrentChannel(ChannelLinkBackground); + var color = activeLinkOffset == -1 ? state.Params.LinkHoverBackColor : state.Params.LinkActiveBackColor; + color = ApplyOpacityValue(color, state.Params.Opacity); + foreach (ref readonly var fragment in fragmentSpan) + { + if (fragment.Link != hoveredLinkOffset && hoveredLinkOffset != -1) + continue; + if (fragment.Link != activeLinkOffset && activeLinkOffset != -1) + continue; + ImGuiNative.ImDrawList_AddRectFilled( + state.Params.DrawList, + state.ScreenOffset + fragment.Offset, + state.ScreenOffset + fragment.Offset + new Vector2(fragment.AdvanceWidth, state.Params.LineHeight), + color, + 0, + ImDrawFlags.None); + } + } + + ImGuiNative.ImDrawListSplitter_Merge(state.Splitter, state.Params.DrawList); + + var payloadEnumerator = new ReadOnlySeStringSpan( + hoveredLinkOffset == -1 ? ReadOnlySpan.Empty : sss.Data[hoveredLinkOffset..]).GetEnumerator(); + if (!payloadEnumerator.MoveNext()) + return new() { Size = size, Clicked = clicked, InteractedPayloadOffset = -1 }; + return new() + { + Size = size, + Clicked = clicked, + InteractedPayloadOffset = hoveredLinkOffset, + InteractedPayloadEnvelope = sss.Data.Slice(hoveredLinkOffset, payloadEnumerator.Current.EnvelopeByteLength), + }; + } + + /// Gets the effective char for the given char, or null(\0) if it should not be handled at all. + /// Character to determine. + /// Corresponding rune. + /// Whether to display soft hyphens. + /// Rune corresponding to the unicode codepoint to process, or null(\0) if none. + private static bool TryGetDisplayRune(Rune rune, out Rune displayRune, bool displaySoftHyphen = true) + { + displayRune = rune.Value switch + { + 0 or char.MaxValue => default, + SoftHyphen => displaySoftHyphen ? new('-') : default, + _ when UnicodeData.LineBreak[rune.Value] + is UnicodeLineBreakClass.BK + or UnicodeLineBreakClass.CR + or UnicodeLineBreakClass.LF + or UnicodeLineBreakClass.NL => new(0), + _ => rune, + }; + return displayRune.Value != 0; + } + + /// Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA). + /// Color to process. + /// Swapped color. + private static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16); + + /// Applies the given opacity value ranging from 0 to 1 to an uint value containing a RGBA value. + /// RGBA value to transform. + /// Opacity to apply. + /// Transformed value. + private static uint ApplyOpacityValue(uint rgba, float opacity) => + ((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu); + + private void ReleaseUnmanagedResources() + { + if (this.splitter is not null) + { + ImGuiNative.ImDrawListSplitter_destroy(this.splitter); + this.splitter = null; + } + } + + /// Creates text fragment, taking line and word breaking into account. + /// Draw state. + /// Y offset adjustment for all text fragments. Used to honor + /// . + private void CreateTextFragments(ref DrawState state, float baseY) + { + var prev = 0; + var xy = new Vector2(0, baseY); + var w = 0f; + var linkOffset = -1; + foreach (var (breakAt, mandatory) in new LineBreakEnumerator(state.Raw, UtfEnumeratorFlags.Utf8SeString)) + { + var nextLinkOffset = linkOffset; + for (var first = true; prev < breakAt; first = false) + { + var curr = breakAt; + + // Try to split by link payloads. + foreach (var p in new ReadOnlySeStringSpan(state.Raw.Data[prev..breakAt]).GetOffsetEnumerator()) + { + if (p.Payload.MacroCode == MacroCode.Link) + { + nextLinkOffset = + p.Payload.TryGetExpression(out var e) && + e.TryGetUInt(out var u) && + u == (uint)LinkMacroPayloadType.Terminator + ? -1 + : prev + p.Offset; + + // Split only if we're not splitting at the beginning. + if (p.Offset != 0) + { + curr = prev + p.Offset; + break; + } + + linkOffset = nextLinkOffset; + } + } + + // Create a text fragment without applying wrap width limits for testing. + var fragment = state.CreateFragment(this, prev, curr, curr == breakAt && mandatory, xy, linkOffset); + var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.Params.WrapWidth; + + // Test if the fragment does not fit into the current line and the current line is not empty, + // if this is the first time testing the current break unit. + if (first && xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows) + { + // The break unit as a whole does not fit into the current line. Advance to the next line. + xy.X = 0; + xy.Y += state.Params.LineHeight; + w = 0; + CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true; + fragment.Offset = xy; + + // Now that the fragment is given its own line, test if it overflows again. + overflows = fragment.VisibleWidth > state.Params.WrapWidth; + } + + if (overflows) + { + // Create a fragment again that fits into the given width limit. + var remainingWidth = state.Params.WrapWidth - xy.X; + fragment = state.CreateFragment(this, prev, curr, true, xy, linkOffset, remainingWidth); + } + else if (this.fragments.Count > 0 && xy.X != 0) + { + // New fragment fits into the current line, and it has a previous fragment in the same line. + // If the previous fragment ends with a soft hyphen, adjust its width so that the width of its + // trailing soft hyphens are not considered. + if (this.fragments[^1].EndsWithSoftHyphen) + xy.X += this.fragments[^1].AdvanceWidthWithoutSoftHyphen - this.fragments[^1].AdvanceWidth; + + // Adjust this fragment's offset from kerning distance. + xy.X += state.CalculateDistance(this.fragments[^1].LastRune, fragment.FirstRune); + fragment.Offset = xy; + } + + // If the fragment was not broken by wrap width, update the link payload offset. + if (fragment.To == curr) + linkOffset = nextLinkOffset; + + w = Math.Max(w, xy.X + fragment.VisibleWidth); + xy.X += fragment.AdvanceWidth; + prev = fragment.To; + this.fragments.Add(fragment); + + if (fragment.BreakAfter) + { + xy.X = w = 0; + xy.Y += state.Params.LineHeight; + } + } + } + } + + /// Draws a text fragment. + /// Draw state. + /// Offset of left top corner of this text fragment in pixels w.r.t. + /// . + /// Whether to display soft hyphens in this text fragment. + /// Byte span of the SeString fragment to draw. + /// Rune that preceded this text fragment in the same line, or 0 if none. + /// Byte offset of the link payload that decorates this text fragment in + /// , or -1 if none. + private void DrawTextFragment( + ref DrawState state, + Vector2 offset, + bool displaySoftHyphen, + ReadOnlySpan span, + Rune lastRune, + int link) + { + var gfdTextureSrv = + (nint)UIModule.Instance()->GetRaptureAtkModule()->AtkModule.AtkFontManager.Gfd->Texture-> + D3D11ShaderResourceView; + var x = 0f; + var width = 0f; + foreach (var c in UtfEnumerator.From(span, UtfEnumeratorFlags.Utf8SeString)) + { + if (c is { IsSeStringPayload: true, EffectiveInt: char.MaxValue or ObjectReplacementCharacter }) + { + var enu = new ReadOnlySeStringSpan(span[c.ByteOffset..]).GetOffsetEnumerator(); + if (!enu.MoveNext()) + continue; + + var payload = enu.Current.Payload; + switch (payload.MacroCode) + { + case MacroCode.Color: + state.Params.Color = ApplyOpacityValue( + TouchColorStack(this.colorStack, payload), + state.Params.Opacity); + continue; + case MacroCode.EdgeColor: + state.Params.EdgeColor = TouchColorStack(this.edgeColorStack, payload); + state.Params.EdgeColor = ApplyOpacityValue( + state.Params.ForceEdgeColor ? this.edgeColorStack[0] : state.Params.EdgeColor, + state.Params.EdgeOpacity); + continue; + case MacroCode.ShadowColor: + state.Params.ShadowColor = ApplyOpacityValue( + TouchColorStack(this.shadowColorStack, payload), + state.Params.Opacity); + continue; + case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + // doesn't actually work in chat log + state.Params.Bold = u != 0; + continue; + case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + state.Params.Italic = u != 0; + continue; + case MacroCode.Edge when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + state.Params.Edge = u != 0; + continue; + case MacroCode.Shadow when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + state.Params.Shadow = u != 0; + continue; + case MacroCode.ColorType: + state.Params.Color = ApplyOpacityValue( + TouchColorTypeStack(this.colorStack, this.colorTypes, payload), + state.Params.Opacity); + continue; + case MacroCode.EdgeColorType: + state.Params.EdgeColor = TouchColorTypeStack(this.edgeColorStack, this.edgeColorTypes, payload); + state.Params.EdgeColor = ApplyOpacityValue( + state.Params.ForceEdgeColor ? this.edgeColorStack[0] : state.Params.EdgeColor, + state.Params.EdgeOpacity); + continue; + case MacroCode.Icon: + case MacroCode.Icon2: + { + if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is not (var icon and not None) || + !this.gfd.TryGetEntry((uint)icon, out var gfdEntry) || + gfdEntry.IsEmpty) + continue; + + var size = state.CalculateGfdEntrySize(gfdEntry, out var useHq); + state.SetCurrentChannel(ChannelFore); + state.Draw( + offset + new Vector2(x, MathF.Round((state.Params.LineHeight - size.Y) / 2)), + gfdTextureSrv, + Vector2.Zero, + size, + Vector2.Zero, + useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, + useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1, + ApplyOpacityValue(uint.MaxValue, state.Params.Opacity)); + if (link != -1) + state.DrawLinkUnderline(offset + new Vector2(x, 0), size.X); + + width = Math.Max(width, x + size.X); + x += MathF.Round(size.X); + lastRune = default; + continue; + } + + default: + continue; + } + } + + if (!TryGetDisplayRune(c.EffectiveRune, out var rune, displaySoftHyphen)) + continue; + + ref var g = ref state.FindGlyph(ref rune); + var dist = state.CalculateDistance(lastRune, rune); + lastRune = rune; + + var dxBold = state.Params.Bold ? 2 : 1; + var dyItalic = state.Params.Italic + ? new Vector2(state.Params.FontSize - g.Y0, state.Params.FontSize - g.Y1) / 6 + : Vector2.Zero; + + if (state.Params is { Shadow: true, ShadowColor: >= 0x1000000 }) + { + state.SetCurrentChannel(ChannelShadow); + for (var dx = 0; dx < dxBold; dx++) + state.Draw(offset + new Vector2(x + dist + dx, 1), g, dyItalic, state.Params.ShadowColor); + } + + if ((state.Params.Edge || this.edgeColorStack.Count > 1) && state.Params.EdgeColor >= 0x1000000) + { + state.SetCurrentChannel(ChannelEdge); + for (var dx = -1; dx <= dxBold; dx++) + { + for (var dy = -1; dy <= 1; dy++) + { + if (dx >= 0 && dx < dxBold && dy == 0) + continue; + + state.Draw(offset + new Vector2(x + dist + dx, dy), g, dyItalic, state.Params.EdgeColor); + } + } + } + + state.SetCurrentChannel(ChannelFore); + for (var dx = 0; dx < dxBold; dx++) + state.Draw(offset + new Vector2(x + dist + dx, 0), g, dyItalic, state.Params.Color); + + if (link != -1) + state.DrawLinkUnderline(offset + new Vector2(x + dist, 0), g.AdvanceX); + + width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale)); + x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale); + } + + return; + + static uint TouchColorStack(List rgbaStack, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return rgbaStack[^1]; + + // Color payloads have BGRA values as its parameter. ImGui expects RGBA values. + // Opacity component is ignored. + if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor) + { + // First item in the stack is the color we started to draw with. + if (rgbaStack.Count > 1) + rgbaStack.RemoveAt(rgbaStack.Count - 1); + return rgbaStack[^1]; + } + + if (expr.TryGetUInt(out var bgra)) + { + rgbaStack.Add(SwapRedBlue(bgra) | 0xFF000000u); + return rgbaStack[^1]; + } + + if (expr.TryGetParameterExpression(out var et, out var op) && + et == (int)ExpressionType.GlobalNumber && + op.TryGetInt(out var i) && + RaptureTextModule.Instance() is var rtm && + rtm is not null && + i > 0 && i <= rtm->TextModule.MacroDecoder.GlobalParameters.Count && + rtm->TextModule.MacroDecoder.GlobalParameters[i - 1] is { Type: TextParameterType.Integer } gp) + { + rgbaStack.Add(SwapRedBlue((uint)gp.IntValue) | 0xFF000000u); + return rgbaStack[^1]; + } + + // Fallback value. + rgbaStack.Add(0xFF000000u); + return rgbaStack[^1]; + } + + static uint TouchColorTypeStack(List rgbaStack, uint[] colorTypes, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return rgbaStack[^1]; + if (!expr.TryGetUInt(out var colorTypeIndex)) + return rgbaStack[^1]; + + if (colorTypeIndex == 0) + { + // First item in the stack is the color we started to draw with. + if (rgbaStack.Count > 1) + rgbaStack.RemoveAt(rgbaStack.Count - 1); + return rgbaStack[^1]; + } + + // Opacity component is ignored. + rgbaStack.Add((colorTypeIndex < colorTypes.Length ? colorTypes[colorTypeIndex] : 0u) | 0xFF000000u); + + return rgbaStack[^1]; + } + } + + /// Determines a bitmap icon to display for the given SeString payload. + /// Byte span that should include a SeString payload. + /// Icon to display, or if it should not be displayed as an icon. + private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan sss) + { + var e = new ReadOnlySeStringSpan(sss).GetEnumerator(); + if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2) + return None; + + var payload = e.Current; + switch (payload.MacroCode) + { + // Show the specified icon as-is. + case MacroCode.Icon + when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): + return (BitmapFontIcon)iconId; + + // Apply gamepad key mapping to icons. + case MacroCode.Icon2 + when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): + var configName = (BitmapFontIcon)iconId switch + { + ControllerShoulderLeft => SystemConfigOption.PadButton_L1, + ControllerShoulderRight => SystemConfigOption.PadButton_R1, + ControllerTriggerLeft => SystemConfigOption.PadButton_L2, + ControllerTriggerRight => SystemConfigOption.PadButton_R2, + ControllerButton3 => SystemConfigOption.PadButton_Triangle, + ControllerButton1 => SystemConfigOption.PadButton_Cross, + ControllerButton0 => SystemConfigOption.PadButton_Circle, + ControllerButton2 => SystemConfigOption.PadButton_Square, + ControllerStart => SystemConfigOption.PadButton_Start, + ControllerBack => SystemConfigOption.PadButton_Select, + ControllerAnalogLeftStick => SystemConfigOption.PadButton_LS, + ControllerAnalogLeftStickIn => SystemConfigOption.PadButton_LS, + ControllerAnalogLeftStickUpDown => SystemConfigOption.PadButton_LS, + ControllerAnalogLeftStickLeftRight => SystemConfigOption.PadButton_LS, + ControllerAnalogRightStick => SystemConfigOption.PadButton_RS, + ControllerAnalogRightStickIn => SystemConfigOption.PadButton_RS, + ControllerAnalogRightStickUpDown => SystemConfigOption.PadButton_RS, + ControllerAnalogRightStickLeftRight => SystemConfigOption.PadButton_RS, + _ => (SystemConfigOption?)null, + }; + + if (configName is null || !this.gameConfig.TryGet(configName.Value, out PadButtonValue pb)) + return (BitmapFontIcon)iconId; + + return pb switch + { + PadButtonValue.Autorun_Support => ControllerShoulderLeft, + PadButtonValue.Hotbar_Set_Change => ControllerShoulderRight, + PadButtonValue.XHB_Left_Start => ControllerTriggerLeft, + PadButtonValue.XHB_Right_Start => ControllerTriggerRight, + PadButtonValue.Jump => ControllerButton3, + PadButtonValue.Accept => ControllerButton0, + PadButtonValue.Cancel => ControllerButton1, + PadButtonValue.Map_Sub => ControllerButton2, + PadButtonValue.MainCommand => ControllerStart, + PadButtonValue.HUD_Select => ControllerBack, + PadButtonValue.Move_Operation => (BitmapFontIcon)iconId switch + { + ControllerAnalogLeftStick => ControllerAnalogLeftStick, + ControllerAnalogLeftStickIn => ControllerAnalogLeftStickIn, + ControllerAnalogLeftStickUpDown => ControllerAnalogLeftStickUpDown, + ControllerAnalogLeftStickLeftRight => ControllerAnalogLeftStickLeftRight, + ControllerAnalogRightStick => ControllerAnalogLeftStick, + ControllerAnalogRightStickIn => ControllerAnalogLeftStickIn, + ControllerAnalogRightStickUpDown => ControllerAnalogLeftStickUpDown, + ControllerAnalogRightStickLeftRight => ControllerAnalogLeftStickLeftRight, + _ => (BitmapFontIcon)iconId, + }, + PadButtonValue.Camera_Operation => (BitmapFontIcon)iconId switch + { + ControllerAnalogLeftStick => ControllerAnalogRightStick, + ControllerAnalogLeftStickIn => ControllerAnalogRightStickIn, + ControllerAnalogLeftStickUpDown => ControllerAnalogRightStickUpDown, + ControllerAnalogLeftStickLeftRight => ControllerAnalogRightStickLeftRight, + ControllerAnalogRightStick => ControllerAnalogRightStick, + ControllerAnalogRightStickIn => ControllerAnalogRightStickIn, + ControllerAnalogRightStickUpDown => ControllerAnalogRightStickUpDown, + ControllerAnalogRightStickLeftRight => ControllerAnalogRightStickLeftRight, + _ => (BitmapFontIcon)iconId, + }, + _ => (BitmapFontIcon)iconId, + }; + } + + return None; + } + + /// Represents a text fragment in a SeString span. + /// Starting byte offset (inclusive) in a SeString. + /// Ending byte offset (exclusive) in a SeString. + /// Byte offset of the link that decorates this text fragment, or -1 if none. + /// Offset in pixels w.r.t. . + /// Visible width of this text fragment. This is the width required to draw everything + /// without clipping. + /// Advance width of this text fragment. This is the width required to add to the cursor + /// to position the next fragment correctly. + /// Same with , but trimming all the + /// trailing soft hyphens. + /// Whether to insert a line break after this text fragment. + /// Whether this text fragment ends with one or more soft hyphens. + /// First rune in this text fragment. + /// Last rune in this text fragment, for the purpose of calculating kerning distance with + /// the following text fragment in the same line, if any. + private record struct TextFragment( + int From, + int To, + int Link, + Vector2 Offset, + float VisibleWidth, + float AdvanceWidth, + float AdvanceWidthWithoutSoftHyphen, + bool BreakAfter, + bool EndsWithSoftHyphen, + Rune FirstRune, + Rune LastRune) + { + public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter; + } + + /// Represents a temporary state required for drawing. + private ref struct DrawState( + ReadOnlySeStringSpan raw, + SeStringDrawParams.Resolved @params, + ImDrawListSplitter* splitter) + { + /// Raw SeString span. + public readonly ReadOnlySeStringSpan Raw = raw; + + /// Multiplier value for glyph metrics, so that it scales to . + /// + public readonly float FontSizeScale = @params.FontSize / @params.Font->FontSize; + + /// Value obtained from . + public readonly Vector2 ScreenOffset = @params.ScreenOffset; + + /// Splitter to split . + public readonly ImDrawListSplitter* Splitter = splitter; + + /// Resolved draw parameters from the caller. + public SeStringDrawParams.Resolved Params = @params; + + /// Calculates the size in pixels of a GFD entry when drawn. + /// GFD entry to determine the size. + /// Whether to draw the HQ texture. + /// Determined size of the GFD entry when drawn. + public readonly Vector2 CalculateGfdEntrySize(in GfdFile.GfdEntry gfdEntry, out bool useHq) + { + useHq = this.Params.FontSize > 20; + var targetHeight = useHq ? this.Params.FontSize : 20; + return new(gfdEntry.Width * (targetHeight / gfdEntry.Height), targetHeight); + } + + /// Sets the current channel in the ImGui draw list splitter. + /// Channel to switch to. + public readonly void SetCurrentChannel(int channelIndex) => + ImGuiNative.ImDrawListSplitter_SetCurrentChannel( + this.Splitter, + this.Params.DrawList, + channelIndex); + + /// Draws a single glyph. + /// Offset of the glyph in pixels w.r.t. + /// . + /// Glyph to draw. + /// Transformation for that will push top and bottom pixels to + /// apply faux italicization. + /// Color of the glyph. + public readonly void Draw(Vector2 offset, in ImGuiHelpers.ImFontGlyphReal g, Vector2 dyItalic, uint color) => + this.Draw( + offset + new Vector2( + 0, + MathF.Round(((this.Params.LineHeight - this.Params.Font->FontSize) * this.FontSizeScale) / 2f)), + this.Params.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID, + g.XY0 * this.FontSizeScale, + g.XY1 * this.FontSizeScale, + dyItalic * this.FontSizeScale, + g.UV0, + g.UV1, + color); + + /// Draws a single glyph. + /// Offset of the glyph in pixels w.r.t. + /// . + /// ImGui texture ID to draw from. + /// Left top corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Transformation for and that will push + /// top and bottom pixels to apply faux italicization. + /// Left top corner of the glyph w.r.t. its glyph origin in the source texture. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the source texture. + /// Color of the glyph. + public readonly void Draw( + Vector2 offset, + nint igTextureId, + Vector2 xy0, + Vector2 xy1, + Vector2 dyItalic, + Vector2 uv0, + Vector2 uv1, + uint color = uint.MaxValue) + { + offset += this.ScreenOffset; + ImGuiNative.ImDrawList_AddImageQuad( + this.Params.DrawList, + igTextureId, + offset + new Vector2(xy0.X + dyItalic.X, xy0.Y), + offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.X, xy0.Y), + new(uv0.X, uv0.Y), + new(uv0.X, uv1.Y), + new(uv1.X, uv1.Y), + new(uv1.X, uv0.Y), + color); + } + + /// Draws an underline, for links. + /// Offset of the glyph in pixels w.r.t. + /// . + /// Advance width of the glyph. + public readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth) + { + if (this.Params.LinkUnderlineThickness < 1f) + return; + + var dy = (this.Params.LinkUnderlineThickness - 1) / 2f; + dy += MathF.Round( + (((this.Params.LineHeight - this.Params.FontSize) / 2) + this.Params.Font->Ascent) * + this.FontSizeScale); + this.SetCurrentChannel(ChannelLinkUnderline); + ImGuiNative.ImDrawList_AddLine( + this.Params.DrawList, + this.ScreenOffset + offset + new Vector2(0, dy), + this.ScreenOffset + offset + new Vector2(advanceWidth, dy), + this.Params.Color, + this.Params.LinkUnderlineThickness); + + if (this.Params is { Shadow: true, ShadowColor: >= 0x1000000 }) + { + this.SetCurrentChannel(ChannelShadow); + ImGuiNative.ImDrawList_AddLine( + this.Params.DrawList, + this.ScreenOffset + offset + new Vector2(0, dy + 1), + this.ScreenOffset + offset + new Vector2(advanceWidth, dy + 1), + this.Params.ShadowColor, + this.Params.LinkUnderlineThickness); + } + } + + /// Creates a text fragment. + /// Associated renderer. + /// Starting byte offset (inclusive) in that this fragment deals with. + /// + /// Ending byte offset (exclusive) in that this fragment deals with. + /// Whether to break line after this fragment. + /// Offset in pixels w.r.t. . + /// Byte offset of the link payload in that decorates this + /// text fragment. + /// Optional wrap width to stop at while creating this text fragment. Note that at least + /// one visible character needs to be there in a single text fragment, in which case it is allowed to exceed + /// the wrap width. + /// Newly created text fragment. + public readonly TextFragment CreateFragment( + SeStringRenderer renderer, + int from, + int to, + bool breakAfter, + Vector2 offset, + int activeLinkOffset, + float wrapWidth = float.MaxValue) + { + var x = 0f; + var w = 0f; + var visibleWidth = 0f; + var advanceWidth = 0f; + var advanceWidthWithoutSoftHyphen = 0f; + var firstDisplayRune = default(Rune?); + var lastDisplayRune = default(Rune); + var lastNonSoftHyphenRune = default(Rune); + var endsWithSoftHyphen = false; + foreach (var c in UtfEnumerator.From(this.Raw.Data[from..to], UtfEnumeratorFlags.Utf8SeString)) + { + var byteOffset = from + c.ByteOffset; + var isBreakableWhitespace = false; + var effectiveRune = c.EffectiveRune; + Rune displayRune; + if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } && + renderer.GetBitmapFontIconFor(this.Raw.Data[byteOffset..]) is var icon and not None && + renderer.gfd.TryGetEntry((uint)icon, out var gfdEntry) && + !gfdEntry.IsEmpty) + { + // This is an icon payload. + var size = this.CalculateGfdEntrySize(gfdEntry, out _); + w = Math.Max(w, x + size.X); + x += MathF.Round(size.X); + displayRune = default; + } + else if (TryGetDisplayRune(effectiveRune, out displayRune)) + { + // This is a printable character, or a standard whitespace character. + ref var g = ref this.FindGlyph(ref displayRune); + var dist = this.CalculateDistance(lastDisplayRune, displayRune); + w = Math.Max(w, x + ((dist + g.X1) * this.FontSizeScale)); + x += MathF.Round((dist + g.AdvanceX) * this.FontSizeScale); + + isBreakableWhitespace = + Rune.IsWhiteSpace(displayRune) && + UnicodeData.LineBreak[displayRune.Value] is not UnicodeLineBreakClass.GL; + } + else + { + continue; + } + + if (isBreakableWhitespace) + { + advanceWidth = x; + } + else + { + if (firstDisplayRune is not null && w > wrapWidth && effectiveRune.Value != SoftHyphen) + { + to = byteOffset; + break; + } + + advanceWidth = x; + visibleWidth = w; + } + + firstDisplayRune ??= displayRune; + lastDisplayRune = displayRune; + endsWithSoftHyphen = effectiveRune.Value == SoftHyphen; + if (!endsWithSoftHyphen) + { + advanceWidthWithoutSoftHyphen = x; + lastNonSoftHyphenRune = displayRune; + } + } + + return new( + from, + to, + activeLinkOffset, + offset, + visibleWidth, + advanceWidth, + advanceWidthWithoutSoftHyphen, + breakAfter, + endsWithSoftHyphen, + firstDisplayRune ?? default, + lastNonSoftHyphenRune); + } + + /// Gets the glyph corresponding to the given codepoint. + /// An instance of that represents a character to display. + /// Corresponding glyph, or glyph of a fallback character specified from + /// . + public readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune) + { + var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue + ? ImGuiNative.ImFont_FindGlyph(this.Params.Font, (ushort)rune.Value) + : this.Params.Font->FallbackGlyph; + return ref *(ImGuiHelpers.ImFontGlyphReal*)p; + } + + /// Gets the glyph corresponding to the given codepoint. + /// An instance of that represents a character to display, that will be + /// changed on return to the rune corresponding to the fallback glyph if a glyph not corresponding to the + /// requested glyph is being returned. + /// Corresponding glyph, or glyph of a fallback character specified from + /// . + public readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(ref Rune rune) + { + ref var glyph = ref this.FindGlyph(rune); + if (rune.Value != glyph.Codepoint && !Rune.TryCreate(glyph.Codepoint, out rune)) + rune = Rune.ReplacementChar; + return ref glyph; + } + + /// Gets the kerning adjustment between two glyphs in a succession corresponding to the given runes. + /// + /// Rune representing the glyph on the left side of a pair. + /// Rune representing the glyph on the right side of a pair. + /// Distance adjustment in pixels, scaled to the size specified from + /// , and rounded. + public readonly float CalculateDistance(Rune left, Rune right) + { + // Kerning distance entries are ignored if NUL, U+FFFF(invalid Unicode character), or characters outside + // the basic multilingual plane(BMP) is involved. + if (left.Value is <= 0 or >= char.MaxValue) + return 0; + if (right.Value is <= 0 or >= char.MaxValue) + return 0; + + return MathF.Round( + ImGuiNative.ImFont_GetDistanceAdjustmentForPair( + this.Params.Font, + (ushort)left.Value, + (ushort)right.Value) * this.FontSizeScale); + } + } +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/DerivedGeneralCategory.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/DerivedGeneralCategory.txt similarity index 100% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/DerivedGeneralCategory.txt rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/DerivedGeneralCategory.txt diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/EastAsianWidth.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/EastAsianWidth.txt similarity index 100% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/EastAsianWidth.txt rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/EastAsianWidth.txt diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreak.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreak.txt similarity index 100% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreak.txt rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreak.txt diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs similarity index 98% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs index 49e2298b0..9113ef703 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/LineBreakEnumerator.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs @@ -2,11 +2,11 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeEastAsianWidthClass; -using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeGeneralCategory; -using static Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing.UnicodeLineBreakClass; +using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass; +using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory; +using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Enumerates line break offsets. internal ref struct LineBreakEnumerator diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeData.cs similarity index 98% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeData.cs index ffbd92cc5..3e4f74ada 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeData.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeData.cs @@ -3,7 +3,7 @@ using System.IO; using System.Reflection; using System.Runtime.CompilerServices; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Stores unicode data. internal static class UnicodeData diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEastAsianWidthClass.cs similarity index 90% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEastAsianWidthClass.cs index 0335b29a0..184168795 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEastAsianWidthClass.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEastAsianWidthClass.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Unicode east asian width. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEmojiProperty.cs similarity index 94% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEmojiProperty.cs index 3788d9d99..3952a5178 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeEmojiProperty.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeEmojiProperty.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Unicode emoji property. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeGeneralCategory.cs similarity index 97% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeGeneralCategory.cs index 007666031..f24f5b357 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeGeneralCategory.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeGeneralCategory.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Unicode general category.. /// diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeLineBreakClass.cs similarity index 98% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeLineBreakClass.cs index 0ee5a50a3..bbab3170f 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UnicodeLineBreakClass.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UnicodeLineBreakClass.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Unicode line break class. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Unicode Data")] diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs similarity index 95% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs index 6d319ed92..b73bc85e4 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumerator.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs @@ -8,7 +8,7 @@ using Lumina.Text; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Enumerates a UTF-N byte sequence by codepoint. [DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")] @@ -257,7 +257,11 @@ internal ref struct UtfEnumerator /// Gets the effective char value, with invalid or non-representable codepoints replaced. /// /// if the character should not be displayed at all. - public char EffectiveChar => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue; + public char EffectiveChar + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue; + } /// Gets the effective int value, with invalid codepoints replaced. /// if the character should not be displayed at all. @@ -268,6 +272,14 @@ internal ref struct UtfEnumerator ? 0xFFFD : rune.Value; + /// Gets the effective value, with invalid codepoints replaced. + /// if the character should not be displayed at all. + public Rune EffectiveRune + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.EffectiveInt); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right); diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs similarity index 96% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs index 7d07049da..01380e40c 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfEnumeratorFlags.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs @@ -1,4 +1,4 @@ -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Flags on enumerating a unicode sequence. [Flags] diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs similarity index 99% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs index c35b117a2..6930e6ba4 100644 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/UtfValue.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; /// Represents a single value to be used in a UTF-N byte sequence. [StructLayout(LayoutKind.Explicit, Size = 4)] diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/emoji-data.txt b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/emoji-data.txt similarity index 100% rename from Dalamud/Interface/Internal/ImGuiSeStringRenderer/TextProcessing/emoji-data.txt rename to Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/emoji-data.txt diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs new file mode 100644 index 000000000..543f4c07a --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs @@ -0,0 +1,168 @@ +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.InteropServices; + +using ImGuiNET; + +using Lumina.Text.Payloads; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Render styles for a SeString. +public record struct SeStringDrawParams +{ + /// Gets or sets the target draw list. + /// Target draw list, default(ImDrawListPtr) to not draw, or null to use + /// (the default). + /// If this value is set, will not be called, and ImGui ID will be ignored. + /// + public ImDrawListPtr? TargetDrawList { get; set; } + + /// Gets or sets the font to use. + /// Font to use, or null to use (the default). + public ImFontPtr? Font { get; set; } + + /// Gets or sets the screen offset of the left top corner. + /// Screen offset to draw at, or null to use . + public Vector2? ScreenOffset { get; set; } + + /// Gets or sets the font size. + /// Font size in pixels, or 0 to use the current ImGui font size . + /// + public float? FontSize { get; set; } + + /// Gets or sets the line height ratio. + /// 1 or null (the default) will use as the line height. + /// 2 will make line height twice the . + public float? LineHeight { get; set; } + + /// Gets or sets the wrapping width. + /// Width in pixels, or null to wrap at the end of available content region from + /// (the default). + public float? WrapWidth { get; set; } + + /// Gets or sets the thickness of underline under links. + public float? LinkUnderlineThickness { get; set; } + + /// Gets or sets the opacity, commonly called "alpha". + /// Opacity value ranging from 0(invisible) to 1(fully visible), or null to use the current ImGui + /// opacity from accessed using . + public float? Opacity { get; set; } + + /// Gets or sets the strength of the edge, which will have effects on the edge opacity. + /// Strength value ranging from 0(invisible) to 1(fully visible), or null to use the default value + /// of 0.25f that might be subject to change in the future. + public float? EdgeStrength { get; set; } + + /// Gets or sets the color of the rendered text. + /// Color in RGBA, or null to use (the default). + public uint? Color { get; set; } + + /// Gets or sets the color of the rendered text edge. + /// Color in RGBA, or null to use opaque black (the default). + public uint? EdgeColor { get; set; } + + /// Gets or sets the color of the rendered text shadow. + /// Color in RGBA, or null to use opaque black (the default). + public uint? ShadowColor { get; set; } + + /// Gets or sets the background color of a link when hovered. + /// Color in RGBA, or null to use (the default). + public uint? LinkHoverBackColor { get; set; } + + /// Gets or sets the background color of a link when active. + /// Color in RGBA, or null to use (the default). + public uint? LinkActiveBackColor { get; set; } + + /// Gets or sets a value indicating whether to force the color of the rendered text edge. + /// If set, then and will be + /// ignored. + public bool ForceEdgeColor { get; set; } + + /// Gets or sets a value indicating whether the text is rendered bold. + public bool Bold { get; set; } + + /// Gets or sets a value indicating whether the text is rendered italic. + public bool Italic { get; set; } + + /// Gets or sets a value indicating whether the text is rendered with edge. + public bool Edge { get; set; } + + /// Gets or sets a value indicating whether the text is rendered with shadow. + public bool Shadow { get; set; } + + private readonly unsafe ImFont* EffectiveFont => + (this.Font ?? ImGui.GetFont()) is var f && f.NativePtr is not null + ? f.NativePtr + : throw new ArgumentException("Specified font is empty."); + + private readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f); + + private readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha; + + /// Calculated values from using ImGui styles. + [SuppressMessage( + "StyleCop.CSharp.OrderingRules", + "SA1214:Readonly fields should appear before non-readonly fields", + Justification = "Matching the above order.")] + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct Resolved(in SeStringDrawParams ssdp) + { + /// + public readonly ImDrawList* DrawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList(); + + /// + public readonly ImFont* Font = ssdp.EffectiveFont; + + /// + public readonly Vector2 ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos(); + + /// + public readonly float FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); + + /// + public readonly float LineHeight = MathF.Round(ssdp.EffectiveLineHeight); + + /// + public readonly float WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X; + + /// + public readonly float LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; + + /// + public readonly float Opacity = ssdp.EffectiveOpacity; + + /// + public readonly float EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity; + + /// + public uint Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text); + + /// + public uint EdgeColor = ssdp.EdgeColor ?? 0xFF000000; + + /// + public uint ShadowColor = ssdp.ShadowColor ?? 0xFF000000; + + /// + public readonly uint LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); + + /// + public readonly uint LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); + + /// + public readonly bool ForceEdgeColor = ssdp.ForceEdgeColor; + + /// + public bool Bold = ssdp.Bold; + + /// + public bool Italic = ssdp.Italic; + + /// + public bool Edge = ssdp.Edge; + + /// + public bool Shadow = ssdp.Shadow; + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs new file mode 100644 index 000000000..905e8ed23 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Numerics; + +using Dalamud.Game.Text.SeStringHandling; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Represents the result of n rendered interactable SeString. +public ref struct SeStringDrawResult +{ + private Payload? lazyPayload; + + /// Gets the visible size of the text rendered/to be rendered. + public Vector2 Size { get; init; } + + /// Gets a value indicating whether a payload or the whole text has been clicked. + public bool Clicked { get; init; } + + /// Gets the offset of the interacted payload, or -1 if none. + public int InteractedPayloadOffset { get; init; } + + /// Gets the interacted payload envelope, or if none. + public ReadOnlySpan InteractedPayloadEnvelope { get; init; } + + /// Gets the interacted payload, or null if none. + public Payload? InteractedPayload => + this.lazyPayload ??= + this.InteractedPayloadEnvelope.IsEmpty + ? default + : SeString.Parse(this.InteractedPayloadEnvelope).Payloads.FirstOrDefault(); +} diff --git a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs b/Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs deleted file mode 100644 index face85cfc..000000000 --- a/Dalamud/Interface/Internal/ImGuiSeStringRenderer/SeStringRenderer.cs +++ /dev/null @@ -1,693 +0,0 @@ -using System.Collections.Generic; -using System.Numerics; -using System.Text; - -using BitFaster.Caching.Lru; - -using Dalamud.Data; -using Dalamud.Game.Config; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Interface.Internal.ImGuiSeStringRenderer.TextProcessing; -using Dalamud.Interface.Utility; -using Dalamud.Utility; - -using FFXIVClientStructs.FFXIV.Client.System.String; -using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Client.UI.Misc; - -using ImGuiNET; - -using Lumina.Excel.GeneratedSheets2; -using Lumina.Text.Expressions; -using Lumina.Text.Payloads; -using Lumina.Text.ReadOnly; - -using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon; - -namespace Dalamud.Interface.Internal.ImGuiSeStringRenderer; - -/// Draws SeString. -[ServiceManager.EarlyLoadedService] -internal unsafe class SeStringRenderer : IInternalDisposableService -{ - private const int ChannelShadow = 0; - private const int ChannelEdge = 1; - private const int ChannelFore = 2; - private const int ChannelCount = 3; - - private const char SoftHyphen = '\u00AD'; - private const char ObjectReplacementCharacter = '\uFFFC'; - - [ServiceManager.ServiceDependency] - private readonly GameConfig gameConfig = Service.Get(); - - private readonly ConcurrentLru cache = new(1024); - - private readonly GfdFile gfd; - private readonly uint[] colorTypes; - private readonly uint[] edgeColorTypes; - - private readonly List words = []; - - private readonly List colorStack = []; - private readonly List edgeColorStack = []; - private readonly List shadowColorStack = []; - private bool bold; - private bool italic; - private Vector2 edge; - private Vector2 shadow; - - private ImDrawListSplitterPtr splitter = new(ImGuiNative.ImDrawListSplitter_ImDrawListSplitter()); - - [ServiceManager.ServiceConstructor] - private SeStringRenderer(DataManager dm) - { - var uiColor = dm.Excel.GetSheet()!; - var maxId = 0; - foreach (var row in uiColor) - maxId = (int)Math.Max(row.RowId, maxId); - - this.colorTypes = new uint[maxId + 1]; - this.edgeColorTypes = new uint[maxId + 1]; - foreach (var row in uiColor) - { - this.colorTypes[row.RowId] = BgraToRgba((row.UIForeground >> 8) | (row.UIForeground << 24)); - this.edgeColorTypes[row.RowId] = BgraToRgba((row.UIGlow >> 8) | (row.UIGlow << 24)); - } - - this.gfd = dm.GetFile("common/font/gfdata.gfd")!; - - return; - - static uint BgraToRgba(uint x) - { - var buf = (byte*)&x; - (buf[0], buf[2]) = (buf[2], buf[0]); - return x; - } - } - - /// Finalizes an instance of the class. - ~SeStringRenderer() => this.ReleaseUnmanagedResources(); - - /// - void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); - - /// Creates and caches a SeString from a text macro representation, and then draws it. - /// SeString text macro representation. - /// Wrapping width. If a non-positive number is provided, then the remainder of the width - /// will be used. - public void CompileAndDrawWrapped(string text, float wrapWidth = 0) - { - ThreadSafety.AssertMainThread(); - - this.DrawWrapped( - this.cache.GetOrAdd( - text, - static text => - { - var outstr = default(Utf8String); - outstr.Ctor(); - RaptureTextModule.Instance()->MacroEncoder.EncodeString(&outstr, text.ReplaceLineEndings("
")); - var res = new ReadOnlySeString(outstr.AsSpan().ToArray()); - outstr.Dtor(); - return res; - }).AsSpan(), - wrapWidth); - } - - /// - public void DrawWrapped(in Utf8String utf8String, float wrapWidth = 0) => - this.DrawWrapped(utf8String.AsSpan(), wrapWidth); - - /// Draws a SeString. - /// SeString to draw. - /// Wrapping width. If a non-positive number is provided, then the remainder of the width - /// will be used. - public void DrawWrapped(ReadOnlySeStringSpan sss, float wrapWidth = 0) - { - ThreadSafety.AssertMainThread(); - - if (wrapWidth <= 0) - wrapWidth = ImGui.GetContentRegionAvail().X; - - this.words.Clear(); - this.colorStack.Clear(); - this.edgeColorStack.Clear(); - this.shadowColorStack.Clear(); - - this.colorStack.Add(ImGui.GetColorU32(ImGuiCol.Text)); - this.edgeColorStack.Add(0); - this.shadowColorStack.Add(0); - this.bold = this.italic = false; - this.edge = Vector2.One; - this.shadow = Vector2.Zero; - - var state = new DrawState( - sss, - ImGui.GetWindowDrawList(), - this.splitter, - ImGui.GetFont(), - ImGui.GetFontSize(), - ImGui.GetCursorScreenPos()); - this.CreateTextFragments(ref state, wrapWidth); - - var size = Vector2.Zero; - for (var i = 0; i < this.words.Count; i++) - { - var word = this.words[i]; - this.DrawWord( - ref state, - word.Offset, - state.Raw.Data[word.From..word.To], - i == 0 - ? '\0' - : this.words[i - 1].IsSoftHyphenVisible - ? this.words[i - 1].LastRuneRepr - : this.words[i - 1].LastRuneRepr2); - - if (word.IsSoftHyphenVisible && i > 0) - { - this.DrawWord( - ref state, - word.Offset + new Vector2(word.AdvanceWidthWithoutLastRune, 0), - "-"u8, - this.words[i - 1].LastRuneRepr); - } - - size = Vector2.Max(size, word.Offset + new Vector2(word.VisibleWidth, state.FontSize)); - } - - state.Splitter.Merge(state.DrawList); - - ImGui.Dummy(size); - } - - /// Gets the printable char for the given char, or null(\0) if it should not be handled at all. - /// Character to determine. - /// Character to print, or null(\0) if none. - private static Rune? ToPrintableRune(int c) => c switch - { - char.MaxValue => null, - SoftHyphen => new('-'), - _ when UnicodeData.LineBreak[c] - is UnicodeLineBreakClass.BK - or UnicodeLineBreakClass.CR - or UnicodeLineBreakClass.LF - or UnicodeLineBreakClass.NL => new(0), - _ => new(c), - }; - - private void ReleaseUnmanagedResources() - { - if (this.splitter.NativePtr is not null) - this.splitter.Destroy(); - this.splitter = default; - } - - private void CreateTextFragments(ref DrawState state, float wrapWidth) - { - var prev = 0; - var runningOffset = Vector2.Zero; - var runningWidth = 0f; - foreach (var (curr, mandatory) in new LineBreakEnumerator(state.Raw, UtfEnumeratorFlags.Utf8SeString)) - { - var fragment = state.CreateFragment(this, prev, curr, mandatory, runningOffset); - var nextRunningWidth = Math.Max(runningWidth, runningOffset.X + fragment.VisibleWidth); - if (nextRunningWidth <= wrapWidth) - { - // New fragment fits in the current line. - if (this.words.Count > 0) - { - char lastFragmentEnd; - if (this.words[^1].EndsWithSoftHyphen) - { - runningOffset.X += this.words[^1].AdvanceWidthWithoutLastRune - this.words[^1].AdvanceWidth; - lastFragmentEnd = this.words[^1].LastRuneRepr; - } - else - { - lastFragmentEnd = this.words[^1].LastRuneRepr2; - } - - runningOffset.X += MathF.Round( - state.Font.GetDistanceAdjustmentForPair(lastFragmentEnd, fragment.FirstRuneRepr) * - state.FontSizeScale); - fragment = fragment with { Offset = runningOffset }; - } - - this.words.Add(fragment); - runningWidth = nextRunningWidth; - runningOffset.X += fragment.AdvanceWidth; - prev = curr; - } - else if (fragment.VisibleWidth <= wrapWidth) - { - // New fragment does not fit in the current line, but it will fit in the next line. - // Implicit conditions: runningWidth > 0, this.words.Count > 0 - runningWidth = fragment.VisibleWidth; - runningOffset.X = fragment.AdvanceWidth; - runningOffset.Y += state.FontSize; - prev = curr; - this.words[^1] = this.words[^1] with { MandatoryBreakAfter = true }; - this.words.Add(fragment with { Offset = runningOffset with { X = 0 } }); - } - else - { - // New fragment does not fit in the given width, and it needs to be broken down. - while (prev < curr) - { - if (runningOffset.X > 0) - { - runningOffset.X = 0; - runningOffset.Y += state.FontSize; - } - - fragment = state.CreateFragment(this, prev, curr, mandatory, runningOffset, wrapWidth); - runningWidth = fragment.VisibleWidth; - runningOffset.X = fragment.AdvanceWidth; - prev = fragment.To; - if (this.words.Count > 0) - this.words[^1] = this.words[^1] with { MandatoryBreakAfter = true }; - this.words.Add(fragment); - } - } - - if (fragment.MandatoryBreakAfter) - { - runningOffset.X = runningWidth = 0; - runningOffset.Y += state.FontSize; - } - } - } - - private void DrawWord(ref DrawState state, Vector2 offset, ReadOnlySpan span, char lastRuneRepr) - { - var gfdTextureSrv = - (nint)UIModule.Instance()->GetRaptureAtkModule()->AtkModule.AtkFontManager.Gfd->Texture-> - D3D11ShaderResourceView; - var x = 0f; - var width = 0f; - foreach (var c in UtfEnumerator.From(span, UtfEnumeratorFlags.Utf8SeString)) - { - if (c.IsSeStringPayload) - { - var enu = new ReadOnlySeStringSpan(span[c.ByteOffset..]).GetEnumerator(); - if (!enu.MoveNext()) - continue; - - var payload = enu.Current; - switch (payload.MacroCode) - { - case MacroCode.Color: - TouchColorStack(this.colorStack, payload); - continue; - case MacroCode.EdgeColor: - TouchColorStack(this.edgeColorStack, payload); - continue; - case MacroCode.ShadowColor: - TouchColorStack(this.shadowColorStack, payload); - continue; - case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): - this.bold = u != 0; - continue; - case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): - this.italic = u != 0; - continue; - case MacroCode.Edge when payload.TryGetExpression(out var e1, out var e2) && - e1.TryGetInt(out var v1) && e2.TryGetInt(out var v2): - this.edge = new(v1, v2); - continue; - case MacroCode.Shadow when payload.TryGetExpression(out var e1, out var e2) && - e1.TryGetInt(out var v1) && e2.TryGetInt(out var v2): - this.shadow = new(v1, v2); - continue; - case MacroCode.ColorType: - TouchColorTypeStack(this.colorStack, this.colorTypes, payload); - continue; - case MacroCode.EdgeColorType: - TouchColorTypeStack(this.edgeColorStack, this.edgeColorTypes, payload); - continue; - case MacroCode.Icon: - case MacroCode.Icon2: - { - if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is not (var icon and not None) || - !this.gfd.TryGetEntry((uint)icon, out var gfdEntry) || - gfdEntry.IsEmpty) - continue; - - var useHq = state.FontSize > 19; - var sizeScale = (state.FontSize + 1) / gfdEntry.Height; - state.SetCurrentChannel(ChannelFore); - state.Draw( - offset + new Vector2(x, 0), - gfdTextureSrv, - Vector2.Zero, - gfdEntry.Size * sizeScale, - Vector2.Zero, - useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, - useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1); - width = Math.Max(width, x + (gfdEntry.Width * sizeScale)); - x += MathF.Round(gfdEntry.Width * sizeScale); - lastRuneRepr = '\0'; - continue; - } - - default: - continue; - } - } - - if (ToPrintableRune(c.EffectiveChar) is not { } rune) - continue; - - var runeRepr = rune.Value is >= 0 and < char.MaxValue ? (char)rune.Value : '\uFFFE'; - if (runeRepr != 0) - { - var dist = state.Font.GetDistanceAdjustmentForPair(lastRuneRepr, runeRepr); - ref var g = ref *(ImGuiHelpers.ImFontGlyphReal*)state.Font.FindGlyph(runeRepr).NativePtr; - - var dyItalic = this.italic - ? new Vector2(state.Font.FontSize - g.Y0, state.Font.FontSize - g.Y1) / 6 - : Vector2.Zero; - - if (this.shadow != Vector2.Zero && this.shadowColorStack[^1] >= 0x1000000) - { - state.SetCurrentChannel(ChannelShadow); - state.Draw( - offset + this.shadow + new Vector2(x + dist, 0), - g, - dyItalic, - this.shadowColorStack[^1]); - } - - if (this.edge != Vector2.Zero && this.edgeColorStack[^1] >= 0x1000000) - { - state.SetCurrentChannel(ChannelEdge); - for (var dx = -this.edge.X; dx <= this.edge.X; dx++) - { - for (var dy = -this.edge.Y; dy <= this.edge.Y; dy++) - { - if (dx == 0 && dy == 0) - continue; - - state.Draw(offset + new Vector2(x + dist + dx, dy), g, dyItalic, this.edgeColorStack[^1]); - } - } - } - - state.SetCurrentChannel(ChannelFore); - for (var dx = this.bold ? 1 : 0; dx >= 0; dx--) - state.Draw(offset + new Vector2(x + dist + dx, 0), g, dyItalic, this.colorStack[^1]); - - width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale)); - x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale); - } - - lastRuneRepr = runeRepr; - } - - return; - - static void TouchColorStack(List stack, ReadOnlySePayloadSpan payload) - { - if (!payload.TryGetExpression(out var expr)) - return; - if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor && stack.Count > 1) - stack.RemoveAt(stack.Count - 1); - else if (expr.TryGetUInt(out var u)) - stack.Add(u); - } - - static void TouchColorTypeStack(List stack, uint[] colorTypes, ReadOnlySePayloadSpan payload) - { - if (!payload.TryGetExpression(out var expr)) - return; - if (!expr.TryGetUInt(out var u)) - return; - if (u != 0) - stack.Add(u < colorTypes.Length ? colorTypes[u] : 0u); - else if (stack.Count > 1) - stack.RemoveAt(stack.Count - 1); - } - } - - private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan sss) - { - var e = new ReadOnlySeStringSpan(sss).GetEnumerator(); - if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2) - return None; - - var payload = e.Current; - switch (payload.MacroCode) - { - case MacroCode.Icon - when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): - return (BitmapFontIcon)iconId; - case MacroCode.Icon2 - when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): - var configName = (BitmapFontIcon)iconId switch - { - ControllerShoulderLeft => SystemConfigOption.PadButton_L1, - ControllerShoulderRight => SystemConfigOption.PadButton_R1, - ControllerTriggerLeft => SystemConfigOption.PadButton_L2, - ControllerTriggerRight => SystemConfigOption.PadButton_R2, - ControllerButton3 => SystemConfigOption.PadButton_Triangle, - ControllerButton1 => SystemConfigOption.PadButton_Cross, - ControllerButton0 => SystemConfigOption.PadButton_Circle, - ControllerButton2 => SystemConfigOption.PadButton_Square, - ControllerStart => SystemConfigOption.PadButton_Start, - ControllerBack => SystemConfigOption.PadButton_Select, - ControllerAnalogLeftStick => SystemConfigOption.PadButton_LS, - ControllerAnalogLeftStickIn => SystemConfigOption.PadButton_LS, - ControllerAnalogLeftStickUpDown => SystemConfigOption.PadButton_LS, - ControllerAnalogLeftStickLeftRight => SystemConfigOption.PadButton_LS, - ControllerAnalogRightStick => SystemConfigOption.PadButton_RS, - ControllerAnalogRightStickIn => SystemConfigOption.PadButton_RS, - ControllerAnalogRightStickUpDown => SystemConfigOption.PadButton_RS, - ControllerAnalogRightStickLeftRight => SystemConfigOption.PadButton_RS, - _ => (SystemConfigOption?)null, - }; - - if (configName is null || !this.gameConfig.TryGet(configName.Value, out PadButtonValue pb)) - return (BitmapFontIcon)iconId; - - return pb switch - { - PadButtonValue.Autorun_Support => ControllerShoulderLeft, - PadButtonValue.Hotbar_Set_Change => ControllerShoulderRight, - PadButtonValue.XHB_Left_Start => ControllerTriggerLeft, - PadButtonValue.XHB_Right_Start => ControllerTriggerRight, - PadButtonValue.Jump => ControllerButton3, - PadButtonValue.Accept => ControllerButton1, - PadButtonValue.Cancel => ControllerButton0, - PadButtonValue.Map_Sub => ControllerButton2, - PadButtonValue.MainCommand => ControllerStart, - PadButtonValue.HUD_Select => ControllerBack, - PadButtonValue.Move_Operation => (BitmapFontIcon)iconId switch - { - ControllerAnalogLeftStick => ControllerAnalogLeftStick, - ControllerAnalogLeftStickIn => ControllerAnalogLeftStickIn, - ControllerAnalogLeftStickUpDown => ControllerAnalogLeftStickUpDown, - ControllerAnalogLeftStickLeftRight => ControllerAnalogLeftStickLeftRight, - ControllerAnalogRightStick => ControllerAnalogLeftStick, - ControllerAnalogRightStickIn => ControllerAnalogLeftStickIn, - ControllerAnalogRightStickUpDown => ControllerAnalogLeftStickUpDown, - ControllerAnalogRightStickLeftRight => ControllerAnalogLeftStickLeftRight, - _ => (BitmapFontIcon)iconId, - }, - PadButtonValue.Camera_Operation => (BitmapFontIcon)iconId switch - { - ControllerAnalogLeftStick => ControllerAnalogRightStick, - ControllerAnalogLeftStickIn => ControllerAnalogRightStickIn, - ControllerAnalogLeftStickUpDown => ControllerAnalogRightStickUpDown, - ControllerAnalogLeftStickLeftRight => ControllerAnalogRightStickLeftRight, - ControllerAnalogRightStick => ControllerAnalogRightStick, - ControllerAnalogRightStickIn => ControllerAnalogRightStickIn, - ControllerAnalogRightStickUpDown => ControllerAnalogRightStickUpDown, - ControllerAnalogRightStickLeftRight => ControllerAnalogRightStickLeftRight, - _ => (BitmapFontIcon)iconId, - }, - _ => (BitmapFontIcon)iconId, - }; - } - - return None; - } - - private readonly record struct TextFragment( - int From, - int To, - Vector2 Offset, - float VisibleWidth, - float AdvanceWidth, - float AdvanceWidthWithoutLastRune, - bool MandatoryBreakAfter, - bool EndsWithSoftHyphen, - char FirstRuneRepr, - char LastRuneRepr, - char LastRuneRepr2) - { - public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.MandatoryBreakAfter; - } - - private ref struct DrawState - { - public readonly ReadOnlySeStringSpan Raw; - public readonly float FontSize; - public readonly float FontSizeScale; - public readonly Vector2 ScreenOffset; - - public ImDrawListPtr DrawList; - public ImDrawListSplitterPtr Splitter; - public ImFontPtr Font; - - public DrawState( - ReadOnlySeStringSpan raw, - ImDrawListPtr drawList, - ImDrawListSplitterPtr splitter, - ImFontPtr font, - float fontSize, - Vector2 screenOffset) - { - this.Raw = raw; - this.DrawList = drawList; - this.Splitter = splitter; - this.Font = font; - this.FontSize = fontSize; - this.FontSizeScale = fontSize / font.FontSize; - this.ScreenOffset = screenOffset; - - splitter.Split(drawList, ChannelCount); - } - - public void SetCurrentChannel(int channelIndex) => this.Splitter.SetCurrentChannel(this.DrawList, channelIndex); - - public void Draw(Vector2 offset, in ImGuiHelpers.ImFontGlyphReal g, Vector2 dyItalic, uint color) => - this.Draw( - offset, - this.Font.ContainerAtlas.Textures[g.TextureIndex].TexID, - g.XY0 * this.FontSizeScale, - g.XY1 * this.FontSizeScale, - dyItalic * this.FontSizeScale, - g.UV0, - g.UV1, - color); - - public void Draw( - Vector2 offset, - nint igTextureId, - Vector2 xy0, - Vector2 xy1, - Vector2 dyItalic, - Vector2 uv0, - Vector2 uv1, - uint color = uint.MaxValue) - { - offset += this.ScreenOffset; - this.DrawList.AddImageQuad( - igTextureId, - offset + new Vector2(xy0.X + dyItalic.X, xy0.Y), - offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y), - offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y), - offset + new Vector2(xy1.X + dyItalic.X, xy0.Y), - new(uv0.X, uv0.Y), - new(uv0.X, uv1.Y), - new(uv1.X, uv1.Y), - new(uv1.X, uv0.Y), - color); - } - - public TextFragment CreateFragment( - SeStringRenderer renderer, - int from, - int to, - bool mandatoryBreakAfter, - Vector2 offset, - float wrapWidth = float.PositiveInfinity) - { - var lastNonSpace = from; - - var x = 0f; - var w = 0f; - var visibleWidth = 0f; - var advanceWidth = 0f; - var prevAdvanceWidth = 0f; - var firstRuneRepr = char.MaxValue; - var lastRuneRepr = default(char); - var lastRuneRepr2 = default(char); - var endsWithSoftHyphen = false; - foreach (var c in UtfEnumerator.From(this.Raw.Data[from..to], UtfEnumeratorFlags.Utf8SeString)) - { - prevAdvanceWidth = x; - lastRuneRepr2 = lastRuneRepr; - endsWithSoftHyphen = c.EffectiveChar == SoftHyphen; - - var byteOffset = from + c.ByteOffset; - var isBreakableWhitespace = false; - if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } && - renderer.GetBitmapFontIconFor(this.Raw.Data[byteOffset..]) is var icon and not None && - renderer.gfd.TryGetEntry((uint)icon, out var gfdEntry) && - !gfdEntry.IsEmpty) - { - var sizeScale = (this.FontSize + 1) / gfdEntry.Height; - w = Math.Max(w, x + (gfdEntry.Width * sizeScale)); - x += MathF.Round(gfdEntry.Width * sizeScale); - lastRuneRepr = default; - } - else if (ToPrintableRune(c.EffectiveChar) is { } rune) - { - var runeRepr = rune.Value is >= 0 and < char.MaxValue ? (char)rune.Value : '\uFFFE'; - if (runeRepr != 0) - { - var dist = this.Font.GetDistanceAdjustmentForPair(lastRuneRepr, runeRepr); - ref var g = ref *(ImGuiHelpers.ImFontGlyphReal*)this.Font.FindGlyph(runeRepr).NativePtr; - w = Math.Max(w, x + ((dist + g.X1) * this.FontSizeScale)); - x += MathF.Round((dist + g.AdvanceX) * this.FontSizeScale); - } - - isBreakableWhitespace = Rune.IsWhiteSpace(rune) && - UnicodeData.LineBreak[rune.Value] is not UnicodeLineBreakClass.GL; - lastRuneRepr = runeRepr; - } - else - { - continue; - } - - if (firstRuneRepr == char.MaxValue) - firstRuneRepr = lastRuneRepr; - - if (isBreakableWhitespace) - { - advanceWidth = x; - } - else - { - if (w > wrapWidth && lastNonSpace != from && !endsWithSoftHyphen) - { - to = byteOffset; - break; - } - - advanceWidth = x; - visibleWidth = w; - lastNonSpace = byteOffset + c.ByteLength; - } - } - - return new( - from, - to, - offset, - visibleWidth, - advanceWidth, - prevAdvanceWidth, - mandatoryBreakAfter, - endsWithSoftHyphen, - firstRuneRepr, - lastRuneRepr, - lastRuneRepr2); - } - } -} diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 5db6d393a..f1a025d93 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -1,12 +1,17 @@ using System.Numerics; using Dalamud.Game.Gui; -using Dalamud.Interface.Internal.ImGuiSeStringRenderer; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Utility; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; +using Lumina.Text.ReadOnly; + // Customised version of https://github.com/aers/FFXIVUIDebug namespace Dalamud.Interface.Internal; @@ -204,10 +209,22 @@ internal unsafe class UiDebug var textNode = (AtkTextNode*)node; ImGui.Text("text: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textNode->NodeText); + Service.Get().Draw(textNode->NodeText); ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); + ImGui.SameLine(); + if (ImGui.Button($"Encode##{(ulong)textNode:X}")) + { + using var tmp = new Utf8String(); + RaptureTextModule.Instance()->MacroEncoder.EncodeString(&tmp, textNode->NodeText.StringPtr); + textNode->NodeText.Copy(&tmp); + } + + ImGui.SameLine(); + if (ImGui.Button($"Decode##{(ulong)textNode:X}")) + textNode->NodeText.SetString(new ReadOnlySeStringSpan(textNode->NodeText.StringPtr).ToString()); + ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); int b = textNode->AlignmentFontType; if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) @@ -233,7 +250,7 @@ internal unsafe class UiDebug var counterNode = (AtkCounterNode*)node; ImGui.Text("text: "); ImGui.SameLine(); - Service.Get().DrawWrapped(counterNode->NodeText); + Service.Get().Draw(counterNode->NodeText); break; case NodeType.Image: var imageNode = (AtkImageNode*)node; @@ -372,31 +389,31 @@ internal unsafe class UiDebug var textInputComponent = (AtkComponentTextInput*)compNode->Component; ImGui.Text("InputBase Text1: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->AtkComponentInputBase.UnkText1); + Service.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText1); ImGui.Text("InputBase Text2: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->AtkComponentInputBase.UnkText2); + Service.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText2); ImGui.Text("Text1: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->UnkText01); + Service.Get().Draw(textInputComponent->UnkText01); ImGui.Text("Text2: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->UnkText02); + Service.Get().Draw(textInputComponent->UnkText02); ImGui.Text("Text3: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->UnkText03); + Service.Get().Draw(textInputComponent->UnkText03); ImGui.Text("Text4: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->UnkText04); + Service.Get().Draw(textInputComponent->UnkText04); ImGui.Text("Text5: "); ImGui.SameLine(); - Service.Get().DrawWrapped(textInputComponent->UnkText05); + Service.Get().Draw(textInputComponent->UnkText05); break; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index e2182aba4..6e48e2d08 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,9 +1,21 @@ -using System.Text; +using System.Linq; +using System.Text; +using Dalamud.Data; +using Dalamud.Game.Gui; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Utility; +using Dalamud.Utility; using ImGuiNET; +using Lumina.Excel.GeneratedSheets2; +using Lumina.Text; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// @@ -13,6 +25,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget { private ImVectorWrapper testStringBuffer; private string testString = string.Empty; + private Addon[]? addons; + private ReadOnlySeString? uicolor; + private ReadOnlySeString? logkind; + private SeStringDrawParams style; + private bool interactable; /// public string DisplayName { get; init; } = "SeStringRenderer Test"; @@ -24,20 +41,234 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget public bool Ready { get; set; } /// - public void Load() => this.Ready = true; + public void Load() + { + this.style = default; + this.addons = null; + this.uicolor = null; + this.logkind = null; + this.testString = string.Empty; + this.interactable = true; + this.Ready = true; + } /// public void Draw() { + var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ?? ImGui.GetColorU32(ImGuiCol.Text)); + if (ImGui.ColorEdit4("Color", ref t2)) + this.style.Color = ImGui.ColorConvertFloat4ToU32(t2); + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ?? 0xFF000000u); + if (ImGui.ColorEdit4("Edge Color", ref t2)) + this.style.EdgeColor = ImGui.ColorConvertFloat4ToU32(t2); + + ImGui.SameLine(); + var t = this.style.ForceEdgeColor; + if (ImGui.Checkbox("Forced", ref t)) + this.style.ForceEdgeColor = t; + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ?? 0xFF000000u); + if (ImGui.ColorEdit4("Shadow Color", ref t2)) + this.style.ShadowColor = ImGui.ColorConvertFloat4ToU32(t2); + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered)); + if (ImGui.ColorEdit4("Link Hover Color", ref t2)) + this.style.LinkHoverBackColor = ImGui.ColorConvertFloat4ToU32(t2); + + t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive)); + if (ImGui.ColorEdit4("Link Active Color", ref t2)) + this.style.LinkActiveBackColor = ImGui.ColorConvertFloat4ToU32(t2); + + var t3 = this.style.LineHeight ?? 1f; + if (ImGui.DragFloat("Line Height", ref t3, 0.01f, 0.4f, 3f, "%.02f")) + this.style.LineHeight = t3; + + t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha; + if (ImGui.DragFloat("Opacity", ref t3, 0.005f, 0f, 1f, "%.02f")) + this.style.Opacity = t3; + + t3 = this.style.EdgeStrength ?? 0.25f; + if (ImGui.DragFloat("Edge Strength", ref t3, 0.005f, 0f, 1f, "%.02f")) + this.style.EdgeStrength = t3; + + t = this.style.Edge; + if (ImGui.Checkbox("Edge", ref t)) + this.style.Edge = t; + + ImGui.SameLine(); + t = this.style.Bold; + if (ImGui.Checkbox("Bold", ref t)) + this.style.Bold = t; + + ImGui.SameLine(); + t = this.style.Italic; + if (ImGui.Checkbox("Italic", ref t)) + this.style.Italic = t; + + ImGui.SameLine(); + t = this.style.Shadow; + if (ImGui.Checkbox("Shadow", ref t)) + this.style.Shadow = t; + + ImGui.SameLine(); + t = this.style.LinkUnderlineThickness > 0f; + if (ImGui.Checkbox("Link Underline", ref t)) + this.style.LinkUnderlineThickness = t ? 1f : 0f; + + ImGui.SameLine(); + t = this.style.WrapWidth is null; + if (ImGui.Checkbox("Word Wrap", ref t)) + this.style.WrapWidth = t ? null : float.PositiveInfinity; + + ImGui.SameLine(); + t = this.interactable; + if (ImGui.Checkbox("Interactable", ref t)) + this.interactable = t; + + if (ImGui.CollapsingHeader("UIColor Preview")) + { + if (this.uicolor is null) + { + var tt = new SeStringBuilder(); + foreach (var uc in Service.Get().GetExcelSheet()!) + { + tt.Append($"#{uc.RowId}: ") + .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(uc.RowId).EndMacro() + .Append("Edge ") + .BeginMacro(MacroCode.ColorType).AppendUIntExpression(uc.RowId).EndMacro() + .Append("Edge+Color ") + .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(0).EndMacro() + .Append("Color ") + .BeginMacro(MacroCode.ColorType).AppendUIntExpression(0).EndMacro(); + if (uc.RowId >= 500) + { + if (uc.RowId % 2 == 0) + { + tt.BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(uc.RowId).EndMacro() + .BeginMacro(MacroCode.ColorType).AppendUIntExpression(uc.RowId + 1).EndMacro() + .Append($" => color#{uc.RowId + 1} + edge#{uc.RowId}") + .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(0).EndMacro() + .BeginMacro(MacroCode.ColorType).AppendUIntExpression(0).EndMacro(); + } + else + { + tt.BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(uc.RowId).EndMacro() + .BeginMacro(MacroCode.ColorType).AppendUIntExpression(uc.RowId - 1).EndMacro() + .Append($" => color#{uc.RowId - 1} + edge#{uc.RowId}") + .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(0).EndMacro() + .BeginMacro(MacroCode.ColorType).AppendUIntExpression(0).EndMacro(); + } + } + + tt.BeginMacro(MacroCode.NewLine).EndMacro(); + } + + this.uicolor = tt.ToReadOnlySeString(); + } + + ImGuiHelpers.SeStringWrapped(this.uicolor.Value.Data.Span, this.style); + } + + if (ImGui.CollapsingHeader("LogKind Preview")) + { + if (this.logkind is null) + { + var tt = new SeStringBuilder(); + foreach (var uc in Service.Get().GetExcelSheet()!) + { + var ucsp = uc.Format.AsReadOnly().AsSpan(); + if (ucsp.IsEmpty) + continue; + + tt.Append($"#{uc.RowId}: "); + foreach (var p in ucsp.GetOffsetEnumerator()) + { + if (p.Payload.Type == ReadOnlySePayloadType.Macro && p.Payload.MacroCode == MacroCode.String) + { + tt.Append("Text"u8); + continue; + } + + tt.Append(new ReadOnlySeStringSpan(ucsp.Data.Slice(p.Offset, p.Payload.EnvelopeByteLength))); + } + + tt.BeginMacro(MacroCode.NewLine).EndMacro(); + } + + this.logkind = tt.ToReadOnlySeString(); + } + + ImGuiHelpers.SeStringWrapped(this.logkind.Value.Data.Span, this.style); + } + + if (ImGui.CollapsingHeader("Addon Table")) + { + this.addons ??= Service.Get().GetExcelSheet()!.ToArray(); + if (ImGui.BeginTable("Addon Sheet", 3)) + { + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn("Row ID", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("0000000").X); + ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn( + "Misc", + ImGuiTableColumnFlags.WidthFixed, + ImGui.CalcTextSize("AAAAAAAAAAAAAAAAA").X); + ImGui.TableHeadersRow(); + + var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + clipper.Begin(this.addons.Length); + while (clipper.Step()) + { + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + ImGui.TableNextRow(); + ImGui.PushID(i); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"{this.addons[i].RowId}"); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGuiHelpers.SeStringWrapped(this.addons[i].Text.AsReadOnly(), this.style); + + ImGui.TableNextColumn(); + if (ImGui.Button("Print to Chat")) + Service.Get().Print(this.addons[i].Text.ToDalamudString()); + + ImGui.PopID(); + } + } + + clipper.Destroy(); + ImGui.EndTable(); + } + } + if (ImGui.Button("Reset Text") || this.testStringBuffer.IsDisposed) { this.testStringBuffer.Dispose(); this.testStringBuffer = ImVectorWrapper.CreateFromSpan( - "
Lorem ipsum dolor sit amet, conse<->ctetur adipi<->scing elit. Maece<->nas digni<->ssim sem at inter<->dum ferme<->ntum. Praes<->ent ferme<->ntum conva<->llis velit sit amet hendr<->erit. Sed eu nibh magna. Integ<->er nec lacus in velit porta euism<->od sed et lacus. Sed non mauri<->s venen<->atis, matti<->s metus in, aliqu<->et dolor. Aliqu<->am erat volut<->pat. Nulla venen<->atis velit ac susci<->pit euism<->od. suspe<->ndisse maxim<->us viver<->ra dui id dapib<->us. Nam torto<->r dolor, eleme<->ntum quis orci id, pulvi<->nar fring<->illa quam. Pelle<->ntesque laore<->et viver<->ra torto<->r eget matti<->s. Vesti<->bulum eget porta ante, a molli<->s nulla. Curab<->itur a ligul<->a leo. Aliqu<->am volut<->pat sagit<->tis dapib<->us.\n\nFusce iacul<->is aliqu<->am mi, eget portt<->itor arcu solli<->citudin conse<->ctetur. suspe<->ndisse aliqu<->am commo<->do tinci<->dunt. Duis sed posue<->re tellu<->s. Sed phare<->tra ex vel torto<->r pelle<->ntesque, inter<->dum porta sapie<->n digni<->ssim. Queue Dun Scait<->h. Cras aliqu<->et at nulla quis moles<->tie. Vesti<->bulum eu ligul<->a sapie<->n. Curab<->itur digni<->ssim feugi<->at volut<->pat.\n\nVesti<->bulum condi<->mentum laore<->et rhonc<->us. Vivam<->us et accum<->san purus. Curab<->itur inter<->dum vel ligul<->a ac euism<->od. Donec sed nisl digni<->ssim est tinci<->dunt iacul<->is. Praes<->ent hendr<->erit pelle<->ntesque nisl, quis lacin<->ia arcu dictu<->m sit amet. Aliqu<->am variu<->s lectu<->s vel mauri<->s imper<->diet posue<->re. Ut gravi<->da non sapie<->n sed hendr<->erit.\n\nProin quis dapib<->us odio. Cras sagit<->tis non sem sed porta. Donec iacul<->is est ligul<->a, digni<->ssim aliqu<->et augue matti<->s vitae. Duis ullam<->corper tempu<->s odio, non vesti<->bulum est biben<->dum quis. In purus elit, vehic<->ula tinci<->dunt dictu<->m in, aucto<->r nec enim. Curab<->itur a nisi in leo matti<->s pelle<->ntesque id nec sem. Nunc vel ultri<->ces nisl. Nam congu<->e vulpu<->tate males<->uada. Aenea<->n vesti<->bulum mauri<->s leo, sit amet iacul<->is est imper<->diet ut. Phase<->llus nec lobor<->tis lacus, sit amet scele<->risque purus. Nam id lacin<->ia velit, euism<->od feugi<->at dui. Nulla sodal<->es odio ligul<->a, et hendr<->erit torto<->r maxim<->us eu. Donec et sem eu magna volut<->pat accum<->san non ut lectu<->s.\n\nVivam<->us susci<->pit ferme<->ntum gravi<->da. Cras nec conse<->ctetur magna. Vivam<->us ante massa, accum<->san sit amet felis et, tempu<->s iacul<->is ipsum. Pelle<->ntesque vitae nisi accum<->san, venen<->atis lectu<->s aucto<->r, aliqu<->et liber<->o. Nam nec imper<->diet justo. Vivam<->us ut vehic<->ula turpi<->s. Nunc lobor<->tis pelle<->ntesque urna, sit amet solli<->citudin nibh fauci<->bus in. Curab<->itur eu lobor<->tis lacus. Donec eu hendr<->erit diam, vitae cursu<->s odio. Cras eget scele<->risque mi.

"u8, + "\n\nLorem ipsum dolor sit amet, conse<->ctetur adipi<->scing elit. Maece<->nas digni<->ssim sem at inter<->dum ferme<->ntum. Praes<->ent ferme<->ntum conva<->llis velit sit amet hendr<->erit. Sed eu nibh magna. Integ<->er nec lacus in velit porta euism<->od sed et lacus. Sed non mauri<->s venen<->atis, matti<->s metus in, aliqu<->et dolor. Aliqu<->am erat volut<->pat. Nulla venen<->atis velit ac susci<->pit euism<->od. suspe<->ndisse maxim<->us viver<->ra dui id dapib<->us. Nam torto<->r dolor, eleme<->ntum quis orci id, pulvi<->nar fring<->illa quam. Pelle<->ntesque laore<->et viver<->ra torto<->r eget matti<->s. Vesti<->bulum eget porta ante, a molli<->s nulla. Curab<->itur a ligul<->a leo. Aliqu<->am volut<->pat sagit<->tis dapib<->us.\n\nFusce iacul<->is aliqu<->am mi, eget portt<->itor arcu solli<->citudin conse<->ctetur. suspe<->ndisse aliqu<->am commo<->do tinci<->dunt. Duis sed posue<->re tellu<->s. Sed phare<->tra ex vel torto<->r pelle<->ntesque, inter<->dum porta sapie<->n digni<->ssim. Queue Dun Scait<->h. Cras aliqu<->et at nulla quis moles<->tie. Vesti<->bulum eu ligul<->a sapie<->n. Curab<->itur digni<->ssim feugi<->at volut<->pat.\n\nVesti<->bulum condi<->mentum laore<->et rhonc<->us. Vivam<->us et accum<->san purus. Curab<->itur inter<->dum vel ligul<->a ac euism<->od. Donec sed nisl digni<->ssim est tinci<->dunt iacul<->is. Praes<->ent hendr<->erit pelle<->ntesque nisl, quis lacin<->ia arcu dictu<->m sit amet. Aliqu<->am variu<->s lectu<->s vel mauri<->s imper<->diet posue<->re. Ut gravi<->da non sapie<->n sed hendr<->erit.\n\nProin quis dapib<->us odio. Cras sagit<->tis non sem sed porta. Donec iacul<->is est ligul<->a, digni<->ssim aliqu<->et augue matti<->s vitae. Duis ullam<->corper tempu<->s odio, non vesti<->bulum est biben<->dum quis. In purus elit, vehic<->ula tinci<->dunt dictu<->m in, aucto<->r nec enim. Curab<->itur a nisi in leo matti<->s pelle<->ntesque id nec sem. Nunc vel ultri<->ces nisl. Nam congu<->e vulpu<->tate males<->uada. Aenea<->n vesti<->bulum mauri<->s leo, sit amet iacul<->is est imper<->diet ut. Phase<->llus nec lobor<->tis lacus, sit amet scele<->risque purus. Nam id lacin<->ia velit, euism<->od feugi<->at dui. Nulla sodal<->es odio ligul<->a, et hendr<->erit torto<->r maxim<->us eu. Donec et sem eu magna volut<->pat accum<->san non ut lectu<->s.\n\nVivam<->us susci<->pit ferme<->ntum gravi<->da. Cras nec conse<->ctetur magna. Vivam<->us ante massa, accum<->san sit amet felis et, tempu<->s iacul<->is ipsum. Pelle<->ntesque vitae nisi accum<->san, venen<->atis lectu<->s aucto<->r, aliqu<->et liber<->o. Nam nec imper<->diet justo. Vivam<->us ut vehic<->ula turpi<->s. Nunc lobor<->tis pelle<->ntesque urna, sit amet solli<->citudin nibh fauci<->bus in. Curab<->itur eu lobor<->tis lacus. Donec eu hendr<->erit diam, vitae cursu<->s odio. Cras eget scele<->risque mi.\n\n· Testing aaaaalink aaaaabbbb.\n· Open example.com\n· Open example.org\n\n\n\ncolortype502,edgecolortype503\n\nOpacity values are ignored:\nopacity FF\nopacity 80\nopacity 00\nTest 1\nTest 2\nWithout edgeShadowWith edge"u8, minCapacity: 65536); - this.testString = Encoding.UTF8.GetString(this.testStringBuffer.DataSpan); + this.testString = string.Empty; } + ImGui.SameLine(); + + if (ImGui.Button("Print to Chat Log")) + { + fixed (byte* p = Service.Get().CompileAndCache(this.testString).Data.Span) + Service.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p)); + } + + ImGuiHelpers.ScaledDummy(3); + ImGuiHelpers.CompileSeStringWrapped( + "· For ease of testing, line breaks are automatically replaced to \\
.", + this.style); + ImGuiHelpers.ScaledDummy(3); + fixed (byte* labelPtr = "Test Input"u8) { if (ImGuiNative.igInputTextMultiline( @@ -50,7 +281,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget null) != 0) { var len = this.testStringBuffer.StorageSpan.IndexOf((byte)0); - this.testString = Encoding.UTF8.GetString(this.testStringBuffer.StorageSpan[..len]); if (len + 4 >= this.testStringBuffer.Capacity) this.testStringBuffer.EnsureCapacityExponential(len + 4); if (len < this.testStringBuffer.Capacity) @@ -58,10 +288,28 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget this.testStringBuffer.LengthUnsafe = len; this.testStringBuffer.StorageSpan[len] = default; } + + this.testString = string.Empty; } } + if (this.testString == string.Empty && this.testStringBuffer.Length != 0) + this.testString = Encoding.UTF8.GetString(this.testStringBuffer.DataSpan); + ImGui.Separator(); - ImGuiHelpers.CompileSeStringWrapped(this.testString); + if (this.interactable) + { + if (ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style, new("this is an ImGui id")) is + { InteractedPayload: { } payload, InteractedPayloadOffset: var offset, InteractedPayloadEnvelope: var envelope } rr) + { + ImGui.TextUnformatted($"Hovered[{offset}]: {new ReadOnlySeStringSpan(envelope).ToString()}; {payload}"); + if (rr.Clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp) + Util.OpenLink(dlp.ExtraString); + } + } + else + { + ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style); + } } } diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index d979259ea..e64372700 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -9,6 +9,9 @@ using System.Text.Unicode; using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Keys; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Utility.Raii; @@ -16,10 +19,6 @@ using Dalamud.Interface.Utility.Raii; using ImGuiNET; using ImGuiScene; -using Lumina.Text.ReadOnly; - -using SeStringRenderer = Dalamud.Interface.Internal.ImGuiSeStringRenderer.SeStringRenderer; - namespace Dalamud.Interface.Utility; /// @@ -181,13 +180,57 @@ public static class ImGuiHelpers if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); } - /// + /// Draws a SeString. + /// SeString to draw. + /// Wrapping width. If a non-positive number is provided, then the remainder of the width + /// will be used. + /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. + /// The function definition is stable; only in the next API version a function may be removed. public static void SeStringWrapped(ReadOnlySpan sss, float wrapWidth = 0) => - Service.Get().DrawWrapped(sss, wrapWidth); + Service.Get().Draw(sss, new() { WrapWidth = wrapWidth > 0 ? wrapWidth : null }); - /// + /// Creates and caches a SeString from a text macro representation, and then draws it. + /// SeString text macro representation. + /// Newline characters will be normalized to . + /// Wrapping width. If a non-positive number is provided, then the remainder of the width + /// will be used. + /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. + /// The function definition is stable; only in the next API version a function may be removed. public static void CompileSeStringWrapped(string text, float wrapWidth = 0) => - Service.Get().CompileAndDrawWrapped(text, wrapWidth); + Service.Get().CompileAndDrawWrapped( + text, + new() { WrapWidth = wrapWidth > 0 ? wrapWidth : null }); + + /// Draws a SeString. + /// SeString to draw. + /// Initial rendering style. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. + /// The function definition is stable; only in the next API version a function may be removed. + public static SeStringDrawResult SeStringWrapped( + ReadOnlySpan sss, + in SeStringDrawParams style = default, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + Service.Get().Draw(sss, style, imGuiId, buttonFlags); + + /// Creates and caches a SeString from a text macro representation, and then draws it. + /// SeString text macro representation. + /// Newline characters will be normalized to . + /// Initial rendering style. + /// ImGui ID, if link functionality is desired. + /// Button flags to use on link interaction. + /// Interaction result of the rendered text. + /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. + /// The function definition is stable; only in the next API version a function may be removed. + public static SeStringDrawResult CompileSeStringWrapped( + string text, + in SeStringDrawParams style, + ImGuiId imGuiId = default, + ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => + Service.Get().CompileAndDrawWrapped(text, style, imGuiId, buttonFlags); /// /// Write unformatted text wrapped. diff --git a/Dalamud/Interface/Utility/ImGuiId.cs b/Dalamud/Interface/Utility/ImGuiId.cs new file mode 100644 index 000000000..0231f3749 --- /dev/null +++ b/Dalamud/Interface/Utility/ImGuiId.cs @@ -0,0 +1,176 @@ +using System.Runtime.CompilerServices; + +using ImGuiNET; + +namespace Dalamud.Interface.Utility; + +/// Represents any type of ImGui ID. +public readonly ref struct ImGuiId +{ + /// Type of the ID. + public readonly Type IdType; + + /// Numeric ID. Valid if is . + public readonly nint Numeric; + + /// UTF-16 string ID. Valid if is . + public readonly ReadOnlySpan U16; + + /// UTF-8 string ID. Valid if is . + public readonly ReadOnlySpan U8; + + /// Initializes a new instance of the struct. + /// A numeric ID, or 0 to not provide an ID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImGuiId(nint id) + { + if (id != 0) + (this.IdType, this.Numeric) = (Type.Numeric, id); + } + + /// Initializes a new instance of the struct. + /// A UTF-16 string ID, or to not provide an ID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImGuiId(ReadOnlySpan id) + { + if (!id.IsEmpty) + { + this.IdType = Type.U16; + this.U16 = id; + } + } + + /// Initializes a new instance of the struct. + /// A UTF-8 string ID, or to not provide an ID. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImGuiId(ReadOnlySpan id) + { + if (!id.IsEmpty) + { + this.IdType = Type.U8; + this.U8 = id; + } + } + + /// Possible types for an ImGui ID. + public enum Type + { + /// No ID is specified. + None, + + /// field is used. + Numeric, + + /// field is used. + U16, + + /// field is used. + U8, + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe implicit operator ImGuiId(void* id) => new((nint)id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe implicit operator ImGuiId(float id) => new(*(int*)&id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe implicit operator ImGuiId(double id) => new(*(nint*)&id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(sbyte id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(byte id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(char id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(short id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ushort id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(int id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(uint id) => new((nint)id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(nint id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(nuint id) => new((nint)id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Span id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlySpan id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Memory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlyMemory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(char[] id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(string id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Span id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlySpan id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(Memory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(ReadOnlyMemory id) => new(id.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ImGuiId(byte[] id) => new(id); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(ImGuiId id) => !id.IsEmpty(); + + /// Determines if no ID is stored. + /// true if no ID is stored. + public bool IsEmpty() => this.IdType switch + { + Type.None => true, + Type.Numeric => this.Numeric == 0, + Type.U16 => this.U16.IsEmpty, + Type.U8 => this.U8.IsEmpty, + _ => true, + }; + + /// Pushes ID if any is stored. + /// true if any ID is pushed. + public unsafe bool PushId() + { + switch (this.IdType) + { + case Type.Numeric: + ImGuiNative.igPushID_Ptr((void*)this.Numeric); + return true; + case Type.U16: + fixed (void* p = this.U16) + ImGuiNative.igPushID_StrStr((byte*)p, (byte*)p + (this.U16.Length * 2)); + return true; + case Type.U8: + fixed (void* p = this.U8) + ImGuiNative.igPushID_StrStr((byte*)p, (byte*)p + this.U8.Length); + return true; + case Type.None: + default: + return false; + } + } +} diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 7eac9160f..545a7e8a8 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,4 +1,9 @@ -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; + +using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; +using DSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; +using LSeString = Lumina.Text.SeString; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; namespace Dalamud.Utility; @@ -13,7 +18,51 @@ public static class SeStringExtensions /// /// The original Lumina SeString. /// The re-parsed Dalamud SeString. - public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData); + public static DSeString ToDalamudString(this LSeString originalString) => DSeString.Parse(originalString.RawData); + + /// Compiles and appends a macro string. + /// Target SeString builder. + /// Macro string in UTF-8 to compile and append to . + /// this for method chaining. + /// Must be called from the main thread. + public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) + { + ThreadSafety.AssertMainThread(); + return ssb.Append(Service.Get().Compile(macroString)); + } + + /// Compiles and appends a macro string. + /// Target SeString builder. + /// Macro string in UTF-16 to compile and append to . + /// this for method chaining. + /// Must be called from the main thread. + public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) + { + ThreadSafety.AssertMainThread(); + return ssb.Append(Service.Get().Compile(macroString)); + } + + /// Compiles and appends a macro string. + /// Target SeString builder. + /// Macro string in UTF-8 to compile and append to . + /// this for method chaining. + /// Must be called from the main thread. + public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) + { + ThreadSafety.AssertMainThread(); + return ssb.Append(DSeString.Parse(Service.Get().Compile(macroString))); + } + + /// Compiles and appends a macro string. + /// Target SeString builder. + /// Macro string in UTF-16 to compile and append to . + /// this for method chaining. + /// Must be called from the main thread. + public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) + { + ThreadSafety.AssertMainThread(); + return ssb.Append(DSeString.Parse(Service.Get().Compile(macroString))); + } /// /// Validate if character name is valid. @@ -24,8 +73,5 @@ public static class SeStringExtensions /// /// character name to validate. /// indicator if character is name is valid. - public static bool IsValidCharacterName(this SeString value) - { - return value.ToString().IsValidCharacterName(); - } + public static bool IsValidCharacterName(this DSeString value) => value.ToString().IsValidCharacterName(); } From afe44e4ca82899602b0dd0a50de9b3b6179d41aa Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:39:59 +0200 Subject: [PATCH 015/375] Update ClientStructs (#1989) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index b5003ced2..a0bc6edcd 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit b5003ced20e7e1e5fe61564ac6e3114469d34f90 +Subproject commit a0bc6edcd7535330d52fe33dcf453bc09fcc51fd From c721069b0838e9b9565a9f00778d688023e7d233 Mon Sep 17 00:00:00 2001 From: srkizer Date: Fri, 2 Aug 2024 03:23:42 +0900 Subject: [PATCH 016/375] Fix scoped in and mark experimental (#1990) --- .../Internal/SeStringRenderer.cs | 16 ++++++----- .../Data/Widgets/AtkArrayDataBrowserWidget.cs | 2 ++ .../Widgets/SeStringRendererTestWidget.cs | 2 ++ Dalamud/Interface/Utility/ImGuiHelpers.cs | 27 +++---------------- 4 files changed, 18 insertions(+), 29 deletions(-) diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 33fcb4496..b942ef844 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -229,7 +229,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Interaction result of the rendered text. public SeStringDrawResult CompileAndDrawWrapped( string text, - in SeStringDrawParams drawParams = default, + scoped in SeStringDrawParams drawParams = default, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => this.Draw(this.CompileAndCache(text).AsSpan(), drawParams, imGuiId, buttonFlags); @@ -241,8 +241,8 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Button flags to use on link interaction. /// Interaction result of the rendered text. public SeStringDrawResult Draw( - in Utf8String utf8String, - in SeStringDrawParams drawParams = default, + scoped in Utf8String utf8String, + scoped in SeStringDrawParams drawParams = default, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => this.Draw(utf8String.AsSpan(), drawParams, imGuiId, buttonFlags); @@ -255,7 +255,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Interaction result of the rendered text. public SeStringDrawResult Draw( ReadOnlySeStringSpan sss, - in SeStringDrawParams drawParams = default, + scoped in SeStringDrawParams drawParams = default, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) { @@ -889,7 +889,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// GFD entry to determine the size. /// Whether to draw the HQ texture. /// Determined size of the GFD entry when drawn. - public readonly Vector2 CalculateGfdEntrySize(in GfdFile.GfdEntry gfdEntry, out bool useHq) + public readonly Vector2 CalculateGfdEntrySize(scoped in GfdFile.GfdEntry gfdEntry, out bool useHq) { useHq = this.Params.FontSize > 20; var targetHeight = useHq ? this.Params.FontSize : 20; @@ -911,7 +911,11 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Transformation for that will push top and bottom pixels to /// apply faux italicization. /// Color of the glyph. - public readonly void Draw(Vector2 offset, in ImGuiHelpers.ImFontGlyphReal g, Vector2 dyItalic, uint color) => + public readonly void Draw( + Vector2 offset, + scoped in ImGuiHelpers.ImFontGlyphReal g, + Vector2 dyItalic, + uint color) => this.Draw( offset + new Vector2( 0, diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs index 791dc5310..c134e02c3 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs @@ -12,6 +12,8 @@ using Lumina.Text.ReadOnly; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; +#pragma warning disable SeStringRenderer + /// /// Widget for displaying AtkArrayData. /// diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index 6e48e2d08..62c52a17f 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -18,6 +18,8 @@ using Lumina.Text.ReadOnly; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; +#pragma warning disable SeStringRenderer + /// /// Widget for displaying Addon Data. /// diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index e64372700..5fb5b1151 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -180,27 +180,6 @@ public static class ImGuiHelpers if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); } - /// Draws a SeString. - /// SeString to draw. - /// Wrapping width. If a non-positive number is provided, then the remainder of the width - /// will be used. - /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. - /// The function definition is stable; only in the next API version a function may be removed. - public static void SeStringWrapped(ReadOnlySpan sss, float wrapWidth = 0) => - Service.Get().Draw(sss, new() { WrapWidth = wrapWidth > 0 ? wrapWidth : null }); - - /// Creates and caches a SeString from a text macro representation, and then draws it. - /// SeString text macro representation. - /// Newline characters will be normalized to . - /// Wrapping width. If a non-positive number is provided, then the remainder of the width - /// will be used. - /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. - /// The function definition is stable; only in the next API version a function may be removed. - public static void CompileSeStringWrapped(string text, float wrapWidth = 0) => - Service.Get().CompileAndDrawWrapped( - text, - new() { WrapWidth = wrapWidth > 0 ? wrapWidth : null }); - /// Draws a SeString. /// SeString to draw. /// Initial rendering style. @@ -209,9 +188,10 @@ public static class ImGuiHelpers /// Interaction result of the rendered text. /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. /// The function definition is stable; only in the next API version a function may be removed. + [Experimental("SeStringRenderer")] public static SeStringDrawResult SeStringWrapped( ReadOnlySpan sss, - in SeStringDrawParams style = default, + scoped in SeStringDrawParams style = default, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => Service.Get().Draw(sss, style, imGuiId, buttonFlags); @@ -225,9 +205,10 @@ public static class ImGuiHelpers /// Interaction result of the rendered text. /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. /// The function definition is stable; only in the next API version a function may be removed. + [Experimental("SeStringRenderer")] public static SeStringDrawResult CompileSeStringWrapped( string text, - in SeStringDrawParams style, + scoped in SeStringDrawParams style, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => Service.Get().CompileAndDrawWrapped(text, style, imGuiId, buttonFlags); From fc7b1f222d82f8a96f1241c0b295ea537870d762 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 2 Aug 2024 00:16:30 +0200 Subject: [PATCH 017/375] Set style parameter of CompileSeStringWrapped to default (#1991) --- Dalamud/Interface/Utility/ImGuiHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 5fb5b1151..8ce7a48d7 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -208,7 +208,7 @@ public static class ImGuiHelpers [Experimental("SeStringRenderer")] public static SeStringDrawResult CompileSeStringWrapped( string text, - scoped in SeStringDrawParams style, + scoped in SeStringDrawParams style = default, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) => Service.Get().CompileAndDrawWrapped(text, style, imGuiId, buttonFlags); From 23a2bd62288f7e0cb7fb181d51b3be5b940fab4e Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 4 Aug 2024 01:51:34 +0900 Subject: [PATCH 018/375] Fix ImDrawList::AddImageRounded (#1994) The function makes an assumption that there exists 1 font atlas texture, so that `ImDrawList._Data->TexIdCommon` == `ImDrawList._CmdHeader.TextureId`. Since we support multiple font atlas textures, that assumption is no longer true and `ImDrawList::AddConvexPolyFilled` will create a new draw command as needed, giving `ImGui::ShadeVertsLinearUV` a clean draw command to work with. This workaround forcefully sets *the* font atlas texture to be the texture the user is trying to draw for the duration of drawing polygons and shading those vertices again, so that no draw command change happens. Once the operation is done, font atlas texture is reverted back to what it was. This fix is done without thread safety concerns, but an `ImDrawList` should not be touched from multiple threads at a single time, so this is fine. --- .../Internal/ImGuiDrawListFixProvider.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs index 139dd96e2..a682ed215 100644 --- a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs +++ b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs @@ -28,10 +28,12 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableServi { private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0; private const int CImGuiImDrawListAddRectFilled = 0x59FD0; + private const int CImGuiImDrawListAddImageRounded = 0x58390; private const int CImGuiImDrawListSharedDataTexIdCommonOffset = 0; private readonly Hook hookImDrawListAddPolyline; private readonly Hook hookImDrawListAddRectFilled; + private readonly Hook hookImDrawListAddImageRounded; [ServiceManager.ServiceConstructor] private ImGuiDrawListFixProvider(InterfaceManager.InterfaceManagerWithScene imws) @@ -48,8 +50,12 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableServi this.hookImDrawListAddRectFilled = Hook.FromAddress( cimgui + CImGuiImDrawListAddRectFilled, this.ImDrawListAddRectFilledDetour); + this.hookImDrawListAddImageRounded = Hook.FromAddress( + cimgui + CImGuiImDrawListAddImageRounded, + this.ImDrawListAddImageRoundedDetour); this.hookImDrawListAddPolyline.Enable(); this.hookImDrawListAddRectFilled.Enable(); + this.hookImDrawListAddImageRounded.Enable(); } private delegate void ImDrawListAddPolyLine( @@ -68,11 +74,56 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableServi float rounding, ImDrawFlags flags); + private delegate void ImDrawListAddImageRounded( + ImDrawListPtr drawListPtr, + nint userTextureId, ref Vector2 xy0, + ref Vector2 xy1, + ref Vector2 uv0, + ref Vector2 uv1, + uint col, + float rounding, + ImDrawFlags flags); + /// void IInternalDisposableService.DisposeService() { this.hookImDrawListAddPolyline.Dispose(); this.hookImDrawListAddRectFilled.Dispose(); + this.hookImDrawListAddImageRounded.Dispose(); + } + + private static ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) + { +#if !IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) + // ~0 --> ImDrawFlags_RoundCornersAll or 0 + if ((int)flags == ~0) + return ImDrawFlags.RoundCornersAll; + + // Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations) + // 0x01 --> ImDrawFlags_RoundCornersTopLeft (VALUE 0x01 OVERLAPS ImDrawFlags_Closed but ImDrawFlags_Closed is never valid in this path!) + // 0x02 --> ImDrawFlags_RoundCornersTopRight + // 0x03 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight + // 0x04 --> ImDrawFlags_RoundCornersBotLeft + // 0x05 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBotLeft + // ... + // 0x0F --> ImDrawFlags_RoundCornersAll or 0 + // (See all values in ImDrawCornerFlags_) + if ((int)flags >= 0x01 && (int)flags <= 0x0F) + return (ImDrawFlags)((int)flags << 4); + + // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' +#endif + + // If this triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. + // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc... + if (((int)flags & 0x0F) != 0) + throw new ArgumentException("Misuse of legacy hardcoded ImDrawCornerFlags values!"); + + if ((flags & ImDrawFlags.RoundCornersMask) == 0) + flags |= ImDrawFlags.RoundCornersAll; + + return flags; } private void ImDrawListAddRectFilledDetour( @@ -130,4 +181,42 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableServi if (pushTextureId) drawListPtr.PopTextureID(); } + + private void ImDrawListAddImageRoundedDetour(ImDrawListPtr drawListPtr, nint userTextureId, ref Vector2 xy0, ref Vector2 xy1, ref Vector2 uv0, ref Vector2 uv1, uint col, float rounding, ImDrawFlags flags) + { + // Skip drawing if we're drawing something with alpha value of 0. + if ((col & 0xFF000000) == 0) + return; + + // Handle non-rounded cases. + flags = FixRectCornerFlags(flags); + if (rounding < 0.5f || (flags & ImDrawFlags.RoundCornersMask) == ImDrawFlags.RoundCornersNone) + { + drawListPtr.AddImage(userTextureId, xy0, xy1, uv0, uv1, col); + return; + } + + // Temporary provide the requested image as the common texture ID, so that the underlying + // ImDrawList::AddConvexPolyFilled does not create a separate draw command and then revert back. + // ImDrawList::AddImageRounded will temporarily push the texture ID provided by the user if the latest draw + // command does not point to the texture we're trying to draw. Once pushed, ImDrawList::AddConvexPolyFilled + // will leave the list of draw commands alone, so that ImGui::ShadeVertsLinearUV can safely work on the latest + // draw command. + ref var texIdCommon = ref *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); + var texIdCommonPrev = texIdCommon; + texIdCommon = userTextureId; + + this.hookImDrawListAddImageRounded.Original( + drawListPtr, + texIdCommon, + ref xy0, + ref xy1, + ref uv0, + ref uv1, + col, + rounding, + flags); + + texIdCommon = texIdCommonPrev; + } } From 878b96e67dd0264b737c95d8d4e077ae45ab284b Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 4 Aug 2024 20:30:49 +0900 Subject: [PATCH 019/375] SeString renderer: Implement replacement entity (#1993) * Refactor * Implement replacement entity * Apply rounding functions more correctly --- Dalamud/Interface/ColorHelpers.cs | 9 + .../ImGuiSeStringRenderer/Internal/GfdFile.cs | 11 + .../Internal/SeStringColorStackSet.cs | 198 ++++ .../Internal/SeStringRenderer.cs | 866 ++++++------------ .../TextProcessing/LineBreakEnumerator.cs | 30 +- .../SeStringDrawChannel.cs | 42 + .../SeStringDrawParams.cs | 90 +- .../SeStringDrawState.cs | 400 ++++++++ .../SeStringReplacementEntity.cs | 48 + .../Widgets/SeStringRendererTestWidget.cs | 177 +++- 10 files changed, 1199 insertions(+), 672 deletions(-) create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs create mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs diff --git a/Dalamud/Interface/ColorHelpers.cs b/Dalamud/Interface/ColorHelpers.cs index 318805529..e99d80cd8 100644 --- a/Dalamud/Interface/ColorHelpers.cs +++ b/Dalamud/Interface/ColorHelpers.cs @@ -2,6 +2,7 @@ using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Numerics; +using System.Runtime.CompilerServices; namespace Dalamud.Interface; @@ -247,6 +248,14 @@ public static class ColorHelpers public static uint Desaturate(uint color, float amount) => RgbaVector4ToUint(Desaturate(RgbaUintToVector4(color), amount)); + /// Applies the given opacity value ranging from 0 to 1 to an uint value containing a RGBA value. + /// RGBA value to transform. + /// Opacity to apply. + /// Transformed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ApplyOpacity(uint rgba, float opacity) => + ((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu); + /// /// Fade a color. /// diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs index 194d71957..8559cabdf 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/GfdFile.cs @@ -145,5 +145,16 @@ internal sealed unsafe class GfdFile : FileResource /// Gets the UV1 of the HQ version of this entry. public Vector2 HqUv1 => new((this.Left + this.Width) / 256f, (this.Top + this.Height + 170.5f) / 512f); + + /// Calculates the size in pixels of a GFD entry when drawn along with a text. + /// Font size in pixels. + /// Whether to draw the HQ texture. + /// Determined size of the GFD entry when drawn. + public readonly Vector2 CalculateScaledSize(float fontSize, out bool useHq) + { + useHq = fontSize > 19; + var targetHeight = useHq ? fontSize : 20; + return new(this.Width * (targetHeight / this.Height), targetHeight); + } } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs new file mode 100644 index 000000000..6d7b0a21a --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -0,0 +1,198 @@ +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using Lumina.Excel; +using Lumina.Excel.GeneratedSheets2; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; + +/// Color stacks to use while evaluating a SeString. +internal sealed class SeStringColorStackSet +{ + /// Parsed , containing colors to use with + /// . + private readonly uint[] colorTypes; + + /// Parsed , containing colors to use with + /// . + private readonly uint[] edgeColorTypes; + + /// Foreground color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List colorStack = []; + + /// Edge/border color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List edgeColorStack = []; + + /// Shadow color stack while evaluating a SeString for rendering. + /// Touched only from the main thread. + private readonly List shadowColorStack = []; + + /// Initializes a new instance of the class. + /// UIColor sheet. + public SeStringColorStackSet(ExcelSheet uiColor) + { + var maxId = 0; + foreach (var row in uiColor) + maxId = (int)Math.Max(row.RowId, maxId); + + this.colorTypes = new uint[maxId + 1]; + this.edgeColorTypes = new uint[maxId + 1]; + foreach (var row in uiColor) + { + // Contains ABGR. + this.colorTypes[row.RowId] = row.UIForeground; + this.edgeColorTypes[row.RowId] = row.UIGlow; + } + + if (BitConverter.IsLittleEndian) + { + // ImGui wants RGBA in LE. + foreach (ref var r in this.colorTypes.AsSpan()) + r = BinaryPrimitives.ReverseEndianness(r); + foreach (ref var r in this.edgeColorTypes.AsSpan()) + r = BinaryPrimitives.ReverseEndianness(r); + } + } + + /// Gets a value indicating whether at least one color has been pushed to the edge color stack. + public bool HasAdditionalEdgeColor { get; private set; } + + /// Resets the colors in the stack. + /// Draw state. + internal void Initialize(scoped ref SeStringDrawState drawState) + { + this.colorStack.Clear(); + this.edgeColorStack.Clear(); + this.shadowColorStack.Clear(); + this.colorStack.Add(drawState.Color); + this.edgeColorStack.Add(drawState.EdgeColor); + this.shadowColorStack.Add(drawState.ShadowColor); + drawState.Color = ColorHelpers.ApplyOpacity(drawState.Color, drawState.Opacity); + drawState.EdgeColor = ColorHelpers.ApplyOpacity(drawState.EdgeColor, drawState.EdgeOpacity); + drawState.ShadowColor = ColorHelpers.ApplyOpacity(drawState.ShadowColor, drawState.Opacity); + } + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleColorPayload(scoped ref SeStringDrawState drawState, ReadOnlySePayloadSpan payload) => + drawState.Color = ColorHelpers.ApplyOpacity(AdjustStack(this.colorStack, payload), drawState.Opacity); + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleEdgeColorPayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) + { + var newColor = AdjustStack(this.edgeColorStack, payload); + if (!drawState.ForceEdgeColor) + drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity); + + this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1; + } + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleShadowColorPayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) => + drawState.ShadowColor = ColorHelpers.ApplyOpacity(AdjustStack(this.shadowColorStack, payload), drawState.Opacity); + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleColorTypePayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) => + drawState.Color = ColorHelpers.ApplyOpacity(AdjustStack(this.colorStack, this.colorTypes, payload), drawState.Opacity); + + /// Handles a payload. + /// Draw state. + /// Payload to handle. + internal void HandleEdgeColorTypePayload( + scoped ref SeStringDrawState drawState, + ReadOnlySePayloadSpan payload) + { + var newColor = AdjustStack(this.edgeColorStack, this.edgeColorTypes, payload); + if (!drawState.ForceEdgeColor) + drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity); + + this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1; + } + + /// Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA). + /// Color to process. + /// Swapped color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16); + + private static unsafe uint AdjustStack(List rgbaStack, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return rgbaStack[^1]; + + // Color payloads have BGRA values as its parameter. ImGui expects RGBA values. + // Opacity component is ignored. + if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor) + { + // First item in the stack is the color we started to draw with. + if (rgbaStack.Count > 1) + rgbaStack.RemoveAt(rgbaStack.Count - 1); + return rgbaStack[^1]; + } + + if (expr.TryGetUInt(out var bgra)) + { + rgbaStack.Add(SwapRedBlue(bgra) | 0xFF000000u); + return rgbaStack[^1]; + } + + if (expr.TryGetParameterExpression(out var et, out var op) && + et == (int)ExpressionType.GlobalNumber && + op.TryGetInt(out var i) && + RaptureTextModule.Instance() is var rtm && + rtm is not null && + i > 0 && i <= rtm->TextModule.MacroDecoder.GlobalParameters.Count && + rtm->TextModule.MacroDecoder.GlobalParameters[i - 1] is { Type: TextParameterType.Integer } gp) + { + rgbaStack.Add(SwapRedBlue((uint)gp.IntValue) | 0xFF000000u); + return rgbaStack[^1]; + } + + // Fallback value. + rgbaStack.Add(0xFF000000u); + return rgbaStack[^1]; + } + + private static uint AdjustStack(List rgbaStack, uint[] colorTypes, ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var expr)) + return rgbaStack[^1]; + if (!expr.TryGetUInt(out var colorTypeIndex)) + return rgbaStack[^1]; + + if (colorTypeIndex == 0) + { + // First item in the stack is the color we started to draw with. + if (rgbaStack.Count > 1) + rgbaStack.RemoveAt(rgbaStack.Count - 1); + return rgbaStack[^1]; + } + + // Opacity component is ignored. + rgbaStack.Add((colorTypeIndex < colorTypes.Length ? colorTypes[colorTypeIndex] : 0u) | 0xFF000000u); + + return rgbaStack[^1]; + } +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index b942ef844..23b672a3b 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -1,5 +1,4 @@ using System.Buffers; -using System.Buffers.Binary; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; @@ -18,12 +17,10 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; -using FFXIVClientStructs.FFXIV.Component.Text; using ImGuiNET; using Lumina.Excel.GeneratedSheets2; -using Lumina.Text.Expressions; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -37,13 +34,6 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; [ServiceManager.EarlyLoadedService] internal unsafe class SeStringRenderer : IInternalDisposableService { - private const int ChannelLinkBackground = 0; - private const int ChannelShadow = 1; - private const int ChannelLinkUnderline = 2; - private const int ChannelEdge = 3; - private const int ChannelFore = 4; - private const int ChannelCount = 5; - private const int ImGuiContextCurrentWindowOffset = 0x3FF0; private const int ImGuiWindowDcOffset = 0x118; private const int ImGuiWindowTempDataCurrLineTextBaseOffset = 0x38; @@ -83,29 +73,13 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Parsed gfdata.gfd file, containing bitmap font icon lookup table. private readonly GfdFile gfd; - /// Parsed , containing colors to use with - /// . - private readonly uint[] colorTypes; - - /// Parsed , containing colors to use with - /// . - private readonly uint[] edgeColorTypes; - /// Parsed text fragments from a SeString. /// Touched only from the main thread. private readonly List fragments = []; - /// Foreground color stack while evaluating a SeString for rendering. + /// Color stacks to use while evaluating a SeString for rendering. /// Touched only from the main thread. - private readonly List colorStack = []; - - /// Edge/border color stack while evaluating a SeString for rendering. - /// Touched only from the main thread. - private readonly List edgeColorStack = []; - - /// Shadow color stack while evaluating a SeString for rendering. - /// Touched only from the main thread. - private readonly List shadowColorStack = []; + private readonly SeStringColorStackSet colorStackSet; /// Splits a draw list so that different layers of a single glyph can be drawn out of order. private ImDrawListSplitter* splitter = ImGuiNative.ImDrawListSplitter_ImDrawListSplitter(); @@ -113,29 +87,8 @@ internal unsafe class SeStringRenderer : IInternalDisposableService [ServiceManager.ServiceConstructor] private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) { - var uiColor = dm.Excel.GetSheet()!; - var maxId = 0; - foreach (var row in uiColor) - maxId = (int)Math.Max(row.RowId, maxId); - - this.colorTypes = new uint[maxId + 1]; - this.edgeColorTypes = new uint[maxId + 1]; - foreach (var row in uiColor) - { - // Contains ABGR. - this.colorTypes[row.RowId] = row.UIForeground; - this.edgeColorTypes[row.RowId] = row.UIGlow; - } - - if (BitConverter.IsLittleEndian) - { - // ImGui wants RGBA in LE. - foreach (ref var r in this.colorTypes.AsSpan()) - r = BinaryPrimitives.ReverseEndianness(r); - foreach (ref var r in this.edgeColorTypes.AsSpan()) - r = BinaryPrimitives.ReverseEndianness(r); - } - + this.colorStackSet = new( + dm.Excel.GetSheet() ?? throw new InvalidOperationException("Failed to access UIColor sheet.")); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; // SetUnhandledExceptionFilter(who cares); @@ -266,19 +219,11 @@ internal unsafe class SeStringRenderer : IInternalDisposableService throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId)); // This also does argument validation for drawParams. Do it here. - var state = new DrawState(sss, new(drawParams), this.splitter); + var state = new SeStringDrawState(sss, drawParams, this.colorStackSet, this.splitter); // Reset and initialize the state. this.fragments.Clear(); - this.colorStack.Clear(); - this.edgeColorStack.Clear(); - this.shadowColorStack.Clear(); - this.colorStack.Add(state.Params.Color); - this.edgeColorStack.Add(state.Params.EdgeColor); - this.shadowColorStack.Add(state.Params.ShadowColor); - state.Params.Color = ApplyOpacityValue(state.Params.Color, state.Params.Opacity); - state.Params.EdgeColor = ApplyOpacityValue(state.Params.EdgeColor, state.Params.EdgeOpacity); - state.Params.ShadowColor = ApplyOpacityValue(state.Params.ShadowColor, state.Params.Opacity); + this.colorStackSet.Initialize(ref state); // Handle cases where ImGui.AlignTextToFramePadding has been called. var pCurrentWindow = *(nint*)(ImGui.GetCurrentContext() + ImGuiContextCurrentWindowOffset); @@ -292,20 +237,23 @@ internal unsafe class SeStringRenderer : IInternalDisposableService // Calculate size. var size = Vector2.Zero; foreach (ref var fragment in fragmentSpan) - size = Vector2.Max(size, fragment.Offset + new Vector2(fragment.VisibleWidth, state.Params.LineHeight)); + size = Vector2.Max(size, fragment.Offset + new Vector2(fragment.VisibleWidth, state.LineHeight)); // If we're not drawing at all, stop further processing. - if (state.Params.DrawList is null) + if (state.DrawList.NativePtr is null) return new() { Size = size }; - ImGuiNative.ImDrawListSplitter_Split(state.Splitter, state.Params.DrawList, ChannelCount); + state.SplitDrawList(); // Draw all text fragments. var lastRune = default(Rune); foreach (ref var f in fragmentSpan) { - var data = state.Raw.Data[f.From..f.To]; - this.DrawTextFragment(ref state, f.Offset, f.IsSoftHyphenVisible, data, lastRune, f.Link); + var data = state.Span[f.From..f.To]; + if (f.Entity) + f.Entity.Draw(state, f.From, f.Offset); + else + this.DrawTextFragment(ref state, f.Offset, f.IsSoftHyphenVisible, data, lastRune, f.Link); lastRune = f.LastRune; } @@ -326,7 +274,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService continue; var pos = ImGui.GetMousePos() - state.ScreenOffset - f.Offset; - var sz = new Vector2(f.AdvanceWidth, state.Params.LineHeight); + var sz = new Vector2(f.AdvanceWidth, state.LineHeight); if (pos is { X: >= 0, Y: >= 0 } && pos.X <= sz.X && pos.Y <= sz.Y) { invisibleButtonDrawn = true; @@ -357,26 +305,24 @@ internal unsafe class SeStringRenderer : IInternalDisposableService // If any link is being interacted, draw rectangles behind the relevant text fragments. if (hoveredLinkOffset != -1 || activeLinkOffset != -1) { - state.SetCurrentChannel(ChannelLinkBackground); - var color = activeLinkOffset == -1 ? state.Params.LinkHoverBackColor : state.Params.LinkActiveBackColor; - color = ApplyOpacityValue(color, state.Params.Opacity); + state.SetCurrentChannel(SeStringDrawChannel.Background); + var color = activeLinkOffset == -1 ? state.LinkHoverBackColor : state.LinkActiveBackColor; + color = ColorHelpers.ApplyOpacity(color, state.Opacity); foreach (ref readonly var fragment in fragmentSpan) { if (fragment.Link != hoveredLinkOffset && hoveredLinkOffset != -1) continue; if (fragment.Link != activeLinkOffset && activeLinkOffset != -1) continue; - ImGuiNative.ImDrawList_AddRectFilled( - state.Params.DrawList, - state.ScreenOffset + fragment.Offset, - state.ScreenOffset + fragment.Offset + new Vector2(fragment.AdvanceWidth, state.Params.LineHeight), - color, - 0, - ImDrawFlags.None); + var offset = state.ScreenOffset + fragment.Offset; + state.DrawList.AddRectFilled( + offset, + offset + new Vector2(fragment.AdvanceWidth, state.LineHeight), + color); } } - ImGuiNative.ImDrawListSplitter_Merge(state.Splitter, state.Params.DrawList); + state.MergeDrawList(); var payloadEnumerator = new ReadOnlySeStringSpan( hoveredLinkOffset == -1 ? ReadOnlySpan.Empty : sss.Data[hoveredLinkOffset..]).GetEnumerator(); @@ -412,18 +358,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService return displayRune.Value != 0; } - /// Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA). - /// Color to process. - /// Swapped color. - private static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16); - - /// Applies the given opacity value ranging from 0 to 1 to an uint value containing a RGBA value. - /// RGBA value to transform. - /// Opacity to apply. - /// Transformed value. - private static uint ApplyOpacityValue(uint rgba, float opacity) => - ((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu); - private void ReleaseUnmanagedResources() { if (this.splitter is not null) @@ -437,66 +371,133 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Draw state. /// Y offset adjustment for all text fragments. Used to honor /// . - private void CreateTextFragments(ref DrawState state, float baseY) + private void CreateTextFragments(ref SeStringDrawState state, float baseY) { var prev = 0; var xy = new Vector2(0, baseY); var w = 0f; - var linkOffset = -1; - foreach (var (breakAt, mandatory) in new LineBreakEnumerator(state.Raw, UtfEnumeratorFlags.Utf8SeString)) + var link = -1; + foreach (var (breakAt, mandatory) in new LineBreakEnumerator(state.Span, UtfEnumeratorFlags.Utf8SeString)) { - var nextLinkOffset = linkOffset; + // Might have happened if custom entity was longer than the previous break unit. + if (prev > breakAt) + continue; + + var nextLink = link; for (var first = true; prev < breakAt; first = false) { var curr = breakAt; + var entity = default(SeStringReplacementEntity); - // Try to split by link payloads. - foreach (var p in new ReadOnlySeStringSpan(state.Raw.Data[prev..breakAt]).GetOffsetEnumerator()) + // Try to split by link payloads and custom entities. + foreach (var p in new ReadOnlySeStringSpan(state.Span[prev..breakAt]).GetOffsetEnumerator()) { - if (p.Payload.MacroCode == MacroCode.Link) + var break2 = false; + switch (p.Payload.Type) { - nextLinkOffset = - p.Payload.TryGetExpression(out var e) && - e.TryGetUInt(out var u) && - u == (uint)LinkMacroPayloadType.Terminator - ? -1 - : prev + p.Offset; + case ReadOnlySePayloadType.Text when state.GetEntity is { } getEntity: + foreach (var oe in UtfEnumerator.From(p.Payload.Body, UtfEnumeratorFlags.Utf8)) + { + var entityOffset = prev + p.Offset + oe.ByteOffset; + entity = getEntity(state, entityOffset); + if (!entity) + continue; - // Split only if we're not splitting at the beginning. - if (p.Offset != 0) + if (prev == entityOffset) + { + curr = entityOffset + entity.ByteLength; + } + else + { + entity = default; + curr = entityOffset; + } + + break2 = true; + break; + } + + break; + + case ReadOnlySePayloadType.Macro when + state.GetEntity is { } getEntity && + getEntity(state, prev + p.Offset) is { ByteLength: > 0 } entity1: + entity = entity1; + if (p.Offset == 0) + { + curr = prev + p.Offset + entity.ByteLength; + } + else + { + entity = default; + curr = prev + p.Offset; + } + + break2 = true; + break; + + case ReadOnlySePayloadType.Macro when p.Payload.MacroCode == MacroCode.Link: { - curr = prev + p.Offset; + nextLink = + p.Payload.TryGetExpression(out var e) && + e.TryGetUInt(out var u) && + u == (uint)LinkMacroPayloadType.Terminator + ? -1 + : prev + p.Offset; + + // Split only if we're not splitting at the beginning. + if (p.Offset != 0) + { + curr = prev + p.Offset; + break2 = true; + break; + } + + link = nextLink; + break; } - linkOffset = nextLinkOffset; + case ReadOnlySePayloadType.Invalid: + default: + break; } + + if (break2) break; } // Create a text fragment without applying wrap width limits for testing. - var fragment = state.CreateFragment(this, prev, curr, curr == breakAt && mandatory, xy, linkOffset); - var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.Params.WrapWidth; + var fragment = this.CreateFragment(state, prev, curr, curr == breakAt && mandatory, xy, link, entity); + var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.WrapWidth; - // Test if the fragment does not fit into the current line and the current line is not empty, - // if this is the first time testing the current break unit. - if (first && xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows) + // Test if the fragment does not fit into the current line and the current line is not empty. + if (xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows) { - // The break unit as a whole does not fit into the current line. Advance to the next line. - xy.X = 0; - xy.Y += state.Params.LineHeight; - w = 0; - CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true; - fragment.Offset = xy; + // Introduce break if this is the first time testing the current break unit or the current fragment + // is an entity. + if (first || entity) + { + // The break unit as a whole does not fit into the current line. Advance to the next line. + xy.X = 0; + xy.Y += state.LineHeight; + w = 0; + CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true; + fragment.Offset = xy; - // Now that the fragment is given its own line, test if it overflows again. - overflows = fragment.VisibleWidth > state.Params.WrapWidth; + // Now that the fragment is given its own line, test if it overflows again. + overflows = fragment.VisibleWidth > state.WrapWidth; + } } if (overflows) { - // Create a fragment again that fits into the given width limit. - var remainingWidth = state.Params.WrapWidth - xy.X; - fragment = state.CreateFragment(this, prev, curr, true, xy, linkOffset, remainingWidth); + // A replacement entity may not be broken down further. + if (!entity) + { + // Create a fragment again that fits into the given width limit. + var remainingWidth = state.WrapWidth - xy.X; + fragment = this.CreateFragment(state, prev, curr, true, xy, link, entity, remainingWidth); + } } else if (this.fragments.Count > 0 && xy.X != 0) { @@ -507,13 +508,13 @@ internal unsafe class SeStringRenderer : IInternalDisposableService xy.X += this.fragments[^1].AdvanceWidthWithoutSoftHyphen - this.fragments[^1].AdvanceWidth; // Adjust this fragment's offset from kerning distance. - xy.X += state.CalculateDistance(this.fragments[^1].LastRune, fragment.FirstRune); + xy.X += state.CalculateScaledDistance(this.fragments[^1].LastRune, fragment.FirstRune); fragment.Offset = xy; } // If the fragment was not broken by wrap width, update the link payload offset. if (fragment.To == curr) - linkOffset = nextLinkOffset; + link = nextLink; w = Math.Max(w, xy.X + fragment.VisibleWidth); xy.X += fragment.AdvanceWidth; @@ -523,7 +524,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService if (fragment.BreakAfter) { xy.X = w = 0; - xy.Y += state.Params.LineHeight; + xy.Y += state.LineHeight; } } } @@ -537,9 +538,9 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Byte span of the SeString fragment to draw. /// Rune that preceded this text fragment in the same line, or 0 if none. /// Byte offset of the link payload that decorates this text fragment in - /// , or -1 if none. + /// , or -1 if none. private void DrawTextFragment( - ref DrawState state, + ref SeStringDrawState state, Vector2 offset, bool displaySoftHyphen, ReadOnlySpan span, @@ -559,186 +560,47 @@ internal unsafe class SeStringRenderer : IInternalDisposableService if (!enu.MoveNext()) continue; - var payload = enu.Current.Payload; - switch (payload.MacroCode) + if (state.HandleStyleAdjustingPayloads(enu.Current.Payload)) + continue; + + if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is var icon and not None && + this.gfd.TryGetEntry((uint)icon, out var gfdEntry) && + !gfdEntry.IsEmpty) { - case MacroCode.Color: - state.Params.Color = ApplyOpacityValue( - TouchColorStack(this.colorStack, payload), - state.Params.Opacity); - continue; - case MacroCode.EdgeColor: - state.Params.EdgeColor = TouchColorStack(this.edgeColorStack, payload); - state.Params.EdgeColor = ApplyOpacityValue( - state.Params.ForceEdgeColor ? this.edgeColorStack[0] : state.Params.EdgeColor, - state.Params.EdgeOpacity); - continue; - case MacroCode.ShadowColor: - state.Params.ShadowColor = ApplyOpacityValue( - TouchColorStack(this.shadowColorStack, payload), - state.Params.Opacity); - continue; - case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): - // doesn't actually work in chat log - state.Params.Bold = u != 0; - continue; - case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): - state.Params.Italic = u != 0; - continue; - case MacroCode.Edge when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): - state.Params.Edge = u != 0; - continue; - case MacroCode.Shadow when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): - state.Params.Shadow = u != 0; - continue; - case MacroCode.ColorType: - state.Params.Color = ApplyOpacityValue( - TouchColorTypeStack(this.colorStack, this.colorTypes, payload), - state.Params.Opacity); - continue; - case MacroCode.EdgeColorType: - state.Params.EdgeColor = TouchColorTypeStack(this.edgeColorStack, this.edgeColorTypes, payload); - state.Params.EdgeColor = ApplyOpacityValue( - state.Params.ForceEdgeColor ? this.edgeColorStack[0] : state.Params.EdgeColor, - state.Params.EdgeOpacity); - continue; - case MacroCode.Icon: - case MacroCode.Icon2: - { - if (this.GetBitmapFontIconFor(span[c.ByteOffset..]) is not (var icon and not None) || - !this.gfd.TryGetEntry((uint)icon, out var gfdEntry) || - gfdEntry.IsEmpty) - continue; + var size = gfdEntry.CalculateScaledSize(state.FontSize, out var useHq); + state.SetCurrentChannel(SeStringDrawChannel.Foreground); + state.Draw( + gfdTextureSrv, + offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)), + size, + useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, + useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1, + ColorHelpers.ApplyOpacity(uint.MaxValue, state.Opacity)); + if (link != -1) + state.DrawLinkUnderline(offset + new Vector2(x, 0), size.X); - var size = state.CalculateGfdEntrySize(gfdEntry, out var useHq); - state.SetCurrentChannel(ChannelFore); - state.Draw( - offset + new Vector2(x, MathF.Round((state.Params.LineHeight - size.Y) / 2)), - gfdTextureSrv, - Vector2.Zero, - size, - Vector2.Zero, - useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, - useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1, - ApplyOpacityValue(uint.MaxValue, state.Params.Opacity)); - if (link != -1) - state.DrawLinkUnderline(offset + new Vector2(x, 0), size.X); - - width = Math.Max(width, x + size.X); - x += MathF.Round(size.X); - lastRune = default; - continue; - } - - default: - continue; + width = Math.Max(width, x + size.X); + x += MathF.Round(size.X); + lastRune = default; } + + continue; } if (!TryGetDisplayRune(c.EffectiveRune, out var rune, displaySoftHyphen)) continue; ref var g = ref state.FindGlyph(ref rune); - var dist = state.CalculateDistance(lastRune, rune); + var dist = state.CalculateScaledDistance(lastRune, rune); + var advanceWidth = MathF.Round(g.AdvanceX * state.FontSizeScale); lastRune = rune; - var dxBold = state.Params.Bold ? 2 : 1; - var dyItalic = state.Params.Italic - ? new Vector2(state.Params.FontSize - g.Y0, state.Params.FontSize - g.Y1) / 6 - : Vector2.Zero; - - if (state.Params is { Shadow: true, ShadowColor: >= 0x1000000 }) - { - state.SetCurrentChannel(ChannelShadow); - for (var dx = 0; dx < dxBold; dx++) - state.Draw(offset + new Vector2(x + dist + dx, 1), g, dyItalic, state.Params.ShadowColor); - } - - if ((state.Params.Edge || this.edgeColorStack.Count > 1) && state.Params.EdgeColor >= 0x1000000) - { - state.SetCurrentChannel(ChannelEdge); - for (var dx = -1; dx <= dxBold; dx++) - { - for (var dy = -1; dy <= 1; dy++) - { - if (dx >= 0 && dx < dxBold && dy == 0) - continue; - - state.Draw(offset + new Vector2(x + dist + dx, dy), g, dyItalic, state.Params.EdgeColor); - } - } - } - - state.SetCurrentChannel(ChannelFore); - for (var dx = 0; dx < dxBold; dx++) - state.Draw(offset + new Vector2(x + dist + dx, 0), g, dyItalic, state.Params.Color); - + state.DrawGlyph(g, offset + new Vector2(x + dist, 0)); if (link != -1) - state.DrawLinkUnderline(offset + new Vector2(x + dist, 0), g.AdvanceX); + state.DrawLinkUnderline(offset + new Vector2(x + dist, 0), advanceWidth); width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale)); - x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale); - } - - return; - - static uint TouchColorStack(List rgbaStack, ReadOnlySePayloadSpan payload) - { - if (!payload.TryGetExpression(out var expr)) - return rgbaStack[^1]; - - // Color payloads have BGRA values as its parameter. ImGui expects RGBA values. - // Opacity component is ignored. - if (expr.TryGetPlaceholderExpression(out var p) && p == (int)ExpressionType.StackColor) - { - // First item in the stack is the color we started to draw with. - if (rgbaStack.Count > 1) - rgbaStack.RemoveAt(rgbaStack.Count - 1); - return rgbaStack[^1]; - } - - if (expr.TryGetUInt(out var bgra)) - { - rgbaStack.Add(SwapRedBlue(bgra) | 0xFF000000u); - return rgbaStack[^1]; - } - - if (expr.TryGetParameterExpression(out var et, out var op) && - et == (int)ExpressionType.GlobalNumber && - op.TryGetInt(out var i) && - RaptureTextModule.Instance() is var rtm && - rtm is not null && - i > 0 && i <= rtm->TextModule.MacroDecoder.GlobalParameters.Count && - rtm->TextModule.MacroDecoder.GlobalParameters[i - 1] is { Type: TextParameterType.Integer } gp) - { - rgbaStack.Add(SwapRedBlue((uint)gp.IntValue) | 0xFF000000u); - return rgbaStack[^1]; - } - - // Fallback value. - rgbaStack.Add(0xFF000000u); - return rgbaStack[^1]; - } - - static uint TouchColorTypeStack(List rgbaStack, uint[] colorTypes, ReadOnlySePayloadSpan payload) - { - if (!payload.TryGetExpression(out var expr)) - return rgbaStack[^1]; - if (!expr.TryGetUInt(out var colorTypeIndex)) - return rgbaStack[^1]; - - if (colorTypeIndex == 0) - { - // First item in the stack is the color we started to draw with. - if (rgbaStack.Count > 1) - rgbaStack.RemoveAt(rgbaStack.Count - 1); - return rgbaStack[^1]; - } - - // Opacity component is ignored. - rgbaStack.Add((colorTypeIndex < colorTypes.Length ? colorTypes[colorTypeIndex] : 0u) | 0xFF000000u); - - return rgbaStack[^1]; + x += dist + advanceWidth; } } @@ -831,11 +693,138 @@ internal unsafe class SeStringRenderer : IInternalDisposableService return None; } + /// Creates a text fragment. + /// Draw state. + /// Starting byte offset (inclusive) in that this fragment + /// deals with. + /// Ending byte offset (exclusive) in that this fragment deals + /// with. + /// Whether to break line after this fragment. + /// Offset in pixels w.r.t. . + /// Byte offset of the link payload in that + /// decorates this text fragment. + /// Entity to display in place of this fragment. + /// Optional wrap width to stop at while creating this text fragment. Note that at least + /// one visible character needs to be there in a single text fragment, in which case it is allowed to exceed + /// the wrap width. + /// Newly created text fragment. + private TextFragment CreateFragment( + scoped in SeStringDrawState state, + int from, + int to, + bool breakAfter, + Vector2 offset, + int link, + SeStringReplacementEntity entity, + float wrapWidth = float.MaxValue) + { + if (entity) + { + return new( + from, + to, + link, + offset, + entity, + entity.Size.X, + entity.Size.X, + entity.Size.X, + false, + false, + default, + default); + } + + var x = 0f; + var w = 0f; + var visibleWidth = 0f; + var advanceWidth = 0f; + var advanceWidthWithoutSoftHyphen = 0f; + var firstDisplayRune = default(Rune?); + var lastDisplayRune = default(Rune); + var lastNonSoftHyphenRune = default(Rune); + var endsWithSoftHyphen = false; + foreach (var c in UtfEnumerator.From(state.Span[from..to], UtfEnumeratorFlags.Utf8SeString)) + { + var byteOffset = from + c.ByteOffset; + var isBreakableWhitespace = false; + var effectiveRune = c.EffectiveRune; + Rune displayRune; + if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } && + this.GetBitmapFontIconFor(state.Span[byteOffset..]) is var icon and not None && + this.gfd.TryGetEntry((uint)icon, out var gfdEntry) && + !gfdEntry.IsEmpty) + { + // This is an icon payload. + var size = gfdEntry.CalculateScaledSize(state.FontSize, out _); + w = Math.Max(w, x + size.X); + x += MathF.Round(size.X); + displayRune = default; + } + else if (TryGetDisplayRune(effectiveRune, out displayRune)) + { + // This is a printable character, or a standard whitespace character. + ref var g = ref state.FindGlyph(ref displayRune); + var dist = state.CalculateScaledDistance(lastDisplayRune, displayRune); + w = Math.Max(w, x + dist + MathF.Round(g.X1 * state.FontSizeScale)); + x += dist + MathF.Round(g.AdvanceX * state.FontSizeScale); + + isBreakableWhitespace = + Rune.IsWhiteSpace(displayRune) && + UnicodeData.LineBreak[displayRune.Value] is not UnicodeLineBreakClass.GL; + } + else + { + continue; + } + + if (isBreakableWhitespace) + { + advanceWidth = x; + } + else + { + if (firstDisplayRune is not null && w > wrapWidth && effectiveRune.Value != SoftHyphen) + { + to = byteOffset; + break; + } + + advanceWidth = x; + visibleWidth = w; + } + + firstDisplayRune ??= displayRune; + lastDisplayRune = displayRune; + endsWithSoftHyphen = effectiveRune.Value == SoftHyphen; + if (!endsWithSoftHyphen) + { + advanceWidthWithoutSoftHyphen = x; + lastNonSoftHyphenRune = displayRune; + } + } + + return new( + from, + to, + link, + offset, + entity, + visibleWidth, + advanceWidth, + advanceWidthWithoutSoftHyphen, + breakAfter, + endsWithSoftHyphen, + firstDisplayRune ?? default, + lastNonSoftHyphenRune); + } + /// Represents a text fragment in a SeString span. /// Starting byte offset (inclusive) in a SeString. /// Ending byte offset (exclusive) in a SeString. /// Byte offset of the link that decorates this text fragment, or -1 if none. /// Offset in pixels w.r.t. . + /// Replacement entity, if any. /// Visible width of this text fragment. This is the width required to draw everything /// without clipping. /// Advance width of this text fragment. This is the width required to add to the cursor @@ -852,6 +841,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService int To, int Link, Vector2 Offset, + SeStringReplacementEntity Entity, float VisibleWidth, float AdvanceWidth, float AdvanceWidthWithoutSoftHyphen, @@ -862,292 +852,4 @@ internal unsafe class SeStringRenderer : IInternalDisposableService { public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter; } - - /// Represents a temporary state required for drawing. - private ref struct DrawState( - ReadOnlySeStringSpan raw, - SeStringDrawParams.Resolved @params, - ImDrawListSplitter* splitter) - { - /// Raw SeString span. - public readonly ReadOnlySeStringSpan Raw = raw; - - /// Multiplier value for glyph metrics, so that it scales to . - /// - public readonly float FontSizeScale = @params.FontSize / @params.Font->FontSize; - - /// Value obtained from . - public readonly Vector2 ScreenOffset = @params.ScreenOffset; - - /// Splitter to split . - public readonly ImDrawListSplitter* Splitter = splitter; - - /// Resolved draw parameters from the caller. - public SeStringDrawParams.Resolved Params = @params; - - /// Calculates the size in pixels of a GFD entry when drawn. - /// GFD entry to determine the size. - /// Whether to draw the HQ texture. - /// Determined size of the GFD entry when drawn. - public readonly Vector2 CalculateGfdEntrySize(scoped in GfdFile.GfdEntry gfdEntry, out bool useHq) - { - useHq = this.Params.FontSize > 20; - var targetHeight = useHq ? this.Params.FontSize : 20; - return new(gfdEntry.Width * (targetHeight / gfdEntry.Height), targetHeight); - } - - /// Sets the current channel in the ImGui draw list splitter. - /// Channel to switch to. - public readonly void SetCurrentChannel(int channelIndex) => - ImGuiNative.ImDrawListSplitter_SetCurrentChannel( - this.Splitter, - this.Params.DrawList, - channelIndex); - - /// Draws a single glyph. - /// Offset of the glyph in pixels w.r.t. - /// . - /// Glyph to draw. - /// Transformation for that will push top and bottom pixels to - /// apply faux italicization. - /// Color of the glyph. - public readonly void Draw( - Vector2 offset, - scoped in ImGuiHelpers.ImFontGlyphReal g, - Vector2 dyItalic, - uint color) => - this.Draw( - offset + new Vector2( - 0, - MathF.Round(((this.Params.LineHeight - this.Params.Font->FontSize) * this.FontSizeScale) / 2f)), - this.Params.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID, - g.XY0 * this.FontSizeScale, - g.XY1 * this.FontSizeScale, - dyItalic * this.FontSizeScale, - g.UV0, - g.UV1, - color); - - /// Draws a single glyph. - /// Offset of the glyph in pixels w.r.t. - /// . - /// ImGui texture ID to draw from. - /// Left top corner of the glyph w.r.t. its glyph origin in the target draw list. - /// Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list. - /// Transformation for and that will push - /// top and bottom pixels to apply faux italicization. - /// Left top corner of the glyph w.r.t. its glyph origin in the source texture. - /// Right bottom corner of the glyph w.r.t. its glyph origin in the source texture. - /// Color of the glyph. - public readonly void Draw( - Vector2 offset, - nint igTextureId, - Vector2 xy0, - Vector2 xy1, - Vector2 dyItalic, - Vector2 uv0, - Vector2 uv1, - uint color = uint.MaxValue) - { - offset += this.ScreenOffset; - ImGuiNative.ImDrawList_AddImageQuad( - this.Params.DrawList, - igTextureId, - offset + new Vector2(xy0.X + dyItalic.X, xy0.Y), - offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y), - offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y), - offset + new Vector2(xy1.X + dyItalic.X, xy0.Y), - new(uv0.X, uv0.Y), - new(uv0.X, uv1.Y), - new(uv1.X, uv1.Y), - new(uv1.X, uv0.Y), - color); - } - - /// Draws an underline, for links. - /// Offset of the glyph in pixels w.r.t. - /// . - /// Advance width of the glyph. - public readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth) - { - if (this.Params.LinkUnderlineThickness < 1f) - return; - - var dy = (this.Params.LinkUnderlineThickness - 1) / 2f; - dy += MathF.Round( - (((this.Params.LineHeight - this.Params.FontSize) / 2) + this.Params.Font->Ascent) * - this.FontSizeScale); - this.SetCurrentChannel(ChannelLinkUnderline); - ImGuiNative.ImDrawList_AddLine( - this.Params.DrawList, - this.ScreenOffset + offset + new Vector2(0, dy), - this.ScreenOffset + offset + new Vector2(advanceWidth, dy), - this.Params.Color, - this.Params.LinkUnderlineThickness); - - if (this.Params is { Shadow: true, ShadowColor: >= 0x1000000 }) - { - this.SetCurrentChannel(ChannelShadow); - ImGuiNative.ImDrawList_AddLine( - this.Params.DrawList, - this.ScreenOffset + offset + new Vector2(0, dy + 1), - this.ScreenOffset + offset + new Vector2(advanceWidth, dy + 1), - this.Params.ShadowColor, - this.Params.LinkUnderlineThickness); - } - } - - /// Creates a text fragment. - /// Associated renderer. - /// Starting byte offset (inclusive) in that this fragment deals with. - /// - /// Ending byte offset (exclusive) in that this fragment deals with. - /// Whether to break line after this fragment. - /// Offset in pixels w.r.t. . - /// Byte offset of the link payload in that decorates this - /// text fragment. - /// Optional wrap width to stop at while creating this text fragment. Note that at least - /// one visible character needs to be there in a single text fragment, in which case it is allowed to exceed - /// the wrap width. - /// Newly created text fragment. - public readonly TextFragment CreateFragment( - SeStringRenderer renderer, - int from, - int to, - bool breakAfter, - Vector2 offset, - int activeLinkOffset, - float wrapWidth = float.MaxValue) - { - var x = 0f; - var w = 0f; - var visibleWidth = 0f; - var advanceWidth = 0f; - var advanceWidthWithoutSoftHyphen = 0f; - var firstDisplayRune = default(Rune?); - var lastDisplayRune = default(Rune); - var lastNonSoftHyphenRune = default(Rune); - var endsWithSoftHyphen = false; - foreach (var c in UtfEnumerator.From(this.Raw.Data[from..to], UtfEnumeratorFlags.Utf8SeString)) - { - var byteOffset = from + c.ByteOffset; - var isBreakableWhitespace = false; - var effectiveRune = c.EffectiveRune; - Rune displayRune; - if (c is { IsSeStringPayload: true, MacroCode: MacroCode.Icon or MacroCode.Icon2 } && - renderer.GetBitmapFontIconFor(this.Raw.Data[byteOffset..]) is var icon and not None && - renderer.gfd.TryGetEntry((uint)icon, out var gfdEntry) && - !gfdEntry.IsEmpty) - { - // This is an icon payload. - var size = this.CalculateGfdEntrySize(gfdEntry, out _); - w = Math.Max(w, x + size.X); - x += MathF.Round(size.X); - displayRune = default; - } - else if (TryGetDisplayRune(effectiveRune, out displayRune)) - { - // This is a printable character, or a standard whitespace character. - ref var g = ref this.FindGlyph(ref displayRune); - var dist = this.CalculateDistance(lastDisplayRune, displayRune); - w = Math.Max(w, x + ((dist + g.X1) * this.FontSizeScale)); - x += MathF.Round((dist + g.AdvanceX) * this.FontSizeScale); - - isBreakableWhitespace = - Rune.IsWhiteSpace(displayRune) && - UnicodeData.LineBreak[displayRune.Value] is not UnicodeLineBreakClass.GL; - } - else - { - continue; - } - - if (isBreakableWhitespace) - { - advanceWidth = x; - } - else - { - if (firstDisplayRune is not null && w > wrapWidth && effectiveRune.Value != SoftHyphen) - { - to = byteOffset; - break; - } - - advanceWidth = x; - visibleWidth = w; - } - - firstDisplayRune ??= displayRune; - lastDisplayRune = displayRune; - endsWithSoftHyphen = effectiveRune.Value == SoftHyphen; - if (!endsWithSoftHyphen) - { - advanceWidthWithoutSoftHyphen = x; - lastNonSoftHyphenRune = displayRune; - } - } - - return new( - from, - to, - activeLinkOffset, - offset, - visibleWidth, - advanceWidth, - advanceWidthWithoutSoftHyphen, - breakAfter, - endsWithSoftHyphen, - firstDisplayRune ?? default, - lastNonSoftHyphenRune); - } - - /// Gets the glyph corresponding to the given codepoint. - /// An instance of that represents a character to display. - /// Corresponding glyph, or glyph of a fallback character specified from - /// . - public readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune) - { - var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue - ? ImGuiNative.ImFont_FindGlyph(this.Params.Font, (ushort)rune.Value) - : this.Params.Font->FallbackGlyph; - return ref *(ImGuiHelpers.ImFontGlyphReal*)p; - } - - /// Gets the glyph corresponding to the given codepoint. - /// An instance of that represents a character to display, that will be - /// changed on return to the rune corresponding to the fallback glyph if a glyph not corresponding to the - /// requested glyph is being returned. - /// Corresponding glyph, or glyph of a fallback character specified from - /// . - public readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(ref Rune rune) - { - ref var glyph = ref this.FindGlyph(rune); - if (rune.Value != glyph.Codepoint && !Rune.TryCreate(glyph.Codepoint, out rune)) - rune = Rune.ReplacementChar; - return ref glyph; - } - - /// Gets the kerning adjustment between two glyphs in a succession corresponding to the given runes. - /// - /// Rune representing the glyph on the left side of a pair. - /// Rune representing the glyph on the right side of a pair. - /// Distance adjustment in pixels, scaled to the size specified from - /// , and rounded. - public readonly float CalculateDistance(Rune left, Rune right) - { - // Kerning distance entries are ignored if NUL, U+FFFF(invalid Unicode character), or characters outside - // the basic multilingual plane(BMP) is involved. - if (left.Value is <= 0 or >= char.MaxValue) - return 0; - if (right.Value is <= 0 or >= char.MaxValue) - return 0; - - return MathF.Round( - ImGuiNative.ImFont_GetDistanceAdjustmentForPair( - this.Params.Font, - (ushort)left.Value, - (ushort)right.Value) * this.FontSizeScale); - } - } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs index 9113ef703..fa994bcd2 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs @@ -12,9 +12,11 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; internal ref struct LineBreakEnumerator { private readonly UtfEnumeratorFlags enumeratorFlags; - private readonly int dataLength; private UtfEnumerator enumerator; + private int dataLength; + private int currentByteOffsetDelta; + private Entry class1; private Entry class2; @@ -24,8 +26,6 @@ internal ref struct LineBreakEnumerator private int consecutiveRegionalIndicators; - private bool finished; - /// Initializes a new instance of the struct. /// UTF-N byte sequence. /// Flags to pass to sub-enumerator. @@ -58,11 +58,25 @@ internal ref struct LineBreakEnumerator /// public (int ByteOffset, bool Mandatory) Current { get; private set; } + /// Gets a value indicating whether the end of the underlying span has been reached. + public bool Finished { get; private set; } + + /// Resumes enumeration with the given data. + /// The data. + /// Offset to add to .ByteOffset. + public void ResumeWith(ReadOnlySpan data, int offsetDelta) + { + this.enumerator = UtfEnumerator.From(data, this.enumeratorFlags); + this.dataLength = data.Length; + this.currentByteOffsetDelta = offsetDelta; + this.Finished = false; + } + /// [SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement", Justification = "No")] public bool MoveNext() { - if (this.finished) + if (this.Finished) return false; while (this.enumerator.MoveNext()) @@ -77,10 +91,10 @@ internal ref struct LineBreakEnumerator switch (this.HandleCharacter(effectiveInt)) { case LineBreakMode.Mandatory: - this.Current = (this.enumerator.Current.ByteOffset, true); + this.Current = (this.enumerator.Current.ByteOffset + this.currentByteOffsetDelta, true); return true; case LineBreakMode.Optional: - this.Current = (this.enumerator.Current.ByteOffset, false); + this.Current = (this.enumerator.Current.ByteOffset + this.currentByteOffsetDelta, false); return true; case LineBreakMode.Prohibited: default: @@ -90,8 +104,8 @@ internal ref struct LineBreakEnumerator // Start and end of text: // LB3 Always break at the end of text. - this.Current = (this.dataLength, true); - this.finished = true; + this.Current = (this.dataLength + this.currentByteOffsetDelta, true); + this.Finished = true; return true; } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs new file mode 100644 index 000000000..d34a9ee5b --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawChannel.cs @@ -0,0 +1,42 @@ +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Predefined channels for drawing onto, for out-of-order drawing. +// Notes: values must be consecutively increasing, starting from 0. Higher values has higher priority. +public enum SeStringDrawChannel +{ + /// Next draw operation on the draw list will be put below . + BelowBackground, + + /// Next draw operation on the draw list will be put onto the background channel. + Background, + + /// Next draw operation on the draw list will be put above . + AboveBackground, + + /// Next draw operation on the draw list will be put below . + BelowShadow, + + /// Next draw operation on the draw list will be put onto the shadow channel. + Shadow, + + /// Next draw operation on the draw list will be put above . + AboveShadow, + + /// Next draw operation on the draw list will be put below . + BelowEdge, + + /// Next draw operation on the draw list will be put onto the edge channel. + Edge, + + /// Next draw operation on the draw list will be put above . + AboveEdge, + + /// Next draw operation on the draw list will be put below . + BelowForeground, + + /// Next draw operation on the draw list will be put onto the foreground channel. + Foreground, + + /// Next draw operation on the draw list will be put above . + AboveForeground, +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs index 543f4c07a..cdd5e1db6 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs @@ -1,6 +1,4 @@ -using System.Diagnostics.CodeAnalysis; using System.Numerics; -using System.Runtime.InteropServices; using ImGuiNET; @@ -18,14 +16,18 @@ public record struct SeStringDrawParams /// public ImDrawListPtr? TargetDrawList { get; set; } - /// Gets or sets the font to use. - /// Font to use, or null to use (the default). - public ImFontPtr? Font { get; set; } + /// Gets or sets the function to be called on every codepoint and payload for the purpose of offering + /// chances to draw something else instead of glyphs or SeString payload entities. + public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; set; } /// Gets or sets the screen offset of the left top corner. /// Screen offset to draw at, or null to use . public Vector2? ScreenOffset { get; set; } + /// Gets or sets the font to use. + /// Font to use, or null to use (the default). + public ImFontPtr? Font { get; set; } + /// Gets or sets the font size. /// Font size in pixels, or 0 to use the current ImGui font size . /// @@ -86,83 +88,23 @@ public record struct SeStringDrawParams public bool Italic { get; set; } /// Gets or sets a value indicating whether the text is rendered with edge. + /// If an edge color is pushed with or + /// , it will be drawn regardless. Set to + /// true and set to 0 to fully disable edge. public bool Edge { get; set; } /// Gets or sets a value indicating whether the text is rendered with shadow. public bool Shadow { get; set; } - private readonly unsafe ImFont* EffectiveFont => + /// Gets the effective font. + internal readonly unsafe ImFont* EffectiveFont => (this.Font ?? ImGui.GetFont()) is var f && f.NativePtr is not null ? f.NativePtr : throw new ArgumentException("Specified font is empty."); - private readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f); + /// Gets the effective line height in pixels. + internal readonly float EffectiveLineHeight => (this.FontSize ?? ImGui.GetFontSize()) * (this.LineHeight ?? 1f); - private readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha; - - /// Calculated values from using ImGui styles. - [SuppressMessage( - "StyleCop.CSharp.OrderingRules", - "SA1214:Readonly fields should appear before non-readonly fields", - Justification = "Matching the above order.")] - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct Resolved(in SeStringDrawParams ssdp) - { - /// - public readonly ImDrawList* DrawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList(); - - /// - public readonly ImFont* Font = ssdp.EffectiveFont; - - /// - public readonly Vector2 ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos(); - - /// - public readonly float FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); - - /// - public readonly float LineHeight = MathF.Round(ssdp.EffectiveLineHeight); - - /// - public readonly float WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X; - - /// - public readonly float LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; - - /// - public readonly float Opacity = ssdp.EffectiveOpacity; - - /// - public readonly float EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity; - - /// - public uint Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text); - - /// - public uint EdgeColor = ssdp.EdgeColor ?? 0xFF000000; - - /// - public uint ShadowColor = ssdp.ShadowColor ?? 0xFF000000; - - /// - public readonly uint LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); - - /// - public readonly uint LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); - - /// - public readonly bool ForceEdgeColor = ssdp.ForceEdgeColor; - - /// - public bool Bold = ssdp.Bold; - - /// - public bool Italic = ssdp.Italic; - - /// - public bool Edge = ssdp.Edge; - - /// - public bool Shadow = ssdp.Shadow; - } + /// Gets the effective opacity. + internal readonly float EffectiveOpacity => this.Opacity ?? ImGui.GetStyle().Alpha; } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs new file mode 100644 index 000000000..d8348e5f2 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs @@ -0,0 +1,400 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Utility; + +using ImGuiNET; + +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Calculated values from using ImGui styles. +[StructLayout(LayoutKind.Sequential)] +public unsafe ref struct SeStringDrawState +{ + private static readonly int ChannelCount = Enum.GetValues().Length; + + private readonly ImDrawList* drawList; + private readonly SeStringColorStackSet colorStackSet; + private readonly ImDrawListSplitter* splitter; + + /// Initializes a new instance of the struct. + /// Raw SeString byte span. + /// Instance of to initialize from. + /// Instance of to use. + /// Instance of ImGui Splitter to use. + internal SeStringDrawState( + ReadOnlySpan span, + scoped in SeStringDrawParams ssdp, + SeStringColorStackSet colorStackSet, + ImDrawListSplitter* splitter) + { + this.colorStackSet = colorStackSet; + this.splitter = splitter; + this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList(); + this.Span = span; + this.GetEntity = ssdp.GetEntity; + this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos(); + this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y)); + this.Font = ssdp.EffectiveFont; + this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); + this.FontSizeScale = this.FontSize / this.Font->FontSize; + this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight); + this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X; + this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; + this.Opacity = ssdp.EffectiveOpacity; + this.EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity; + this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text); + this.EdgeColor = ssdp.EdgeColor ?? 0xFF000000; + this.ShadowColor = ssdp.ShadowColor ?? 0xFF000000; + this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); + this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); + this.ForceEdgeColor = ssdp.ForceEdgeColor; + this.Bold = ssdp.Bold; + this.Italic = ssdp.Italic; + this.Edge = ssdp.Edge; + this.Shadow = ssdp.Shadow; + } + + /// + public readonly ImDrawListPtr DrawList => new(this.drawList); + + /// Gets the raw SeString byte span. + public ReadOnlySpan Span { get; } + + /// + public SeStringReplacementEntity.GetEntityDelegate? GetEntity { get; } + + /// + public Vector2 ScreenOffset { get; } + + /// + public ImFont* Font { get; } + + /// + public float FontSize { get; } + + /// Gets the multiplier value for glyph metrics, so that it scales to . + /// Multiplied to , + /// , and distance values from + /// . + public float FontSizeScale { get; } + + /// + public float LineHeight { get; } + + /// + public float WrapWidth { get; } + + /// + public float LinkUnderlineThickness { get; } + + /// + public float Opacity { get; } + + /// + public float EdgeOpacity { get; } + + /// + public uint Color { get; set; } + + /// + public uint EdgeColor { get; set; } + + /// + public uint ShadowColor { get; set; } + + /// + public uint LinkHoverBackColor { get; } + + /// + public uint LinkActiveBackColor { get; } + + /// + public bool ForceEdgeColor { get; } + + /// + public bool Bold { get; set; } + + /// + public bool Italic { get; set; } + + /// + public bool Edge { get; set; } + + /// + public bool Shadow { get; set; } + + /// Gets a value indicating whether the edge should be drawn. + public readonly bool ShouldDrawEdge => + (this.Edge || this.colorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000; + + /// Gets a value indicating whether the edge should be drawn. + public readonly bool ShouldDrawShadow => this is { Shadow: true, ShadowColor: >= 0x1000000 }; + + /// Gets a value indicating whether the edge should be drawn. + public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 }; + + /// Sets the current channel in the ImGui draw list splitter. + /// Channel to switch to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetCurrentChannel(SeStringDrawChannel channelIndex) => + ImGuiNative.ImDrawListSplitter_SetCurrentChannel(this.splitter, this.drawList, (int)channelIndex); + + /// Draws a single texture. + /// ImGui texture ID to draw from. + /// Offset of the glyph in pixels w.r.t. . + /// Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Left top corner of the glyph w.r.t. its glyph origin in the source texture. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the source texture. + /// Color of the glyph in RGBA. + public readonly void Draw( + nint igTextureId, + Vector2 offset, + Vector2 size, + Vector2 uv0, + Vector2 uv1, + uint color = uint.MaxValue) + { + offset += this.ScreenOffset; + this.DrawList.AddImageQuad( + igTextureId, + offset, + offset + size with { X = 0 }, + offset + size, + offset + size with { Y = 0 }, + new(uv0.X, uv0.Y), + new(uv0.X, uv1.Y), + new(uv1.X, uv1.Y), + new(uv1.X, uv0.Y), + color); + } + + /// Draws a single texture. + /// ImGui texture ID to draw from. + /// Offset of the glyph in pixels w.r.t. . + /// Left top corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the target draw list. + /// Left top corner of the glyph w.r.t. its glyph origin in the source texture. + /// Right bottom corner of the glyph w.r.t. its glyph origin in the source texture. + /// Color of the glyph in RGBA. + /// Transformation for and that will push + /// top and bottom pixels to apply faux italicization by and + /// respectively. + public readonly void Draw( + nint igTextureId, + Vector2 offset, + Vector2 xy0, + Vector2 xy1, + Vector2 uv0, + Vector2 uv1, + uint color = uint.MaxValue, + Vector2 dyItalic = default) + { + offset += this.ScreenOffset; + this.DrawList.AddImageQuad( + igTextureId, + offset + new Vector2(xy0.X + dyItalic.X, xy0.Y), + offset + new Vector2(xy0.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.Y, xy1.Y), + offset + new Vector2(xy1.X + dyItalic.X, xy0.Y), + new(uv0.X, uv0.Y), + new(uv0.X, uv1.Y), + new(uv1.X, uv1.Y), + new(uv1.X, uv0.Y), + color); + } + + /// Draws a single glyph using current styling configurations. + /// Glyph to draw. + /// Offset of the glyph in pixels w.r.t. . + internal readonly void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset) + { + var texId = this.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID; + var xy0 = new Vector2( + MathF.Round(g.X0 * this.FontSizeScale), + MathF.Round(g.Y0 * this.FontSizeScale)); + var xy1 = new Vector2( + MathF.Round(g.X1 * this.FontSizeScale), + MathF.Round(g.Y1 * this.FontSizeScale)); + var dxBold = this.Bold ? 2 : 1; + var dyItalic = this.Italic + ? new Vector2(this.FontSize - xy0.Y, this.FontSize - xy1.Y) / 6 + : Vector2.Zero; + // Note: dyItalic values can be non-rounded; the glyph will be rendered sheared anyway. + + offset.Y += MathF.Round((this.LineHeight - this.FontSize) / 2f); + + if (this.ShouldDrawShadow) + { + this.SetCurrentChannel(SeStringDrawChannel.Shadow); + for (var i = 0; i < dxBold; i++) + this.Draw(texId, offset + new Vector2(i, 1), xy0, xy1, g.UV0, g.UV1, this.ShadowColor, dyItalic); + } + + if (this.ShouldDrawEdge) + { + this.SetCurrentChannel(SeStringDrawChannel.Edge); + + // Top & Bottom + for (var i = -1; i <= dxBold; i++) + { + this.Draw(texId, offset + new Vector2(i, -1), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + this.Draw(texId, offset + new Vector2(i, 1), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + } + + // Left & Right + this.Draw(texId, offset + new Vector2(-1, 0), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + this.Draw(texId, offset + new Vector2(1, 0), xy0, xy1, g.UV0, g.UV1, this.EdgeColor, dyItalic); + } + + if (this.ShouldDrawForeground) + { + this.SetCurrentChannel(SeStringDrawChannel.Foreground); + for (var i = 0; i < dxBold; i++) + this.Draw(texId, offset + new Vector2(i, 0), xy0, xy1, g.UV0, g.UV1, this.Color, dyItalic); + } + } + + /// Draws an underline, for links. + /// Offset of the glyph in pixels w.r.t. + /// . + /// Advance width of the glyph. + internal readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth) + { + if (this.LinkUnderlineThickness < 1f) + return; + + offset += this.ScreenOffset; + offset.Y += (this.LinkUnderlineThickness - 1) / 2f; + offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale)); + + this.SetCurrentChannel(SeStringDrawChannel.Foreground); + this.DrawList.AddLine( + offset, + offset + new Vector2(advanceWidth, 0), + this.Color, + this.LinkUnderlineThickness); + + if (this is { Shadow: true, ShadowColor: >= 0x1000000 }) + { + this.SetCurrentChannel(SeStringDrawChannel.Shadow); + this.DrawList.AddLine( + offset + new Vector2(0, 1), + offset + new Vector2(advanceWidth, 1), + this.ShadowColor, + this.LinkUnderlineThickness); + } + } + + /// Gets the glyph corresponding to the given codepoint. + /// An instance of that represents a character to display. + /// Corresponding glyph, or glyph of a fallback character specified from + /// . + internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune) + { + var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue + ? ImGuiNative.ImFont_FindGlyph(this.Font, (ushort)rune.Value) + : this.Font->FallbackGlyph; + return ref *(ImGuiHelpers.ImFontGlyphReal*)p; + } + + /// Gets the glyph corresponding to the given codepoint. + /// An instance of that represents a character to display, that will be + /// changed on return to the rune corresponding to the fallback glyph if a glyph not corresponding to the + /// requested glyph is being returned. + /// Corresponding glyph, or glyph of a fallback character specified from + /// . + internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(ref Rune rune) + { + ref var glyph = ref this.FindGlyph(rune); + if (rune.Value != glyph.Codepoint && !Rune.TryCreate(glyph.Codepoint, out rune)) + rune = Rune.ReplacementChar; + return ref glyph; + } + + /// Gets the kerning adjustment between two glyphs in a succession corresponding to the given runes. + /// + /// Rune representing the glyph on the left side of a pair. + /// Rune representing the glyph on the right side of a pair. + /// Distance adjustment in pixels, scaled to the size specified from + /// , and rounded. + internal readonly float CalculateScaledDistance(Rune left, Rune right) + { + // Kerning distance entries are ignored if NUL, U+FFFF(invalid Unicode character), or characters outside + // the basic multilingual plane(BMP) is involved. + if (left.Value is <= 0 or >= char.MaxValue) + return 0; + if (right.Value is <= 0 or >= char.MaxValue) + return 0; + + return MathF.Round( + ImGuiNative.ImFont_GetDistanceAdjustmentForPair( + this.Font, + (ushort)left.Value, + (ushort)right.Value) * this.FontSizeScale); + } + + /// Handles style adjusting payloads. + /// Payload to handle. + /// true if the payload was handled. + internal bool HandleStyleAdjustingPayloads(ReadOnlySePayloadSpan payload) + { + switch (payload.MacroCode) + { + case MacroCode.Color: + this.colorStackSet.HandleColorPayload(ref this, payload); + return true; + + case MacroCode.EdgeColor: + this.colorStackSet.HandleEdgeColorPayload(ref this, payload); + return true; + + case MacroCode.ShadowColor: + this.colorStackSet.HandleShadowColorPayload(ref this, payload); + return true; + + case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + // doesn't actually work in chat log + this.Bold = u != 0; + return true; + + case MacroCode.Italic when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.Italic = u != 0; + return true; + + case MacroCode.Edge when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.Edge = u != 0; + return true; + + case MacroCode.Shadow when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): + this.Shadow = u != 0; + return true; + + case MacroCode.ColorType: + this.colorStackSet.HandleColorTypePayload(ref this, payload); + return true; + + case MacroCode.EdgeColorType: + this.colorStackSet.HandleEdgeColorTypePayload(ref this, payload); + return true; + + default: + return false; + } + } + + /// Splits the draw list. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly void SplitDrawList() => + ImGuiNative.ImDrawListSplitter_Split(this.splitter, this.drawList, ChannelCount); + + /// Merges the draw list. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly void MergeDrawList() => ImGuiNative.ImDrawListSplitter_Merge(this.splitter, this.drawList); +} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs new file mode 100644 index 000000000..b14e12073 --- /dev/null +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringReplacementEntity.cs @@ -0,0 +1,48 @@ +using System.Numerics; + +namespace Dalamud.Interface.ImGuiSeStringRenderer; + +/// Replacement entity to draw instead while rendering a SeString. +public readonly record struct SeStringReplacementEntity +{ + /// Initializes a new instance of the struct. + /// Number of bytes taken by this entity. Must be at least 0. If 0, then the entity + /// is considered as empty. + /// Size of this entity in pixels. Components must be non-negative. + /// Draw callback. + public SeStringReplacementEntity(int byteLength, Vector2 size, DrawDelegate draw) + { + ArgumentOutOfRangeException.ThrowIfNegative(byteLength); + ArgumentOutOfRangeException.ThrowIfNegative(size.X, nameof(size)); + ArgumentOutOfRangeException.ThrowIfNegative(size.Y, nameof(size)); + ArgumentNullException.ThrowIfNull(draw); + this.ByteLength = byteLength; + this.Size = size; + this.Draw = draw; + } + + /// Gets the replacement entity. + /// Draw state. + /// Byte offset in . + /// Replacement entity definition, or default if none. + public delegate SeStringReplacementEntity GetEntityDelegate(scoped in SeStringDrawState state, int byteOffset); + + /// Draws the replacement entity. + /// Draw state. + /// Byte offset in . + /// Relative offset in pixels w.r.t. . + public delegate void DrawDelegate(scoped in SeStringDrawState state, int byteOffset, Vector2 offset); + + /// Gets the number of bytes taken by this entity. + public int ByteLength { get; init; } + + /// Gets the size of this entity in pixels. + public Vector2 Size { get; init; } + + /// Gets the Draw callback. + public DrawDelegate Draw { get; init; } + + /// Gets a value indicating whether this entity is empty. + /// Instance of to test. + public static implicit operator bool(in SeStringReplacementEntity e) => e.ByteLength != 0; +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index 62c52a17f..d0dffce75 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Numerics; using System.Text; using Dalamud.Data; @@ -6,7 +7,9 @@ using Dalamud.Game.Gui; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; +using Dalamud.Storage.Assets; using Dalamud.Utility; using ImGuiNET; @@ -32,6 +35,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; + private bool useEntity; /// public string DisplayName { get; init; } = "SeStringRenderer Test"; @@ -45,12 +49,12 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget /// public void Load() { - this.style = default; + this.style = new() { GetEntity = this.GetEntity }; this.addons = null; this.uicolor = null; this.logkind = null; this.testString = string.Empty; - this.interactable = true; + this.interactable = this.useEntity = true; this.Ready = true; } @@ -85,11 +89,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget var t3 = this.style.LineHeight ?? 1f; if (ImGui.DragFloat("Line Height", ref t3, 0.01f, 0.4f, 3f, "%.02f")) this.style.LineHeight = t3; - + t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha; if (ImGui.DragFloat("Opacity", ref t3, 0.005f, 0f, 1f, "%.02f")) this.style.Opacity = t3; - + t3 = this.style.EdgeStrength ?? 0.25f; if (ImGui.DragFloat("Edge Strength", ref t3, 0.005f, 0f, 1f, "%.02f")) this.style.EdgeStrength = t3; @@ -123,11 +127,15 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Checkbox("Word Wrap", ref t)) this.style.WrapWidth = t ? null : float.PositiveInfinity; - ImGui.SameLine(); t = this.interactable; if (ImGui.Checkbox("Interactable", ref t)) this.interactable = t; + ImGui.SameLine(); + t = this.useEntity; + if (ImGui.Checkbox("Use Entity Replacements", ref t)) + this.useEntity = t; + if (ImGui.CollapsingHeader("UIColor Preview")) { if (this.uicolor is null) @@ -267,7 +275,22 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget ImGuiHelpers.ScaledDummy(3); ImGuiHelpers.CompileSeStringWrapped( - "· For ease of testing, line breaks are automatically replaced to \\
.", + "Optional features implemented for the following test input:
" + + "· line breaks are automatically replaced to \\
.
" + + "· Dalamud will display Dalamud.
" + + "· White will display White.
" + + "· DefaultIcon will display DefaultIcon.
" + + "· DisabledIcon will display DisabledIcon.
" + + "· OutdatedInstallableIcon will display OutdatedInstallableIcon.
" + + "· TroubleIcon will display TroubleIcon.
" + + "· DevPluginIcon will display DevPluginIcon.
" + + "· UpdateIcon will display UpdateIcon.
" + + "· InstalledIcon will display InstalledIcon.
" + + "· ThirdIcon will display ThirdIcon.
" + + "· ThirdInstalledIcon will display ThirdInstalledIcon.
" + + "· ChangelogApiBumpIcon will display ChangelogApiBumpIcon.
" + + "· icon(5) will display icon(5). This is different from \\(5)>.
" + + "· tex(ui/loadingimage/-nowloading_base25_hr1.tex) will display tex(ui/loadingimage/-nowloading_base25_hr1.tex).", this.style); ImGuiHelpers.ScaledDummy(3); @@ -302,10 +325,14 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (this.interactable) { if (ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style, new("this is an ImGui id")) is - { InteractedPayload: { } payload, InteractedPayloadOffset: var offset, InteractedPayloadEnvelope: var envelope } rr) + { + InteractedPayload: { } payload, InteractedPayloadOffset: var offset, + InteractedPayloadEnvelope: var envelope, + Clicked: var clicked + }) { ImGui.TextUnformatted($"Hovered[{offset}]: {new ReadOnlySeStringSpan(envelope).ToString()}; {payload}"); - if (rr.Clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp) + if (clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp) Util.OpenLink(dlp.ExtraString); } } @@ -314,4 +341,138 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style); } } + + private SeStringReplacementEntity GetEntity(scoped in SeStringDrawState state, int byteOffset) + { + if (!this.useEntity) + return default; + if (state.Span[byteOffset..].StartsWith("Dalamud"u8)) + return new(7, new(state.FontSize, state.FontSize), DrawDalamud); + if (state.Span[byteOffset..].StartsWith("White"u8)) + return new(5, new(state.FontSize, state.FontSize), DrawWhite); + if (state.Span[byteOffset..].StartsWith("DefaultIcon"u8)) + return new(11, new(state.FontSize, state.FontSize), DrawDefaultIcon); + if (state.Span[byteOffset..].StartsWith("DisabledIcon"u8)) + return new(12, new(state.FontSize, state.FontSize), DrawDisabledIcon); + if (state.Span[byteOffset..].StartsWith("OutdatedInstallableIcon"u8)) + return new(23, new(state.FontSize, state.FontSize), DrawOutdatedInstallableIcon); + if (state.Span[byteOffset..].StartsWith("TroubleIcon"u8)) + return new(11, new(state.FontSize, state.FontSize), DrawTroubleIcon); + if (state.Span[byteOffset..].StartsWith("DevPluginIcon"u8)) + return new(13, new(state.FontSize, state.FontSize), DrawDevPluginIcon); + if (state.Span[byteOffset..].StartsWith("UpdateIcon"u8)) + return new(10, new(state.FontSize, state.FontSize), DrawUpdateIcon); + if (state.Span[byteOffset..].StartsWith("ThirdIcon"u8)) + return new(9, new(state.FontSize, state.FontSize), DrawThirdIcon); + if (state.Span[byteOffset..].StartsWith("ThirdInstalledIcon"u8)) + return new(18, new(state.FontSize, state.FontSize), DrawThirdInstalledIcon); + if (state.Span[byteOffset..].StartsWith("ChangelogApiBumpIcon"u8)) + return new(20, new(state.FontSize, state.FontSize), DrawChangelogApiBumpIcon); + if (state.Span[byteOffset..].StartsWith("InstalledIcon"u8)) + return new(13, new(state.FontSize, state.FontSize), DrawInstalledIcon); + if (state.Span[byteOffset..].StartsWith("tex("u8)) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + var tex = Service + .Get() + .Shared + .GetFromGame(Encoding.UTF8.GetString(state.Span[(byteOffset + 4)..(byteOffset + off)])) + .GetWrapOrEmpty(); + return new(off + 1, tex.Size * (state.FontSize / tex.Size.Y), DrawTexture); + } + + if (state.Span[byteOffset..].StartsWith("icon("u8)) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + if (int.TryParse(state.Span[(byteOffset + 5)..(byteOffset + off)], out var parsed)) + { + var tex = Service + .Get() + .Shared + .GetFromGameIcon(parsed) + .GetWrapOrEmpty(); + return new(off + 1, tex.Size * (state.FontSize / tex.Size.Y), DrawIcon); + } + } + + return default; + + static void DrawTexture(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + var tex = Service + .Get() + .Shared + .GetFromGame(Encoding.UTF8.GetString(state.Span[(byteOffset + 4)..(byteOffset + off)])) + .GetWrapOrEmpty(); + state.Draw( + tex.ImGuiHandle, + offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2), + tex.Size * (state.FontSize / tex.Size.Y), + Vector2.Zero, + Vector2.One); + } + + static void DrawIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) + { + var off = state.Span[byteOffset..].IndexOf((byte)')'); + if (!int.TryParse(state.Span[(byteOffset + 5)..(byteOffset + off)], out var parsed)) + return; + var tex = Service + .Get() + .Shared + .GetFromGameIcon(parsed) + .GetWrapOrEmpty(); + state.Draw( + tex.ImGuiHandle, + offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2), + tex.Size * (state.FontSize / tex.Size.Y), + Vector2.Zero, + Vector2.One); + } + + static void DrawAsset(scoped in SeStringDrawState state, Vector2 offset, DalamudAsset asset) => + state.Draw( + Service.Get().GetDalamudTextureWrap(asset).ImGuiHandle, + offset + new Vector2(0, (state.LineHeight - state.FontSize) / 2), + new(state.FontSize, state.FontSize), + Vector2.Zero, + Vector2.One); + + static void DrawDalamud(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.LogoSmall); + + static void DrawWhite(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.White4X4); + + static void DrawDefaultIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.DefaultIcon); + + static void DrawDisabledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.DisabledIcon); + + static void DrawOutdatedInstallableIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.OutdatedInstallableIcon); + + static void DrawTroubleIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.TroubleIcon); + + static void DrawDevPluginIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.DevPluginIcon); + + static void DrawUpdateIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.UpdateIcon); + + static void DrawInstalledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.InstalledIcon); + + static void DrawThirdIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.ThirdIcon); + + static void DrawThirdInstalledIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.ThirdInstalledIcon); + + static void DrawChangelogApiBumpIcon(scoped in SeStringDrawState state, int byteOffset, Vector2 offset) => + DrawAsset(state, offset, DalamudAsset.ChangelogApiBumpIcon); + } } From 424f129969a43d970b1b118b8bccbdbb94d23561 Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 4 Aug 2024 20:31:08 +0900 Subject: [PATCH 020/375] TextureManager: cleanup shared textures off main thread periodically (#1984) * TextureManager: cleanup shared textures off main thread periodically The cleanup function was already written in a threadsafe way. This commit moves that functionality to a background low priority thread, and does that every 60 frames. * fix --- .../Internal/TextureManager.SharedTextures.cs | 84 +++++++++++++------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs index 156ffa56f..d7e185b68 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.SharedTextures.cs @@ -4,6 +4,8 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using BitFaster.Caching.Lru; @@ -65,12 +67,21 @@ internal sealed partial class TextureManager private readonly ConcurrentDictionary<(Assembly, string), SharedImmediateTexture> manifestResourceDict = new(); private readonly HashSet invalidatedTextures = new(); + private readonly Thread sharedTextureReleaseThread; + + private readonly CancellationTokenSource disposingCancellationTokenSource = new(); + /// Initializes a new instance of the class. /// An instance of . public SharedTextureManager(TextureManager textureManager) { this.textureManager = textureManager; - this.textureManager.framework.Update += this.FrameworkOnUpdate; + + this.sharedTextureReleaseThread = new(this.ReleaseSharedTextures) + { + Priority = ThreadPriority.Lowest, + }; + this.sharedTextureReleaseThread.Start(); } /// Gets all the loaded textures from game resources. @@ -90,14 +101,20 @@ internal sealed partial class TextureManager Justification = "Debug use only; users are expected to lock around this")] public ICollection ForDebugInvalidatedTextures => this.invalidatedTextures; + private SharedTextureManager NonDisposed => + this.disposingCancellationTokenSource.IsCancellationRequested + ? throw new ObjectDisposedException(nameof(SharedTextureManager)) + : this; + /// public void Dispose() { - this.textureManager.framework.Update -= this.FrameworkOnUpdate; + this.disposingCancellationTokenSource.Cancel(); this.lookupCache.Clear(); ReleaseSelfReferences(this.gameDict); ReleaseSelfReferences(this.fileDict); ReleaseSelfReferences(this.manifestResourceDict); + this.sharedTextureReleaseThread.Join(); return; static void ReleaseSelfReferences(ConcurrentDictionary dict) @@ -111,12 +128,14 @@ internal sealed partial class TextureManager /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromGameIcon(in GameIconLookup lookup) => - this.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue)); + this.NonDisposed.GetFromGame(this.lookupCache.GetOrAdd(lookup, this.GetIconPathByValue)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetFromGameIcon(in GameIconLookup lookup, [NotNullWhen(true)] out SharedImmediateTexture.PureImpl? texture) { + ObjectDisposedException.ThrowIf(this.disposingCancellationTokenSource.IsCancellationRequested, this); + texture = null; if (!this.lookupCache.TryGet(lookup, out var path)) @@ -134,29 +153,29 @@ internal sealed partial class TextureManager /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromGame(string path) => - this.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder) + this.NonDisposed.gameDict.GetOrAdd(path, GamePathSharedImmediateTexture.CreatePlaceholder) .PublicUseInstance; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromFile(string path) => - this.GetFromFile(new FileInfo(path)); + this.NonDisposed.GetFromFile(new FileInfo(path)); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromFile(FileInfo file) => - this.GetFromFileAbsolute(file.FullName); + this.NonDisposed.GetFromFileAbsolute(file.FullName); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromFileAbsolute(string fullPath) => - this.fileDict.GetOrAdd(fullPath, FileSystemSharedImmediateTexture.CreatePlaceholder) + this.NonDisposed.fileDict.GetOrAdd(fullPath, FileSystemSharedImmediateTexture.CreatePlaceholder) .PublicUseInstance; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public SharedImmediateTexture.PureImpl GetFromManifestResource(Assembly assembly, string name) => - this.manifestResourceDict.GetOrAdd( + this.NonDisposed.manifestResourceDict.GetOrAdd( (assembly, name), ManifestResourceSharedImmediateTexture.CreatePlaceholder) .PublicUseInstance; @@ -166,6 +185,9 @@ internal sealed partial class TextureManager /// The path to invalidate. public void FlushFromGameCache(string path) { + if (this.disposingCancellationTokenSource.IsCancellationRequested) + return; + if (this.gameDict.TryRemove(path, out var r)) { if (r.ReleaseSelfReference(true) != 0) @@ -178,19 +200,33 @@ internal sealed partial class TextureManager [MethodImpl(MethodImplOptions.AggressiveInlining)] private string GetIconPathByValue(GameIconLookup lookup) => - this.textureManager.TryGetIconPath(lookup, out var path) ? path : throw new IconNotFoundException(lookup); + this.NonDisposed.textureManager.TryGetIconPath(lookup, out var path) + ? path + : throw new IconNotFoundException(lookup); - private void FrameworkOnUpdate(IFramework unused) + private void ReleaseSharedTextures() { - RemoveFinalReleased(this.gameDict); - RemoveFinalReleased(this.fileDict); - RemoveFinalReleased(this.manifestResourceDict); - - // ReSharper disable once InconsistentlySynchronizedField - if (this.invalidatedTextures.Count != 0) + while (!this.disposingCancellationTokenSource.IsCancellationRequested) { - lock (this.invalidatedTextures) - this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate); + RemoveFinalReleased(this.gameDict); + RemoveFinalReleased(this.fileDict); + RemoveFinalReleased(this.manifestResourceDict); + + // ReSharper disable once InconsistentlySynchronizedField + if (this.invalidatedTextures.Count != 0) + { + lock (this.invalidatedTextures) + this.invalidatedTextures.RemoveWhere(TextureFinalReleasePredicate); + } + + try + { + this.textureManager.framework.DelayTicks(60).Wait(this.disposingCancellationTokenSource.Token); + } + catch (Exception) + { + // who cares + } } return; @@ -198,13 +234,13 @@ internal sealed partial class TextureManager [MethodImpl(MethodImplOptions.AggressiveInlining)] static void RemoveFinalReleased(ConcurrentDictionary dict) { - if (!dict.IsEmpty) + if (dict.IsEmpty) + return; + + foreach (var (k, v) in dict) { - foreach (var (k, v) in dict) - { - if (TextureFinalReleasePredicate(v)) - _ = dict.TryRemove(k, out _); - } + if (TextureFinalReleasePredicate(v)) + _ = dict.TryRemove(k, out _); } } From 968dbc5bb710529ea38c6f9d93781d73a98310c6 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 4 Aug 2024 14:39:37 +0200 Subject: [PATCH 021/375] build: 10.0.0.11 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 5aa87d963..cb615fed9 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.10 + 10.0.0.11 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 169cf32a0e7d8460296aad8805cac84e2a94a7b3 Mon Sep 17 00:00:00 2001 From: srkizer Date: Mon, 5 Aug 2024 00:42:07 +0900 Subject: [PATCH 022/375] Fix NRE from Class/Job/Level change handler (#1996) --- Dalamud/Game/ClientState/ClientState.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index edf5e7ff9..9dd999860 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -185,11 +185,11 @@ internal sealed class ClientState : IInternalDisposableService, IClientState switch (type) { - case UIModulePacketType.ClassJobChange: + case UIModulePacketType.ClassJobChange when this.ClassJobChanged is { } callback: { var classJobId = uintParam; - foreach (var action in this.ClassJobChanged.GetInvocationList().Cast()) + foreach (var action in callback.GetInvocationList().Cast()) { try { @@ -204,12 +204,12 @@ internal sealed class ClientState : IInternalDisposableService, IClientState break; } - case UIModulePacketType.LevelChange: + case UIModulePacketType.LevelChange when this.LevelChanged is { } callback: { var classJobId = *(uint*)packet; var level = *(ushort*)((nint)packet + 4); - foreach (var action in this.LevelChanged.GetInvocationList().Cast()) + foreach (var action in callback.GetInvocationList().Cast()) { try { From eb2724f36632edf1a64a5ad9854a4782e2df7eef Mon Sep 17 00:00:00 2001 From: srkizer Date: Mon, 5 Aug 2024 00:44:25 +0900 Subject: [PATCH 023/375] Bump Lumina to 4.1.1 (#1997) --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 2 +- Dalamud/Dalamud.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 626788a7a..a81ab54fc 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,7 +27,7 @@ - + diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index cb615fed9..403013962 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,7 +71,7 @@ - + From 694b42a378f338fb90e44ed40de06e908d66e46b Mon Sep 17 00:00:00 2001 From: srkizer Date: Mon, 5 Aug 2024 00:46:05 +0900 Subject: [PATCH 024/375] Fix UIColor handling per theme (#1995) UIColor sheet has color sets per theme. Updated `UIColorWidget` to reflect that, and added `SeStringDrawParams.ThemeIndex` to let users choose which theme color set to use while drawing SeString from Dalamud. --- Dalamud/Interface/ColorHelpers.cs | 6 + .../Internal/SeStringColorStackSet.cs | 60 ++++---- .../Internal/SeStringRenderer.cs | 46 ++++-- .../SeStringDrawParams.cs | 7 + .../SeStringDrawState.cs | 6 + .../Internal/Windows/Data/DataWindow.cs | 2 +- .../Widgets/SeStringRendererTestWidget.cs | 55 ++----- .../Windows/Data/Widgets/UIColorWidget.cs | 140 +++++++++++++++--- 8 files changed, 215 insertions(+), 107 deletions(-) diff --git a/Dalamud/Interface/ColorHelpers.cs b/Dalamud/Interface/ColorHelpers.cs index e99d80cd8..a3ae6799e 100644 --- a/Dalamud/Interface/ColorHelpers.cs +++ b/Dalamud/Interface/ColorHelpers.cs @@ -256,6 +256,12 @@ public static class ColorHelpers public static uint ApplyOpacity(uint rgba, float opacity) => ((uint)MathF.Round((rgba >> 24) * opacity) << 24) | (rgba & 0xFFFFFFu); + /// Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA). + /// Color to process. + /// Swapped color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16); + /// /// Fade a color. /// diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs index 6d7b0a21a..dc99b7faa 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -1,6 +1,5 @@ using System.Buffers.Binary; using System.Collections.Generic; -using System.Runtime.CompilerServices; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.Text; @@ -16,13 +15,9 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; /// Color stacks to use while evaluating a SeString. internal sealed class SeStringColorStackSet { - /// Parsed , containing colors to use with - /// . - private readonly uint[] colorTypes; - - /// Parsed , containing colors to use with + /// Parsed , containing colors to use with and /// . - private readonly uint[] edgeColorTypes; + private readonly uint[,] colorTypes; /// Foreground color stack while evaluating a SeString for rendering. /// Touched only from the main thread. @@ -38,28 +33,30 @@ internal sealed class SeStringColorStackSet /// Initializes a new instance of the class. /// UIColor sheet. - public SeStringColorStackSet(ExcelSheet uiColor) + public unsafe SeStringColorStackSet(ExcelSheet uiColor) { var maxId = 0; foreach (var row in uiColor) maxId = (int)Math.Max(row.RowId, maxId); - this.colorTypes = new uint[maxId + 1]; - this.edgeColorTypes = new uint[maxId + 1]; + this.colorTypes = new uint[maxId + 1, 4]; foreach (var row in uiColor) { // Contains ABGR. - this.colorTypes[row.RowId] = row.UIForeground; - this.edgeColorTypes[row.RowId] = row.UIGlow; + this.colorTypes[row.RowId, 0] = row.UIForeground; + this.colorTypes[row.RowId, 1] = row.UIGlow; + this.colorTypes[row.RowId, 2] = row.Unknown0; + this.colorTypes[row.RowId, 3] = row.Unknown1; } if (BitConverter.IsLittleEndian) { // ImGui wants RGBA in LE. - foreach (ref var r in this.colorTypes.AsSpan()) - r = BinaryPrimitives.ReverseEndianness(r); - foreach (ref var r in this.edgeColorTypes.AsSpan()) - r = BinaryPrimitives.ReverseEndianness(r); + fixed (uint* p = this.colorTypes) + { + foreach (ref var r in new Span(p, this.colorTypes.GetLength(0) * this.colorTypes.GetLength(1))) + r = BinaryPrimitives.ReverseEndianness(r); + } } } @@ -107,7 +104,8 @@ internal sealed class SeStringColorStackSet internal void HandleShadowColorPayload( scoped ref SeStringDrawState drawState, ReadOnlySePayloadSpan payload) => - drawState.ShadowColor = ColorHelpers.ApplyOpacity(AdjustStack(this.shadowColorStack, payload), drawState.Opacity); + drawState.ShadowColor = + ColorHelpers.ApplyOpacity(AdjustStack(this.shadowColorStack, payload), drawState.Opacity); /// Handles a payload. /// Draw state. @@ -115,7 +113,9 @@ internal sealed class SeStringColorStackSet internal void HandleColorTypePayload( scoped ref SeStringDrawState drawState, ReadOnlySePayloadSpan payload) => - drawState.Color = ColorHelpers.ApplyOpacity(AdjustStack(this.colorStack, this.colorTypes, payload), drawState.Opacity); + drawState.Color = ColorHelpers.ApplyOpacity( + this.AdjustStackByType(this.colorStack, payload, drawState.ThemeIndex), + drawState.Opacity); /// Handles a payload. /// Draw state. @@ -124,19 +124,13 @@ internal sealed class SeStringColorStackSet scoped ref SeStringDrawState drawState, ReadOnlySePayloadSpan payload) { - var newColor = AdjustStack(this.edgeColorStack, this.edgeColorTypes, payload); + var newColor = this.AdjustStackByType(this.edgeColorStack, payload, drawState.ThemeIndex); if (!drawState.ForceEdgeColor) drawState.EdgeColor = ColorHelpers.ApplyOpacity(newColor, drawState.EdgeOpacity); this.HasAdditionalEdgeColor = this.edgeColorStack.Count > 1; } - /// Swaps red and blue channels of a given color in ARGB(BB GG RR AA) and ABGR(RR GG BB AA). - /// Color to process. - /// Swapped color. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint SwapRedBlue(uint x) => (x & 0xFF00FF00u) | ((x >> 16) & 0xFF) | ((x & 0xFF) << 16); - private static unsafe uint AdjustStack(List rgbaStack, ReadOnlySePayloadSpan payload) { if (!payload.TryGetExpression(out var expr)) @@ -154,7 +148,10 @@ internal sealed class SeStringColorStackSet if (expr.TryGetUInt(out var bgra)) { - rgbaStack.Add(SwapRedBlue(bgra) | 0xFF000000u); + // NOTE: if it reads a `0`, then it seems to be doing something else. + // See case 0x12 from `Component::GUI::AtkFontAnalyzerBase.vf4`. + // Fix when someone figures what's this about. + rgbaStack.Add(ColorHelpers.SwapRedBlue(bgra) | 0xFF000000u); return rgbaStack[^1]; } @@ -166,7 +163,7 @@ internal sealed class SeStringColorStackSet i > 0 && i <= rtm->TextModule.MacroDecoder.GlobalParameters.Count && rtm->TextModule.MacroDecoder.GlobalParameters[i - 1] is { Type: TextParameterType.Integer } gp) { - rgbaStack.Add(SwapRedBlue((uint)gp.IntValue) | 0xFF000000u); + rgbaStack.Add(ColorHelpers.SwapRedBlue((uint)gp.IntValue) | 0xFF000000u); return rgbaStack[^1]; } @@ -175,13 +172,14 @@ internal sealed class SeStringColorStackSet return rgbaStack[^1]; } - private static uint AdjustStack(List rgbaStack, uint[] colorTypes, ReadOnlySePayloadSpan payload) + private uint AdjustStackByType(List rgbaStack, ReadOnlySePayloadSpan payload, int themeIndex) { if (!payload.TryGetExpression(out var expr)) return rgbaStack[^1]; if (!expr.TryGetUInt(out var colorTypeIndex)) return rgbaStack[^1]; + // Component::GUI::AtkFontAnalyzerBase.vf4: passing 0 will pop the color off the stack. if (colorTypeIndex == 0) { // First item in the stack is the color we started to draw with. @@ -191,8 +189,12 @@ internal sealed class SeStringColorStackSet } // Opacity component is ignored. - rgbaStack.Add((colorTypeIndex < colorTypes.Length ? colorTypes[colorTypeIndex] : 0u) | 0xFF000000u); + var color = themeIndex >= 0 && themeIndex < this.colorTypes.GetLength(1) && + colorTypeIndex < this.colorTypes.GetLength(0) + ? this.colorTypes[colorTypeIndex, themeIndex] + : 0u; + rgbaStack.Add(color | 0xFF000000u); return rgbaStack[^1]; } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 23b672a3b..bdd1f30d8 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -547,9 +547,8 @@ internal unsafe class SeStringRenderer : IInternalDisposableService Rune lastRune, int link) { - var gfdTextureSrv = - (nint)UIModule.Instance()->GetRaptureAtkModule()->AtkModule.AtkFontManager.Gfd->Texture-> - D3D11ShaderResourceView; + // This might temporarily return 0 while logging in. + var gfdTextureSrv = GetGfdTextureSrv(); var x = 0f; var width = 0f; foreach (var c in UtfEnumerator.From(span, UtfEnumeratorFlags.Utf8SeString)) @@ -569,13 +568,17 @@ internal unsafe class SeStringRenderer : IInternalDisposableService { var size = gfdEntry.CalculateScaledSize(state.FontSize, out var useHq); state.SetCurrentChannel(SeStringDrawChannel.Foreground); - state.Draw( - gfdTextureSrv, - offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)), - size, - useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, - useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1, - ColorHelpers.ApplyOpacity(uint.MaxValue, state.Opacity)); + if (gfdTextureSrv != 0) + { + state.Draw( + gfdTextureSrv, + offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)), + size, + useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, + useHq ? gfdEntry.HqUv1 : gfdEntry.Uv1, + ColorHelpers.ApplyOpacity(uint.MaxValue, state.Opacity)); + } + if (link != -1) state.DrawLinkUnderline(offset + new Vector2(x, 0), size.X); @@ -602,6 +605,29 @@ internal unsafe class SeStringRenderer : IInternalDisposableService width = Math.Max(width, x + dist + (g.X1 * state.FontSizeScale)); x += dist + advanceWidth; } + + return; + + static nint GetGfdTextureSrv() + { + var uim = UIModule.Instance(); + if (uim is null) + return 0; + + var ram = uim->GetRaptureAtkModule(); + if (ram is null) + return 0; + + var gfd = ram->AtkModule.AtkFontManager.Gfd; + if (gfd is null) + return 0; + + var tex = gfd->Texture; + if (tex is null) + return 0; + + return (nint)tex->D3D11ShaderResourceView; + } } /// Determines a bitmap icon to display for the given SeString payload. diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs index cdd5e1db6..e03f60a32 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawParams.cs @@ -56,6 +56,13 @@ public record struct SeStringDrawParams /// of 0.25f that might be subject to change in the future. public float? EdgeStrength { get; set; } + /// Gets or sets the theme that will decide the colors to use for + /// and . + /// 0 to use colors for Dark theme, 1 to use colors for Light theme, 2 to use colors + /// for Classic FF theme, 3 to use colors for Clear Blue theme, or null to use the theme set from the + /// game configuration. + public int? ThemeIndex { get; set; } + /// Gets or sets the color of the rendered text. /// Color in RGBA, or null to use (the default). public uint? Color { get; set; } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs index d8348e5f2..5f95ac1b9 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs @@ -6,6 +6,8 @@ using System.Text; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; using Lumina.Text.Payloads; @@ -55,6 +57,7 @@ public unsafe ref struct SeStringDrawState this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); this.ForceEdgeColor = ssdp.ForceEdgeColor; + this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType; this.Bold = ssdp.Bold; this.Italic = ssdp.Italic; this.Edge = ssdp.Edge; @@ -100,6 +103,9 @@ public unsafe ref struct SeStringDrawState /// public float EdgeOpacity { get; } + /// + public int ThemeIndex { get; } + /// public uint Color { get; set; } diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 9e1464105..8115987a0 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -56,7 +56,7 @@ internal class DataWindow : Window, IDisposable new TaskSchedulerWidget(), new TexWidget(), new ToastWidget(), - new UIColorWidget(), + new UiColorWidget(), }; private readonly IOrderedEnumerable orderedModules; diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index d0dffce75..2119ebc4d 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -12,6 +12,8 @@ using Dalamud.Interface.Utility; using Dalamud.Storage.Assets; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; using Lumina.Excel.GeneratedSheets2; @@ -28,10 +30,10 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal unsafe class SeStringRendererTestWidget : IDataWindowWidget { + private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; private ImVectorWrapper testStringBuffer; private string testString = string.Empty; private Addon[]? addons; - private ReadOnlySeString? uicolor; private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; @@ -51,7 +53,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget { this.style = new() { GetEntity = this.GetEntity }; this.addons = null; - this.uicolor = null; this.logkind = null; this.testString = string.Empty; this.interactable = this.useEntity = true; @@ -117,6 +118,12 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Checkbox("Shadow", ref t)) this.style.Shadow = t; + ImGui.SameLine(); + var t4 = this.style.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType; + ImGui.PushItemWidth(ImGui.CalcTextSize("WWWWWWWWWWWWWW").X); + if (ImGui.Combo("##theme", ref t4, ThemeNames, ThemeNames.Length)) + this.style.ThemeIndex = t4; + ImGui.SameLine(); t = this.style.LinkUnderlineThickness > 0f; if (ImGui.Checkbox("Link Underline", ref t)) @@ -136,50 +143,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Checkbox("Use Entity Replacements", ref t)) this.useEntity = t; - if (ImGui.CollapsingHeader("UIColor Preview")) - { - if (this.uicolor is null) - { - var tt = new SeStringBuilder(); - foreach (var uc in Service.Get().GetExcelSheet()!) - { - tt.Append($"#{uc.RowId}: ") - .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(uc.RowId).EndMacro() - .Append("Edge ") - .BeginMacro(MacroCode.ColorType).AppendUIntExpression(uc.RowId).EndMacro() - .Append("Edge+Color ") - .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(0).EndMacro() - .Append("Color ") - .BeginMacro(MacroCode.ColorType).AppendUIntExpression(0).EndMacro(); - if (uc.RowId >= 500) - { - if (uc.RowId % 2 == 0) - { - tt.BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(uc.RowId).EndMacro() - .BeginMacro(MacroCode.ColorType).AppendUIntExpression(uc.RowId + 1).EndMacro() - .Append($" => color#{uc.RowId + 1} + edge#{uc.RowId}") - .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(0).EndMacro() - .BeginMacro(MacroCode.ColorType).AppendUIntExpression(0).EndMacro(); - } - else - { - tt.BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(uc.RowId).EndMacro() - .BeginMacro(MacroCode.ColorType).AppendUIntExpression(uc.RowId - 1).EndMacro() - .Append($" => color#{uc.RowId - 1} + edge#{uc.RowId}") - .BeginMacro(MacroCode.EdgeColorType).AppendUIntExpression(0).EndMacro() - .BeginMacro(MacroCode.ColorType).AppendUIntExpression(0).EndMacro(); - } - } - - tt.BeginMacro(MacroCode.NewLine).EndMacro(); - } - - this.uicolor = tt.ToReadOnlySeString(); - } - - ImGuiHelpers.SeStringWrapped(this.uicolor.Value.Data.Span, this.style); - } - if (ImGui.CollapsingHeader("LogKind Preview")) { if (this.logkind is null) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs index 1f8b4c62c..b3e7346df 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs @@ -1,8 +1,13 @@ -using System.Numerics; +using System.Buffers.Binary; +using System.Linq; +using System.Numerics; using Dalamud.Data; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Storage.Assets; using ImGuiNET; + using Lumina.Excel.GeneratedSheets; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -10,13 +15,15 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying all UI Colors from Lumina. /// -internal class UIColorWidget : IDataWindowWidget +internal class UiColorWidget : IDataWindowWidget { + private UIColor[]? colors; + /// - public string[]? CommandShortcuts { get; init; } = { "uicolor" }; - + public string[]? CommandShortcuts { get; init; } = ["uicolor"]; + /// - public string DisplayName { get; init; } = "UIColor"; + public string DisplayName { get; init; } = "UIColor"; /// public bool Ready { get; set; } @@ -25,33 +32,124 @@ internal class UIColorWidget : IDataWindowWidget public void Load() { this.Ready = true; + this.colors = null; } /// - public void Draw() + public unsafe void Draw() { - var colorSheet = Service.Get().GetExcelSheet(); - if (colorSheet is null) return; + this.colors ??= Service.Get().GetExcelSheet()?.ToArray(); + if (this.colors is null) return; - foreach (var color in colorSheet) + ImGui.TextUnformatted("Color notation is #RRGGBB."); + if (!ImGui.BeginTable("UIColor", 5)) + return; + + ImGui.TableSetupScrollFreeze(0, 1); + var basew = ImGui.CalcTextSize("9").X; + ImGui.TableSetupColumn("Row ID", ImGuiTableColumnFlags.WidthFixed, basew * 7); + ImGui.TableSetupColumn("Dark", ImGuiTableColumnFlags.WidthFixed, basew * 17); + ImGui.TableSetupColumn("Light", ImGuiTableColumnFlags.WidthFixed, basew * 17); + ImGui.TableSetupColumn("Classic FF", ImGuiTableColumnFlags.WidthFixed, basew * 17); + ImGui.TableSetupColumn("Clear Blue", ImGuiTableColumnFlags.WidthFixed, basew * 17); + ImGui.TableHeadersRow(); + + var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + clipper.Begin(this.colors.Length); + while (clipper.Step()) { - this.DrawUiColor(color); + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + var id = this.colors[i].RowId; + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"{id}"); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_col1"); + DrawColorColumn(this.colors[i].UIForeground); + if (id is >= 500 and < 580) + DrawEdgePreview(id, this.colors[i].UIForeground, this.colors[i + 1].UIForeground); + ImGui.PopID(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_col2"); + DrawColorColumn(this.colors[i].UIGlow); + if (id is >= 500 and < 580) + DrawEdgePreview(id, this.colors[i].UIGlow, this.colors[i + 1].UIGlow); + ImGui.PopID(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_col3"); + DrawColorColumn(this.colors[i].Unknown2); + if (id is >= 500 and < 580) + DrawEdgePreview(id, this.colors[i].Unknown2, this.colors[i + 1].Unknown2); + ImGui.PopID(); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.PushID($"row{id}_col4"); + DrawColorColumn(this.colors[i].Unknown3); + if (id is >= 500 and < 580) + DrawEdgePreview(id, this.colors[i].Unknown3, this.colors[i + 1].Unknown3); + ImGui.PopID(); + } } + + clipper.Destroy(); + ImGui.EndTable(); } - - private void DrawUiColor(UIColor color) + + private static void DrawColorColumn(uint sheetColor) { - ImGui.Text($"[{color.RowId:D3}] "); + sheetColor = BinaryPrimitives.ReverseEndianness(sheetColor); + ImGui.Image( + Service.Get().White4X4.ImGuiHandle, + new(ImGui.GetFrameHeight()), + Vector2.Zero, + Vector2.One, + ImGui.ColorConvertU32ToFloat4(sheetColor | 0xFF000000u)); ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.Unknown2), $"Unknown2 "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.UIForeground), "UIForeground "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.Unknown3), "Unknown3 "); - ImGui.SameLine(); - ImGui.TextColored(this.ConvertToVector4(color.UIGlow), "UIGlow"); + ImGui.TextUnformatted($"#{sheetColor & 0xFF:X02}{(sheetColor >> 8) & 0xFF:X02}{(sheetColor >> 16) & 0xFF:X02}"); } - + + private static void DrawEdgePreview(uint id, uint sheetColor, uint sheetColor2) + { + ImGui.SameLine(); + if (Service.Get().Draw( + new("+E"u8), + new() + { + Edge = true, + Color = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, + EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, + }, + "+E"u8).Clicked) + ImGui.SetClipboardText($"+E"); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"+E"); + + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + if (Service.Get().Draw( + new("+F"u8), + new() + { + Edge = true, + Color = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, + EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, + }, + "+F"u8).Clicked) + ImGui.SetClipboardText($"+E"); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip($"+E"); + } + private Vector4 ConvertToVector4(uint color) { var r = (byte)(color >> 24); From 88bb224894aa6bb4c523ce2f36d30306bb7ed2f2 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 4 Aug 2024 17:46:47 +0200 Subject: [PATCH 025/375] build: 10.0.0.12 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 403013962..0b66016c6 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.11 + 10.0.0.12 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 018524fb8f73e8bbdf3d55b5648dabe67caca944 Mon Sep 17 00:00:00 2001 From: srkizer Date: Wed, 7 Aug 2024 05:31:24 +0900 Subject: [PATCH 026/375] Fix UIColor layout (#1998) * Fix UIColor layout * Handle correctly --- .../Internal/SeStringColorStackSet.cs | 10 +- .../Windows/Data/Widgets/UIColorWidget.cs | 161 +++++++++++------- 2 files changed, 105 insertions(+), 66 deletions(-) diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs index dc99b7faa..5a7e87e1b 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -148,10 +148,12 @@ internal sealed class SeStringColorStackSet if (expr.TryGetUInt(out var bgra)) { - // NOTE: if it reads a `0`, then it seems to be doing something else. - // See case 0x12 from `Component::GUI::AtkFontAnalyzerBase.vf4`. - // Fix when someone figures what's this about. - rgbaStack.Add(ColorHelpers.SwapRedBlue(bgra) | 0xFF000000u); + // adds the color on the top of the stack. This makes usages like effectively + // become a no-op if no value is provided. + if (bgra == 0) + rgbaStack.Add(rgbaStack[^1]); + else + rgbaStack.Add(ColorHelpers.SwapRedBlue(bgra) | 0xFF000000u); return rgbaStack[^1]; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs index b3e7346df..575bce23c 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs @@ -1,9 +1,13 @@ using System.Buffers.Binary; using System.Linq; using System.Numerics; +using System.Text; using Dalamud.Data; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Utility; using Dalamud.Storage.Assets; using ImGuiNET; @@ -41,21 +45,35 @@ internal class UiColorWidget : IDataWindowWidget this.colors ??= Service.Get().GetExcelSheet()?.ToArray(); if (this.colors is null) return; - ImGui.TextUnformatted("Color notation is #RRGGBB."); + Service.Get().CompileAndDrawWrapped( + "· Color notation is #" + + "RR" + + "GG" + + "BB.
" + + "· Click on a color to copy the color code.
" + + "· Hover on a color to preview the text with edge, when the next color has been used together."); if (!ImGui.BeginTable("UIColor", 5)) return; ImGui.TableSetupScrollFreeze(0, 1); - var basew = ImGui.CalcTextSize("9").X; - ImGui.TableSetupColumn("Row ID", ImGuiTableColumnFlags.WidthFixed, basew * 7); - ImGui.TableSetupColumn("Dark", ImGuiTableColumnFlags.WidthFixed, basew * 17); - ImGui.TableSetupColumn("Light", ImGuiTableColumnFlags.WidthFixed, basew * 17); - ImGui.TableSetupColumn("Classic FF", ImGuiTableColumnFlags.WidthFixed, basew * 17); - ImGui.TableSetupColumn("Clear Blue", ImGuiTableColumnFlags.WidthFixed, basew * 17); + var rowidw = ImGui.CalcTextSize("9999999").X; + var colorw = ImGui.CalcTextSize("#999999").X; + colorw = Math.Max(colorw, ImGui.CalcTextSize("#AAAAAA").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#BBBBBB").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#CCCCCC").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#DDDDDD").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#EEEEEE").X); + colorw = Math.Max(colorw, ImGui.CalcTextSize("#FFFFFF").X); + colorw += ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X; + ImGui.TableSetupColumn("Row ID", ImGuiTableColumnFlags.WidthFixed, rowidw); + ImGui.TableSetupColumn("Dark", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableSetupColumn("Light", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableSetupColumn("Classic FF", ImGuiTableColumnFlags.WidthFixed, colorw); + ImGui.TableSetupColumn("Clear Blue", ImGuiTableColumnFlags.WidthFixed, colorw); ImGui.TableHeadersRow(); var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - clipper.Begin(this.colors.Length); + clipper.Begin(this.colors.Length, ImGui.GetFrameHeightWithSpacing()); while (clipper.Step()) { for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) @@ -70,32 +88,32 @@ internal class UiColorWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col1"); - DrawColorColumn(this.colors[i].UIForeground); - if (id is >= 500 and < 580) + if (this.DrawColorColumn(this.colors[i].UIForeground) && + i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) DrawEdgePreview(id, this.colors[i].UIForeground, this.colors[i + 1].UIForeground); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col2"); - DrawColorColumn(this.colors[i].UIGlow); - if (id is >= 500 and < 580) + if (this.DrawColorColumn(this.colors[i].UIGlow) && + i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) DrawEdgePreview(id, this.colors[i].UIGlow, this.colors[i + 1].UIGlow); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col3"); - DrawColorColumn(this.colors[i].Unknown2); - if (id is >= 500 and < 580) + if (this.DrawColorColumn(this.colors[i].Unknown2) && + i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) DrawEdgePreview(id, this.colors[i].Unknown2, this.colors[i + 1].Unknown2); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col4"); - DrawColorColumn(this.colors[i].Unknown3); - if (id is >= 500 and < 580) + if (this.DrawColorColumn(this.colors[i].Unknown3) && + i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) DrawEdgePreview(id, this.colors[i].Unknown3, this.colors[i + 1].Unknown3); ImGui.PopID(); } @@ -105,58 +123,77 @@ internal class UiColorWidget : IDataWindowWidget ImGui.EndTable(); } - private static void DrawColorColumn(uint sheetColor) - { - sheetColor = BinaryPrimitives.ReverseEndianness(sheetColor); - ImGui.Image( - Service.Get().White4X4.ImGuiHandle, - new(ImGui.GetFrameHeight()), - Vector2.Zero, - Vector2.One, - ImGui.ColorConvertU32ToFloat4(sheetColor | 0xFF000000u)); - ImGui.SameLine(); - ImGui.TextUnformatted($"#{sheetColor & 0xFF:X02}{(sheetColor >> 8) & 0xFF:X02}{(sheetColor >> 16) & 0xFF:X02}"); - } - private static void DrawEdgePreview(uint id, uint sheetColor, uint sheetColor2) { - ImGui.SameLine(); - if (Service.Get().Draw( - new("+E"u8), - new() - { - Edge = true, - Color = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, - EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, - }, - "+E"u8).Clicked) - ImGui.SetClipboardText($"+E"); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip($"+E"); + ImGui.BeginTooltip(); + Span buf = stackalloc byte[256]; + var ptr = 0; + ptr += Encoding.UTF8.GetBytes("", buf[ptr..]); + Service.Get().Draw( + buf[..ptr], + new() + { + Edge = true, + Color = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, + EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, + WrapWidth = float.PositiveInfinity, + }); - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - if (Service.Get().Draw( - new("+F"u8), - new() - { - Edge = true, - Color = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, - EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, - }, - "+F"u8).Clicked) - ImGui.SetClipboardText($"+E"); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip($"+E"); + ptr = 0; + ptr += Encoding.UTF8.GetBytes("", buf[ptr..]); + Service.Get().Draw( + buf[..ptr], + new() + { + Edge = true, + Color = BinaryPrimitives.ReverseEndianness(sheetColor2) | 0xFF000000u, + EdgeColor = BinaryPrimitives.ReverseEndianness(sheetColor) | 0xFF000000u, + WrapWidth = float.PositiveInfinity, + }); + ImGui.EndTooltip(); } - private Vector4 ConvertToVector4(uint color) + private bool DrawColorColumn(uint sheetColor) { - var r = (byte)(color >> 24); - var g = (byte)(color >> 16); - var b = (byte)(color >> 8); - var a = (byte)color; + sheetColor = BinaryPrimitives.ReverseEndianness(sheetColor); + var rgbtext = $"#{sheetColor & 0xFF:X02}{(sheetColor >> 8) & 0xFF:X02}{(sheetColor >> 16) & 0xFF:X02}"; + var size = new Vector2(ImGui.GetFrameHeight()); + size.X += ImGui.CalcTextSize(rgbtext).X + ImGui.GetStyle().FramePadding.X; - return new Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); + var off = ImGui.GetCursorScreenPos(); + ImGui.GetWindowDrawList().AddRectFilled( + off, + off + new Vector2(size.Y), + sheetColor | 0xFF000000u); + ImGui.GetWindowDrawList().AddText( + off + ImGui.GetStyle().FramePadding + new Vector2(size.Y, 0), + ImGui.GetColorU32(ImGuiCol.Text), + rgbtext); + + if (ImGui.InvisibleButton("##copy", size)) + { + ImGui.SetClipboardText(rgbtext); + Service.Get().AddNotification( + new() + { + Content = $"Copied \"{rgbtext}\".", + Title = this.DisplayName, + Type = NotificationType.Success, + }); + } + + return ImGui.IsItemHovered(); } } From 82472ffc11f7abfdf4f06704f45c8085a278f5f4 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:49:38 +0200 Subject: [PATCH 027/375] Update ClientStructs (#1999) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index a0bc6edcd..e62154c9d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit a0bc6edcd7535330d52fe33dcf453bc09fcc51fd +Subproject commit e62154c9d0fb717a7713508d079f7e21fc418a84 From 861a688b896ead216b6be2cfc9eb75476543cb88 Mon Sep 17 00:00:00 2001 From: srkizer Date: Fri, 9 Aug 2024 02:40:40 +0900 Subject: [PATCH 028/375] LoadingDialog: fix possible racecon (#2004) --- Dalamud/Service/LoadingDialog.cs | 108 ++++++++++++------------------ Dalamud/Service/ServiceManager.cs | 8 +-- 2 files changed, 44 insertions(+), 72 deletions(-) diff --git a/Dalamud/Service/LoadingDialog.cs b/Dalamud/Service/LoadingDialog.cs index f788ffb71..424087743 100644 --- a/Dalamud/Service/LoadingDialog.cs +++ b/Dalamud/Service/LoadingDialog.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; +using System.Threading.Tasks; using CheapLoc; @@ -31,15 +32,13 @@ namespace Dalamud; "StyleCop.CSharp.LayoutRules", "SA1519:Braces should not be omitted from multi-line child statement", Justification = "Multiple fixed blocks")] -internal sealed unsafe class LoadingDialog +internal sealed class LoadingDialog { private readonly RollingList logs = new(20); + private readonly TaskCompletionSource hwndTaskDialog = new(); private Thread? thread; - private HWND hwndTaskDialog; private DateTime firstShowTime; - private State currentState = State.LoadingDalamud; - private bool canHide; /// /// Enum representing the state of the dialog. @@ -72,35 +71,13 @@ internal sealed unsafe class LoadingDialog /// /// Gets or sets the current state of the dialog. /// - public State CurrentState - { - get => this.currentState; - set - { - if (this.currentState == value) - return; - - this.currentState = value; - this.UpdateMainInstructionText(); - } - } + public State CurrentState { get; set; } = State.LoadingDalamud; /// /// Gets or sets a value indicating whether the dialog can be hidden by the user. /// /// Thrown if called before the dialog has been created. - public bool CanHide - { - get => this.canHide; - set - { - if (this.canHide == value) - return; - - this.canHide = value; - this.UpdateButtonEnabled(); - } - } + public bool CanHide { get; set; } /// /// Show the dialog. @@ -110,7 +87,7 @@ internal sealed unsafe class LoadingDialog if (IsGloballyHidden) return; - if (this.thread?.IsAlive == true) + if (this.thread is not null) return; this.thread = new Thread(this.ThreadStart) @@ -126,22 +103,28 @@ internal sealed unsafe class LoadingDialog /// /// Hide the dialog. /// - public void HideAndJoin() + /// A representing the asynchronous operation. + public async Task HideAndJoin() { IsGloballyHidden = true; - if (this.thread?.IsAlive is not true) + if (this.hwndTaskDialog.TrySetCanceled() || this.hwndTaskDialog.Task.IsCanceled) return; - SendMessageW(this.hwndTaskDialog, WM.WM_CLOSE, default, default); - this.thread.Join(); + try + { + SendMessageW(await this.hwndTaskDialog.Task, WM.WM_CLOSE, default, default); + } + catch (OperationCanceledException) + { + // ignore + } + + this.thread?.Join(); } - private void UpdateMainInstructionText() + private unsafe void UpdateMainInstructionText(HWND hwnd) { - if (this.hwndTaskDialog == default) - return; - - fixed (void* pszText = this.currentState switch + fixed (void* pszText = this.CurrentState switch { State.LoadingDalamud => Loc.Localize( "LoadingDialogMainInstructionLoadingDalamud", @@ -156,18 +139,15 @@ internal sealed unsafe class LoadingDialog }) { SendMessageW( - this.hwndTaskDialog, + hwnd, (uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT, (WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_MAIN_INSTRUCTION, (LPARAM)pszText); } } - private void UpdateContentText() + private unsafe void UpdateContentText(HWND hwnd) { - if (this.hwndTaskDialog == default) - return; - var contentBuilder = new StringBuilder( Loc.Localize( "LoadingDialogContentInfo", @@ -213,14 +193,14 @@ internal sealed unsafe class LoadingDialog fixed (void* pszText = contentBuilder.ToString()) { SendMessageW( - this.hwndTaskDialog, + hwnd, (uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT, (WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_CONTENT, (LPARAM)pszText); } } - private void UpdateExpandedInformation() + private unsafe void UpdateExpandedInformation(HWND hwnd) { const int maxCharactersPerLine = 80; @@ -261,57 +241,51 @@ internal sealed unsafe class LoadingDialog fixed (void* pszText = sb.ToString()) { SendMessageW( - this.hwndTaskDialog, + hwnd, (uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT, (WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION, (LPARAM)pszText); } } - private void UpdateButtonEnabled() - { - if (this.hwndTaskDialog == default) - return; - - SendMessageW(this.hwndTaskDialog, (uint)TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, IDOK, this.canHide ? 1 : 0); - } + private void UpdateButtonEnabled(HWND hwnd) => + SendMessageW(hwnd, (uint)TASKDIALOG_MESSAGES.TDM_ENABLE_BUTTON, IDOK, this.CanHide ? 1 : 0); private HRESULT TaskDialogCallback(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { switch ((TASKDIALOG_NOTIFICATIONS)msg) { case TASKDIALOG_NOTIFICATIONS.TDN_CREATED: - this.hwndTaskDialog = hwnd; + if (!this.hwndTaskDialog.TrySetResult(hwnd)) + return E.E_FAIL; - this.UpdateMainInstructionText(); - this.UpdateContentText(); - this.UpdateExpandedInformation(); - this.UpdateButtonEnabled(); + this.UpdateMainInstructionText(hwnd); + this.UpdateContentText(hwnd); + this.UpdateExpandedInformation(hwnd); + this.UpdateButtonEnabled(hwnd); SendMessageW(hwnd, (int)TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0); // Bring to front + ShowWindow(hwnd, SW.SW_SHOW); SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE); SetWindowPos(hwnd, HWND.HWND_NOTOPMOST, 0, 0, 0, 0, SWP.SWP_NOSIZE | SWP.SWP_NOMOVE); - ShowWindow(hwnd, SW.SW_SHOW); SetForegroundWindow(hwnd); SetFocus(hwnd); SetActiveWindow(hwnd); return S.S_OK; - case TASKDIALOG_NOTIFICATIONS.TDN_DESTROYED: - this.hwndTaskDialog = default; - return S.S_OK; - case TASKDIALOG_NOTIFICATIONS.TDN_TIMER: - this.UpdateContentText(); - this.UpdateExpandedInformation(); + this.UpdateMainInstructionText(hwnd); + this.UpdateContentText(hwnd); + this.UpdateExpandedInformation(hwnd); + this.UpdateButtonEnabled(hwnd); return S.S_OK; } return S.S_OK; } - private void ThreadStart() + private unsafe void ThreadStart() { // We don't have access to the asset service here. var workingDirectory = Service.Get().StartInfo.WorkingDirectory; @@ -386,7 +360,7 @@ internal sealed unsafe class LoadingDialog gch = GCHandle.Alloc((Func)this.TaskDialogCallback); taskDialogConfig.lpCallbackData = GCHandle.ToIntPtr(gch); - TaskDialogIndirect(&taskDialogConfig, null, null, null).ThrowOnError(); + TaskDialogIndirect(&taskDialogConfig, null, null, null); } catch (Exception e) { diff --git a/Dalamud/Service/ServiceManager.cs b/Dalamud/Service/ServiceManager.cs index 29016bc69..206b24736 100644 --- a/Dalamud/Service/ServiceManager.cs +++ b/Dalamud/Service/ServiceManager.cs @@ -280,11 +280,8 @@ internal static class ServiceManager Log.Error(e, "Failed resolving blocking services"); } - finally - { - loadingDialog.HideAndJoin(); - } + await loadingDialog.HideAndJoin(); return; async Task WaitWithTimeoutConsent(IEnumerable tasksEnumerable, LoadingDialog.State state) @@ -414,13 +411,14 @@ internal static class ServiceManager try { BlockingServicesLoadedTaskCompletionSource.SetException(e); - loadingDialog.HideAndJoin(); } catch (Exception) { // don't care, as this means task result/exception has already been set } + await loadingDialog.HideAndJoin(); + while (tasks.Any()) { await Task.WhenAny(tasks); From bba3298538e1f3ae095da7fb89f2661a3fe91935 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 8 Aug 2024 19:45:26 +0200 Subject: [PATCH 029/375] build: 10.0.0.13 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 0b66016c6..7abc6f088 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.12 + 10.0.0.13 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From ae4c561e09fed76d64ce74cffd50740ec8b82c0a Mon Sep 17 00:00:00 2001 From: wolfcomp <4028289+wolfcomp@users.noreply.github.com> Date: Mon, 12 Aug 2024 22:36:31 +0200 Subject: [PATCH 030/375] Add an ULD display to data window (#1938) * prelim uld data display * change uld list to a string array * fix some uld parts not having their texture id set. * update uld browser to use themes and get locations from sig scanning * undo ClientStructs change during rebase * fix capitalization during rebase --- Dalamud/Game/SigScanner.cs | 28 ++ .../Internal/Windows/Data/DataWindow.cs | 1 + .../Windows/Data/Widgets/UldWidget.cs | 369 ++++++++++++++++++ Dalamud/Plugin/Services/ISigScanner.cs | 9 +- 4 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index ff6a9e327..c448d4d00 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -310,6 +310,34 @@ public class SigScanner : IDisposable, ISigScanner } } + /// + public nint[] ScanAllText(string signature) + { + var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; + var ret = new List(); + while (mBase < this.TextSectionBase + this.TextSectionSize) + { + try + { + var scanRet = Scan(mBase, this.TextSectionSize, signature); + if (scanRet == IntPtr.Zero) + break; + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + ret.Add(scanRet); + mBase = scanRet + 1; + } + catch (KeyNotFoundException) + { + break; + } + } + + return ret.ToArray(); + } + /// public void Dispose() { diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 8115987a0..8aeeb7f7b 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -57,6 +57,7 @@ internal class DataWindow : Window, IDisposable new TexWidget(), new ToastWidget(), new UiColorWidget(), + new UldWidget(), }; private readonly IOrderedEnumerable orderedModules; diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs new file mode 100644 index 000000000..5a3d0b4fb --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs @@ -0,0 +1,369 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Memory; + +using ImGuiNET; + +using Lumina.Data.Files; +using Lumina.Data.Parsing.Uld; + +using static Lumina.Data.Parsing.Uld.Keyframes; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Displays the full data of the selected ULD element. +/// +internal class UldWidget : IDataWindowWidget +{ + private int selectedUld; + private int selectedFrameData; + private int selectedTimeline; + private int selectedParts; + private int selectedUldStyle; + // ULD styles can be hardcoded for now as they don't add new ones regularly. Can later try and find where to load these from in the game EXE. + private (string Display, string Location)[] uldStyles = [ + ("Dark", "uld/"), + ("Light", "uld/light/"), + ("Classic FF", "uld/third/"), + ("Clear Blue", "uld/fourth/") + ]; + + /// + public string[]? CommandShortcuts { get; init; } = { "uld" }; + + /// + public string DisplayName { get; init; } = "ULD"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + UldWidgetData.ReloadStrings(); + this.Ready = true; + } + + /// + public void Draw() + { + var uldString = UldWidgetData.GetUldStrings(); + if (ImGui.Combo("Select Uld", ref this.selectedUld, uldString.Select(t => t.Display).ToArray(), uldString.Length)) + this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; // reset selected parts when changing ULD + ImGui.Combo("Uld theme", ref this.selectedUldStyle, this.uldStyles.Select(t => t.Display).ToArray(), this.uldStyles.Length); + + var dataManager = Service.Get(); + var textureManager = Service.Get(); + + var uld = dataManager.GetFile(uldString[this.selectedUld].Loc); + + if (uld == null) + { + ImGui.Text("Failed to load ULD file."); + return; + } + + if (ImGui.CollapsingHeader("Texture Entries")) + { + if (!ImGui.BeginTable("##uldTextureEntries", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) + return; + ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableHeadersRow(); + + foreach (var textureEntry in uld.AssetData) + this.DrawTextureEntry(textureEntry); + + ImGui.EndTable(); + } + + if (ImGui.CollapsingHeader("Timeline")) + { + ImGui.SliderInt("Timeline", ref this.selectedTimeline, 0, uld.Timelines.Length - 1); + this.DrawTimelines(uld.Timelines[this.selectedTimeline]); + } + + if (ImGui.CollapsingHeader("Parts")) + { + ImGui.SliderInt("Parts", ref this.selectedParts, 0, uld.Parts.Length - 1); + this.DrawParts(uld.Parts[this.selectedParts], uld.AssetData, dataManager, textureManager); + } + } + + private unsafe void DrawTextureEntry(UldRoot.TextureEntry textureEntry) + { + ImGui.TableNextColumn(); + fixed (char* p = textureEntry.Path) + ImGui.TextUnformatted(new string(p)); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(textureEntry.Id.ToString()); + } + + private void DrawTimelines(UldRoot.Timeline timeline) + { + ImGui.SliderInt("FrameData", ref this.selectedFrameData, 0, timeline.FrameData.Length - 1); + var frameData = timeline.FrameData[this.selectedFrameData]; + ImGui.TextUnformatted($"FrameInfo: {frameData.StartFrame} -> {frameData.EndFrame}"); + ImGui.Indent(); + foreach (var frameDataKeyGroup in frameData.KeyGroups) + { + ImGui.TextUnformatted($"{frameDataKeyGroup.Usage:G} {frameDataKeyGroup.Type:G}"); + foreach (var keyframe in frameDataKeyGroup.Frames) + this.DrawTimelineKeyGroupFrame(keyframe); + } + + ImGui.Unindent(); + } + + private void DrawTimelineKeyGroupFrame(IKeyframe frame) + { + switch (frame) + { + case BaseKeyframeData baseKeyframeData: + ImGui.TextUnformatted($"Time: {baseKeyframeData.Time} | Interpolation: {baseKeyframeData.Interpolation} | Acceleration: {baseKeyframeData.Acceleration} | Deceleration: {baseKeyframeData.Deceleration}"); + break; + case Float1Keyframe float1Keyframe: + this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {float1Keyframe.Value}"); + break; + case Float2Keyframe float2Keyframe: + this.DrawTimelineKeyGroupFrame(float2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {float2Keyframe.Value[0]} | Value2: {float2Keyframe.Value[1]}"); + break; + case Float3Keyframe float3Keyframe: + this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {float3Keyframe.Value[0]} | Value2: {float3Keyframe.Value[1]} | Value3: {float3Keyframe.Value[2]}"); + break; + case SByte1Keyframe sbyte1Keyframe: + this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {sbyte1Keyframe.Value}"); + break; + case SByte2Keyframe sbyte2Keyframe: + this.DrawTimelineKeyGroupFrame(sbyte2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {sbyte2Keyframe.Value[0]} | Value2: {sbyte2Keyframe.Value[1]}"); + break; + case SByte3Keyframe sbyte3Keyframe: + this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {sbyte3Keyframe.Value[0]} | Value2: {sbyte3Keyframe.Value[1]} | Value3: {sbyte3Keyframe.Value[2]}"); + break; + case Byte1Keyframe byte1Keyframe: + this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {byte1Keyframe.Value}"); + break; + case Byte2Keyframe byte2Keyframe: + this.DrawTimelineKeyGroupFrame(byte2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {byte2Keyframe.Value[0]} | Value2: {byte2Keyframe.Value[1]}"); + break; + case Byte3Keyframe byte3Keyframe: + this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {byte3Keyframe.Value[0]} | Value2: {byte3Keyframe.Value[1]} | Value3: {byte3Keyframe.Value[2]}"); + break; + case Short1Keyframe short1Keyframe: + this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {short1Keyframe.Value}"); + break; + case Short2Keyframe short2Keyframe: + this.DrawTimelineKeyGroupFrame(short2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {short2Keyframe.Value[0]} | Value2: {short2Keyframe.Value[1]}"); + break; + case Short3Keyframe short3Keyframe: + this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {short3Keyframe.Value[0]} | Value2: {short3Keyframe.Value[1]} | Value3: {short3Keyframe.Value[2]}"); + break; + case UShort1Keyframe ushort1Keyframe: + this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {ushort1Keyframe.Value}"); + break; + case UShort2Keyframe ushort2Keyframe: + this.DrawTimelineKeyGroupFrame(ushort2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {ushort2Keyframe.Value[0]} | Value2: {ushort2Keyframe.Value[1]}"); + break; + case UShort3Keyframe ushort3Keyframe: + this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {ushort3Keyframe.Value[0]} | Value2: {ushort3Keyframe.Value[1]} | Value3: {ushort3Keyframe.Value[2]}"); + break; + case Int1Keyframe int1Keyframe: + this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {int1Keyframe.Value}"); + break; + case Int2Keyframe int2Keyframe: + this.DrawTimelineKeyGroupFrame(int2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {int2Keyframe.Value[0]} | Value2: {int2Keyframe.Value[1]}"); + break; + case Int3Keyframe int3Keyframe: + this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {int3Keyframe.Value[0]} | Value2: {int3Keyframe.Value[1]} | Value3: {int3Keyframe.Value[2]}"); + break; + case UInt1Keyframe uint1Keyframe: + this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {uint1Keyframe.Value}"); + break; + case UInt2Keyframe uint2Keyframe: + this.DrawTimelineKeyGroupFrame(uint2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {uint2Keyframe.Value[0]} | Value2: {uint2Keyframe.Value[1]}"); + break; + case UInt3Keyframe uint3Keyframe: + this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {uint3Keyframe.Value[0]} | Value2: {uint3Keyframe.Value[1]} | Value3: {uint3Keyframe.Value[2]}"); + break; + case Bool1Keyframe bool1Keyframe: + this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value: {bool1Keyframe.Value}"); + break; + case Bool2Keyframe bool2Keyframe: + this.DrawTimelineKeyGroupFrame(bool2Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {bool2Keyframe.Value[0]} | Value2: {bool2Keyframe.Value[1]}"); + break; + case Bool3Keyframe bool3Keyframe: + this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Value1: {bool3Keyframe.Value[0]} | Value2: {bool3Keyframe.Value[1]} | Value3: {bool3Keyframe.Value[2]}"); + break; + case ColorKeyframe colorKeyframe: + this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | Add: {colorKeyframe.AddRed} {colorKeyframe.AddGreen} {colorKeyframe.AddBlue} | Multiply: {colorKeyframe.MultiplyRed} {colorKeyframe.MultiplyGreen} {colorKeyframe.MultiplyBlue}"); + break; + case LabelKeyframe labelKeyframe: + this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" | LabelCommand: {labelKeyframe.LabelCommand} | JumpId: {labelKeyframe.JumpId} | LabelId: {labelKeyframe.LabelId}"); + break; + } + } + + private unsafe void DrawParts(UldRoot.PartsData partsData, UldRoot.TextureEntry[] textureEntries, DataManager dataManager, TextureManager textureManager) + { + for (var index = 0; index < partsData.Parts.Length; index++) + { + ImGui.TextUnformatted($"Index: {index}"); + var partsDataPart = partsData.Parts[index]; + ImGui.SameLine(); + if (textureEntries.All(t => t.Id != partsDataPart.TextureId)) + { + ImGui.TextUnformatted($"Could not find texture for id {partsDataPart.TextureId}"); + continue; + } + + var texturePathChars = textureEntries.First(t => t.Id == partsDataPart.TextureId).Path; + string texturePath; + fixed (char* p = texturePathChars) + texturePath = new string(p); + var texFile = dataManager.GetFile(texturePath.Replace("uld/", this.uldStyles[this.selectedUldStyle].Location)); + if (texFile == null) + { + // try loading from default location if not found in selected style + texFile = dataManager.GetFile(texturePath); + if (texFile == null) + { + ImGui.TextUnformatted($"Failed to load texture file {texturePath}"); + continue; + } + } + var wrap = textureManager.CreateFromTexFile(texFile); + var texSize = new Vector2(texFile.Header.Width, texFile.Header.Height); + var uv0 = new Vector2(partsDataPart.U, partsDataPart.V); + var partSize = new Vector2(partsDataPart.W, partsDataPart.H); + var uv1 = uv0 + partSize; + ImGui.Image(wrap.ImGuiHandle, partSize, uv0 / texSize, uv1 / texSize); + wrap.Dispose(); + } + } +} + +/// +/// Contains the raw data for the ULD widget. +/// +internal class UldWidgetData +{ + // 48 8D 15 ?? ?? ?? ?? is the part of the signatures that contain the string location offset + // 48 = 64 bit register prefix + // 8D = LEA instruction + // 15 = register to store offset in (RDX in this case as Component::GUI::AtkUnitBase_LoadUldByName name component is loaded from RDX) + // ?? ?? ?? ?? = offset to string location + private static readonly (string Sig, nint Offset)[] UldSigLocations = [ + ("45 33 C0 48 8D 15 ?? ?? ?? ?? 48 8B CF 48 8B 5C 24 30 48 83 C4 20 5F E9 ?? ?? ?? ??", 6), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CE 48 8B 5C ?? ?? 48 8B 74 ?? ?? 48 83 C4 20 5F E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB 48 83 C4 20 5B E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E8 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB E8 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B0 01 E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3) + ]; + + private static (string Display, string Loc)[]? uldStrings; + + /// + /// Gets all known ULD locations in the game based on a few signatures. + /// + /// Uld locations. + internal static (string Display, string Loc)[] GetUldStrings() + { + if (uldStrings == null) + ParseUldStrings(); + + return uldStrings!; + } + + /// + /// Reloads the ULD strings. + /// + internal static void ReloadStrings() + { + uldStrings = null; + ParseUldStrings(); + } + + private static void ParseUldStrings() + { + // game contains possibly around 1500 ULD files but current sigs only find less than that due to how they are used + var locations = new List(1000); + var sigScanner = new SigScanner(Process.GetCurrentProcess().MainModule!); + foreach (var (uldSig, strLocOffset) in UldSigLocations) + { + var eas = sigScanner.ScanAllText(uldSig); + foreach (var ea in eas) + { + var strLoc = ea + strLocOffset; + // offset instruction is always 4 bytes so need to read as uint and cast to nint for offset calculation + var offset = (nint)MemoryHelper.Read(strLoc); + // strings are always stored as c strings and relative from end of offset instruction + var str = MemoryHelper.ReadStringNullTerminated(strLoc + 4 + offset); + locations.Add(str); + } + } + + uldStrings = locations.Distinct().Order().Select(t => (t, $"ui/uld/{t}.uld")).ToArray(); + } +} diff --git a/Dalamud/Plugin/Services/ISigScanner.cs b/Dalamud/Plugin/Services/ISigScanner.cs index 1aedb01fd..64c06f513 100644 --- a/Dalamud/Plugin/Services/ISigScanner.cs +++ b/Dalamud/Plugin/Services/ISigScanner.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Dalamud.Game; @@ -146,4 +146,11 @@ public interface ISigScanner /// The real offset of the signature, if found. /// true if the signature was found. public bool TryScanText(string signature, out nint result); + + /// + /// Scan for all matching byte signatures in the .text section. + /// + /// The Signature. + /// The list of real offsets of the found elements based on signature. + public nint[] ScanAllText(string signature); } From 7e0c97f59edc7120a141aa268720ef83002a41d5 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Mon, 12 Aug 2024 23:35:14 +0200 Subject: [PATCH 031/375] Update GameInventoryType enum (#2007) --- Dalamud/Game/Inventory/GameInventoryType.cs | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs index 00c65046f..af124de92 100644 --- a/Dalamud/Game/Inventory/GameInventoryType.cs +++ b/Dalamud/Game/Inventory/GameInventoryType.cs @@ -42,7 +42,12 @@ public enum GameInventoryType : ushort Crystals = 2001, /// - /// Mail container. + /// Item attachments of a letter the player is currently composing. + /// + MailEdit = 2002, + + /// + /// Item attachments of a letter the player is currently reading. /// Mail = 2003, @@ -57,7 +62,7 @@ public enum GameInventoryType : ushort HandIn = 2005, /// - /// DamagedGear container. + /// Repair window container. /// DamagedGear = 2007, @@ -66,6 +71,21 @@ public enum GameInventoryType : ushort /// Examine = 2009, + /// + /// Estate Possession Retrieval container. + /// + Reclaim = 2010, + + /// + /// Container for items when changing the exterior housing appearance. + /// + HousingExteriorAppearanceEdit = 2011, + + /// + /// Container for items when changing the interior housing appearance. + /// + HousingInteriorAppearanceEdit = 2012, + /// /// Doman Enclave Reconstruction Reclamation Box. /// @@ -93,10 +113,10 @@ public enum GameInventoryType : ushort /// /// Armory waist container. + /// /// /// This container should be unused as belt items were removed from the game in Shadowbringers. /// - /// ArmoryWaist = 3204, /// From cddad72066ba45633896b81e38f478bce1aaf674 Mon Sep 17 00:00:00 2001 From: srkizer Date: Wed, 14 Aug 2024 00:45:00 +0900 Subject: [PATCH 032/375] Add error handlings for UldWidget (#2011) * Add error handlings for UldWidget * fixes --- Dalamud/Data/DataManager.cs | 11 + Dalamud/Game/SigScanner.cs | 54 +-- .../Data/DataWindowWidgetExtensions.cs | 58 +++ .../Windows/Data/Widgets/TexWidget.cs | 65 +-- .../Windows/Data/Widgets/UldWidget.cs | 446 +++++++++++++----- Dalamud/Plugin/Services/IDataManager.cs | 13 + Dalamud/Plugin/Services/ISigScanner.cs | 10 + 7 files changed, 444 insertions(+), 213 deletions(-) create mode 100644 Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 58eb930a0..78fa83b00 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; using Dalamud.Game; using Dalamud.IoC; @@ -148,6 +149,16 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default; } + /// + public Task GetFileAsync(string path, CancellationToken cancellationToken) where T : FileResource => + GameData.ParseFilePath(path) is { } filePath && + this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) + ? Task.Run( + () => repository.GetFile(filePath.Category, filePath) ?? throw new FileNotFoundException( + "Failed to load file, most likely because the file could not be found."), + cancellationToken) + : Task.FromException(new FileNotFoundException("The file could not be found.")); + /// public bool FileExists(string path) => this.GameData.FileExists(path); diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index c448d4d00..92c230f54 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using Iced.Intel; using Newtonsoft.Json; @@ -21,8 +23,8 @@ public class SigScanner : IDisposable, ISigScanner { private readonly FileInfo? cacheFile; - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; + private nint moduleCopyPtr; + private nint moduleCopyOffset; private ConcurrentDictionary? textCache; @@ -116,8 +118,8 @@ public class SigScanner : IDisposable, ISigScanner /// The found offset. public static IntPtr Scan(IntPtr baseAddress, int size, string signature) { - var (needle, mask) = ParseSignature(signature); - var index = IndexOf(baseAddress, size, needle, mask); + var (needle, mask, badShift) = ParseSignature(signature); + var index = IndexOf(baseAddress, size, needle, mask, badShift); if (index < 0) throw new KeyNotFoundException($"Can't find a signature of {signature}"); return baseAddress + index; @@ -310,32 +312,29 @@ public class SigScanner : IDisposable, ISigScanner } } - /// - public nint[] ScanAllText(string signature) + /// + public nint[] ScanAllText(string signature) => this.ScanAllText(signature, default).ToArray(); + + /// + public IEnumerable ScanAllText(string signature, CancellationToken cancellationToken) { + var (needle, mask, badShift) = ParseSignature(signature); var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; - var ret = new List(); while (mBase < this.TextSectionBase + this.TextSectionSize) { - try - { - var scanRet = Scan(mBase, this.TextSectionSize, signature); - if (scanRet == IntPtr.Zero) - break; + cancellationToken.ThrowIfCancellationRequested(); - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - ret.Add(scanRet); - mBase = scanRet + 1; - } - catch (KeyNotFoundException) - { + var index = IndexOf(mBase, this.TextSectionSize, needle, mask, badShift); + if (index < 0) break; - } - } - return ret.ToArray(); + var scanRet = mBase + index; + if (this.IsCopy) + scanRet -= this.moduleCopyOffset; + + yield return scanRet; + mBase = scanRet + 1; + } } /// @@ -384,7 +383,7 @@ public class SigScanner : IDisposable, ISigScanner return IntPtr.Add(sigLocation, 5 + jumpOffset); } - private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) + private static (byte[] Needle, bool[] Mask, int[] BadShift) ParseSignature(string signature) { signature = signature.Replace(" ", string.Empty); if (signature.Length % 2 != 0) @@ -407,14 +406,13 @@ public class SigScanner : IDisposable, ISigScanner mask[i] = false; } - return (needle, mask); + return (needle, mask, BuildBadCharTable(needle, mask)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) + private static unsafe int IndexOf(nint bufferPtr, int bufferLength, byte[] needle, bool[] mask, int[] badShift) { if (needle.Length > bufferLength) return -1; - var badShift = BuildBadCharTable(needle, mask); var last = needle.Length - 1; var offset = 0; var maxoffset = bufferLength - needle.Length; @@ -513,7 +511,7 @@ public class SigScanner : IDisposable, ISigScanner this.Module.ModuleMemorySize, this.Module.ModuleMemorySize); - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); + this.moduleCopyOffset = this.moduleCopyPtr - this.Module.BaseAddress; } private void Load() diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs new file mode 100644 index 000000000..24adb8bc5 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindowWidgetExtensions.cs @@ -0,0 +1,58 @@ +using System.Numerics; + +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.ImGuiNotification.Internal; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Data; + +/// Useful functions for implementing data window widgets. +internal static class DataWindowWidgetExtensions +{ + /// Draws a text column, and make it copiable by clicking. + /// Owner widget. + /// String to display. + /// Whether to align to right. + /// Whether to offset to frame padding. + public static void TextColumnCopiable(this IDataWindowWidget widget, string s, bool alignRight, bool framepad) + { + var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0); + if (framepad) + ImGui.AlignTextToFramePadding(); + if (alignRight) + { + var width = ImGui.CalcTextSize(s).X; + var xoff = ImGui.GetColumnWidth() - width; + offset.X += xoff; + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xoff); + ImGui.TextUnformatted(s); + } + else + { + ImGui.TextUnformatted(s); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding); + var vp = ImGui.GetWindowViewport(); + var wrx = (vp.WorkPos.X + vp.WorkSize.X) - offset.X; + ImGui.SetNextWindowSizeConstraints(Vector2.One, new(wrx, float.MaxValue)); + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(wrx); + ImGui.TextWrapped(s.Replace("%", "%%")); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(s); + Service.Get().AddNotification( + $"Copied {ImGui.TableGetColumnName()} to clipboard.", + widget.DisplayName, + NotificationType.Success); + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs index 0d2b744b4..07b2d01ff 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Interface.Components; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.TextureWraps; @@ -457,7 +455,7 @@ internal class TexWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - this.TextCopiable($"0x{wrap.ResourceAddress:X}", true, true); + this.TextColumnCopiable($"0x{wrap.ResourceAddress:X}", true, true); ImGui.TableNextColumn(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Save)) @@ -476,24 +474,24 @@ internal class TexWidget : IDataWindowWidget } ImGui.TableNextColumn(); - this.TextCopiable(wrap.Name, false, true); + this.TextColumnCopiable(wrap.Name, false, true); ImGui.TableNextColumn(); - this.TextCopiable($"{wrap.Width:n0}", true, true); + this.TextColumnCopiable($"{wrap.Width:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable($"{wrap.Height:n0}", true, true); + this.TextColumnCopiable($"{wrap.Height:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString(), false, true); + this.TextColumnCopiable(Enum.GetName(wrap.Format)?[12..] ?? wrap.Format.ToString(), false, true); ImGui.TableNextColumn(); var bytes = wrap.RawSpecs.EstimatedBytes; - this.TextCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true); + this.TextColumnCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true); ImGui.TableNextColumn(); lock (wrap.OwnerPlugins) - this.TextCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true); + this.TextColumnCopiable(string.Join(", ", wrap.OwnerPlugins.Select(static x => x.Name)), false, true); ImGui.PopID(); } @@ -570,16 +568,16 @@ internal class TexWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - this.TextCopiable($"{texture.InstanceIdForDebug:n0}", true, true); + this.TextColumnCopiable($"{texture.InstanceIdForDebug:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable(texture.SourcePathForDebug, false, true); + this.TextColumnCopiable(texture.SourcePathForDebug, false, true); ImGui.TableNextColumn(); - this.TextCopiable($"{texture.RefCountForDebug:n0}", true, true); + this.TextColumnCopiable($"{texture.RefCountForDebug:n0}", true, true); ImGui.TableNextColumn(); - this.TextCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true); + this.TextColumnCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true); ImGui.TableNextColumn(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Save)) @@ -864,47 +862,6 @@ internal class TexWidget : IDataWindowWidget ImGuiHelpers.ScaledDummy(10); } - private void TextCopiable(string s, bool alignRight, bool framepad) - { - var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0); - if (framepad) - ImGui.AlignTextToFramePadding(); - if (alignRight) - { - var width = ImGui.CalcTextSize(s).X; - var xoff = ImGui.GetColumnWidth() - width; - offset.X += xoff; - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + xoff); - ImGui.TextUnformatted(s); - } - else - { - ImGui.TextUnformatted(s); - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding); - var vp = ImGui.GetWindowViewport(); - var wrx = (vp.WorkPos.X + vp.WorkSize.X) - offset.X; - ImGui.SetNextWindowSizeConstraints(Vector2.One, new(wrx, float.MaxValue)); - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(wrx); - ImGui.TextWrapped(s.Replace("%", "%%")); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); - } - - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText(s); - Service.Get().AddNotification( - $"Copied {ImGui.TableGetColumnName()} to clipboard.", - this.DisplayName, - NotificationType.Success); - } - } - private record TextureEntry( IDalamudTextureWrap? SharedResource = null, Task? Api10 = null, diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs index 5a3d0b4fb..ec39e38f1 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UldWidget.cs @@ -2,10 +2,15 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Numerics; +using System.Threading; +using System.Threading.Tasks; using Dalamud.Data; using Dalamud.Game; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; using Dalamud.Memory; using ImGuiNET; @@ -22,21 +27,39 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class UldWidget : IDataWindowWidget { + // ULD styles can be hardcoded for now as they don't add new ones regularly. Can later try and find where to load these from in the game EXE. + private static readonly string[] ThemeDisplayNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; + private static readonly string[] ThemeBasePaths = ["ui/uld/", "ui/uld/light/", "ui/uld/third/", "ui/uld/fourth/"]; + + // 48 8D 15 ?? ?? ?? ?? is the part of the signatures that contain the string location offset + // 48 = 64 bit register prefix + // 8D = LEA instruction + // 15 = register to store offset in (RDX in this case as Component::GUI::AtkUnitBase_LoadUldByName name component is loaded from RDX) + // ?? ?? ?? ?? = offset to string location + private static readonly (string Sig, nint Offset)[] UldSigLocations = + [ + ("45 33 C0 48 8D 15 ?? ?? ?? ?? 48 8B CF 48 8B 5C 24 30 48 83 C4 20 5F E9 ?? ?? ?? ??", 6), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CE 48 8B 5C ?? ?? 48 8B 74 ?? ?? 48 83 C4 20 5F E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB 48 83 C4 20 5B E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E8 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB E8 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 41 B0 01 E9 ?? ?? ?? ??", 3), + ("48 8D 15 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3) + ]; + + private CancellationTokenSource? cts; + private Task? uldNamesTask; + private int selectedUld; private int selectedFrameData; private int selectedTimeline; private int selectedParts; - private int selectedUldStyle; - // ULD styles can be hardcoded for now as they don't add new ones regularly. Can later try and find where to load these from in the game EXE. - private (string Display, string Location)[] uldStyles = [ - ("Dark", "uld/"), - ("Light", "uld/light/"), - ("Classic FF", "uld/third/"), - ("Clear Blue", "uld/fourth/") - ]; + private int selectedTheme; + private Task? selectedUldFileTask; /// - public string[]? CommandShortcuts { get; init; } = { "uld" }; + public string[]? CommandShortcuts { get; init; } = ["uld"]; /// public string DisplayName { get; init; } = "ULD"; @@ -47,63 +70,241 @@ internal class UldWidget : IDataWindowWidget /// public void Load() { - UldWidgetData.ReloadStrings(); + this.cts?.Cancel(); + ClearTask(ref this.uldNamesTask); + this.uldNamesTask = null; + this.cts = new(); + this.Ready = true; + this.selectedUld = this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; + this.selectedTheme = 0; + this.selectedUldFileTask = null; } /// public void Draw() { - var uldString = UldWidgetData.GetUldStrings(); - if (ImGui.Combo("Select Uld", ref this.selectedUld, uldString.Select(t => t.Display).ToArray(), uldString.Length)) - this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; // reset selected parts when changing ULD - ImGui.Combo("Uld theme", ref this.selectedUldStyle, this.uldStyles.Select(t => t.Display).ToArray(), this.uldStyles.Length); + string[] uldNames; + var ct = (this.cts ??= new()).Token; + switch (this.uldNamesTask ??= ParseUldStringsAsync(ct)) + { + case { IsCompletedSuccessfully: true } t: + uldNames = t.Result; + break; + case { Exception: { } loadException }: + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudRed, loadException.ToString()); + return; + case { IsCanceled: true }: + ClearTask(ref this.uldNamesTask); + goto default; + default: + ImGui.TextUnformatted("Loading..."); + return; + } + + var selectedUldPrev = this.selectedUld; + ImGui.Combo("##selectUld", ref this.selectedUld, uldNames, uldNames.Length); + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectUldLeft", FontAwesomeIcon.AngleLeft)) + this.selectedUld = ((this.selectedUld + uldNames.Length) - 1) % uldNames.Length; + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectUldRight", FontAwesomeIcon.AngleRight)) + this.selectedUld = (this.selectedUld + 1) % uldNames.Length; + ImGui.SameLine(); + ImGui.TextUnformatted("Select ULD File"); + if (selectedUldPrev != this.selectedUld) + { + // reset selected parts when changing ULD + this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; + ClearTask(ref this.selectedUldFileTask); + } + + ImGui.Combo("##selectTheme", ref this.selectedTheme, ThemeDisplayNames, ThemeDisplayNames.Length); + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectThemeLeft", FontAwesomeIcon.AngleLeft)) + this.selectedTheme = ((this.selectedTheme + ThemeDisplayNames.Length) - 1) % ThemeDisplayNames.Length; + ImGui.SameLine(); + if (ImGuiComponents.IconButton("selectThemeRight", FontAwesomeIcon.AngleRight)) + this.selectedTheme = (this.selectedTheme + 1) % ThemeDisplayNames.Length; + ImGui.SameLine(); + ImGui.TextUnformatted("Select Theme"); var dataManager = Service.Get(); var textureManager = Service.Get(); - var uld = dataManager.GetFile(uldString[this.selectedUld].Loc); - - if (uld == null) + UldFile uld; + switch (this.selectedUldFileTask ??= + dataManager.GetFileAsync($"ui/uld/{uldNames[this.selectedUld]}.uld", ct)) { - ImGui.Text("Failed to load ULD file."); - return; + case { IsCompletedSuccessfully: true }: + uld = this.selectedUldFileTask.Result; + break; + case { Exception: { } loadException }: + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Failed to load ULD file.\n{loadException}"); + return; + case { IsCanceled: true }: + this.selectedUldFileTask = null; + goto default; + default: + ImGui.TextUnformatted("Loading..."); + return; } if (ImGui.CollapsingHeader("Texture Entries")) { - if (!ImGui.BeginTable("##uldTextureEntries", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) - return; - ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableHeadersRow(); + if (ForceNullable(uld.AssetData) is null) + { + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Error: {nameof(UldFile.AssetData)} is not populated."); + } + else if (ImGui.BeginTable("##uldTextureEntries", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders)) + { + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000").X); + ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview___").X); + ImGui.TableHeadersRow(); - foreach (var textureEntry in uld.AssetData) - this.DrawTextureEntry(textureEntry); + foreach (var textureEntry in uld.AssetData) + this.DrawTextureEntry(textureEntry, textureManager); - ImGui.EndTable(); + ImGui.EndTable(); + } } - if (ImGui.CollapsingHeader("Timeline")) + if (ImGui.CollapsingHeader("Timeline##TimelineCollapsingHeader")) { - ImGui.SliderInt("Timeline", ref this.selectedTimeline, 0, uld.Timelines.Length - 1); - this.DrawTimelines(uld.Timelines[this.selectedTimeline]); + if (ForceNullable(uld.Timelines) is null) + { + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Error: {nameof(UldFile.Timelines)} is not populated."); + } + else if (uld.Timelines.Length == 0) + { + ImGui.TextUnformatted("No entry exists."); + } + else + { + ImGui.SliderInt("Timeline##TimelineSlider", ref this.selectedTimeline, 0, uld.Timelines.Length - 1); + this.DrawTimelines(uld.Timelines[this.selectedTimeline]); + } } - if (ImGui.CollapsingHeader("Parts")) + if (ImGui.CollapsingHeader("Parts##PartsCollapsingHeader")) { - ImGui.SliderInt("Parts", ref this.selectedParts, 0, uld.Parts.Length - 1); - this.DrawParts(uld.Parts[this.selectedParts], uld.AssetData, dataManager, textureManager); + if (ForceNullable(uld.Parts) is null) + { + ImGuiHelpers.SafeTextColoredWrapped( + ImGuiColors.DalamudRed, + $"Error: {nameof(UldFile.Parts)} is not populated."); + } + else if (uld.Parts.Length == 0) + { + ImGui.TextUnformatted("No entry exists."); + } + else + { + ImGui.SliderInt("Parts##PartsSlider", ref this.selectedParts, 0, uld.Parts.Length - 1); + this.DrawParts(uld.Parts[this.selectedParts], uld.AssetData, textureManager); + } } + + return; + static T? ForceNullable(T smth) => smth; } - private unsafe void DrawTextureEntry(UldRoot.TextureEntry textureEntry) + /// + /// Gets all known ULD locations in the game based on a few signatures. + /// + /// Uld locations. + private static Task ParseUldStringsAsync(CancellationToken cancellationToken) => + Task.Run( + () => + { + // game contains possibly around 1500 ULD files but current sigs only find less than that due to how they are used + var locations = new List(1000); + var sigScanner = new SigScanner(Process.GetCurrentProcess().MainModule!); + foreach (var (uldSig, strLocOffset) in UldSigLocations) + { + foreach (var ea in sigScanner.ScanAllText(uldSig, cancellationToken)) + { + var strLoc = ea + strLocOffset; + // offset instruction is always 4 bytes so need to read as uint and cast to nint for offset calculation + var offset = (nint)MemoryHelper.Read(strLoc); + // strings are always stored as c strings and relative from end of offset instruction + var str = MemoryHelper.ReadStringNullTerminated(strLoc + 4 + offset); + locations.Add(str); + } + } + + return locations.Distinct().Order().ToArray(); + }, + cancellationToken); + + private static void ClearTask(ref Task? task) { - ImGui.TableNextColumn(); - fixed (char* p = textureEntry.Path) - ImGui.TextUnformatted(new string(p)); + try + { + task?.Wait(); + } + catch + { + // ignore + } + + task = null; + } + + private static string GetStringNullTerminated(ReadOnlySpan text) + { + var index = text.IndexOf((char)0); + return index == -1 ? new(text) : new(text[..index]); + } + + private string ToThemedPath(string path) => + ThemeBasePaths[this.selectedTheme] + path[ThemeBasePaths[0].Length..]; + + private void DrawTextureEntry(UldRoot.TextureEntry textureEntry, TextureManager textureManager) + { + var path = GetStringNullTerminated(textureEntry.Path); ImGui.TableNextColumn(); ImGui.TextUnformatted(textureEntry.Id.ToString()); + + ImGui.TableNextColumn(); + this.TextColumnCopiable(path, false, false); + + ImGui.TableNextColumn(); + if (string.IsNullOrWhiteSpace(path)) + return; + + ImGui.TextUnformatted("Preview"); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + + var texturePath = GetStringNullTerminated(textureEntry.Path); + ImGui.TextUnformatted($"Base path at {texturePath}:"); + if (textureManager.Shared.GetFromGame(texturePath).TryGetWrap(out var wrap, out var e)) + ImGui.Image(wrap.ImGuiHandle, wrap.Size); + else if (e is not null) + ImGui.TextUnformatted(e.ToString()); + + if (this.selectedTheme != 0) + { + var texturePathThemed = this.ToThemedPath(texturePath); + ImGui.TextUnformatted($"Themed path at {texturePathThemed}:"); + if (textureManager.Shared.GetFromGame(texturePathThemed).TryGetWrap(out wrap, out e)) + ImGui.Image(wrap.ImGuiHandle, wrap.Size); + else if (e is not null) + ImGui.TextUnformatted(e.ToString()); + } + + ImGui.EndTooltip(); + } } private void DrawTimelines(UldRoot.Timeline timeline) @@ -127,7 +328,8 @@ internal class UldWidget : IDataWindowWidget switch (frame) { case BaseKeyframeData baseKeyframeData: - ImGui.TextUnformatted($"Time: {baseKeyframeData.Time} | Interpolation: {baseKeyframeData.Interpolation} | Acceleration: {baseKeyframeData.Acceleration} | Deceleration: {baseKeyframeData.Deceleration}"); + ImGui.TextUnformatted( + $"Time: {baseKeyframeData.Time} | Interpolation: {baseKeyframeData.Interpolation} | Acceleration: {baseKeyframeData.Acceleration} | Deceleration: {baseKeyframeData.Deceleration}"); break; case Float1Keyframe float1Keyframe: this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe); @@ -142,7 +344,8 @@ internal class UldWidget : IDataWindowWidget case Float3Keyframe float3Keyframe: this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {float3Keyframe.Value[0]} | Value2: {float3Keyframe.Value[1]} | Value3: {float3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {float3Keyframe.Value[0]} | Value2: {float3Keyframe.Value[1]} | Value3: {float3Keyframe.Value[2]}"); break; case SByte1Keyframe sbyte1Keyframe: this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe); @@ -157,7 +360,8 @@ internal class UldWidget : IDataWindowWidget case SByte3Keyframe sbyte3Keyframe: this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {sbyte3Keyframe.Value[0]} | Value2: {sbyte3Keyframe.Value[1]} | Value3: {sbyte3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {sbyte3Keyframe.Value[0]} | Value2: {sbyte3Keyframe.Value[1]} | Value3: {sbyte3Keyframe.Value[2]}"); break; case Byte1Keyframe byte1Keyframe: this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe); @@ -172,7 +376,8 @@ internal class UldWidget : IDataWindowWidget case Byte3Keyframe byte3Keyframe: this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {byte3Keyframe.Value[0]} | Value2: {byte3Keyframe.Value[1]} | Value3: {byte3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {byte3Keyframe.Value[0]} | Value2: {byte3Keyframe.Value[1]} | Value3: {byte3Keyframe.Value[2]}"); break; case Short1Keyframe short1Keyframe: this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe); @@ -187,7 +392,8 @@ internal class UldWidget : IDataWindowWidget case Short3Keyframe short3Keyframe: this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {short3Keyframe.Value[0]} | Value2: {short3Keyframe.Value[1]} | Value3: {short3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {short3Keyframe.Value[0]} | Value2: {short3Keyframe.Value[1]} | Value3: {short3Keyframe.Value[2]}"); break; case UShort1Keyframe ushort1Keyframe: this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe); @@ -202,7 +408,8 @@ internal class UldWidget : IDataWindowWidget case UShort3Keyframe ushort3Keyframe: this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {ushort3Keyframe.Value[0]} | Value2: {ushort3Keyframe.Value[1]} | Value3: {ushort3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {ushort3Keyframe.Value[0]} | Value2: {ushort3Keyframe.Value[1]} | Value3: {ushort3Keyframe.Value[2]}"); break; case Int1Keyframe int1Keyframe: this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe); @@ -217,7 +424,8 @@ internal class UldWidget : IDataWindowWidget case Int3Keyframe int3Keyframe: this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {int3Keyframe.Value[0]} | Value2: {int3Keyframe.Value[1]} | Value3: {int3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {int3Keyframe.Value[0]} | Value2: {int3Keyframe.Value[1]} | Value3: {int3Keyframe.Value[2]}"); break; case UInt1Keyframe uint1Keyframe: this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe); @@ -232,7 +440,8 @@ internal class UldWidget : IDataWindowWidget case UInt3Keyframe uint3Keyframe: this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {uint3Keyframe.Value[0]} | Value2: {uint3Keyframe.Value[1]} | Value3: {uint3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {uint3Keyframe.Value[0]} | Value2: {uint3Keyframe.Value[1]} | Value3: {uint3Keyframe.Value[2]}"); break; case Bool1Keyframe bool1Keyframe: this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe); @@ -247,123 +456,98 @@ internal class UldWidget : IDataWindowWidget case Bool3Keyframe bool3Keyframe: this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Value1: {bool3Keyframe.Value[0]} | Value2: {bool3Keyframe.Value[1]} | Value3: {bool3Keyframe.Value[2]}"); + ImGui.TextUnformatted( + $" | Value1: {bool3Keyframe.Value[0]} | Value2: {bool3Keyframe.Value[1]} | Value3: {bool3Keyframe.Value[2]}"); break; case ColorKeyframe colorKeyframe: this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | Add: {colorKeyframe.AddRed} {colorKeyframe.AddGreen} {colorKeyframe.AddBlue} | Multiply: {colorKeyframe.MultiplyRed} {colorKeyframe.MultiplyGreen} {colorKeyframe.MultiplyBlue}"); + ImGui.TextUnformatted( + $" | Add: {colorKeyframe.AddRed} {colorKeyframe.AddGreen} {colorKeyframe.AddBlue} | Multiply: {colorKeyframe.MultiplyRed} {colorKeyframe.MultiplyGreen} {colorKeyframe.MultiplyBlue}"); break; case LabelKeyframe labelKeyframe: this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe); ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" | LabelCommand: {labelKeyframe.LabelCommand} | JumpId: {labelKeyframe.JumpId} | LabelId: {labelKeyframe.LabelId}"); + ImGui.TextUnformatted( + $" | LabelCommand: {labelKeyframe.LabelCommand} | JumpId: {labelKeyframe.JumpId} | LabelId: {labelKeyframe.LabelId}"); break; } } - private unsafe void DrawParts(UldRoot.PartsData partsData, UldRoot.TextureEntry[] textureEntries, DataManager dataManager, TextureManager textureManager) + private void DrawParts( + UldRoot.PartsData partsData, + UldRoot.TextureEntry[] textureEntries, + TextureManager textureManager) { for (var index = 0; index < partsData.Parts.Length; index++) { ImGui.TextUnformatted($"Index: {index}"); var partsDataPart = partsData.Parts[index]; ImGui.SameLine(); - if (textureEntries.All(t => t.Id != partsDataPart.TextureId)) + + char[]? path = null; + foreach (var textureEntry in textureEntries) + { + if (textureEntry.Id != partsDataPart.TextureId) + continue; + path = textureEntry.Path; + break; + } + + if (path is null) { ImGui.TextUnformatted($"Could not find texture for id {partsDataPart.TextureId}"); continue; } - var texturePathChars = textureEntries.First(t => t.Id == partsDataPart.TextureId).Path; - string texturePath; - fixed (char* p = texturePathChars) - texturePath = new string(p); - var texFile = dataManager.GetFile(texturePath.Replace("uld/", this.uldStyles[this.selectedUldStyle].Location)); - if (texFile == null) + var texturePath = GetStringNullTerminated(path); + if (string.IsNullOrWhiteSpace(texturePath)) + { + ImGui.TextUnformatted("Texture path is empty."); + continue; + } + + var texturePathThemed = this.ToThemedPath(texturePath); + if (textureManager.Shared.GetFromGame(texturePathThemed).TryGetWrap(out var wrap, out var e)) + { + texturePath = texturePathThemed; + } + else { // try loading from default location if not found in selected style - texFile = dataManager.GetFile(texturePath); - if (texFile == null) + if (!textureManager.Shared.GetFromGame(texturePath).TryGetWrap(out wrap, out var e2)) { - ImGui.TextUnformatted($"Failed to load texture file {texturePath}"); - continue; + // neither the supposedly original path nor themed path had a file we could load. + if (e is not null && e2 is not null) + { + ImGui.TextUnformatted($"{texturePathThemed}: {e}\n{texturePath}: {e2}"); + continue; + } } } - var wrap = textureManager.CreateFromTexFile(texFile); - var texSize = new Vector2(texFile.Header.Width, texFile.Header.Height); - var uv0 = new Vector2(partsDataPart.U, partsDataPart.V); + var partSize = new Vector2(partsDataPart.W, partsDataPart.H); - var uv1 = uv0 + partSize; - ImGui.Image(wrap.ImGuiHandle, partSize, uv0 / texSize, uv1 / texSize); - wrap.Dispose(); - } - } -} - -/// -/// Contains the raw data for the ULD widget. -/// -internal class UldWidgetData -{ - // 48 8D 15 ?? ?? ?? ?? is the part of the signatures that contain the string location offset - // 48 = 64 bit register prefix - // 8D = LEA instruction - // 15 = register to store offset in (RDX in this case as Component::GUI::AtkUnitBase_LoadUldByName name component is loaded from RDX) - // ?? ?? ?? ?? = offset to string location - private static readonly (string Sig, nint Offset)[] UldSigLocations = [ - ("45 33 C0 48 8D 15 ?? ?? ?? ?? 48 8B CF 48 8B 5C 24 30 48 83 C4 20 5F E9 ?? ?? ?? ??", 6), - ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CE 48 8B 5C ?? ?? 48 8B 74 ?? ?? 48 83 C4 20 5F E9 ?? ?? ?? ??", 3), - ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB 48 83 C4 20 5B E9 ?? ?? ?? ??", 3), - ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E8 ?? ?? ?? ??", 3), - ("48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3), - ("48 8D 15 ?? ?? ?? ?? 45 33 C0 48 8B CB E8 ?? ?? ?? ??", 3), - ("48 8D 15 ?? ?? ?? ?? 41 B0 01 E9 ?? ?? ?? ??", 3), - ("48 8D 15 ?? ?? ?? ?? 45 33 C0 E9 ?? ?? ?? ??", 3) - ]; - - private static (string Display, string Loc)[]? uldStrings; - - /// - /// Gets all known ULD locations in the game based on a few signatures. - /// - /// Uld locations. - internal static (string Display, string Loc)[] GetUldStrings() - { - if (uldStrings == null) - ParseUldStrings(); - - return uldStrings!; - } - - /// - /// Reloads the ULD strings. - /// - internal static void ReloadStrings() - { - uldStrings = null; - ParseUldStrings(); - } - - private static void ParseUldStrings() - { - // game contains possibly around 1500 ULD files but current sigs only find less than that due to how they are used - var locations = new List(1000); - var sigScanner = new SigScanner(Process.GetCurrentProcess().MainModule!); - foreach (var (uldSig, strLocOffset) in UldSigLocations) - { - var eas = sigScanner.ScanAllText(uldSig); - foreach (var ea in eas) + if (wrap is null) { - var strLoc = ea + strLocOffset; - // offset instruction is always 4 bytes so need to read as uint and cast to nint for offset calculation - var offset = (nint)MemoryHelper.Read(strLoc); - // strings are always stored as c strings and relative from end of offset instruction - var str = MemoryHelper.ReadStringNullTerminated(strLoc + 4 + offset); - locations.Add(str); + ImGuiHelpers.ScaledDummy(partSize); + } + else + { + var uv0 = new Vector2(partsDataPart.U, partsDataPart.V); + var uv1 = uv0 + partSize; + ImGui.Image(wrap.ImGuiHandle, partSize * ImGuiHelpers.GlobalScale, uv0 / wrap.Size, uv1 / wrap.Size); + } + + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(texturePath); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy:"); + ImGui.TextUnformatted(texturePath); + ImGui.EndTooltip(); } } - - uldStrings = locations.Distinct().Order().Select(t => (t, $"ui/uld/{t}.uld")).ToArray(); } } diff --git a/Dalamud/Plugin/Services/IDataManager.cs b/Dalamud/Plugin/Services/IDataManager.cs index dd649bd57..cead130aa 100644 --- a/Dalamud/Plugin/Services/IDataManager.cs +++ b/Dalamud/Plugin/Services/IDataManager.cs @@ -1,3 +1,6 @@ +using System.Threading; +using System.Threading.Tasks; + using Dalamud.Game; using Lumina; @@ -61,6 +64,16 @@ public interface IDataManager /// The of the file. public T? GetFile(string path) where T : FileResource; + /// + /// Get a with the given path, of the given type. + /// + /// The type of resource. + /// The path inside of the game files. + /// Cancellation token. + /// A containing the of the file on success. + /// + public Task GetFileAsync(string path, CancellationToken cancellationToken) where T : FileResource; + /// /// Check if the file with the given path exists within the game's index files. /// diff --git a/Dalamud/Plugin/Services/ISigScanner.cs b/Dalamud/Plugin/Services/ISigScanner.cs index 64c06f513..c0ebd9310 100644 --- a/Dalamud/Plugin/Services/ISigScanner.cs +++ b/Dalamud/Plugin/Services/ISigScanner.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Diagnostics; +using System.Threading; namespace Dalamud.Game; @@ -153,4 +155,12 @@ public interface ISigScanner /// The Signature. /// The list of real offsets of the found elements based on signature. public nint[] ScanAllText(string signature); + + /// + /// Scan for all matching byte signatures in the .text section. + /// + /// The Signature. + /// Cancellation token. + /// Enumerable yielding the real offsets of the found elements based on signature. + public IEnumerable ScanAllText(string signature, CancellationToken cancellationToken); } From bd9eaed763300cdb01a92e0037f62474683448e4 Mon Sep 17 00:00:00 2001 From: ALymphocyte <80072093+ALymphocyte@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:05:45 -0700 Subject: [PATCH 033/375] Improve BattleNpcSubKind (#2010) This adds BattleNpcSubKind.NpcPartyMember and documents BattleNpcSubKind.Enemy better. --- Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs index 9c10a84ab..ff356989a 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs @@ -27,7 +27,12 @@ public enum BattleNpcSubKind : byte Chocobo = 3, /// - /// BattleNpc representing a standard enemy. + /// BattleNpc representing a standard enemy. This includes allies (overworld guards and allies in single-player duties). /// Enemy = 5, + + /// + /// BattleNpc representing an NPC party member (from Duty Support, Trust, or Grand Company Command Mission). + /// + NpcPartyMember = 9, } From ecf8e323c4fc5267fd6adcf928d41e7a16c82aef Mon Sep 17 00:00:00 2001 From: srkizer Date: Thu, 15 Aug 2024 22:22:33 +0900 Subject: [PATCH 034/375] Misc fixes (#2013) --- Dalamud/Game/Gui/Dtr/DtrBarEntry.cs | 4 +-- .../Internal/SeStringRenderer.cs | 29 +++++++++++-------- .../Widgets/SeStringRendererTestWidget.cs | 26 ++++++++++++++++- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index a2fa73ec6..651f59b1b 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -26,12 +26,12 @@ public interface IReadOnlyDtrBarEntry /// /// Gets the text of this entry. /// - public SeString Text { get; } + public SeString? Text { get; } /// /// Gets a tooltip to be shown when the user mouses over the dtr entry. /// - public SeString Tooltip { get; } + public SeString? Tooltip { get; } /// /// Gets a value indicating whether this entry should be shown. diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index bdd1f30d8..380afc6c9 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -225,13 +225,8 @@ internal unsafe class SeStringRenderer : IInternalDisposableService this.fragments.Clear(); this.colorStackSet.Initialize(ref state); - // Handle cases where ImGui.AlignTextToFramePadding has been called. - var pCurrentWindow = *(nint*)(ImGui.GetCurrentContext() + ImGuiContextCurrentWindowOffset); - var pWindowDc = pCurrentWindow + ImGuiWindowDcOffset; - var currLineTextBaseOffset = *(float*)(pWindowDc + ImGuiWindowTempDataCurrLineTextBaseOffset); - // Analyze the provided SeString and break it up to text fragments. - this.CreateTextFragments(ref state, currLineTextBaseOffset); + this.CreateTextFragments(ref state); var fragmentSpan = CollectionsMarshal.AsSpan(this.fragments); // Calculate size. @@ -245,6 +240,18 @@ internal unsafe class SeStringRenderer : IInternalDisposableService state.SplitDrawList(); + // Handle cases where ImGui.AlignTextToFramePadding has been called. + var pCurrentWindow = *(nint*)(ImGui.GetCurrentContext() + ImGuiContextCurrentWindowOffset); + var pWindowDc = pCurrentWindow + ImGuiWindowDcOffset; + var currLineTextBaseOffset = *(float*)(pWindowDc + ImGuiWindowTempDataCurrLineTextBaseOffset); + var itemSize = size; + if (currLineTextBaseOffset != 0f) + { + itemSize.Y += 2 * currLineTextBaseOffset; + foreach (ref var f in fragmentSpan) + f.Offset += new Vector2(0, currLineTextBaseOffset); + } + // Draw all text fragments. var lastRune = default(Rune); foreach (ref var f in fragmentSpan) @@ -259,7 +266,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService // Create an ImGui item, if a target draw list is not manually set. if (drawParams.TargetDrawList is null) - ImGui.Dummy(size); + ImGui.Dummy(itemSize); // Handle link interactions. var clicked = false; @@ -296,7 +303,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService if (!invisibleButtonDrawn) { ImGui.SetCursorScreenPos(state.ScreenOffset); - clicked = ImGui.InvisibleButton("##text", size, buttonFlags); + clicked = ImGui.InvisibleButton("##text", itemSize, buttonFlags); } ImGui.PopID(); @@ -369,12 +376,10 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// Creates text fragment, taking line and word breaking into account. /// Draw state. - /// Y offset adjustment for all text fragments. Used to honor - /// . - private void CreateTextFragments(ref SeStringDrawState state, float baseY) + private void CreateTextFragments(ref SeStringDrawState state) { var prev = 0; - var xy = new Vector2(0, baseY); + var xy = Vector2.Zero; var w = 0f; var link = -1; foreach (var (breakAt, mandatory) in new LineBreakEnumerator(state.Span, UtfEnumeratorFlags.Utf8SeString)) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index 2119ebc4d..17aba0c71 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -38,6 +38,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private SeStringDrawParams style; private bool interactable; private bool useEntity; + private bool alignToFramePadding; /// public string DisplayName { get; init; } = "SeStringRenderer Test"; @@ -56,6 +57,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget this.logkind = null; this.testString = string.Empty; this.interactable = this.useEntity = true; + this.alignToFramePadding = false; this.Ready = true; } @@ -143,6 +145,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Checkbox("Use Entity Replacements", ref t)) this.useEntity = t; + ImGui.SameLine(); + t = this.alignToFramePadding; + if (ImGui.Checkbox("Align to Frame Padding", ref t)) + this.alignToFramePadding = t; + if (ImGui.CollapsingHeader("LogKind Preview")) { if (this.logkind is null) @@ -284,7 +291,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (this.testString == string.Empty && this.testStringBuffer.Length != 0) this.testString = Encoding.UTF8.GetString(this.testStringBuffer.DataSpan); - ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + if (this.interactable) { if (ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style, new("this is an ImGui id")) is @@ -294,15 +303,30 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget Clicked: var clicked }) { + ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted($"Hovered[{offset}]: {new ReadOnlySeStringSpan(envelope).ToString()}; {payload}"); if (clicked && payload is DalamudLinkPayload { Plugin: "test" } dlp) Util.OpenLink(dlp.ExtraString); } + else + { + ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + ImGuiHelpers.CompileSeStringWrapped("If a link is hovered, it will be displayed here.", this.style); + } } else { ImGuiHelpers.CompileSeStringWrapped(this.testString, this.style); } + + ImGui.Separator(); + if (this.alignToFramePadding) + ImGui.AlignTextToFramePadding(); + ImGuiHelpers.CompileSeStringWrapped("Extra line for alignment testing.", this.style); } private SeStringReplacementEntity GetEntity(scoped in SeStringDrawState state, int byteOffset) From 7a45fb05e32e626513c3b4de297e81af24be1615 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:05:15 +0900 Subject: [PATCH 035/375] Cache DalamudAssetAttribute lookups (#2014) --- .../Windows/Data/Widgets/ImGuiWidget.cs | 2 +- .../Storage/Assets/DalamudAssetExtensions.cs | 35 +++++++++++++++++-- Dalamud/Storage/Assets/DalamudAssetManager.cs | 8 ++--- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs index 05d831b57..1476ce2e6 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs @@ -364,7 +364,7 @@ internal class ImGuiWidget : IDataWindowWidget public static readonly string[] AssetSources = Enum.GetValues() - .Where(x => x.GetAttribute()?.Purpose is DalamudAssetPurpose.TextureFromPng) + .Where(x => x.GetPurpose() is DalamudAssetPurpose.TextureFromPng) .Select(Enum.GetName) .ToArray(); diff --git a/Dalamud/Storage/Assets/DalamudAssetExtensions.cs b/Dalamud/Storage/Assets/DalamudAssetExtensions.cs index 9181f1a5d..9052a1c6d 100644 --- a/Dalamud/Storage/Assets/DalamudAssetExtensions.cs +++ b/Dalamud/Storage/Assets/DalamudAssetExtensions.cs @@ -1,4 +1,7 @@ -using Dalamud.Utility; +using System.Collections.Frozen; +using System.Collections.Generic; + +using Dalamud.Utility; namespace Dalamud.Storage.Assets; @@ -7,11 +10,37 @@ namespace Dalamud.Storage.Assets; /// public static class DalamudAssetExtensions { + private static readonly FrozenDictionary AttributeCache = CreateCache(); + /// /// Gets the purpose. /// /// The asset. /// The purpose. - public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) => - asset.GetAttribute()?.Purpose ?? DalamudAssetPurpose.Empty; + public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) + { + return GetAssetAttribute(asset)?.Purpose ?? DalamudAssetPurpose.Empty; + } + + /// + /// Gets the attribute. + /// + /// The asset. + /// The attribute. + internal static DalamudAssetAttribute? GetAssetAttribute(this DalamudAsset asset) + { + return AttributeCache.GetValueOrDefault(asset); + } + + private static FrozenDictionary CreateCache() + { + var dict = new Dictionary(); + + foreach (var asset in Enum.GetValues()) + { + dict.Add(asset, asset.GetAttribute()); + } + + return dict.ToFrozenDictionary(); + } } diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index 0109339fe..f750de64a 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -68,7 +68,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud Task.WhenAll( Enum.GetValues() .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAttribute()?.Required is true) + .Where(x => x.GetAssetAttribute()?.Required is true) .Select(this.CreateStreamAsync) .Select(x => x.ToContentDisposedTask())) .ContinueWith( @@ -83,7 +83,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud Task.WhenAll( Enum.GetValues() .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAttribute()?.Required is false) + .Where(x => x.GetAssetAttribute()?.Required is false) .Select(this.CreateStreamAsync) .Select(x => x.ToContentDisposedTask(true))) .ContinueWith(r => Log.Verbose($"Optional assets load state: {r}")); @@ -120,7 +120,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud /// [Pure] public bool IsStreamImmediatelyAvailable(DalamudAsset asset) => - asset.GetAttribute()?.Data is not null + asset.GetAssetAttribute()?.Data is not null || this.fileStreams[asset]?.IsCompletedSuccessfully is true; /// @@ -140,7 +140,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud [Pure] public Task CreateStreamAsync(DalamudAsset asset) { - if (asset.GetAttribute() is { Data: { } rawData }) + if (asset.GetAssetAttribute() is { Data: { } rawData }) return Task.FromResult(new MemoryStream(rawData, false)); Task task; From fdfdee1fcbfaa785c55301cf60fd4cefab26245d Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 17 Aug 2024 21:43:29 +0200 Subject: [PATCH 036/375] Update ClientStructs (#2009) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index e62154c9d..87faf9414 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit e62154c9d0fb717a7713508d079f7e21fc418a84 +Subproject commit 87faf94145fd1d8554741e909e3e3d6e8c2b490f From 0a8f9b73fb9940d71f2ccb41305c2af04c016a2f Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sun, 18 Aug 2024 07:58:45 +0900 Subject: [PATCH 037/375] Make ServiceScope IAsyncDisposable ServiceScope.Dispose was not waiting for scoped services to complete disposing. This had an effect of letting a new plugin instance register a DtrBar entry before previous plugin instance's entry got unregistered. This change also cleans up unloading procedure in LocalPlugin. --- Dalamud/Game/Gui/Dtr/DtrBar.cs | 47 +++- Dalamud/IoC/Internal/ServiceScope.cs | 150 ++++++++--- Dalamud/Plugin/Internal/PluginManager.cs | 69 ++--- .../Plugin/Internal/Types/LocalDevPlugin.cs | 7 +- Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 252 +++++++++++------- .../Types/PluginLoaderDisposalMode.cs | 19 ++ Dalamud/Utility/TaskExtensions.cs | 15 ++ 7 files changed, 375 insertions(+), 184 deletions(-) create mode 100644 Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 04e7fea07..4d328d78c 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -114,10 +114,42 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar { if (existingEntry.Title == title) { - existingEntry.ShouldBeRemoved = false; + if (existingEntry.ShouldBeRemoved) + { + if (plugin == existingEntry.OwnerPlugin) + { + Log.Debug( + "Reviving entry: {what}; owner: {plugin}({pluginId})", + title, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); + } + else + { + Log.Debug( + "Reviving entry: {what}; old owner: {old}({oldId}); new owner: {new}({newId})", + title, + existingEntry.OwnerPlugin?.InternalName, + existingEntry.OwnerPlugin?.EffectiveWorkingPluginId, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); + existingEntry.OwnerPlugin = plugin; + } + + existingEntry.ShouldBeRemoved = false; + } + this.entriesLock.ExitUpgradeableReadLock(); if (plugin == existingEntry.OwnerPlugin) return existingEntry; + + Log.Debug( + "Entry already has a different owner: {what}; owner: {old}({oldId}); requester: {new}({newId})", + title, + existingEntry.OwnerPlugin?.InternalName, + existingEntry.OwnerPlugin?.EffectiveWorkingPluginId, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); throw new ArgumentException("An entry with the same title already exists."); } } @@ -125,6 +157,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.entriesLock.EnterWriteLock(); var entry = new DtrBarEntry(this.configuration, title, null) { Text = text, OwnerPlugin = plugin }; this.entries.Add(entry); + Log.Debug("Adding entry: {what}; owner: {owner}", title, plugin); // Add the entry to the end of the order list, if it's not there already. var dtrOrder = this.configuration.DtrOrder ??= []; @@ -159,14 +192,23 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar { if (!entry.Added) { + Log.Debug("Removing entry immediately because it is not added yet: {what}", entry.Title); this.entriesLock.EnterWriteLock(); this.RemoveEntry(entry); this.entries.Remove(entry); this.entriesReadOnlyCopy = null; this.entriesLock.ExitWriteLock(); } + else if (!entry.ShouldBeRemoved) + { + Log.Debug("Queueing entry for removal: {what}", entry.Title); + entry.Remove(); + } + else + { + Log.Debug("Entry is already marked for removal: {what}", entry.Title); + } - entry.Remove(); break; } } @@ -313,6 +355,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar var data = this.entries[i]; if (data.ShouldBeRemoved) { + Log.Debug("Removing entry from Framework.Update: {what}", data.Title); this.entriesLock.EnterWriteLock(); this.entries.RemoveAt(i); this.RemoveEntry(data); diff --git a/Dalamud/IoC/Internal/ServiceScope.cs b/Dalamud/IoC/Internal/ServiceScope.cs index 4fc299f6e..5ce8bc7d0 100644 --- a/Dalamud/IoC/Internal/ServiceScope.cs +++ b/Dalamud/IoC/Internal/ServiceScope.cs @@ -1,16 +1,18 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using Serilog; +using Dalamud.Game; +using Dalamud.Utility; namespace Dalamud.IoC.Internal; /// /// Container enabling the creation of scoped services. /// -internal interface IServiceScope : IDisposable +internal interface IServiceScope : IAsyncDisposable { /// /// Register objects that may be injected to scoped services, @@ -47,21 +49,57 @@ internal class ServiceScopeImpl : IServiceScope private readonly List privateScopedObjects = []; private readonly ConcurrentDictionary> scopeCreatedObjects = new(); + private readonly ReaderWriterLockSlim disposeLock = new(LockRecursionPolicy.SupportsRecursion); + private bool disposed; + /// Initializes a new instance of the class. /// The container this scope will use to create services. public ServiceScopeImpl(ServiceContainer container) => this.container = container; /// - public void RegisterPrivateScopes(params object[] scopes) => - this.privateScopedObjects.AddRange(scopes); + public void RegisterPrivateScopes(params object[] scopes) + { + this.disposeLock.EnterReadLock(); + try + { + ObjectDisposedException.ThrowIf(this.disposed, this); + this.privateScopedObjects.AddRange(scopes); + } + finally + { + this.disposeLock.ExitReadLock(); + } + } /// - public Task CreateAsync(Type objectType, params object[] scopedObjects) => - this.container.CreateAsync(objectType, scopedObjects, this); + public Task CreateAsync(Type objectType, params object[] scopedObjects) + { + this.disposeLock.EnterReadLock(); + try + { + ObjectDisposedException.ThrowIf(this.disposed, this); + return this.container.CreateAsync(objectType, scopedObjects, this); + } + finally + { + this.disposeLock.ExitReadLock(); + } + } /// - public Task InjectPropertiesAsync(object instance, params object[] scopedObjects) => - this.container.InjectProperties(instance, scopedObjects, this); + public Task InjectPropertiesAsync(object instance, params object[] scopedObjects) + { + this.disposeLock.EnterReadLock(); + try + { + ObjectDisposedException.ThrowIf(this.disposed, this); + return this.container.InjectProperties(instance, scopedObjects, this); + } + finally + { + this.disposeLock.ExitReadLock(); + } + } /// /// Create a service scoped to this scope, with private scoped objects. @@ -69,39 +107,73 @@ internal class ServiceScopeImpl : IServiceScope /// The type of object to create. /// Additional scoped objects. /// The created object, or null. - public Task CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) => - this.scopeCreatedObjects.GetOrAdd( - objectType, - static (objectType, p) => p.Scope.container.CreateAsync( - objectType, - p.Objects.Concat(p.Scope.privateScopedObjects).ToArray()), - (Scope: this, Objects: scopedObjects)); - - /// - public void Dispose() + public Task CreatePrivateScopedObject(Type objectType, params object[] scopedObjects) { - foreach (var objectTask in this.scopeCreatedObjects) + this.disposeLock.EnterReadLock(); + try { - objectTask.Value.ContinueWith( - static r => - { - if (!r.IsCompletedSuccessfully) - { - if (r.Exception is { } e) - Log.Error(e, "{what}: Failed to load.", nameof(ServiceScopeImpl)); - return; - } - - switch (r.Result) - { - case IInternalDisposableService d: - d.DisposeService(); - break; - case IDisposable d: - d.Dispose(); - break; - } - }); + ObjectDisposedException.ThrowIf(this.disposed, this); + return this.scopeCreatedObjects.GetOrAdd( + objectType, + static (objectType, p) => p.Scope.container.CreateAsync( + objectType, + p.Objects.Concat(p.Scope.privateScopedObjects).ToArray()), + (Scope: this, Objects: scopedObjects)); + } + finally + { + this.disposeLock.ExitReadLock(); } } + + /// + public async ValueTask DisposeAsync() + { + this.disposeLock.EnterWriteLock(); + this.disposed = true; + this.disposeLock.ExitWriteLock(); + + List? exceptions = null; + while (!this.scopeCreatedObjects.IsEmpty) + { + try + { + await Task.WhenAll( + this.scopeCreatedObjects.Keys.Select( + async type => + { + if (!this.scopeCreatedObjects.Remove(type, out var serviceTask)) + return; + + switch (await serviceTask) + { + case IInternalDisposableService d: + d.DisposeService(); + break; + case IAsyncDisposable d: + await d.DisposeAsync(); + break; + case IDisposable d: + d.Dispose(); + break; + } + })); + } + catch (AggregateException ae) + { + exceptions ??= []; + exceptions.AddRange(ae.Flatten().InnerExceptions); + } + } + + // Unless Dalamud is unloading (plugin cannot be reloading at that point), ensure that there are no more + // event callback call in progress when this function returns. Since above service dispose operations should + // have unregistered the event listeners, on next framework tick, none can be running anymore. + // This has an additional effect of ensuring that DtrBar entries are completely removed on return. + // Note that this still does not handle Framework.RunOnTick with specified delays. + await (Service.GetNullable()?.DelayTicks(1) ?? Task.CompletedTask).SuppressException(); + + if (exceptions is not null) + throw new AggregateException(exceptions); + } } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 910472f5f..b42b51525 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -375,54 +375,39 @@ internal class PluginManager : IInternalDisposableService /// void IInternalDisposableService.DisposeService() { - var disposablePlugins = - this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray(); - if (disposablePlugins.Any()) - { - // Unload them first, just in case some of plugin codes are still running via callbacks initiated externally. - foreach (var plugin in disposablePlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync)) - { - try - { - plugin.UnloadAsync(true, false).Wait(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error unloading {plugin.Name}"); - } - } + DisposeAsync( + this.installedPluginsList + .Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError) + .ToArray(), + this.configuration).Wait(); + return; - Task.WaitAll(disposablePlugins - .Where(plugin => plugin.Manifest.CanUnloadAsync) - .Select(plugin => Task.Run(async () => - { - try - { - await plugin.UnloadAsync(true, false); - } - catch (Exception ex) - { - Log.Error(ex, $"Error unloading {plugin.Name}"); - } - })).ToArray()); + static async Task DisposeAsync(LocalPlugin[] disposablePlugins, DalamudConfiguration configuration) + { + if (disposablePlugins.Length == 0) + return; + + // Any unload/dispose operation called from this function log errors on their own. + // Ignore all errors. + + // Unload plugins that requires to be unloaded synchronously, + // just in case some plugin codes are still running via callbacks initiated externally. + foreach (var plugin in disposablePlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync)) + await plugin.UnloadAsync(PluginLoaderDisposalMode.None).SuppressException(); + + // Unload plugins that can be unloaded from any thread. + await Task.WhenAll(disposablePlugins.Select(plugin => plugin.UnloadAsync(PluginLoaderDisposalMode.None))) + .SuppressException(); // Just in case plugins still have tasks running that they didn't cancel when they should have, // give them some time to complete it. - Thread.Sleep(this.configuration.PluginWaitBeforeFree ?? PluginWaitBeforeFreeDefault); + // This helps avoid plugins being reloaded from conflicting with itself of previous instance. + await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginWaitBeforeFreeDefault); // Now that we've waited enough, dispose the whole plugin. - // Since plugins should have been unloaded above, this should be done quickly. - foreach (var plugin in disposablePlugins) - { - try - { - plugin.Dispose(); - } - catch (Exception e) - { - Log.Error(e, $"Error disposing {plugin.Name}"); - } - } + // Since plugins should have been unloaded above, this should complete quickly. + await Task.WhenAll(disposablePlugins.Select(plugin => plugin.DisposeAsync().AsTask())) + .SuppressException(); } // NET8 CHORE diff --git a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs index 5a3552199..581bfd724 100644 --- a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -16,7 +15,7 @@ namespace Dalamud.Plugin.Internal.Types; /// This class represents a dev plugin and all facets of its lifecycle. /// The DLL on disk, dependencies, loaded assembly, etc. /// -internal class LocalDevPlugin : LocalPlugin, IDisposable +internal class LocalDevPlugin : LocalPlugin { private static readonly ModuleLog Log = new("PLUGIN"); @@ -101,7 +100,7 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable public List DismissedValidationProblems => this.devSettings.DismissedValidationProblems; /// - public new void Dispose() + public override ValueTask DisposeAsync() { if (this.fileWatcher != null) { @@ -110,7 +109,7 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable this.fileWatcher.Dispose(); } - base.Dispose(); + return base.DisposeAsync(); } /// diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index b94c40918..34641fe4b 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; @@ -20,7 +22,7 @@ namespace Dalamud.Plugin.Internal.Types; /// This class represents a plugin and all facets of its lifecycle. /// The DLL on disk, dependencies, loaded assembly, etc. /// -internal class LocalPlugin : IDisposable +internal class LocalPlugin : IAsyncDisposable { /// /// The underlying manifest for this plugin. @@ -41,6 +43,8 @@ internal class LocalPlugin : IDisposable private Assembly? pluginAssembly; private Type? pluginType; private IDalamudPlugin? instance; + private IServiceScope? serviceScope; + private DalamudPluginInterface? dalamudInterface; /// /// Initializes a new instance of the class. @@ -107,7 +111,7 @@ internal class LocalPlugin : IDisposable /// /// Gets the associated with this plugin. /// - public DalamudPluginInterface? DalamudInterface { get; private set; } + public DalamudPluginInterface? DalamudInterface => this.dalamudInterface; /// /// Gets the path to the plugin DLL. @@ -220,40 +224,11 @@ internal class LocalPlugin : IDisposable /// /// Gets the service scope for this plugin. /// - public IServiceScope? ServiceScope { get; private set; } + public IServiceScope? ServiceScope => this.serviceScope; /// - public void Dispose() - { - var framework = Service.GetNullable(); - var configuration = Service.Get(); - - var didPluginDispose = false; - if (this.instance != null) - { - didPluginDispose = true; - if (this.manifest.CanUnloadAsync || framework == null) - this.instance.Dispose(); - else - framework.RunOnFrameworkThread(() => this.instance.Dispose()).Wait(); - - this.instance = null; - } - - this.DalamudInterface?.Dispose(); - - this.DalamudInterface = null; - - this.ServiceScope?.Dispose(); - this.ServiceScope = null; - - this.pluginType = null; - this.pluginAssembly = null; - - if (this.loader != null && didPluginDispose) - Thread.Sleep(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault); - this.loader?.Dispose(); - } + public virtual async ValueTask DisposeAsync() => + await this.ClearAndDisposeAllResources(PluginLoaderDisposalMode.ImmediateDispose); /// /// Load this plugin. @@ -263,7 +238,6 @@ internal class LocalPlugin : IDisposable /// A task. public async Task LoadAsync(PluginLoadReason reason, bool reloading = false) { - var framework = await Service.GetAsync(); var ioc = await Service.GetAsync(); var pluginManager = await Service.GetAsync(); var dalamud = await Service.GetAsync(); @@ -300,11 +274,17 @@ internal class LocalPlugin : IDisposable if (!this.IsDev) { throw new InvalidPluginOperationException( - $"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); + $"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); } break; case PluginState.Unloaded: + if (this.instance is not null) + { + throw new InvalidPluginOperationException( + "Plugin should have been unloaded but instance is not cleared"); + } + break; case PluginState.Loading: case PluginState.Unloading: @@ -406,39 +386,30 @@ internal class LocalPlugin : IDisposable // NET8 CHORE // PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile); - this.DalamudInterface = - new DalamudPluginInterface(this, reason); + this.dalamudInterface = new(this, reason); - this.ServiceScope = ioc.GetScope(); - this.ServiceScope.RegisterPrivateScopes(this); // Add this LocalPlugin as a private scope, so services can get it + this.serviceScope = ioc.GetScope(); + this.serviceScope.RegisterPrivateScopes(this); // Add this LocalPlugin as a private scope, so services can get it try { - var forceFrameworkThread = this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1; - var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create(); - this.instance = await newInstanceTask.ConfigureAwait(false); - - async Task Create() => - (IDalamudPlugin)await this.ServiceScope!.CreateAsync(this.pluginType!, this.DalamudInterface!); + this.instance = await CreatePluginInstance( + this.manifest, + this.serviceScope, + this.pluginType, + this.dalamudInterface); + this.State = PluginState.Loaded; + Log.Information("Finished loading {PluginName}", this.InternalName); } catch (Exception ex) - { - Log.Error(ex, "Exception during plugin initialization"); - this.instance = null; - } - - if (this.instance == null) { this.State = PluginState.LoadError; - this.UnloadAndDisposeState(); - Log.Error( - "Error while loading {PluginName}, failed to bind and call the plugin constructor", this.InternalName); - return; + ex, + "Error while loading {PluginName}, failed to bind and call the plugin constructor", + this.InternalName); + await this.ClearAndDisposeAllResources(PluginLoaderDisposalMode.ImmediateDispose); } - - this.State = PluginState.Loaded; - Log.Information("Finished loading {PluginName}", this.InternalName); } catch (Exception ex) { @@ -462,14 +433,10 @@ internal class LocalPlugin : IDisposable /// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay /// in the plugin list until it has been actually disposed. /// - /// Unload while reloading. - /// Wait before disposing loader. + /// How to dispose loader. /// The task. - public async Task UnloadAsync(bool reloading = false, bool waitBeforeLoaderDispose = true) + public async Task UnloadAsync(PluginLoaderDisposalMode disposalMode = PluginLoaderDisposalMode.WaitBeforeDispose) { - var configuration = Service.Get(); - var framework = Service.GetNullable(); - await this.pluginLoadStateLock.WaitAsync(); try { @@ -498,31 +465,10 @@ internal class LocalPlugin : IDisposable this.State = PluginState.Unloading; Log.Information("Unloading {PluginName}", this.InternalName); - try - { - if (this.manifest.CanUnloadAsync || framework == null) - this.instance?.Dispose(); - else - await framework.RunOnFrameworkThread(() => this.instance?.Dispose()).ConfigureAwait(false); - } - catch (Exception e) + if (await this.ClearAndDisposeAllResources(disposalMode) is { } ex) { this.State = PluginState.UnloadError; - Log.Error(e, "Could not unload {PluginName}, error in plugin dispose", this.InternalName); - return; - } - finally - { - this.instance = null; - this.UnloadAndDisposeState(); - - if (!reloading) - { - if (waitBeforeLoaderDispose && this.loader != null) - await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault); - this.loader?.Dispose(); - this.loader = null; - } + throw ex; } this.State = PluginState.Unloaded; @@ -549,7 +495,7 @@ internal class LocalPlugin : IDisposable { // Don't unload if we're a dev plugin and have an unload error, this is a bad idea but whatever if (this.IsDev && this.State != PluginState.UnloadError) - await this.UnloadAsync(true); + await this.UnloadAsync(PluginLoaderDisposalMode.None); await this.LoadAsync(PluginLoadReason.Reload, true); } @@ -617,6 +563,26 @@ internal class LocalPlugin : IDisposable { } + /// Creates a new instance of the plugin. + /// Plugin manifest. + /// Service scope. + /// Type of the plugin main class. + /// Instance of . + /// A new instance of the plugin. + private static async Task CreatePluginInstance( + LocalPluginManifest manifest, + IServiceScope scope, + Type type, + DalamudPluginInterface dalamudInterface) + { + var framework = await Service.GetAsync(); + var forceFrameworkThread = manifest.LoadSync && manifest.LoadRequiredState is 0 or 1; + var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create(); + return await newInstanceTask.ConfigureAwait(false); + + async Task Create() => (IDalamudPlugin)await scope.CreateAsync(type, dalamudInterface); + } + private static void SetupLoaderConfig(LoaderConfig config) { config.IsUnloadable = true; @@ -688,18 +654,110 @@ internal class LocalPlugin : IDisposable } } - private void UnloadAndDisposeState() + /// Clears and disposes all resources associated with the plugin instance. + /// Whether to clear and dispose . + /// Exceptions, if any occurred. + private async Task ClearAndDisposeAllResources(PluginLoaderDisposalMode disposalMode) { - if (this.instance != null) - throw new InvalidOperationException("Plugin instance should be disposed at this point"); + List? exceptions = null; + Log.Verbose( + "{name}({id}): {fn}(disposalMode={disposalMode})", + this.InternalName, + this.EffectiveWorkingPluginId, + nameof(this.ClearAndDisposeAllResources), + disposalMode); - this.DalamudInterface?.Dispose(); - this.DalamudInterface = null; - - this.ServiceScope?.Dispose(); - this.ServiceScope = null; + // Clear the plugin instance first. + if (!await AttemptCleanup( + nameof(this.instance), + Interlocked.Exchange(ref this.instance, null), + this.manifest, + static async (inst, manifest) => + { + var framework = Service.GetNullable(); + if (manifest.CanUnloadAsync || framework is null) + inst.Dispose(); + else + await framework.RunOnFrameworkThread(inst.Dispose).ConfigureAwait(false); + })) + { + // Plugin was not loaded; loader is not referenced anyway, so no need to wait. + disposalMode = PluginLoaderDisposalMode.ImmediateDispose; + } + // Fields below are expected to be alive until the plugin is (attempted) disposed. + // Clear them after this point. this.pluginType = null; this.pluginAssembly = null; + + await AttemptCleanup( + nameof(this.serviceScope), + Interlocked.Exchange(ref this.serviceScope, null), + 0, + static (x, _) => x.DisposeAsync()); + + await AttemptCleanup( + nameof(this.dalamudInterface), + Interlocked.Exchange(ref this.dalamudInterface, null), + 0, + static (x, _) => + { + x.Dispose(); + return ValueTask.CompletedTask; + }); + + if (disposalMode != PluginLoaderDisposalMode.None) + { + await AttemptCleanup( + nameof(this.loader), + Interlocked.Exchange(ref this.loader, null), + disposalMode == PluginLoaderDisposalMode.WaitBeforeDispose + ? Service.Get().PluginWaitBeforeFree ?? + PluginManager.PluginWaitBeforeFreeDefault + : 0, + static async (ldr, waitBeforeDispose) => + { + // Just in case plugins still have tasks running that they didn't cancel when they should have, + // give them some time to complete it. + // This helps avoid plugins being reloaded from conflicting with itself of previous instance. + await Task.Delay(waitBeforeDispose); + + ldr.Dispose(); + }); + } + + return exceptions is not null + ? (AggregateException)ExceptionDispatchInfo.SetCurrentStackTrace(new AggregateException(exceptions)) + : null; + + async ValueTask AttemptCleanup( + string name, + T? what, + TContext context, + Func cb) + where T : class + { + if (what is null) + return false; + + try + { + await cb.Invoke(what, context); + Log.Verbose("{name}({id}): {what} disposed", this.InternalName, this.EffectiveWorkingPluginId, name); + } + catch (Exception ex) + { + exceptions ??= []; + exceptions.Add(ex); + Log.Error( + ex, + "{name}({id}): Failed to dispose {what}", + this.InternalName, + this.EffectiveWorkingPluginId, + name); + } + + return true; + } } } diff --git a/Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs b/Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs new file mode 100644 index 000000000..495205089 --- /dev/null +++ b/Dalamud/Plugin/Internal/Types/PluginLoaderDisposalMode.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +using Dalamud.Plugin.Internal.Loader; + +namespace Dalamud.Plugin.Internal.Types; + +/// Specify how to dispose . +internal enum PluginLoaderDisposalMode +{ + /// Do not dispose the plugin loader. + None, + + /// Whether to wait a few before disposing the loader, just in case there are s + /// from the plugin that are still running. + WaitBeforeDispose, + + /// Immediately dispose the plugin loader. + ImmediateDispose, +} diff --git a/Dalamud/Utility/TaskExtensions.cs b/Dalamud/Utility/TaskExtensions.cs index a65956325..2855511a1 100644 --- a/Dalamud/Utility/TaskExtensions.cs +++ b/Dalamud/Utility/TaskExtensions.cs @@ -63,6 +63,21 @@ public static class TaskExtensions #pragma warning restore RS0030 } + /// Ignores any exceptions thrown from the task. + /// Task to ignore exceptions. + /// A task that completes when completes in any state. + public static async Task SuppressException(this Task task) + { + try + { + await task; + } + catch + { + // ignore + } + } + private static bool IsWaitingValid(Task task) { // In the case the task has been started with the LongRunning flag, it will not be in the TPL thread pool and we can allow waiting regardless. From 9e95ab8ff72730131485bcc94b749d031be14a86 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sun, 18 Aug 2024 08:12:00 +0900 Subject: [PATCH 038/375] fix log --- Dalamud/Game/Gui/Dtr/DtrBar.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 4d328d78c..f37b3addc 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -157,7 +157,11 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.entriesLock.EnterWriteLock(); var entry = new DtrBarEntry(this.configuration, title, null) { Text = text, OwnerPlugin = plugin }; this.entries.Add(entry); - Log.Debug("Adding entry: {what}; owner: {owner}", title, plugin); + Log.Debug( + "Adding entry: {what}; owner: {owner}({id})", + title, + plugin?.InternalName, + plugin?.EffectiveWorkingPluginId); // Add the entry to the end of the order list, if it's not there already. var dtrOrder = this.configuration.DtrOrder ??= []; From 6db7acca201a0447526f39596afa48fc30a444df Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sun, 18 Aug 2024 08:18:30 +0900 Subject: [PATCH 039/375] Do not throw already unloaded exception on exit --- Dalamud/Plugin/Internal/PluginManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index b42b51525..f390664b6 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -396,7 +396,9 @@ internal class PluginManager : IInternalDisposableService await plugin.UnloadAsync(PluginLoaderDisposalMode.None).SuppressException(); // Unload plugins that can be unloaded from any thread. - await Task.WhenAll(disposablePlugins.Select(plugin => plugin.UnloadAsync(PluginLoaderDisposalMode.None))) + await Task.WhenAll( + disposablePlugins.Where(plugin => plugin.Manifest.CanUnloadAsync) + .Select(plugin => plugin.UnloadAsync(PluginLoaderDisposalMode.None))) .SuppressException(); // Just in case plugins still have tasks running that they didn't cancel when they should have, From 0acab6935a30b9c30c2684d6dda042af9d6cb2fe Mon Sep 17 00:00:00 2001 From: James Keesey Date: Sat, 17 Aug 2024 18:03:42 -0700 Subject: [PATCH 040/375] Added two new helpers, one for PushItemWidth and the other for PushTextWrapPos. --- Dalamud/Interface/Utility/Raii/EndObjects.cs | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Utility/Raii/EndObjects.cs b/Dalamud/Interface/Utility/Raii/EndObjects.cs index 401af5415..261c071c3 100644 --- a/Dalamud/Interface/Utility/Raii/EndObjects.cs +++ b/Dalamud/Interface/Utility/Raii/EndObjects.cs @@ -77,6 +77,30 @@ public static partial class ImRaii return new EndUnconditionally(ImGui.EndTooltip, true); } + /// + /// Pushes the item width for the next widget and returns an IDisposable that pops + /// the width when done. + /// + /// The width to set the next widget to. + /// An for use in a using statement. + public static IEndObject ItemWidth(float width) + { + ImGui.PushItemWidth(width); + return new EndUnconditionally(ImGui.PopItemWidth, true); + } + + /// + /// Pushes the item wrapping width for the next string written and returns an IDisposable + /// that pops the wrap width when done. + /// + /// The wrap width to set the next text written to. + /// An for use in a using statement. + public static IEndObject TextWrapPos(float pos) + { + ImGui.PushTextWrapPos(pos); + return new EndUnconditionally(ImGui.PopTextWrapPos, true); + } + public static IEndObject ListBox(string label) => new EndConditionally(ImGui.EndListBox, ImGui.BeginListBox(label)); @@ -110,7 +134,7 @@ public static partial class ImRaii public static unsafe IEndObject TabItem(string label, ImGuiTabItemFlags flags) { ArgumentNullException.ThrowIfNull(label); - + // One-off for now, we should make this into a generic solution if we need it more often const int labelMaxAlloc = 2048; From 981387504bf8751d6830ffc198477088e1615d21 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:57:36 +0900 Subject: [PATCH 041/375] Avoid capturing local variables in TitleBarButton.Click closure (#2019) --- Dalamud/Interface/Windowing/Window.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index 7bf5a0363..d2a51235d 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -351,7 +351,7 @@ public abstract class Window } } - var additionsPopupName = "WindowSystemContextActions"; + const string additionsPopupName = "WindowSystemContextActions"; var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) && !flags.HasFlag(ImGuiWindowFlags.NoTitleBar); var showAdditions = (this.AllowPinning || this.AllowClickthrough) && From 88228102298956f019ab4d2d6b3d98077317346b Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 25 Aug 2024 22:06:21 +0900 Subject: [PATCH 042/375] DalamudAssetManager: avoid locks and lookups (#2015) * Made DalamudAsset-to-something tables into arrays from dictionaries. Number of items in the DalamudAsset enum aren't many, and the numbers are small enough that implementing lookup tables as arrays aren't wasting much memory space. * Removed locking from asset accessors, while still guaranteeing that the load operation happens only once per asset. * ISharedImmediateTexture: made it not even access assets if textures are available. --- Dalamud/DalamudAsset.cs | 1 + .../SeStringDrawResult.cs | 2 +- .../SharedImmediateTexture.cs | 11 +- .../TextureWraps/ForwardingTextureWrap.cs | 6 +- .../Internal/DisposeSuppressingTextureWrap.cs | 13 +- .../Storage/Assets/DalamudAssetExtensions.cs | 51 ++-- Dalamud/Storage/Assets/DalamudAssetManager.cs | 227 ++++++++++-------- Dalamud/Storage/Assets/DalamudAssetPurpose.cs | 2 +- 8 files changed, 168 insertions(+), 145 deletions(-) diff --git a/Dalamud/DalamudAsset.cs b/Dalamud/DalamudAsset.cs index 0d91a4b75..27771116e 100644 --- a/Dalamud/DalamudAsset.cs +++ b/Dalamud/DalamudAsset.cs @@ -9,6 +9,7 @@ namespace Dalamud; /// Any asset can cease to exist at any point, even if the enum value exists.
/// Either ship your own assets, or be prepared for errors. ///
+// Implementation notes: avoid specifying numbers too high here. Lookup table is currently implemented as an array. public enum DalamudAsset { /// diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs index 905e8ed23..f9dae288b 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawResult.cs @@ -5,7 +5,7 @@ using Dalamud.Game.Text.SeStringHandling; namespace Dalamud.Interface.ImGuiSeStringRenderer; -/// Represents the result of n rendered interactable SeString. +/// Represents the result of a rendered interactable SeString. public ref struct SeStringDrawResult { private Payload? lazyPayload; diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs index c71d83fe8..5f9925ed3 100644 --- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs +++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/SharedImmediateTexture.cs @@ -171,17 +171,14 @@ internal abstract class SharedImmediateTexture /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IDalamudTextureWrap GetWrapOrEmpty() => this.GetWrapOrDefault(Service.Get().Empty4X4); + public IDalamudTextureWrap GetWrapOrEmpty() => + this.TryGetWrap(out var texture, out _) ? texture : Service.Get().Empty4X4; /// [return: NotNullIfNotNull(nameof(defaultWrap))] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap) - { - if (!this.TryGetWrap(out var texture, out _)) - texture = null; - return texture ?? defaultWrap; - } + public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap) => + this.TryGetWrap(out var texture, out _) ? texture : defaultWrap; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs index 8b0516e03..7d6ff8580 100644 --- a/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs +++ b/Dalamud/Interface/Textures/TextureWraps/ForwardingTextureWrap.cs @@ -37,7 +37,11 @@ public abstract class ForwardingTextureWrap : IDalamudTextureWrap public Vector2 Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.Width, this.Height); + get + { + var wrap = this.GetWrap(); + return new(wrap.Width, wrap.Height); + } } /// diff --git a/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs b/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs index 0dd5c9f25..3bb984be8 100644 --- a/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs +++ b/Dalamud/Interface/Textures/TextureWraps/Internal/DisposeSuppressingTextureWrap.cs @@ -1,20 +1,13 @@ -using Dalamud.Interface.Internal; - namespace Dalamud.Interface.Textures.TextureWraps.Internal; /// A texture wrap that ignores calls. -internal class DisposeSuppressingTextureWrap : ForwardingTextureWrap +/// The inner wrap. +internal class DisposeSuppressingTextureWrap(IDalamudTextureWrap innerWrap) : ForwardingTextureWrap { - private readonly IDalamudTextureWrap innerWrap; - - /// Initializes a new instance of the class. - /// The inner wrap. - public DisposeSuppressingTextureWrap(IDalamudTextureWrap wrap) => this.innerWrap = wrap; - /// protected override bool TryGetWrap(out IDalamudTextureWrap? wrap) { - wrap = this.innerWrap; + wrap = innerWrap; return true; } } diff --git a/Dalamud/Storage/Assets/DalamudAssetExtensions.cs b/Dalamud/Storage/Assets/DalamudAssetExtensions.cs index 9052a1c6d..4fe72240b 100644 --- a/Dalamud/Storage/Assets/DalamudAssetExtensions.cs +++ b/Dalamud/Storage/Assets/DalamudAssetExtensions.cs @@ -1,46 +1,37 @@ -using System.Collections.Frozen; -using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Dalamud.Utility; namespace Dalamud.Storage.Assets; -/// -/// Extension methods for . -/// +/// Extension methods for . public static class DalamudAssetExtensions { - private static readonly FrozenDictionary AttributeCache = CreateCache(); + private static readonly DalamudAssetAttribute EmptyAttribute = new(DalamudAssetPurpose.Empty, null, false); + private static readonly DalamudAssetAttribute[] AttributeCache = CreateCache(); - /// - /// Gets the purpose. - /// + /// Gets the purpose. /// The asset. /// The purpose. - public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) - { - return GetAssetAttribute(asset)?.Purpose ?? DalamudAssetPurpose.Empty; - } + public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) => asset.GetAssetAttribute().Purpose; - /// - /// Gets the attribute. - /// + /// Gets the attribute. /// The asset. /// The attribute. - internal static DalamudAssetAttribute? GetAssetAttribute(this DalamudAsset asset) + internal static DalamudAssetAttribute GetAssetAttribute(this DalamudAsset asset) => + (int)asset < 0 || (int)asset >= AttributeCache.Length + ? EmptyAttribute + : Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(AttributeCache), (int)asset); + + private static DalamudAssetAttribute[] CreateCache() { - return AttributeCache.GetValueOrDefault(asset); - } - - private static FrozenDictionary CreateCache() - { - var dict = new Dictionary(); - - foreach (var asset in Enum.GetValues()) - { - dict.Add(asset, asset.GetAttribute()); - } - - return dict.ToFrozenDictionary(); + var assets = Enum.GetValues(); + var table = new DalamudAssetAttribute[assets.Max(x => (int)x) + 1]; + table.AsSpan().Fill(EmptyAttribute); + foreach (var asset in assets) + table[(int)asset] = asset.GetAttribute() ?? EmptyAttribute; + return table; } } diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index f750de64a..6fe26b90b 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps.Internal; @@ -36,10 +37,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud private const int DownloadAttemptCount = 10; private const int RenameAttemptCount = 10; - private readonly object syncRoot = new(); private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new(); - private readonly Dictionary?> fileStreams; - private readonly Dictionary?> textureWraps; + private readonly Task?[] fileStreams; + private readonly Task?[] textureWraps; private readonly Dalamud dalamud; private readonly HappyHttpClient httpClient; private readonly string localSourceDirectory; @@ -59,18 +59,18 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud Directory.CreateDirectory(this.localSourceDirectory); this.scopedFinalizer.Add(this.cancellationTokenSource = new()); - this.fileStreams = Enum.GetValues().ToDictionary(x => x, _ => (Task?)null); - this.textureWraps = Enum.GetValues().ToDictionary(x => x, _ => (Task?)null); + var numDalamudAssetSlots = Enum.GetValues().Max(x => (int)x) + 1; + this.fileStreams = new Task?[numDalamudAssetSlots]; + this.textureWraps = new Task?[numDalamudAssetSlots]; // Block until all the required assets to be ready. var loadTimings = Timings.Start("DAM LoadAll"); registerStartupBlocker( Task.WhenAll( Enum.GetValues() - .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAssetAttribute()?.Required is true) + .Where(static x => x.GetAssetAttribute() is { Required: true, Data: null }) .Select(this.CreateStreamAsync) - .Select(x => x.ToContentDisposedTask())) + .Select(static x => x.ToContentDisposedTask())) .ContinueWith( r => { @@ -80,13 +80,13 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud .Unwrap(), "Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available."); + // Begin preloading optional(non-required) assets. Task.WhenAll( Enum.GetValues() - .Where(x => x is not DalamudAsset.Empty4X4) - .Where(x => x.GetAssetAttribute()?.Required is false) + .Where(static x => x.GetAssetAttribute() is { Required: false, Data: null }) .Select(this.CreateStreamAsync) - .Select(x => x.ToContentDisposedTask(true))) - .ContinueWith(r => Log.Verbose($"Optional assets load state: {r}")); + .Select(static x => x.ToContentDisposedTask(true))) + .ContinueWith(static r => Log.Verbose($"Optional assets load state: {r}")); } /// @@ -98,77 +98,97 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud /// void IInternalDisposableService.DisposeService() { - lock (this.syncRoot) - { - if (this.isDisposed) - return; + if (this.isDisposed) + return; - this.isDisposed = true; - } + this.isDisposed = true; this.cancellationTokenSource.Cancel(); Task.WaitAll( Array.Empty() - .Concat(this.fileStreams.Values) - .Concat(this.textureWraps.Values) - .Where(x => x is not null) - .Select(x => x.ContinueWith(r => { _ = r.Exception; })) - .ToArray()); + .Concat(this.fileStreams) + .Concat(this.textureWraps) + .Where(static x => x is not null) + .Select(static x => x.ContinueWith(static r => _ = r.Exception)) + .ToArray()); this.scopedFinalizer.Dispose(); } /// [Pure] public bool IsStreamImmediatelyAvailable(DalamudAsset asset) => - asset.GetAssetAttribute()?.Data is not null - || this.fileStreams[asset]?.IsCompletedSuccessfully is true; + asset.GetAssetAttribute().Data is not null + || this.fileStreams[(int)asset]?.IsCompletedSuccessfully is true; /// [Pure] - public Stream CreateStream(DalamudAsset asset) - { - var s = this.CreateStreamAsync(asset); - s.Wait(); - if (s.IsCompletedSuccessfully) - return s.Result; - if (s.Exception is not null) - throw new AggregateException(s.Exception.InnerExceptions); - throw new OperationCanceledException(); - } + public Stream CreateStream(DalamudAsset asset) => this.CreateStreamAsync(asset).Result; /// [Pure] public Task CreateStreamAsync(DalamudAsset asset) { - if (asset.GetAssetAttribute() is { Data: { } rawData }) - return Task.FromResult(new MemoryStream(rawData, false)); + ObjectDisposedException.ThrowIf(this.isDisposed, this); - Task task; - lock (this.syncRoot) + var attribute = asset.GetAssetAttribute(); + + // The corresponding asset does not exist. + if (attribute.Purpose is DalamudAssetPurpose.Empty) + return Task.FromException(new ArgumentOutOfRangeException(nameof(asset), asset, null)); + + // Special case: raw data is specified from asset definition. + if (attribute.Data is not null) + return Task.FromResult(new MemoryStream(attribute.Data, false)); + + // Range is guaranteed to be satisfied if the asset has a purpose; get the slot for the stream task. + ref var streamTaskRef = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(this.fileStreams), (int)asset); + + // The stream task is already set. + if (streamTaskRef is not null) + return CloneFileStreamAsync(streamTaskRef); + + var tcs = new TaskCompletionSource(); + if (Interlocked.CompareExchange(ref streamTaskRef, tcs.Task, null) is not { } streamTask) { - if (this.isDisposed) - throw new ObjectDisposedException(nameof(DalamudAssetManager)); - - task = this.fileStreams[asset] ??= CreateInnerAsync(); + // The stream task has just been set. Actually start the operation. + // In case it did not correctly finish the task in tcs, set the task to a failed state. + // Do not pass cancellation token here; we always want to touch tcs. + Task.Run( + async () => + { + try + { + tcs.SetResult(await CreateInnerAsync(this, asset)); + } + catch (Exception e) + { + tcs.SetException(e); + } + }, + default); + return CloneFileStreamAsync(tcs.Task); } - return this.TransformImmediate( - task, - x => (Stream)new FileStream( - x.Name, + // Discard the new task, and return the already created task. + tcs.SetCanceled(); + return CloneFileStreamAsync(streamTask); + + static async Task CloneFileStreamAsync(Task fileStreamTask) => + new FileStream( + (await fileStreamTask).Name, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, - FileOptions.Asynchronous | FileOptions.SequentialScan)); + FileOptions.Asynchronous | FileOptions.SequentialScan); - async Task CreateInnerAsync() + static async Task CreateInnerAsync(DalamudAssetManager dam, DalamudAsset asset) { string path; List exceptions = null; - foreach (var name in asset.GetAttributes().Select(x => x.FileName)) + foreach (var name in asset.GetAttributes().Select(static x => x.FileName)) { - if (!File.Exists(path = Path.Combine(this.dalamud.AssetDirectory.FullName, name))) + if (!File.Exists(path = Path.Combine(dam.dalamud.AssetDirectory.FullName, name))) continue; try @@ -177,12 +197,12 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud } catch (Exception e) when (e is not OperationCanceledException) { - exceptions ??= new(); + exceptions ??= []; exceptions.Add(e); } } - if (File.Exists(path = Path.Combine(this.localSourceDirectory, asset.ToString()))) + if (File.Exists(path = Path.Combine(dam.localSourceDirectory, asset.ToString()))) { try { @@ -190,7 +210,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud } catch (Exception e) when (e is not OperationCanceledException) { - exceptions ??= new(); + exceptions ??= []; exceptions.Add(e); } } @@ -211,9 +231,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud await using (var tempPathStream = File.Open(tempPath, FileMode.Create, FileAccess.Write)) { await url.DownloadAsync( - this.httpClient.SharedHttpClient, + dam.httpClient.SharedHttpClient, tempPathStream, - this.cancellationTokenSource.Token); + dam.cancellationTokenSource.Token); } for (var j = RenameAttemptCount; ; j--) @@ -232,7 +252,7 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud nameof(DalamudAssetManager), asset, j); - await Task.Delay(1000, this.cancellationTokenSource.Token); + await Task.Delay(1000, dam.cancellationTokenSource.Token); continue; } @@ -255,14 +275,18 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud nameof(DalamudAssetManager), asset, delay); - await Task.Delay(delay * 1000, this.cancellationTokenSource.Token); + await Task.Delay(delay * 1000, dam.cancellationTokenSource.Token); } throw new FileNotFoundException($"Failed to load the asset {asset}.", asset.ToString()); } - catch (Exception e) when (e is not OperationCanceledException) + catch (OperationCanceledException) { - exceptions ??= new(); + throw; + } + catch (Exception e) + { + exceptions ??= []; exceptions.Add(e); try { @@ -272,9 +296,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud { // don't care } - } - throw new AggregateException(exceptions); + throw new AggregateException(exceptions); + } } } @@ -296,33 +320,63 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud [Pure] public Task GetDalamudTextureWrapAsync(DalamudAsset asset) { - var purpose = asset.GetPurpose(); - if (purpose is not DalamudAssetPurpose.TextureFromPng and not DalamudAssetPurpose.TextureFromRaw) - throw new ArgumentOutOfRangeException(nameof(asset), asset, "The asset cannot be taken as a Texture2D."); + ObjectDisposedException.ThrowIf(this.isDisposed, this); - Task task; - lock (this.syncRoot) + // Check if asset is a texture asset. + if (asset.GetPurpose() is not DalamudAssetPurpose.TextureFromPng and not DalamudAssetPurpose.TextureFromRaw) { - if (this.isDisposed) - throw new ObjectDisposedException(nameof(DalamudAssetManager)); - - task = this.textureWraps[asset] ??= CreateInnerAsync(); + return Task.FromException( + new ArgumentOutOfRangeException( + nameof(asset), + asset, + "The asset does not exist or cannot be taken as a Texture2D.")); } - return task; + // Range is guaranteed to be satisfied if the asset has a purpose; get the slot for the wrap task. + ref var wrapTaskRef = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(this.textureWraps), (int)asset); - async Task CreateInnerAsync() + // The wrap task is already set. + if (wrapTaskRef is not null) + return wrapTaskRef; + + var tcs = new TaskCompletionSource(); + if (Interlocked.CompareExchange(ref wrapTaskRef, tcs.Task, null) is not { } wrapTask) + { + // The stream task has just been set. Actually start the operation. + // In case it did not correctly finish the task in tcs, set the task to a failed state. + // Do not pass cancellation token here; we always want to touch tcs. + Task.Run( + async () => + { + try + { + tcs.SetResult(await CreateInnerAsync(this, asset)); + } + catch (Exception e) + { + tcs.SetException(e); + } + }, + default); + return tcs.Task; + } + + // Discard the new task, and return the already created task. + tcs.SetCanceled(); + return wrapTask; + + static async Task CreateInnerAsync(DalamudAssetManager dam, DalamudAsset asset) { var buf = Array.Empty(); try { var tm = await Service.GetAsync(); - await using var stream = await this.CreateStreamAsync(asset); + await using var stream = await dam.CreateStreamAsync(asset); var length = checked((int)stream.Length); buf = ArrayPool.Shared.Rent(length); stream.ReadExactly(buf, 0, length); var name = $"{nameof(DalamudAsset)}[{Enum.GetName(asset)}]"; - var image = purpose switch + var texture = asset.GetPurpose() switch { DalamudAssetPurpose.TextureFromPng => await tm.CreateFromImageAsync(buf, name), DalamudAssetPurpose.TextureFromRaw => @@ -330,17 +384,9 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud ? await tm.CreateFromRawAsync(raw.Specification, buf, name) : throw new InvalidOperationException( "TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."), - _ => null, + _ => throw new InvalidOperationException(), // cannot happen }; - var disposeDeferred = - this.scopedFinalizer.Add(image) - ?? throw new InvalidOperationException("Something went wrong very badly"); - return new DisposeSuppressingTextureWrap(disposeDeferred); - } - catch (Exception e) - { - Log.Error(e, "[{name}] Failed to load {asset}.", nameof(DalamudAssetManager), asset); - throw; + return new DisposeSuppressingTextureWrap(dam.scopedFinalizer.Add(texture)); } finally { @@ -348,13 +394,4 @@ internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamud } } } - - private Task TransformImmediate(Task task, Func transformer) - { - if (task.IsCompletedSuccessfully) - return Task.FromResult(transformer(task.Result)); - if (task.Exception is { } exc) - return Task.FromException(exc); - return task.ContinueWith(_ => this.TransformImmediate(task, transformer)).Unwrap(); - } } diff --git a/Dalamud/Storage/Assets/DalamudAssetPurpose.cs b/Dalamud/Storage/Assets/DalamudAssetPurpose.cs index b059cb3d6..e6c7bd920 100644 --- a/Dalamud/Storage/Assets/DalamudAssetPurpose.cs +++ b/Dalamud/Storage/Assets/DalamudAssetPurpose.cs @@ -6,7 +6,7 @@ namespace Dalamud.Storage.Assets; public enum DalamudAssetPurpose { /// - /// The asset has no purpose. + /// The asset has no purpose, and is not valid and/or not accessible. /// Empty = 0, From 84fd82aa80386f9b3d04d11044cd57f82d38a3e6 Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 25 Aug 2024 15:06:39 +0200 Subject: [PATCH 043/375] Add .Pop() to the doc about fontHandle examples (#2021) --- Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs index 0a9e9072e..803f6b82c 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs @@ -72,6 +72,7 @@ public interface IFontHandle : IDisposable /// /// fontHandle.Push(); /// ImGui.TextUnformatted("Test 2"); + /// fontHandle.Pop(); /// /// Push a font between two choices. /// From cab84a8e315160322595bdf6ce49d775d9da7c64 Mon Sep 17 00:00:00 2001 From: Pascal Date: Sun, 25 Aug 2024 15:07:32 +0200 Subject: [PATCH 044/375] feat: add new 7.0 BitmapFontIcons & fix few others (#2006) add new 7.0 BitmapFontIcons - Tuiyolal flag - Viper job icon - Pictomancer job icon - Moogle treasure trove fix some outdated icons --- .../Text/SeStringHandling/BitmapFontIcon.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index cb87fc343..7db528f6a 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -488,7 +488,12 @@ public enum BitmapFontIcon : uint /// /// A blue star icon with an orange exclamation mark. /// - BlueStarProblem = 121, + BlueStarProblem = 122, + + /// + /// The PlayStation Plus icon. + /// + PlayStationPlus = 123, /// /// The Disconnecting icon. @@ -578,12 +583,12 @@ public enum BitmapFontIcon : uint /// /// The Alchemist icon. /// - Alchemist = 131, + Alchemist = 141, /// /// The Culinarian icon. /// - Culinarian = 132, + Culinarian = 142, /// /// The Miner icon. @@ -714,4 +719,24 @@ public enum BitmapFontIcon : uint /// The Waiting For Duty Finder icon. /// WaitingForDutyFinder = 168, + + /// + /// The Tural flag icon. + /// + Tural = 169, + + /// + /// The Viper icon. + /// + Viper = 170, + + /// + /// The Pictomancer icon. + /// + Pictomancer = 171, + + /// + /// The Venture Delivery Moogle icon. + /// + VentureDeliveryMoogle = 172, } From 27ef05325282d6037300a87900d26bfca9212863 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sun, 25 Aug 2024 22:23:58 +0900 Subject: [PATCH 045/375] Update docs --- Dalamud/Utility/TaskExtensions.cs | 42 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/Dalamud/Utility/TaskExtensions.cs b/Dalamud/Utility/TaskExtensions.cs index 2855511a1..f03a9e9e9 100644 --- a/Dalamud/Utility/TaskExtensions.cs +++ b/Dalamud/Utility/TaskExtensions.cs @@ -63,20 +63,34 @@ public static class TaskExtensions #pragma warning restore RS0030 } - /// Ignores any exceptions thrown from the task. - /// Task to ignore exceptions. - /// A task that completes when completes in any state. - public static async Task SuppressException(this Task task) - { - try - { - await task; - } - catch - { - // ignore - } - } + /// Creates a new that resolves when completes, ignoring + /// exceptions thrown from the task, if any. + /// Task to await and ignore exceptions on failure. + /// A that completes successfully when completes in any state. + /// + /// Awaiting the returned will always complete without exceptions, but awaiting + /// will throw exceptions if it fails, even after this function is called. + /// + /// + /// Wrong use of this function + /// + /// var task = TaskThrowingException(); + /// task.SuppressException(); + /// await TaskThrowingException(); // This line will throw. + /// + /// + /// + /// Correct use of this function, if waiting for the task + /// await TaskThrowingException().SuppressException(); + /// + /// + /// Fire-and-forget
+ /// If not interested in the execution state of Task (fire-and-forget), simply calling this function will do. + /// This function consumes the task's exception, so that it won't bubble up on later garbage collection. + /// TaskThrowingException().SuppressException(); + ///
+ ///
+ public static Task SuppressException(this Task task) => task.ContinueWith(static r => r.Exception); private static bool IsWaitingValid(Task task) { From a7cb89e2a49103e99e237da3ca8bf9a5357c5d95 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:28:51 +0200 Subject: [PATCH 046/375] Update ClientStructs (#2020) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 87faf9414..9c66d973e 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 87faf94145fd1d8554741e909e3e3d6e8c2b490f +Subproject commit 9c66d973ea84a85ece1b578498c5ae1403b35065 From 7cbc76d64d138eefb71739aaf8100eb432ea52fb Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 27 Aug 2024 06:07:06 +0200 Subject: [PATCH 047/375] Update ClientStructs (#2028) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 9c66d973e..7c5f04e34 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 9c66d973ea84a85ece1b578498c5ae1403b35065 +Subproject commit 7c5f04e346067f7a316ad9072fb8260122ba80f0 From 42a10a1215f4c9f7c8e2f7cf958d579b496457f2 Mon Sep 17 00:00:00 2001 From: srkizer Date: Tue, 27 Aug 2024 17:48:28 +0900 Subject: [PATCH 048/375] Fix SestringRenderer sig (#2030) --- .../ImGuiSeStringRenderer/Internal/SeStringRenderer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 380afc6c9..83c777130 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -95,7 +95,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService // _set_purecall_handler(() => *(int*)0 = 0xff14); // _set_invalid_parameter_handler(() => *(int*)0 = 0xff14); var f = sigScanner.ScanText( - "ff 15 ff 0e e3 01 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ??") + 26; + "ff 15 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ??") + 26; fixed (void* p = &this.setInvalidParameterHandler) *(nint*)p = *(int*)f + f + 4; } From 063f58a49a4166725656bc3948f81582cb33590f Mon Sep 17 00:00:00 2001 From: srkizer Date: Wed, 28 Aug 2024 00:33:47 +0900 Subject: [PATCH 049/375] Fix GameNetwork sig (#2031) * Fix GameNetwork sig * Fix SigScanner and add safeguard to prevent bad jmp resolution --- .../Game/Network/GameNetworkAddressResolver.cs | 2 +- Dalamud/Game/SigScanner.cs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs index fc95bae52..69b97c59d 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -21,6 +21,6 @@ internal sealed class GameNetworkAddressResolver : BaseAddressResolver // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); this.ProcessZonePacketDown = sig.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2"); - this.ProcessZonePacketUp = sig.ScanText("E8 ?? ?? ?? ?? 48 83 C4 28 C3 32 C0 48 83 C4 28 C3 CC"); + this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); } } diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 92c230f54..3422848f3 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -276,8 +276,7 @@ public class SigScanner : IDisposable, ISigScanner } } - var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; - var scanRet = Scan(mBase, this.TextSectionSize, signature); + var scanRet = Scan(this.TextSectionBase, this.TextSectionSize, signature); if (this.IsCopy) scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); @@ -285,7 +284,15 @@ public class SigScanner : IDisposable, ISigScanner var insnByte = Marshal.ReadByte(scanRet); if (insnByte == 0xE8 || insnByte == 0xE9) + { scanRet = ReadJmpCallSig(scanRet); + var rel = scanRet - this.Module.BaseAddress; + if (rel < 0 || rel >= this.TextSectionSize) + { + throw new KeyNotFoundException( + $"Signature \"{signature}\" resolved to 0x{rel:X} which is outside .text section. Possible signature conflicts?"); + } + } // If this is below the module, there's bound to be a problem with the sig/resolution... Let's not save it // TODO: THIS IS A HACK! FIX THE ROOT CAUSE! @@ -319,8 +326,9 @@ public class SigScanner : IDisposable, ISigScanner public IEnumerable ScanAllText(string signature, CancellationToken cancellationToken) { var (needle, mask, badShift) = ParseSignature(signature); - var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; - while (mBase < this.TextSectionBase + this.TextSectionSize) + var mBase = this.TextSectionBase; + var mTo = this.TextSectionBase + this.TextSectionSize; + while (mBase < mTo) { cancellationToken.ThrowIfCancellationRequested(); From b2166fb91084f4545e3dcca99f3ec5f2f2cab483 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Tue, 27 Aug 2024 09:39:11 -0700 Subject: [PATCH 050/375] fix: Broken HoverActionKind IDs (#2034) --- Dalamud/Game/Gui/HoverActionKind.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/Gui/HoverActionKind.cs b/Dalamud/Game/Gui/HoverActionKind.cs index 90ff9d46c..4c7ae9166 100644 --- a/Dalamud/Game/Gui/HoverActionKind.cs +++ b/Dalamud/Game/Gui/HoverActionKind.cs @@ -14,35 +14,35 @@ public enum HoverActionKind /// /// A regular action is hovered. /// - Action = 21, + Action = 28, /// /// A general action is hovered. /// - GeneralAction = 23, + GeneralAction = 30, /// /// A companion order type of action is hovered. /// - CompanionOrder = 24, + CompanionOrder = 31, /// /// A main command type of action is hovered. /// - MainCommand = 25, + MainCommand = 32, /// /// An extras command type of action is hovered. /// - ExtraCommand = 26, + ExtraCommand = 33, /// /// A pet order type of action is hovered. /// - PetOrder = 28, + PetOrder = 35, /// /// A trait is hovered. /// - Trait = 29, + Trait = 36, } From 27ae3686906a9d0f89dd8774617ee907ce6b725e Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 27 Aug 2024 20:37:09 +0200 Subject: [PATCH 051/375] build: 10.0.0.14 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 7abc6f088..a0322aab1 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.13 + 10.0.0.14 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 9de58b0cb9cb0aa0bfc3571b07902538aa25b842 Mon Sep 17 00:00:00 2001 From: Infi Date: Wed, 28 Aug 2024 00:46:15 +0200 Subject: [PATCH 052/375] Change type of JobFlags to ulong (#2035) --- .../Game/Gui/PartyFinder/Types/JobFlags.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs index ff2a3ce2a..475892205 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs @@ -4,160 +4,160 @@ namespace Dalamud.Game.Gui.PartyFinder.Types; /// Job flags for the class. ///
[Flags] -public enum JobFlags +public enum JobFlags : ulong { /// /// Gladiator (GLD). /// - Gladiator = 1 << 1, + Gladiator = 1ul << 1, /// /// Pugilist (PGL). /// - Pugilist = 1 << 2, + Pugilist = 1ul << 2, /// /// Marauder (MRD). /// - Marauder = 1 << 3, + Marauder = 1ul << 3, /// /// Lancer (LNC). /// - Lancer = 1 << 4, + Lancer = 1ul << 4, /// /// Archer (ARC). /// - Archer = 1 << 5, + Archer = 1ul << 5, /// /// Conjurer (CNJ). /// - Conjurer = 1 << 6, + Conjurer = 1ul << 6, /// /// Thaumaturge (THM). /// - Thaumaturge = 1 << 7, + Thaumaturge = 1ul << 7, /// /// Paladin (PLD). /// - Paladin = 1 << 8, + Paladin = 1ul << 8, /// /// Monk (MNK). /// - Monk = 1 << 9, + Monk = 1ul << 9, /// /// Warrior (WAR). /// - Warrior = 1 << 10, + Warrior = 1ul << 10, /// /// Dragoon (DRG). /// - Dragoon = 1 << 11, + Dragoon = 1ul << 11, /// /// Bard (BRD). /// - Bard = 1 << 12, + Bard = 1ul << 12, /// /// White mage (WHM). /// - WhiteMage = 1 << 13, + WhiteMage = 1ul << 13, /// /// Black mage (BLM). /// - BlackMage = 1 << 14, + BlackMage = 1ul << 14, /// /// Arcanist (ACN). /// - Arcanist = 1 << 15, + Arcanist = 1ul << 15, /// /// Summoner (SMN). /// - Summoner = 1 << 16, + Summoner = 1ul << 16, /// /// Scholar (SCH). /// - Scholar = 1 << 17, + Scholar = 1ul << 17, /// /// Rogue (ROG). /// - Rogue = 1 << 18, + Rogue = 1ul << 18, /// /// Ninja (NIN). /// - Ninja = 1 << 19, + Ninja = 1ul << 19, /// /// Machinist (MCH). /// - Machinist = 1 << 20, + Machinist = 1ul << 20, /// /// Dark Knight (DRK). /// - DarkKnight = 1 << 21, + DarkKnight = 1ul << 21, /// /// Astrologian (AST). /// - Astrologian = 1 << 22, + Astrologian = 1ul << 22, /// /// Samurai (SAM). /// - Samurai = 1 << 23, + Samurai = 1ul << 23, /// /// Red mage (RDM). /// - RedMage = 1 << 24, + RedMage = 1ul << 24, /// /// Blue mage (BLM). /// - BlueMage = 1 << 25, + BlueMage = 1ul << 25, /// /// Gunbreaker (GNB). /// - Gunbreaker = 1 << 26, + Gunbreaker = 1ul << 26, /// /// Dancer (DNC). /// - Dancer = 1 << 27, + Dancer = 1ul << 27, /// /// Reaper (RPR). /// - Reaper = 1 << 28, + Reaper = 1ul << 28, /// /// Sage (SGE). /// - Sage = 1 << 29, + Sage = 1ul << 29, /// /// Viper (VPR). /// - Viper = 1 << 30, + Viper = 1ul << 30, /// /// Pictomancer (PCT). /// - Pictomancer = 1 << 31, + Pictomancer = 1ul << 31, } From 0fb7585973ffe22ab9353d853b0084a3f1c0803b Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Wed, 28 Aug 2024 13:23:39 -0700 Subject: [PATCH 053/375] feat: Use Debug Command Handler for Dalamud Commands (#2018) * feat: new command handler that works off a hook * cr comment * Use ClientStructs for sig --- Dalamud/Game/Command/CommandManager.cs | 85 +++++++++----------------- Dalamud/Utility/Util.cs | 2 +- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index aa6798171..078ce8c50 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -2,56 +2,45 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Text.RegularExpressions; using Dalamud.Console; -using Dalamud.Game.Gui; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.Shell; + namespace Dalamud.Game.Command; /// /// This class manages registered in-game slash commands. /// [ServiceManager.EarlyLoadedService] -internal sealed class CommandManager : IInternalDisposableService, ICommandManager +internal sealed unsafe class CommandManager : IInternalDisposableService, ICommandManager { private static readonly ModuleLog Log = new("Command"); private readonly ConcurrentDictionary commandMap = new(); private readonly ConcurrentDictionary<(string, IReadOnlyCommandInfo), string> commandAssemblyNameMap = new(); - private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); - private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled); - private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); - private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled); - private readonly Regex commandRegexCn = new(@"^^(“|「)(?.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); - private readonly Regex currentLangCommandRegex; - [ServiceManager.ServiceDependency] - private readonly ChatGui chatGui = Service.Get(); - + private readonly Hook? tryInvokeDebugCommandHook; + [ServiceManager.ServiceDependency] private readonly ConsoleManager console = Service.Get(); [ServiceManager.ServiceConstructor] private CommandManager(Dalamud dalamud) { - this.currentLangCommandRegex = (ClientLanguage)dalamud.StartInfo.Language switch - { - ClientLanguage.Japanese => this.commandRegexJp, - ClientLanguage.English => this.commandRegexEn, - ClientLanguage.German => this.commandRegexDe, - ClientLanguage.French => this.commandRegexFr, - _ => this.commandRegexEn, - }; + this.tryInvokeDebugCommandHook = Hook.FromAddress( + (nint)ShellCommands.MemberFunctionPointers.TryInvokeDebugCommand, + this.OnTryInvokeDebugCommand); + this.tryInvokeDebugCommandHook.Enable(); - this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled; this.console.Invoke += this.ConsoleOnInvoke; } @@ -113,7 +102,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); } } - + /// /// Add a command handler, which you can use to add your own custom commands to the in-game chat. /// @@ -131,7 +120,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag Log.Error("Command {CommandName} is already registered", command); return false; } - + if (!this.commandAssemblyNameMap.TryAdd((command, info), loaderAssemblyName)) { this.commandMap.Remove(command, out _); @@ -184,7 +173,8 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag ///
/// The name of the assembly. /// A list of commands and their associated activation string. - public List> GetHandlersByAssemblyName(string assemblyName) + public List> GetHandlersByAssemblyName( + string assemblyName) { return this.commandAssemblyNameMap.Where(c => c.Value == assemblyName).ToList(); } @@ -193,37 +183,20 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag void IInternalDisposableService.DisposeService() { this.console.Invoke -= this.ConsoleOnInvoke; - this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled; + this.tryInvokeDebugCommandHook?.Dispose(); } - + private bool ConsoleOnInvoke(string arg) { return arg.StartsWith('/') && this.ProcessCommand(arg); } - private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) + private int OnTryInvokeDebugCommand(ShellCommands* self, Utf8String* command, UIModule* uiModule) { - if (type == XivChatType.ErrorMessage && timestamp == 0) - { - var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"]; - if (cmdMatch.Success) - { - // Yes, it's a chat command. - var command = cmdMatch.Value; - if (this.ProcessCommand(command)) isHandled = true; - } - else - { - // Always match for china, since they patch in language files without changing the ClientLanguage. - cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"]; - if (cmdMatch.Success) - { - // Yes, it's a Chinese fallback chat command. - var command = cmdMatch.Value; - if (this.ProcessCommand(command)) isHandled = true; - } - } - } + var result = this.tryInvokeDebugCommandHook!.OriginalDisposeSafe(self, command, uiModule); + if (result != -1) return result; + + return this.ProcessCommand(command->ToString()) ? 0 : result; } } @@ -238,7 +211,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager { private static readonly ModuleLog Log = new("Command"); - + [ServiceManager.ServiceDependency] private readonly CommandManager commandManagerService = Service.Get(); @@ -253,10 +226,10 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand { this.pluginInfo = localPlugin; } - + /// public ReadOnlyDictionary Commands => this.commandManagerService.Commands; - + /// void IInternalDisposableService.DisposeService() { @@ -264,7 +237,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand { this.commandManagerService.RemoveHandler(command); } - + this.pluginRegisteredCommands.Clear(); } @@ -275,7 +248,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand /// public void DispatchCommand(string command, string argument, IReadOnlyCommandInfo info) => this.commandManagerService.DispatchCommand(command, argument, info); - + /// public bool AddHandler(string command, CommandInfo info) { @@ -294,7 +267,7 @@ internal class CommandManagerPluginScoped : IInternalDisposableService, ICommand return false; } - + /// public bool RemoveHandler(string command) { diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 7d7bb1380..d8e05716e 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -158,7 +158,7 @@ public static class Util var asm = typeof(Util).Assembly; var attrs = asm.GetCustomAttributes(); - return gitHashInternal = attrs.First(a => a.Key == "GitHash").Value; + return gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value ?? "N/A"; } /// From bf8690fc601b440b2487b7acf7162f313aa32a2f Mon Sep 17 00:00:00 2001 From: ItsBexy Date: Mon, 2 Sep 2024 21:01:49 -0600 Subject: [PATCH 054/375] Update Addon Inspector This updates the Addon Inspector with lots of new features and functionality. - Features from Caraxi's fork of UiDebug have been incorporated, such as the Element Selector UI and address search. - Any addon or node can now pop out into its own window. - Revised the visual style of node field/property information. - Color values are now visually displayed. - Any nodes or components that are referenced by fields within the addon will now show that field name in the inspector. - Added editors for nodes, allowing complete control over most of their properties. - Improved texture display for Image nodes (and Image node variant types). The active part of the texture is now highlighted, and the boundaries of other parts can be shown via mouseover. - Highlighting of node bounds onscreen is now more accurate, factoring in rotation (including when using the Element Selector). - Display of animation timelines has been revamped, showing a table of keyframes for each animation. A standalone SamplePlugin-based version is available here: https://github.com/ItsBexy/UiDebug2 --- .../UiDebug2/Browsing/AddonTree.FieldNames.cs | 196 ++++++++ .../Internal/UiDebug2/Browsing/AddonTree.cs | 242 +++++++++ .../Internal/UiDebug2/Browsing/Events.cs | 68 +++ .../Browsing/NodeTree.ClippingMask.cs | 35 ++ .../UiDebug2/Browsing/NodeTree.Collision.cs | 24 + .../UiDebug2/Browsing/NodeTree.Component.cs | 283 +++++++++++ .../UiDebug2/Browsing/NodeTree.Counter.cs | 36 ++ .../UiDebug2/Browsing/NodeTree.Editor.cs | 384 ++++++++++++++ .../UiDebug2/Browsing/NodeTree.Image.cs | 317 ++++++++++++ .../Browsing/NodeTree.NineGrid.Offsets.cs | 69 +++ .../UiDebug2/Browsing/NodeTree.NineGrid.cs | 89 ++++ .../UiDebug2/Browsing/NodeTree.Res.cs | 402 +++++++++++++++ .../UiDebug2/Browsing/NodeTree.Text.cs | 126 +++++ .../Browsing/TimelineTree.KeyGroupColumn.cs | 90 ++++ .../UiDebug2/Browsing/TimelineTree.cs | 381 ++++++++++++++ .../Internal/UiDebug2/ElementSelector.cs | 475 ++++++++++++++++++ .../Internal/UiDebug2/Popout.Addon.cs | 50 ++ .../Internal/UiDebug2/Popout.Node.cs | 69 +++ .../Internal/UiDebug2/UiDebug2.Sidebar.cs | 214 ++++++++ .../Interface/Internal/UiDebug2/UiDebug2.cs | 112 +++++ .../Internal/UiDebug2/Utility/Gui.cs | 204 ++++++++ .../Internal/UiDebug2/Utility/NodeBounds.cs | 170 +++++++ .../Data/Widgets/AddonInspectorWidget.cs | 6 +- 23 files changed, 4039 insertions(+), 3 deletions(-) create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs new file mode 100644 index 000000000..e72de2b23 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -0,0 +1,196 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +using FFXIVClientStructs.Attributes; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static System.Reflection.BindingFlags; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +public unsafe partial class AddonTree +{ + private static readonly Dictionary AddonTypeDict = []; + + /// + /// Gets or sets a collection of names corresponding to pointers documented within the addon struct. + /// + internal Dictionary> FieldNames { get; set; } = []; + + private object? GetAddonObj(AtkUnitBase* addon) + { + if (addon == null) + { + return null; + } + + if (!AddonTypeDict.ContainsKey(this.AddonName)) + { + AddonTypeDict.Add(this.AddonName, null); + + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + foreach (var t in from t in a.GetTypes() + where t.IsPublic + let xivAddonAttr = (Addon?)t.GetCustomAttribute(typeof(Addon), false) + where xivAddonAttr != null + where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName) + select t) + { + AddonTypeDict[this.AddonName] = t; + break; + } + } + catch + { + // ignored + } + } + } + + return AddonTypeDict.TryGetValue(this.AddonName, out var result) && result != null ? Marshal.PtrToStructure(new(addon), result) : *addon; + } + + private void PopulateFieldNames(nint ptr) + { + this.PopulateFieldNames(this.GetAddonObj((AtkUnitBase*)ptr), ptr); + } + + private void PopulateFieldNames(object? obj, nint baseAddr, List? path = null) + { + if (obj == null) + { + return; + } + + path ??= []; + var baseType = obj.GetType(); + + foreach (var field in baseType.GetFields(Static | Public | NonPublic | Instance)) + { + if (field.GetCustomAttribute(typeof(FieldOffsetAttribute)) is FieldOffsetAttribute offset) + { + try + { + var fieldAddr = baseAddr + offset.Value; + var name = field.Name[0] == '_' ? char.ToUpperInvariant(field.Name[1]) + field.Name[2..] : field.Name; + var fieldType = field.FieldType; + + if (!field.IsStatic && fieldType.IsPointer) + { + var pointer = (nint)Pointer.Unbox((Pointer)field.GetValue(obj)!); + var itemType = fieldType.GetElementType(); + ParsePointer(fieldAddr, pointer, itemType, name); + } + else if (fieldType.IsExplicitLayout) + { + ParseExplicitField(fieldAddr, field, fieldType, name); + } + else if (fieldType.Name.Contains("FixedSizeArray")) + { + ParseFixedSizeArray(fieldAddr, fieldType, name); + } + } + catch (Exception ex) + { + Log.Warning($"Failed to parse field at {offset.Value:X} in {baseType}!\n{ex}"); + } + } + } + + void ParseExplicitField(nint fieldAddr, FieldInfo field, MemberInfo fieldType, string name) + { + try + { + if (this.FieldNames.TryAdd(fieldAddr, new List(path!) { name }) && fieldType.DeclaringType == baseType) + { + this.PopulateFieldNames(field.GetValue(obj), fieldAddr, new List(path) { name }); + } + } + catch (Exception ex) + { + Log.Warning($"Failed to parse explicit field: {fieldType} {name} in {baseType}!\n{ex}"); + } + } + + void ParseFixedSizeArray(nint fieldAddr, Type fieldType, string name) + { + try + { + var spanLength = (int)(fieldType.CustomAttributes.ToArray()[0].ConstructorArguments[0].Value ?? 0); + + if (spanLength <= 0) + { + return; + } + + var itemType = fieldType.UnderlyingSystemType.GenericTypeArguments[0]; + + if (!itemType.IsGenericType) + { + var size = Marshal.SizeOf(itemType); + for (var i = 0; i < spanLength; i++) + { + var itemAddr = fieldAddr + (size * i); + var itemName = $"{name}[{i}]"; + + this.FieldNames.TryAdd(itemAddr, new List(path!) { itemName }); + + var item = Marshal.PtrToStructure(itemAddr, itemType); + if (itemType.DeclaringType == baseType) + { + this.PopulateFieldNames(item, itemAddr, new List(path) { name }); + } + } + } + else if (itemType.Name.Contains("Pointer")) + { + itemType = itemType.GenericTypeArguments[0]; + + for (var i = 0; i < spanLength; i++) + { + var itemAddr = fieldAddr + (0x08 * i); + var pointer = Marshal.ReadIntPtr(itemAddr); + ParsePointer(itemAddr, pointer, itemType, $"{name}[{i}]"); + } + } + } + catch (Exception ex) + { + Log.Warning($"Failed to parse fixed size array: {fieldType} {name} in {baseType}!\n{ex}"); + } + } + + void ParsePointer(nint fieldAddr, nint pointer, Type? itemType, string name) + { + try + { + if (pointer == 0) + { + return; + } + + this.FieldNames.TryAdd(fieldAddr, new List(path!) { name }); + this.FieldNames.TryAdd(pointer, new List(path) { name }); + + if (itemType?.DeclaringType != baseType || itemType.IsPointer) + { + return; + } + + var item = Marshal.PtrToStructure(pointer, itemType); + this.PopulateFieldNames(item, pointer, new List(path) { name }); + } + catch (Exception ex) + { + Log.Warning($"Failed to parse pointer: {itemType}* {name} in {baseType}!\n{ex}"); + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs new file mode 100644 index 000000000..6d6377530 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs @@ -0,0 +1,242 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Components; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.ElementSelector; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A class representing an , allowing it to be browsed within an ImGui window. +/// +public unsafe partial class AddonTree : IDisposable +{ + private readonly nint initialPtr; + + private AddonPopoutWindow? window; + + private AddonTree(string name, nint ptr) + { + this.AddonName = name; + this.initialPtr = ptr; + this.PopulateFieldNames(ptr); + } + + /// + /// Gets the name of the addon this tree represents. + /// + internal string AddonName { get; init; } + + /// + /// Gets or sets a collection of trees representing nodes within this addon. + /// + internal Dictionary NodeTrees { get; set; } = []; + + /// + public void Dispose() + { + foreach (var nodeTree in this.NodeTrees) + { + nodeTree.Value.Dispose(); + } + + this.NodeTrees.Clear(); + this.FieldNames.Clear(); + AddonTrees.Remove(this.AddonName); + if (this.window != null && PopoutWindows.Windows.Contains(this.window)) + { + PopoutWindows.RemoveWindow(this.window); + this.window?.Dispose(); + } + + GC.SuppressFinalize(this); + } + + /// + /// Gets an instance of for the given addon name (or creates one if none are found). + /// The tree can then be drawn within the Addon Inspector and browsed. + /// + /// The name of the addon. + /// The for the named addon. Returns null if it does not exist, or if it is not at the expected address. + internal static AddonTree? GetOrCreate(string? name) + { + if (name == null) + { + return null; + } + + try + { + var ptr = GameGui.GetAddonByName(name); + + if ((AtkUnitBase*)ptr != null) + { + if (AddonTrees.TryGetValue(name, out var tree)) + { + if (tree.initialPtr == ptr) + { + return tree; + } + + tree.Dispose(); + } + + var newTree = new AddonTree(name, ptr); + AddonTrees.Add(name, newTree); + return newTree; + } + } + catch + { + Log.Warning("Couldn't get addon!"); + } + + return null; + } + + /// + /// Draws this AddonTree within a window. + /// + internal void Draw() + { + if (!this.ValidateAddon(out var addon)) + { + return; + } + + var isVisible = addon->IsVisible; + + ImGui.Text($"{this.AddonName}"); + ImGui.SameLine(); + + ImGui.SameLine(); + ImGui.TextColored(isVisible ? new(0.1f, 1f, 0.1f, 1f) : new(0.6f, 0.6f, 0.6f, 1), isVisible ? "Visible" : "Not Visible"); + + ImGui.SameLine(ImGui.GetWindowWidth() - 100); + + if (ImGuiComponents.IconButton($"##vis{(nint)addon:X}", isVisible ? Eye : EyeSlash, isVisible ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1))) + { + addon->IsVisible = !isVisible; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Visibility"); + } + + ImGui.SameLine(); + if (ImGuiComponents.IconButton("pop", this.window?.IsOpen == true ? Times : ArrowUpRightFromSquare, null)) + { + this.TogglePopout(); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Popout Window"); + } + + ImGui.Separator(); + + PrintFieldValuePair("Address", $"{(nint)addon:X}"); + + var uldManager = addon->UldManager; + PrintFieldValuePairs( + ("X", $"{addon->X}"), + ("Y", $"{addon->X}"), + ("Scale", $"{addon->Scale}"), + ("Widget Count", $"{uldManager.ObjectCount}")); + + ImGui.Separator(); + + var addonObj = this.GetAddonObj(addon); + if (addonObj != null) + { + ShowStruct(addonObj, (ulong)addon); + } + + ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); + ImGui.Separator(); + + ResNodeTree.PrintNodeList(uldManager.NodeList, uldManager.NodeListCount, this); + + ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); + ImGui.Separator(); + + ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); + + if (SearchResults.Length > 0 && Countdown <= 0) + { + SearchResults = []; + } + } + + /// + /// Checks whether a given exists somewhere within this 's associated (or any of its s). + /// + /// The node to check. + /// true if the node was found. + internal bool ContainsNode(AtkResNode* node) => this.ValidateAddon(out var addon) && FindNode(node, addon); + + private static bool FindNode(AtkResNode* node, AtkUnitBase* addon) => addon != null && FindNode(node, addon->UldManager); + + private static bool FindNode(AtkResNode* node, AtkComponentNode* compNode) => compNode != null && FindNode(node, compNode->Component->UldManager); + + private static bool FindNode(AtkResNode* node, AtkUldManager uldManager) => FindNode(node, uldManager.NodeList, uldManager.NodeListCount); + + private static bool FindNode(AtkResNode* node, AtkResNode** list, int count) + { + for (var i = 0; i < count; i++) + { + var listNode = list[i]; + if (listNode == node) + { + return true; + } + + if ((int)listNode->Type >= 1000 && FindNode(node, (AtkComponentNode*)listNode)) + { + return true; + } + } + + return false; + } + + /// + /// Checks whether the addon exists at the expected address. If the addon is null or has a new address, disposes this instance of . + /// + /// The addon, if successfully found. + /// true if the addon is found. + private bool ValidateAddon(out AtkUnitBase* addon) + { + addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName); + if (addon == null || (nint)addon != this.initialPtr) + { + this.Dispose(); + return false; + } + + return true; + } + + private void TogglePopout() + { + if (this.window == null) + { + this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.initialPtr}"); + PopoutWindows.AddWindow(this.window); + } + else + { + this.window.IsOpen = !this.window.IsOpen; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs new file mode 100644 index 000000000..bd1efe6c7 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -0,0 +1,68 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// Class that prints the events table for a node, where applicable. +/// +public static class Events +{ + /// + /// Prints out each for a given node. + /// + /// The node to print events for. + internal static unsafe void PrintEvents(AtkResNode* node) + { + var evt = node->AtkEventManager.Event; + if (evt == null) + { + return; + } + + if (ImGui.TreeNode($"Events##{(nint)node:X}eventTree")) + { + if (ImGui.BeginTable($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg)) + { + ImGui.TableSetupColumn("#", WidthFixed); + ImGui.TableSetupColumn("Type", WidthFixed); + ImGui.TableSetupColumn("Param", WidthFixed); + ImGui.TableSetupColumn("Flags", WidthFixed); + ImGui.TableSetupColumn("Unk29", WidthFixed); + ImGui.TableSetupColumn("Target", WidthFixed); + ImGui.TableSetupColumn("Listener", WidthFixed); + + ImGui.TableHeadersRow(); + + var i = 0; + while (evt != null) + { + ImGui.TableNextColumn(); + ImGui.Text($"{i++}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Type}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Param}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Flags}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Unk29}"); + ImGui.TableNextColumn(); + Gui.ClickToCopyText($"{(nint)evt->Target:X}"); + ImGui.TableNextColumn(); + Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); + evt = evt->NextEvent; + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs new file mode 100644 index 000000000..cfba1a2bc --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.ClippingMask.cs @@ -0,0 +1,35 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe class ClippingMaskNodeTree : ImageNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal ClippingMaskNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + private protected override uint PartId => this.CmNode->PartId; + + /// + private protected override AtkUldPartsList* PartsList => this.CmNode->PartsList; + + private AtkClippingMaskNode* CmNode => (AtkClippingMaskNode*)this.Node; + + /// + private protected override void PrintNodeObject() => ShowStruct(this.CmNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) => this.DrawTextureAndParts(); +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs new file mode 100644 index 000000000..c447afac9 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Collision.cs @@ -0,0 +1,24 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe class CollisionNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal CollisionNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + private protected override void PrintNodeObject() => ShowStruct((AtkCollisionNode*)this.Node); +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs new file mode 100644 index 000000000..2c95924c5 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs @@ -0,0 +1,283 @@ +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.ComponentType; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe class ComponentNodeTree : ResNodeTree +{ + private readonly AtkUldManager* uldManager; + + private readonly ComponentType componentType; + + private readonly AtkComponentBase* component; + + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal ComponentNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + this.component = ((AtkComponentNode*)node)->Component; + this.uldManager = &this.component->UldManager; + this.NodeType = 0; + this.componentType = ((AtkUldComponentInfo*)this.uldManager->Objects)->ComponentType; + } + + /// + private protected override string GetHeaderText() + { + var childCount = (int)this.uldManager->NodeListCount; + return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.component:X})"; + } + + /// + private protected override void PrintNodeObject() + { + base.PrintNodeObject(); + this.PrintComponentObject(); + ImGui.SameLine(); + ImGui.NewLine(); + this.PrintComponentDataObject(); + ImGui.SameLine(); + ImGui.NewLine(); + } + + /// + private protected override void PrintChildNodes() + { + base.PrintChildNodes(); + var count = this.uldManager->NodeListCount; + PrintNodeListAsTree(this.uldManager->NodeList, count, $"Node List [{count}]:", this.AddonTree, new(0f, 0.5f, 0.8f, 1f)); + } + + /// + private protected override void PrintFieldNames() + { + this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); + this.PrintFieldName((nint)this.component, new(0f, 0.5f, 0.8f, 1f)); + } + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (this.component == null) + { + return; + } + + switch (this.componentType) + { + case TextInput: + var textInputComponent = (AtkComponentTextInput*)this.component; + ImGui.Text( + $"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); + ImGui.Text( + $"InputBase Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); + ImGui.Text( + $"Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText01.StringPtr))}"); + ImGui.Text( + $"Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText02.StringPtr))}"); + ImGui.Text( + $"Text3: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText03.StringPtr))}"); + ImGui.Text( + $"Text4: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText04.StringPtr))}"); + ImGui.Text( + $"Text5: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText05.StringPtr))}"); + break; + case List: + case TreeList: + var l = (AtkComponentList*)this.component; + if (ImGui.SmallButton("Inc.Selected")) + { + l->SelectedItemIndex++; + } + + break; + default: + break; + } + } + + private void PrintComponentObject() + { + PrintFieldValuePair("Component", $"{(nint)this.component:X}"); + + ImGui.SameLine(); + + switch (this.componentType) + { + case Button: + ShowStruct((AtkComponentButton*)this.component); + break; + case Slider: + ShowStruct((AtkComponentSlider*)this.component); + break; + case Window: + ShowStruct((AtkComponentWindow*)this.component); + break; + case CheckBox: + ShowStruct((AtkComponentCheckBox*)this.component); + break; + case GaugeBar: + ShowStruct((AtkComponentGaugeBar*)this.component); + break; + case RadioButton: + ShowStruct((AtkComponentRadioButton*)this.component); + break; + case TextInput: + ShowStruct((AtkComponentTextInput*)this.component); + break; + case Icon: + ShowStruct((AtkComponentIcon*)this.component); + break; + case NumericInput: + ShowStruct((AtkComponentNumericInput*)this.component); + break; + case List: + ShowStruct((AtkComponentList*)this.component); + break; + case TreeList: + ShowStruct((AtkComponentTreeList*)this.component); + break; + case DropDownList: + ShowStruct((AtkComponentDropDownList*)this.component); + break; + case ScrollBar: + ShowStruct((AtkComponentScrollBar*)this.component); + break; + case ListItemRenderer: + ShowStruct((AtkComponentListItemRenderer*)this.component); + break; + case IconText: + ShowStruct((AtkComponentIconText*)this.component); + break; + case ComponentType.DragDrop: + ShowStruct((AtkComponentDragDrop*)this.component); + break; + case GuildLeveCard: + ShowStruct((AtkComponentGuildLeveCard*)this.component); + break; + case TextNineGrid: + ShowStruct((AtkComponentTextNineGrid*)this.component); + break; + case JournalCanvas: + ShowStruct((AtkComponentJournalCanvas*)this.component); + break; + case HoldButton: + ShowStruct((AtkComponentHoldButton*)this.component); + break; + case Portrait: + ShowStruct((AtkComponentPortrait*)this.component); + break; + default: + ShowStruct(this.component); + break; + } + } + + private void PrintComponentDataObject() + { + var componentData = this.component->UldManager.ComponentData; + PrintFieldValuePair("Data", $"{(nint)componentData:X}"); + + if (componentData != null) + { + ImGui.SameLine(); + switch (this.componentType) + { + case Base: + ShowStruct(componentData); + break; + case Button: + ShowStruct((AtkUldComponentDataButton*)componentData); + break; + case Window: + ShowStruct((AtkUldComponentDataWindow*)componentData); + break; + case CheckBox: + ShowStruct((AtkUldComponentDataCheckBox*)componentData); + break; + case RadioButton: + ShowStruct((AtkUldComponentDataRadioButton*)componentData); + break; + case GaugeBar: + ShowStruct((AtkUldComponentDataGaugeBar*)componentData); + break; + case Slider: + ShowStruct((AtkUldComponentDataSlider*)componentData); + break; + case TextInput: + ShowStruct((AtkUldComponentDataTextInput*)componentData); + break; + case NumericInput: + ShowStruct((AtkUldComponentDataNumericInput*)componentData); + break; + case List: + ShowStruct((AtkUldComponentDataList*)componentData); + break; + case DropDownList: + ShowStruct((AtkUldComponentDataDropDownList*)componentData); + break; + case Tab: + ShowStruct((AtkUldComponentDataTab*)componentData); + break; + case TreeList: + ShowStruct((AtkUldComponentDataTreeList*)componentData); + break; + case ScrollBar: + ShowStruct((AtkUldComponentDataScrollBar*)componentData); + break; + case ListItemRenderer: + ShowStruct((AtkUldComponentDataListItemRenderer*)componentData); + break; + case Icon: + ShowStruct((AtkUldComponentDataIcon*)componentData); + break; + case IconText: + ShowStruct((AtkUldComponentDataIconText*)componentData); + break; + case ComponentType.DragDrop: + ShowStruct((AtkUldComponentDataDragDrop*)componentData); + break; + case GuildLeveCard: + ShowStruct((AtkUldComponentDataGuildLeveCard*)componentData); + break; + case TextNineGrid: + ShowStruct((AtkUldComponentDataTextNineGrid*)componentData); + break; + case JournalCanvas: + ShowStruct((AtkUldComponentDataJournalCanvas*)componentData); + break; + case Multipurpose: + ShowStruct((AtkUldComponentDataMultipurpose*)componentData); + break; + case Map: + ShowStruct((AtkUldComponentDataMap*)componentData); + break; + case Preview: + ShowStruct((AtkUldComponentDataPreview*)componentData); + break; + case HoldButton: + ShowStruct((AtkUldComponentDataHoldButton*)componentData); + break; + case Portrait: + ShowStruct((AtkUldComponentDataPortrait*)componentData); + break; + default: + ShowStruct(componentData); + break; + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs new file mode 100644 index 000000000..ff40db37a --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Counter.cs @@ -0,0 +1,36 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class CounterNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal CounterNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + private AtkCounterNode* CntNode => (AtkCounterNode*)this.Node; + + /// + private protected override void PrintNodeObject() => ShowStruct(this.CntNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (!isEditorOpen) + { + PrintFieldValuePairs(("Text", ((AtkCounterNode*)this.Node)->NodeText.ToString())); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs new file mode 100644 index 000000000..ad7c09165 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -0,0 +1,384 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Internal.UiDebug2.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Interface.Utility.ImGuiHelpers; +using static ImGuiNET.ImGuiColorEditFlags; +using static ImGuiNET.ImGuiInputTextFlags; +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +internal unsafe partial class ResNodeTree +{ + /// + /// Sets up the table for the node editor, if the "Edit" checkbox is ticked. + /// + private protected void DrawNodeEditorTable() + { + ImGui.BeginTable($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); + + this.DrawEditorRows(); + + ImGui.EndTable(); + } + + /// + /// Draws each row in the node editor table. + /// + private protected virtual void DrawEditorRows() + { + var pos = new Vector2(this.Node->X, this.Node->Y); + var size = new Vector2(this.Node->Width, this.Node->Height); + var scale = new Vector2(this.Node->ScaleX, this.Node->ScaleY); + var origin = new Vector2(this.Node->OriginX, this.Node->OriginY); + var angle = (float)((this.Node->Rotation * (180 / Math.PI)) + 360); + + var rgba = RgbaUintToVector4(this.Node->Color.RGBA); + var mult = new Vector3(this.Node->MultiplyRed, this.Node->MultiplyGreen, this.Node->MultiplyBlue) / 255f; + var add = new Vector3(this.Node->AddRed, this.Node->AddGreen, this.Node->AddBlue); + + var hov = false; + + ImGui.TableSetupColumn("Labels", WidthFixed); + ImGui.TableSetupColumn("Editors", WidthFixed); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Position:"); + + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}position", ref pos, 1, default, default, "%.0f")) + { + this.Node->X = pos.X; + this.Node->Y = pos.Y; + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("X", "Y") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Size:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}size", ref size, 1, 0, default, "%.0f")) + { + this.Node->Width = (ushort)Math.Max(size.X, 0); + this.Node->Height = (ushort)Math.Max(size.Y, 0); + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("Width", "Height") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Scale:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}scale", ref scale, 0.05f)) + { + this.Node->ScaleX = scale.X; + this.Node->ScaleY = scale.Y; + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("ScaleX", "ScaleY") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Origin:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}origin", ref origin, 1, default, default, "%.0f")) + { + this.Node->OriginX = origin.X; + this.Node->OriginY = origin.Y; + this.Node->DrawFlags |= 0xD; + } + + hov |= SplitTooltip("OriginX", "OriginY") || ImGui.IsItemActive(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Rotation:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + while (angle > 180) + { + angle -= 360; + } + + if (ImGui.DragFloat($"##{(nint)this.Node:X}rotation", ref angle, 0.05f, default, default, "%.2f°")) + { + this.Node->Rotation = (float)(angle / (180 / Math.PI)); + this.Node->DrawFlags |= 0xD; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Rotation (deg)"); + hov = true; + } + + hov |= ImGui.IsItemActive(); + + if (hov) + { + Vector4 brightYellow = new(1, 1, 0.5f, 0.8f); + new NodeBounds(this.Node).Draw(brightYellow); + new NodeBounds(origin, this.Node).Draw(brightYellow); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("RGBA:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit4($"##{(nint)this.Node:X}RGBA", ref rgba, DisplayHex)) + { + this.Node->Color = new() { RGBA = RgbaVector4ToUint(rgba) }; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Multiply:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit3($"##{(nint)this.Node:X}multiplyRGB", ref mult, DisplayHex)) + { + this.Node->MultiplyRed = (byte)(mult.X * 255); + this.Node->MultiplyGreen = (byte)(mult.Y * 255); + this.Node->MultiplyBlue = (byte)(mult.Z * 255); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Add:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(124); + + if (ImGui.DragFloat3($"##{(nint)this.Node:X}addRGB", ref add, 1, -255, 255, "%.0f")) + { + this.Node->AddRed = (short)add.X; + this.Node->AddGreen = (short)add.Y; + this.Node->AddBlue = (short)add.Z; + } + + SplitTooltip("+/- Red", "+/- Green", "+/- Blue"); + + var addTransformed = (add / 510f) + new Vector3(0.5f); + + ImGui.SameLine(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() - (4 * GlobalScale)); + if (ImGui.ColorEdit3($"##{(nint)this.Node:X}addRGBPicker", ref addTransformed, NoAlpha | NoInputs)) + { + this.Node->AddRed = (short)Math.Floor((addTransformed.X * 510f) - 255f); + this.Node->AddGreen = (short)Math.Floor((addTransformed.Y * 510f) - 255f); + this.Node->AddBlue = (short)Math.Floor((addTransformed.Z * 510f) - 255f); + } + } +} + +/// +internal unsafe partial class CounterNodeTree +{ + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var str = this.CntNode->NodeText.ToString(); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Counter:"); + ImGui.TableNextColumn(); + + ImGui.SetNextItemWidth(150); + if (ImGui.InputText($"##{(nint)this.Node:X}counterEdit", ref str, 512, EnterReturnsTrue)) + { + this.CntNode->SetText(str); + } + } +} + +/// +internal unsafe partial class ImageNodeTree +{ + private static int TexDisplayStyle { get; set; } + + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var partId = (int)this.PartId; + var partcount = this.ImgNode->PartsList->PartCount; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Part Id:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.InputInt($"##partId{(nint)this.Node:X}", ref partId, 1, 1)) + { + if (partId < 0) + { + partId = 0; + } + + if (partId >= partcount) + { + partId = (int)(partcount - 1); + } + + this.ImgNode->PartId = (ushort)partId; + } + } +} + +/// +internal unsafe partial class NineGridNodeTree +{ + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var lr = new Vector2(this.Offsets.Left, this.Offsets.Right); + var tb = new Vector2(this.Offsets.Top, this.Offsets.Bottom); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Ninegrid Offsets:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}ngOffsetLR", ref lr, 1, 0)) + { + this.NgNode->LeftOffset = (short)Math.Max(0, lr.X); + this.NgNode->RightOffset = (short)Math.Max(0, lr.Y); + } + + SplitTooltip("Left", "Right"); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.DragFloat2($"##{(nint)this.Node:X}ngOffsetTB", ref tb, 1, 0)) + { + this.NgNode->TopOffset = (short)Math.Max(0, tb.X); + this.NgNode->BottomOffset = (short)Math.Max(0, tb.Y); + } + + SplitTooltip("Top", "Bottom"); + } +} + +/// +internal unsafe partial class TextNodeTree +{ + private static readonly List FontList = Enum.GetValues().ToList(); + + private static readonly string[] FontNames = Enum.GetNames(); + + /// + private protected override void DrawEditorRows() + { + base.DrawEditorRows(); + + var text = this.TxtNode->NodeText.ToString(); + var fontIndex = FontList.IndexOf(this.TxtNode->FontType); + int fontSize = this.TxtNode->FontSize; + var alignment = this.TxtNode->AlignmentType; + var textColor = RgbaUintToVector4(this.TxtNode->TextColor.RGBA); + var edgeColor = RgbaUintToVector4(this.TxtNode->EdgeColor.RGBA); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Text:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(Math.Max(ImGui.GetWindowContentRegionMax().X - ImGui.GetCursorPosX() - 50f, 150)); + if (ImGui.InputText($"##{(nint)this.Node:X}textEdit", ref text, 512, EnterReturnsTrue)) + { + this.TxtNode->SetText(text); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Font:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.Combo($"##{(nint)this.Node:X}fontType", ref fontIndex, FontNames, FontList.Count)) + { + this.TxtNode->FontType = FontList[fontIndex]; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Font Size:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.InputInt($"##{(nint)this.Node:X}fontSize", ref fontSize, 1, 10)) + { + this.TxtNode->FontSize = (byte)fontSize; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Alignment:"); + ImGui.TableNextColumn(); + if (InputAlignment($"##{(nint)this.Node:X}alignment", ref alignment)) + { + this.TxtNode->AlignmentType = alignment; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Text Color:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit4($"##{(nint)this.Node:X}TextRGB", ref textColor, DisplayHex)) + { + this.TxtNode->TextColor = new() { RGBA = RgbaVector4ToUint(textColor) }; + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text("Edge Color:"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(150); + if (ImGui.ColorEdit4($"##{(nint)this.Node:X}EdgeRGB", ref edgeColor, DisplayHex)) + { + this.TxtNode->EdgeColor = new() { RGBA = RgbaVector4ToUint(edgeColor) }; + } + } + + private static bool InputAlignment(string label, ref AlignmentType alignment) + { + var hAlign = (int)alignment % 3; + var vAlign = ((int)alignment - hAlign) / 3; + + var hAlignInput = IconSelectInput($"{label}H", ref hAlign, new() { 0, 1, 2 }, new() { AlignLeft, AlignCenter, AlignRight }); + var vAlignInput = IconSelectInput($"{label}V", ref vAlign, new() { 0, 1, 2 }, new() { ArrowsUpToLine, GripLines, ArrowsDownToLine }); + + if (hAlignInput || vAlignInput) + { + alignment = (AlignmentType)((vAlign * 3) + hAlign); + return true; + } + + return false; + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs new file mode 100644 index 000000000..f3cc69618 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -0,0 +1,317 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.TextureType; +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class ImageNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal ImageNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + /// Gets the part ID that this node uses. + /// + private protected virtual uint PartId => this.ImgNode->PartId; + + /// + /// Gets the parts list that this node uses. + /// + private protected virtual AtkUldPartsList* PartsList => this.ImgNode->PartsList; + + /// + /// Gets or sets a summary of pertinent data about this 's texture. Updated each time is called. + /// + private protected TextureData TexData { get; set; } + + private AtkImageNode* ImgNode => (AtkImageNode*)this.Node; + + /// + /// Draws the texture inside the window, in either of two styles.

+ /// Full Imagepresents the texture in full as a spritesheet.
+ /// Parts Listpresents the individual parts as rows in a table. + ///
+ private protected void DrawTextureAndParts() + { + this.TexData = new TextureData(this.PartsList, this.PartId); + + if (this.TexData.Texture == null) + { + return; + } + + if (NestedTreePush($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", out _)) + { + PrintFieldValuePairs( + ("Texture Type", $"{this.TexData.TexType}"), + ("Part ID", $"{this.TexData.PartId}"), + ("Part Count", $"{this.TexData.PartCount}")); + + if (this.TexData.Path != null) + { + PrintFieldValuePairs(("Texture Path", this.TexData.Path)); + } + + if (ImGui.RadioButton("Full Image##textureDisplayStyle0", TexDisplayStyle == 0)) + { + TexDisplayStyle = 0; + } + + ImGui.SameLine(); + if (ImGui.RadioButton("Parts List##textureDisplayStyle1", TexDisplayStyle == 1)) + { + TexDisplayStyle = 1; + } + + ImGui.NewLine(); + + if (TexDisplayStyle == 1) + { + this.PrintPartsTable(); + } + else + { + this.DrawFullTexture(); + } + + ImGui.TreePop(); + } + } + + /// + /// Draws an outline of a given part within the texture. + /// + /// The part ID. + /// The absolute position of the cursor onscreen. + /// The relative position of the cursor within the window. + /// The color of the outline. + /// Whether this outline requires the user to mouse over it. + private protected virtual void DrawPartOutline(uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false) + { + var part = this.TexData.PartsList->Parts[partId]; + + var hrFactor = this.TexData.HiRes ? 2f : 1f; + + var uv = new Vector2(part.U, part.V) * hrFactor; + var wh = new Vector2(part.Width, part.Height) * hrFactor; + + var partBegin = cursorScreenPos + uv; + var partEnd = partBegin + wh; + + if (reqHover && !ImGui.IsMouseHoveringRect(partBegin, partEnd)) + { + return; + } + + var savePos = ImGui.GetCursorPos(); + + ImGui.GetWindowDrawList().AddRect(partBegin, partEnd, RgbaVector4ToUint(col)); + + ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20)); + ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}"); + ImGui.SetCursorPos(savePos); + } + + /// + private protected override void PrintNodeObject() => ShowStruct(this.ImgNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + PrintFieldValuePairs( + ("Wrap", $"{this.ImgNode->WrapMode}"), + ("Image Flags", $"0x{this.ImgNode->Flags:X}")); + this.DrawTextureAndParts(); + } + + private static void PrintPartCoords(float u, float v, float w, float h, bool asFloat = false, bool lineBreak = false) + { + ImGui.TextDisabled($"{u}, {v},{(lineBreak ? "\n" : " ")}{w}, {h}"); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Click to copy as Vector2\nShift-click to copy as Vector4"); + } + + var suffix = asFloat ? "f" : string.Empty; + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(ImGui.IsKeyDown(ImGuiKey.ModShift) + ? $"new Vector4({u}{suffix}, {v}{suffix}, {w}{suffix}, {h}{suffix})" + : $"new Vector2({u}{suffix}, {v}{suffix});\nnew Vector2({w}{suffix}, {h}{suffix})"); + } + } + + private void DrawFullTexture() + { + var cursorScreenPos = ImGui.GetCursorScreenPos(); + var cursorLocalPos = ImGui.GetCursorPos(); + + ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(this.TexData.Texture->Width, this.TexData.Texture->Height)); + + for (uint p = 0; p < this.TexData.PartsList->PartCount; p++) + { + if (p == this.TexData.PartId) + { + continue; + } + + this.DrawPartOutline(p, cursorScreenPos, cursorLocalPos, new(0.6f, 0.6f, 0.6f, 1), true); + } + + this.DrawPartOutline(this.TexData.PartId, cursorScreenPos, cursorLocalPos, new(0, 0.85F, 1, 1)); + } + + private void PrintPartsTable() + { + ImGui.BeginTable($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); + ImGui.TableSetupColumn("Part ID", WidthFixed); + ImGui.TableSetupColumn("Part Texture", WidthFixed); + ImGui.TableSetupColumn("Coordinates", WidthFixed); + + ImGui.TableHeadersRow(); + + var tWidth = this.TexData.Texture->Width; + var tHeight = this.TexData.Texture->Height; + var textureSize = new Vector2(tWidth, tHeight); + + for (ushort i = 0; i < this.TexData.PartCount; i++) + { + ImGui.TableNextColumn(); + + if (i == this.TexData.PartId) + { + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0.85F, 1, 1)); + } + + ImGui.Text($"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}"); + + if (i == this.TexData.PartId) + { + ImGui.PopStyleColor(1); + } + + ImGui.TableNextColumn(); + + var part = this.TexData.PartsList->Parts[i]; + var hiRes = this.TexData.HiRes; + + var u = hiRes ? part.U * 2f : part.U; + var v = hiRes ? part.V * 2f : part.V; + var width = hiRes ? part.Width * 2f : part.Width; + var height = hiRes ? part.Height * 2f : part.Height; + + ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(width, height), new Vector2(u, v) / textureSize, new Vector2(u + width, v + height) / textureSize); + + ImGui.TableNextColumn(); + + ImGui.TextColored(!hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Standard:\t"); + ImGui.SameLine(); + var cursX = ImGui.GetCursorPosX(); + + PrintPartCoords(u / 2f, v / 2f, width / 2f, height / 2f); + + ImGui.TextColored(hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Hi-Res:\t"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursX); + + PrintPartCoords(u, v, width, height); + + ImGui.Text("UV:\t"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursX); + + PrintPartCoords(u / tWidth, v / tWidth, (u + width) / tWidth, (v + height) / tHeight, true, true); + } + + ImGui.EndTable(); + } + + /// + /// A summary of pertinent data about a node's texture. + /// + protected struct TextureData + { + /// The texture's partslist. + public AtkUldPartsList* PartsList; + + /// The number of parts in the texture. + public uint PartCount; + + /// The part ID the node is using. + public uint PartId; + + /// The texture itself. + public Texture* Texture = null; + + /// The type of texture. + public TextureType TexType = 0; + + /// The texture's file path (if , otherwise this value is null). + public string? Path = null; + + /// Whether this is a high-resolution texture. + public bool HiRes = false; + + /// + /// Initializes a new instance of the struct. + /// + /// The texture's parts list. + /// The part ID being used by the node. + public TextureData(AtkUldPartsList* partsList, uint partId) + { + this.PartsList = partsList; + this.PartCount = this.PartsList->PartCount; + this.PartId = partId >= this.PartCount ? 0 : partId; + + if (this.PartsList == null) + { + return; + } + + var asset = this.PartsList->Parts[this.PartId].UldAsset; + + if (asset == null) + { + return; + } + + this.TexType = asset->AtkTexture.TextureType; + + if (this.TexType == Resource) + { + var resource = asset->AtkTexture.Resource; + this.Texture = resource->KernelTextureObject; + this.Path = Marshal.PtrToStringAnsi(new(resource->TexFileResourceHandle->ResourceHandle.FileName.BufferPtr)); + } + else + { + this.Texture = this.TexType == KernelTexture ? asset->AtkTexture.KernelTexture : null; + this.Path = null; + } + + this.HiRes = this.Path?.Contains("_hr1") ?? false; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs new file mode 100644 index 000000000..237303155 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs @@ -0,0 +1,69 @@ +using System.Numerics; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +internal unsafe partial class NineGridNodeTree +{ + /// + /// A struct representing the four offsets of an . + /// + internal struct NineGridOffsets + { + /// Top offset. + internal int Top; + + /// Left offset. + internal int Left; + + /// Right offset. + internal int Right; + + /// Bottom offset. + internal int Bottom; + + /// + /// Initializes a new instance of the struct. + /// + /// The top offset. + /// The right offset. + /// The bottom offset. + /// The left offset. + internal NineGridOffsets(int top, int right, int bottom, int left) + { + this.Top = top; + this.Right = right; + this.Left = left; + this.Bottom = bottom; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The node using these offsets. + internal NineGridOffsets(AtkNineGridNode* ngNode) + : this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset) + { + } + + private NineGridOffsets(Vector4 v) + : this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W) + { + } + + public static implicit operator NineGridOffsets(Vector4 v) => new(v); + + public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left); + + public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a; + + public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a; + + /// Prints the offsets in ImGui. + internal readonly void Print() => PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}")); + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs new file mode 100644 index 000000000..3c66d44c3 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs @@ -0,0 +1,89 @@ +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Utility.Util; + +using Vector2 = System.Numerics.Vector2; +using Vector4 = System.Numerics.Vector4; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class NineGridNodeTree : ImageNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal NineGridNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + /// + private protected override uint PartId => this.NgNode->PartId; + + /// + private protected override AtkUldPartsList* PartsList => this.NgNode->PartsList; + + private AtkNineGridNode* NgNode => (AtkNineGridNode*)this.Node; + + private NineGridOffsets Offsets => new(this.NgNode); + + /// + private protected override void DrawPartOutline( + uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false) + { + var part = this.TexData.PartsList->Parts[partId]; + + var hrFactor = this.TexData.HiRes ? 2f : 1f; + var uv = new Vector2(part.U, part.V) * hrFactor; + var wh = new Vector2(part.Width, part.Height) * hrFactor; + var partBegin = cursorScreenPos + uv; + var partEnd = cursorScreenPos + uv + wh; + + var savePos = ImGui.GetCursorPos(); + + if (!reqHover || ImGui.IsMouseHoveringRect(partBegin, partEnd)) + { + var adjustedOffsets = this.Offsets * hrFactor; + var ngBegin1 = partBegin with { X = partBegin.X + adjustedOffsets.Left }; + var ngEnd1 = partEnd with { X = partEnd.X - adjustedOffsets.Right }; + + var ngBegin2 = partBegin with { Y = partBegin.Y + adjustedOffsets.Top }; + var ngEnd2 = partEnd with { Y = partEnd.Y - adjustedOffsets.Bottom }; + + var ngCol = RgbaVector4ToUint(col with { W = 0.75f * col.W }); + + ImGui.GetWindowDrawList() + .AddRect(partBegin, partEnd, RgbaVector4ToUint(col)); + ImGui.GetWindowDrawList().AddRect(ngBegin1, ngEnd1, ngCol); + ImGui.GetWindowDrawList().AddRect(ngBegin2, ngEnd2, ngCol); + + ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20)); + ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}"); + } + + ImGui.SetCursorPos(savePos); + } + + /// + private protected override void PrintNodeObject() => ShowStruct(this.NgNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (!isEditorOpen) + { + ImGui.Text("NineGrid Offsets:\t"); + ImGui.SameLine(); + this.Offsets.Print(); + } + + this.DrawTextureAndParts(); + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs new file mode 100644 index 000000000..222ca30d4 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -0,0 +1,402 @@ +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Internal.UiDebug2.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.Browsing.Events; +using static Dalamud.Interface.Internal.UiDebug2.ElementSelector; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.NodeFlags; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +/// As with the structs they represent, this class serves as the base class for other types of NodeTree. +internal unsafe partial class ResNodeTree : IDisposable +{ + private NodePopoutWindow? window; + + private bool editorOpen; + + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + private protected ResNodeTree(AtkResNode* node, AddonTree addonTree) + { + this.Node = node; + this.AddonTree = addonTree; + this.NodeType = node->Type; + this.AddonTree.NodeTrees.Add((nint)this.Node, this); + } + + /// + /// Gets or sets the this tree represents. + /// + protected internal AtkResNode* Node { get; set; } + + /// + /// Gets the containing this tree. + /// + protected internal AddonTree AddonTree { get; private set; } + + /// + /// Gets this node's type. + /// + private protected NodeType NodeType { get; init; } + + /// + /// Clears this NodeTree's popout window, if it has one. + /// + public void Dispose() + { + if (this.window != null && PopoutWindows.Windows.Contains(this.window)) + { + PopoutWindows.RemoveWindow(this.window); + this.window.Dispose(); + } + } + + /// + /// Gets an instance of (or one of its inheriting types) for the given node. If no instance exists, one is created. + /// + /// The node to get a tree for. + /// The tree for the node's containing addon. + /// An existing or newly-created instance of . + internal static ResNodeTree GetOrCreate(AtkResNode* node, AddonTree addonTree) => + addonTree.NodeTrees.TryGetValue((nint)node, out var nodeTree) ? nodeTree + : (int)node->Type > 1000 + ? new ComponentNodeTree(node, addonTree) + : node->Type switch + { + NodeType.Text => new TextNodeTree(node, addonTree), + NodeType.Image => new ImageNodeTree(node, addonTree), + NodeType.NineGrid => new NineGridNodeTree(node, addonTree), + NodeType.ClippingMask => new ClippingMaskNodeTree(node, addonTree), + NodeType.Counter => new CounterNodeTree(node, addonTree), + NodeType.Collision => new CollisionNodeTree(node, addonTree), + _ => new ResNodeTree(node, addonTree), + }; + + /// + /// Prints a list of NodeTree for a given list of nodes. + /// + /// The address of the start of the list. + /// The number of nodes in the list. + /// The tree for the containing addon. + internal static void PrintNodeList(AtkResNode** nodeList, int count, AddonTree addonTree) + { + for (uint j = 0; j < count; j++) + { + GetOrCreate(nodeList[j], addonTree).Print(j); + } + } + + /// + /// Calls , but outputs the results as a collapsible tree. + /// + /// The address of the start of the list. + /// The number of nodes in the list. + /// The heading text of the tree. + /// The tree for the containing addon. + /// The text color of the heading. + internal static void PrintNodeListAsTree(AtkResNode** nodeList, int count, string label, AddonTree addonTree, Vector4 color) + { + if (count <= 0) + { + return; + } + + ImGui.PushStyleColor(ImGuiCol.Text, color); + var treeOpened = NestedTreePush($"{label}##{(nint)nodeList:X}", color, out var lineStart); + ImGui.PopStyleColor(); + + if (treeOpened) + { + PrintNodeList(nodeList, count, addonTree); + NestedTreePop(lineStart, color); + } + } + + /// + /// Prints this tree in the window. + /// + /// The index of the tree within its containing node or addon, if applicable. + /// Whether the tree should default to being open. + internal void Print(uint? index, bool forceOpen = false) + { + if (SearchResults.Length > 0 && SearchResults[0] == (nint)this.Node) + { + this.PrintWithHighlights(index); + } + else + { + this.PrintTree(index, forceOpen); + } + } + + /// + /// Prints out the tree's header text. + /// + internal void WriteTreeHeading() + { + ImGui.Text(this.GetHeaderText()); + this.PrintFieldNames(); + } + + /// + /// If the given pointer has been identified as a field within the addon struct, this method prints that field's name. + /// + /// The pointer to check. + /// The text color to use. + private protected void PrintFieldName(nint ptr, Vector4 color) + { + if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result)) + { + ImGui.SameLine(); + ImGui.TextColored(color, string.Join(".", result)); + } + } + + /// + /// Builds a string that will serve as the header text for the tree. Indicates the node type, the number of direct children it contains, and its pointer. + /// + /// The resulting header text string. + private protected virtual string GetHeaderText() + { + var count = this.GetDirectChildCount(); + return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)} ({(nint)this.Node:X})"; + } + + /// + /// Prints the node struct. + /// + private protected virtual void PrintNodeObject() + { + ShowStruct(this.Node); + ImGui.SameLine(); + ImGui.NewLine(); + } + + /// + /// Prints any field names for the node. + /// + private protected virtual void PrintFieldNames() => this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); + + /// + /// Prints all direct children of this node. + /// + private protected virtual void PrintChildNodes() + { + var prevNode = this.Node->ChildNode; + while (prevNode != null) + { + GetOrCreate(prevNode, this.AddonTree).Print(null); + prevNode = prevNode->PrevSiblingNode; + } + } + + /// + /// Prints any specific fields pertaining to the specific type of node. + /// + /// Whether the "Edit" box is currently checked. + private protected virtual void PrintFieldsForNodeType(bool isEditorOpen = false) + { + } + + private int GetDirectChildCount() + { + var count = 0; + if (this.Node->ChildNode != null) + { + count++; + + var prev = this.Node->ChildNode; + while (prev->PrevSiblingNode != null) + { + prev = prev->PrevSiblingNode; + count++; + } + } + + return count; + } + + private void PrintWithHighlights(uint? index) + { + if (!Scrolled) + { + ImGui.SetScrollHereY(); + Scrolled = true; + } + + var start = ImGui.GetCursorScreenPos() - new Vector2(5); + this.PrintTree(index, true); + var end = new Vector2(ImGui.GetMainViewport().WorkSize.X, ImGui.GetCursorScreenPos().Y + 5); + + ImGui.GetWindowDrawList().AddRectFilled(start, end, RgbaVector4ToUint(new Vector4(1, 1, 0.2f, 1) { W = Countdown / 200f })); + } + + private void PrintTree(uint? index, bool forceOpen = false) + { + var visible = this.Node->NodeFlags.HasFlag(Visible); + + var displayColor = !visible ? new Vector4(0.8f, 0.8f, 0.8f, 1) : + this.Node->Color.A == 0 ? new(0.015f, 0.575f, 0.355f, 1) : + new(0.1f, 1f, 0.1f, 1f); + + if (forceOpen || SearchResults.Contains((nint)this.Node)) + { + ImGui.SetNextItemOpen(true, ImGuiCond.Always); + } + + ImGui.PushStyleColor(ImGuiCol.Text, displayColor); + + var treePush = NestedTreePush($"{(index == null ? string.Empty : $"[{index}] ")}[#{this.Node->NodeId}]###{(nint)this.Node:X}nodeTree", displayColor, out var lineStart); + + if (ImGui.IsItemHovered()) + { + new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f)); + } + + ImGui.SameLine(); + this.WriteTreeHeading(); + + ImGui.PopStyleColor(); + + if (treePush) + { + try + { + PrintFieldValuePair("Node", $"{(nint)this.Node:X}"); + + ImGui.SameLine(); + this.PrintNodeObject(); + + PrintFieldValuePairs( + ("NodeID", $"{this.Node->NodeId}"), + ("Type", $"{this.Node->Type}")); + + this.DrawBasicControls(); + + if (this.editorOpen) + { + this.DrawNodeEditorTable(); + } + else + { + this.PrintResNodeFields(); + } + + this.PrintFieldsForNodeType(this.editorOpen); + PrintEvents(this.Node); + new TimelineTree(this.Node).Print(); + + this.PrintChildNodes(); + } + catch (Exception ex) + { + ImGui.TextDisabled($"Couldn't display node!\n\n{ex}"); + } + + NestedTreePop(lineStart, displayColor); + } + } + + private void DrawBasicControls() + { + ImGui.SameLine(); + var y = ImGui.GetCursorPosY(); + + ImGui.SetCursorPosY(y - 2); + var isVisible = this.Node->NodeFlags.HasFlag(Visible); + if (ImGuiComponents.IconButton("vis", isVisible ? Eye : EyeSlash, isVisible ? new Vector4(0.0f, 0.8f, 0.2f, 1f) : new(0.6f, 0.6f, 0.6f, 1))) + { + if (isVisible) + { + this.Node->NodeFlags &= ~Visible; + } + else + { + this.Node->NodeFlags |= Visible; + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Visibility"); + } + + ImGui.SameLine(); + ImGui.SetCursorPosY(y - 2); + ImGui.Checkbox($"Edit###editCheckBox{(nint)this.Node}", ref this.editorOpen); + + ImGui.SameLine(); + ImGui.SetCursorPosY(y - 2); + if (ImGuiComponents.IconButton($"###{(nint)this.Node}popoutButton", this.window?.IsOpen == true ? Times : ArrowUpRightFromSquare, null)) + { + this.TogglePopout(); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Toggle Popout Window"); + } + } + + private void TogglePopout() + { + if (this.window != null) + { + this.window.IsOpen = !this.window.IsOpen; + } + else + { + this.window = new NodePopoutWindow(this, $"{this.AddonTree.AddonName}: {this.GetHeaderText()}###nodePopout{(nint)this.Node}"); + PopoutWindows.AddWindow(this.window); + } + } + + private void PrintResNodeFields() + { + PrintFieldValuePairs( + ("X", $"{this.Node->X}"), + ("Y", $"{this.Node->Y}"), + ("Width", $"{this.Node->Width}"), + ("Height", $"{this.Node->Height}"), + ("Priority", $"{this.Node->Priority}"), + ("Depth", $"{this.Node->Depth}"), + ("DrawFlags", $"0x{this.Node->DrawFlags:X}")); + + PrintFieldValuePairs( + ("ScaleX", $"{this.Node->ScaleX:F2}"), + ("ScaleY", $"{this.Node->ScaleY:F2}"), + ("OriginX", $"{this.Node->OriginX}"), + ("OriginY", $"{this.Node->OriginY}"), + ("Rotation", $"{this.Node->Rotation * (180d / Math.PI):F1}° / {this.Node->Rotation:F7}rad ")); + + var color = this.Node->Color; + var add = new Vector3(this.Node->AddRed, this.Node->AddGreen, this.Node->AddBlue); + var multiply = new Vector3(this.Node->MultiplyRed, this.Node->MultiplyGreen, this.Node->MultiplyBlue); + + PrintColor(RgbaUintToVector4(color.RGBA) with { W = 1 }, $"RGB: {SwapEndianness(color.RGBA) >> 8:X6}"); + ImGui.SameLine(); + PrintColor(color, $"Alpha: {color.A}"); + ImGui.SameLine(); + PrintColor((add / new Vector3(510f)) + new Vector3(0.5f), $"Add: {add.X} {add.Y} {add.Z}"); + ImGui.SameLine(); + PrintColor(multiply / 255f, $"Multiply: {multiply.X} {multiply.Y} {multiply.Z}"); + + PrintFieldValuePairs(("Flags", $"0x{(uint)this.Node->NodeFlags:X} ({this.Node->NodeFlags})")); + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs new file mode 100644 index 000000000..8572a5495 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -0,0 +1,126 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A tree for an that can be printed and browsed via ImGui. +/// +internal unsafe partial class TextNodeTree : ResNodeTree +{ + /// + /// Initializes a new instance of the class. + /// + /// The node to create a tree for. + /// The tree representing the containing addon. + internal TextNodeTree(AtkResNode* node, AddonTree addonTree) + : base(node, addonTree) + { + } + + private AtkTextNode* TxtNode => (AtkTextNode*)this.Node; + + private Utf8String NodeText => this.TxtNode->NodeText; + + /// + private protected override void PrintNodeObject() => ShowStruct(this.TxtNode); + + /// + private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) + { + if (isEditorOpen) + { + return; + } + + ImGui.TextColored(new(1), "Text:"); + ImGui.SameLine(); + +#pragma warning disable + try + { + var style = new SeStringDrawParams() + { + Color = TxtNode->TextColor.RGBA, + EdgeColor = TxtNode->EdgeColor.RGBA, + ForceEdgeColor = true, + EdgeStrength = 1f + }; + + ImGuiHelpers.SeStringWrapped(NodeText.AsSpan(), style); + } + catch + { + ImGui.Text(Marshal.PtrToStringAnsi(new(NodeText.StringPtr)) ?? ""); + } +#pragma warning restore + + PrintFieldValuePairs( + ("Font", $"{this.TxtNode->FontType}"), + ("Font Size", $"{this.TxtNode->FontSize}"), + ("Alignment", $"{this.TxtNode->AlignmentType}")); + + PrintColor(this.TxtNode->TextColor, $"Text Color: {SwapEndianness(this.TxtNode->TextColor.RGBA):X8}"); + ImGui.SameLine(); + PrintColor(this.TxtNode->EdgeColor, $"Edge Color: {SwapEndianness(this.TxtNode->EdgeColor.RGBA):X8}"); + + this.PrintPayloads(); + } + + private void PrintPayloads() + { + if (ImGui.TreeNode($"Text Payloads##{(nint)this.Node:X}")) + { + var utf8String = this.NodeText; + var seStringBytes = new byte[utf8String.BufUsed]; + for (var i = 0L; i < utf8String.BufUsed; i++) + { + seStringBytes[i] = utf8String.StringPtr[i]; + } + + var seString = SeString.Parse(seStringBytes); + for (var i = 0; i < seString.Payloads.Count; i++) + { + var payload = seString.Payloads[i]; + ImGui.Text($"[{i}]"); + ImGui.SameLine(); + switch (payload.Type) + { + case PayloadType.RawText when payload is TextPayload tp: + { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + ImGui.Text("Raw Text: '"); + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.6f, 0.6f, 0.6f, 1)); + ImGui.Text(tp.Text); + ImGui.PopStyleColor(); + ImGui.SameLine(); + ImGui.PopStyleVar(); + ImGui.Text("'"); + break; + } + + default: + { + ImGui.Text(payload.ToString()); + break; + } + } + } + + ImGui.TreePop(); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs new file mode 100644 index 000000000..179197128 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +public partial struct TimelineTree +{ + /// + /// An interface for retrieving and printing the contents of a given column in an animation timeline table. + /// + public interface IKeyGroupColumn + { + /// Gets the column's name/heading. + public string Name { get; } + + /// Gets the number of cells in the column. + public int Count { get; } + + /// Gets the column's width. + public float Width { get; } + + /// + /// Calls this column's print function for a given row. + /// + /// The row number. + public void PrintValueAt(int i); + } + + /// + /// A column within an animation timeline table, representing a particular KeyGroup. + /// + /// The value type of the KeyGroup. + public struct KeyGroupColumn : IKeyGroupColumn + { + /// The values of each cell in the column. + public List Values; + + /// The method that should be used to format and print values in this KeyGroup. + public Action PrintFunc; + + /// + /// Initializes a new instance of the struct. + /// + /// The column's name/heading. + /// The method that should be used to format and print values in this KeyGroup. + internal KeyGroupColumn(string name, Action? printFunc = null) + { + this.Name = name; + this.PrintFunc = printFunc ?? PlainTextCell; + this.Values = []; + this.Width = 50; + } + + /// + public string Name { get; set; } + + /// + public float Width { get; init; } + + /// + public readonly int Count => this.Values.Count; + + /// + /// The default print function, if none is specified. + /// + /// The value to print. + public static void PlainTextCell(T value) => ImGui.Text($"{value}"); + + /// + /// Adds a value to this column. + /// + /// The value to add. + public readonly void Add(T val) => this.Values.Add(val); + + /// + public readonly void PrintValueAt(int i) + { + if (this.Values.Count > i) + { + this.PrintFunc.Invoke(this.Values[i]); + } + else + { + ImGui.TextDisabled("..."); + } + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs new file mode 100644 index 000000000..22fb61872 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -0,0 +1,381 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Client.Graphics; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; +using static Dalamud.Utility.Util; +using static FFXIVClientStructs.FFXIV.Component.GUI.NodeType; +using static ImGuiNET.ImGuiTableColumnFlags; +using static ImGuiNET.ImGuiTableFlags; + +// ReSharper disable SuggestBaseTypeForParameter +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +/// +/// A struct allowing a node's animation timeline to be printed and browsed. +/// +public unsafe partial struct TimelineTree +{ + private AtkResNode* node; + + /// + /// Initializes a new instance of the struct. + /// + /// The node whose timelines are to be displayed. + internal TimelineTree(AtkResNode* node) + { + this.node = node; + } + + private AtkTimeline* NodeTimeline => this.node->Timeline; + + private AtkTimelineResource* Resource => this.NodeTimeline->Resource; + + private AtkTimelineAnimation* ActiveAnimation => this.NodeTimeline->ActiveAnimation; + + /// + /// Prints out this timeline tree within a window. + /// + internal void Print() + { + if (this.NodeTimeline == null) + { + return; + } + + var count = this.Resource->AnimationCount; + + if (count > 0) + { + if (NestedTreePush($"Timeline##{(nint)this.node:X}timeline", out _)) + { + PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}"); + + ImGui.SameLine(); + + ShowStruct(this.NodeTimeline); + + PrintFieldValuePairs( + ("Id", $"{this.NodeTimeline->Resource->Id}"), + ("Parent Time", $"{this.NodeTimeline->ParentFrameTime:F2} ({this.NodeTimeline->ParentFrameTime * 30:F0})"), + ("Frame Time", $"{this.NodeTimeline->FrameTime:F2} ({this.NodeTimeline->FrameTime * 30:F0})")); + + PrintFieldValuePairs(("Active Label Id", $"{this.NodeTimeline->ActiveLabelId}"), ("Duration", $"{this.NodeTimeline->LabelFrameIdxDuration}"), ("End Frame", $"{this.NodeTimeline->LabelEndFrameIdx}")); + ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), "Animation List"); + + for (var a = 0; a < count; a++) + { + var animation = this.Resource->Animations[a]; + var isActive = this.ActiveAnimation != null && animation.Equals(*this.ActiveAnimation); + this.PrintAnimation(animation, a, isActive, (nint)(this.NodeTimeline->Resource->Animations + (a * sizeof(AtkTimelineAnimation)))); + } + + ImGui.TreePop(); + } + } + } + + private static void GetFrameColumn(Span keyGroups, List columns, ushort endFrame) + { + for (var i = 0; i < keyGroups.Length; i++) + { + if (keyGroups[i].Type != AtkTimelineKeyGroupType.None) + { + var keyGroup = keyGroups[i]; + var idColumn = new KeyGroupColumn("Frame"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + idColumn.Add(keyGroup.KeyFrames[f].FrameIdx); + } + + if (idColumn.Values.Last() != endFrame) + { + idColumn.Add(endFrame); + } + + columns.Add(idColumn); + break; + } + } + } + + private static void GetPosColumns(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var xColumn = new KeyGroupColumn("X"); + var yColumn = new KeyGroupColumn("Y"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + var (x, y) = keyGroup.KeyFrames[f].Value.Float2; + + xColumn.Add(x); + yColumn.Add(y); + } + + columns.Add(xColumn); + columns.Add(yColumn); + } + + private static void GetRotationColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var rotColumn = new KeyGroupColumn("Rotation", static r => ImGui.Text($"{r * (180d / Math.PI):F1}°")); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + rotColumn.Add(keyGroup.KeyFrames[f].Value.Float); + } + + columns.Add(rotColumn); + } + + private static void GetScaleColumns(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var scaleXColumn = new KeyGroupColumn("ScaleX"); + var scaleYColumn = new KeyGroupColumn("ScaleY"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + var (scaleX, scaleY) = keyGroup.KeyFrames[f].Value.Float2; + + scaleXColumn.Add(scaleX); + scaleYColumn.Add(scaleY); + } + + columns.Add(scaleXColumn); + columns.Add(scaleYColumn); + } + + private static void GetAlphaColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var alphaColumn = new KeyGroupColumn("Alpha", PrintAlpha); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + alphaColumn.Add(keyGroup.KeyFrames[f].Value.Byte); + } + + columns.Add(alphaColumn); + } + + private static void GetTintColumns(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var addRGBColumn = new KeyGroupColumn("Add", PrintAddCell) { Width = 110 }; + var multiplyRGBColumn = new KeyGroupColumn("Multiply", PrintMultiplyCell) { Width = 110 }; + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + var nodeTint = keyGroup.KeyFrames[f].Value.NodeTint; + + addRGBColumn.Add(new Vector3(nodeTint.AddR, nodeTint.AddG, nodeTint.AddB)); + multiplyRGBColumn.Add(nodeTint.MultiplyRGB); + } + + columns.Add(addRGBColumn); + columns.Add(multiplyRGBColumn); + } + + private static void GetTextColorColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var textColorColumn = new KeyGroupColumn("Text Color", PrintRGB); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + textColorColumn.Add(keyGroup.KeyFrames[f].Value.RGB); + } + + columns.Add(textColorColumn); + } + + private static void GetPartIdColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var partColumn = new KeyGroupColumn("Part ID"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + partColumn.Add(keyGroup.KeyFrames[f].Value.UShort); + } + + columns.Add(partColumn); + } + + private static void GetEdgeColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var edgeColorColumn = new KeyGroupColumn("Edge Color", PrintRGB); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + edgeColorColumn.Add(keyGroup.KeyFrames[f].Value.RGB); + } + + columns.Add(edgeColorColumn); + } + + private static void GetLabelColumn(AtkTimelineKeyGroup keyGroup, List columns) + { + if (keyGroup.KeyFrameCount <= 0) + { + return; + } + + var labelColumn = new KeyGroupColumn("Label"); + + for (var f = 0; f < keyGroup.KeyFrameCount; f++) + { + labelColumn.Add(keyGroup.KeyFrames[f].Value.Label.LabelId); + } + + columns.Add(labelColumn); + } + + private static void PrintRGB(ByteColor c) => PrintColor(c, $"0x{SwapEndianness(c.RGBA):X8}"); + + private static void PrintAlpha(byte b) => PrintColor(new Vector4(b / 255f), PadEvenly($"{b}", 25)); + + private static void PrintAddCell(Vector3 add) + { + var fmt = PadEvenly($"{PadEvenly($"{add.X}", 30)}{PadEvenly($"{add.Y}", 30)}{PadEvenly($"{add.Z}", 30)}", 100); + PrintColor(new Vector4((add / new Vector3(510f)) + new Vector3(0.5f), 1), fmt); + } + + private static void PrintMultiplyCell(ByteColor byteColor) + { + var multiply = new Vector3(byteColor.R, byteColor.G, byteColor.B); + var fmt = PadEvenly($"{PadEvenly($"{multiply.X}", 25)}{PadEvenly($"{multiply.Y}", 25)}{PadEvenly($"{multiply.Z}", 25)}", 100); + PrintColor(multiply / 255f, fmt); + } + + private static string PadEvenly(string str, float size) + { + while (ImGui.CalcTextSize(str).X < size * ImGuiHelpers.GlobalScale) + { + str = $" {str} "; + } + + return str; + } + + private void PrintAnimation(AtkTimelineAnimation animation, int a, bool isActive, nint address) + { + var columns = this.BuildColumns(animation); + + ImGui.PushStyleColor(ImGuiCol.Text, isActive ? new Vector4(1, 0.65F, 0.4F, 1) : new(1)); + var treePush = ImGui.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); + ImGui.PopStyleColor(); + + if (treePush) + { + PrintFieldValuePair("Animation", $"{address:X}"); + + ShowStruct((AtkTimelineAnimation*)address); + + if (columns.Count > 0) + { + ImGui.BeginTable($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); + + foreach (var c in columns) + { + ImGui.TableSetupColumn(c.Name, WidthFixed, c.Width); + } + + ImGui.TableHeadersRow(); + + var rows = columns.Select(static c => c.Count).Max(); + + for (var i = 0; i < rows; i++) + { + ImGui.TableNextRow(); + + foreach (var c in columns) + { + ImGui.TableNextColumn(); + c.PrintValueAt(i); + } + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } + + private List BuildColumns(AtkTimelineAnimation animation) + { + var keyGroups = animation.KeyGroups; + var columns = new List(); + + GetFrameColumn(keyGroups, columns, animation.EndFrameIdx); + + GetPosColumns(keyGroups[0], columns); + + GetRotationColumn(keyGroups[1], columns); + + GetScaleColumns(keyGroups[2], columns); + + GetAlphaColumn(keyGroups[3], columns); + + GetTintColumns(keyGroups[4], columns); + + if (this.node->Type is Image or NineGrid or ClippingMask) + { + GetPartIdColumn(keyGroups[5], columns); + } + else if (this.node->Type == Text) + { + GetTextColorColumn(keyGroups[5], columns); + } + + GetEdgeColumn(keyGroups[6], columns); + + GetLabelColumn(keyGroups[7], columns); + + return columns; + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs new file mode 100644 index 000000000..c6cb99037 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -0,0 +1,475 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Internal.UiDebug2.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static System.Globalization.NumberFormatInfo; + +using static Dalamud.Interface.FontAwesomeIcon; +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; +using static Dalamud.Interface.UiBuilder; +using static Dalamud.Interface.Utility.ImGuiHelpers; +using static FFXIVClientStructs.FFXIV.Component.GUI.NodeFlags; +using static ImGuiNET.ImGuiCol; +using static ImGuiNET.ImGuiWindowFlags; + +#pragma warning disable CS0659 + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +/// A tool that enables the user to select UI elements within the inspector by mousing over them onscreen. +/// +internal unsafe class ElementSelector : IDisposable +{ + private const int UnitListCount = 18; + + private readonly UiDebug2 uiDebug2; + + private string addressSearchInput = string.Empty; + + private int index; + + /// + /// Initializes a new instance of the class. + /// + /// The instance of this Element Selector belongs to. + internal ElementSelector(UiDebug2 uiDebug2) + { + this.uiDebug2 = uiDebug2; + } + + /// + /// Gets or sets the results retrieved by the Element Selector. + /// + internal static nint[] SearchResults { get; set; } = []; + + /// + /// Gets or sets a value governing the highlighting of nodes when found via search. + /// + internal static float Countdown { get; set; } + + /// + /// Gets or sets a value indicating whether the window has scrolled down to the position of the search result. + /// + internal static bool Scrolled { get; set; } + + /// + /// Gets or sets a value indicating whether the mouseover UI is currently active. + /// + internal bool Active { get; set; } + + /// + public void Dispose() + { + this.Active = false; + } + + /// + /// Draws the Element Selector and Address Search interface at the bottom of the sidebar. + /// + internal void DrawInterface() + { + ImGui.BeginChild("###sidebar_elementSelector", new(250, 0), true); + + ImGui.PushFont(IconFont); + ImGui.PushStyleColor(Text, this.Active ? new Vector4(1, 1, 0.2f, 1) : new(1)); + if (ImGui.Button($"{(char)ObjectUngroup}")) + { + this.Active = !this.Active; + } + + if (Countdown > 0) + { + Countdown -= 1; + if (Countdown < 0) + { + Countdown = 0; + } + } + + ImGui.PopStyleColor(); + ImGui.PopFont(); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Element Selector"); + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 32); + ImGui.InputTextWithHint("###addressSearchInput", "Address Search", ref this.addressSearchInput, 18, ImGuiInputTextFlags.AutoSelectAll); + ImGui.SameLine(); + + if (ImGuiComponents.IconButton("###elemSelectorAddrSearch", Search) && nint.TryParse(this.addressSearchInput, NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier, InvariantInfo, out var address)) + { + this.PerformSearch(address); + } + + ImGui.EndChild(); + } + + /// + /// Draws the Element Selector's search output within the main window. + /// + internal void DrawSelectorOutput() + { + ImGui.GetIO().WantCaptureKeyboard = true; + ImGui.GetIO().WantCaptureMouse = true; + ImGui.GetIO().WantTextInput = true; + if (ImGui.IsKeyPressed(ImGuiKey.Escape)) + { + this.Active = false; + return; + } + + ImGui.Text("ELEMENT SELECTOR"); + ImGui.TextDisabled("Use the mouse to hover and identify UI elements, then click to jump to them in the inspector"); + ImGui.TextDisabled("Use the scrollwheel to choose between overlapping elements"); + ImGui.TextDisabled("Press ESCAPE to cancel"); + ImGui.Spacing(); + + var mousePos = ImGui.GetMousePos() - MainViewport.Pos; + var addonResults = GetAtkUnitBaseAtPosition(mousePos); + + ImGui.PushStyleColor(WindowBg, new Vector4(0.5f)); + ImGui.BeginChild("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); + ImGui.BeginGroup(); + + Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); + ImGui.Spacing(); + ImGui.Text("RESULTS:\n"); + + var i = 0; + foreach (var a in addonResults) + { + var name = a.Addon->NameString; + ImGui.Text($"[Addon] {name}"); + ImGui.Indent(15); + foreach (var n in a.Nodes) + { + var nSelected = i++ == this.index; + + PrintNodeHeaderOnly(n.Node, nSelected, a.Addon); + + if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + { + this.Active = false; + + this.uiDebug2.SelectedAddonName = a.Addon->NameString; + + var ptrList = new List { (nint)n.Node }; + + var nextNode = n.Node->ParentNode; + while (nextNode != null) + { + ptrList.Add((nint)nextNode); + nextNode = nextNode->ParentNode; + } + + SearchResults = [.. ptrList]; + Countdown = 100; + Scrolled = false; + } + + if (nSelected) + { + n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1)); + } + } + + ImGui.Indent(-15); + } + + if (i != 0) + { + this.index -= (int)ImGui.GetIO().MouseWheel; + while (this.index < 0) + { + this.index += i; + } + + while (this.index >= i) + { + this.index -= i; + } + } + + ImGui.EndGroup(); + ImGui.EndChild(); + ImGui.PopStyleColor(); + } + + private static List GetAtkUnitBaseAtPosition(Vector2 position) + { + var addonResults = new List(); + var unitListBaseAddr = GetUnitListBaseAddr(); + if (unitListBaseAddr == null) + { + return addonResults; + } + + foreach (var unit in UnitListOptions) + { + var unitManager = &unitListBaseAddr[unit.Index]; + + var safeCount = Math.Min(unitManager->Count, unitManager->Entries.Length); + + for (var i = 0; i < safeCount; i++) + { + var addon = unitManager->Entries[i].Value; + + if (addon == null || addon->RootNode == null) + { + continue; + } + + if (!addon->IsVisible || !addon->RootNode->NodeFlags.HasFlag(Visible)) + { + continue; + } + + var addonResult = new AddonResult(addon, []); + + if (addonResults.Contains(addonResult)) + { + continue; + } + + if (addon->X > position.X || addon->Y > position.Y) + { + continue; + } + + if (addon->X + addon->RootNode->Width < position.X) + { + continue; + } + + if (addon->Y + addon->RootNode->Height < position.Y) + { + continue; + } + + addonResult.Nodes.AddRange(GetNodeAtPosition(&addon->UldManager, position, true)); + addonResults.Add(addonResult); + } + } + + return [.. addonResults.OrderBy(static w => w.Area)]; + } + + private static List GetNodeAtPosition(AtkUldManager* uldManager, Vector2 position, bool reverse) + { + var nodeResults = new List(); + for (var i = 0; i < uldManager->NodeListCount; i++) + { + var node = uldManager->NodeList[i]; + + var bounds = new NodeBounds(node); + + if (!bounds.ContainsPoint(position)) + { + continue; + } + + if ((int)node->Type >= 1000) + { + var compNode = (AtkComponentNode*)node; + nodeResults.AddRange(GetNodeAtPosition(&compNode->Component->UldManager, position, false)); + } + + nodeResults.Add(new() { NodeBounds = bounds, Node = node }); + } + + if (reverse) + { + nodeResults.Reverse(); + } + + return nodeResults; + } + + private static bool FindByAddress(AtkUnitBase* atkUnitBase, nint address) + { + if (atkUnitBase->RootNode == null) + { + return false; + } + + if (!FindByAddress(atkUnitBase->RootNode, address, out var path)) + { + return false; + } + + Scrolled = false; + SearchResults = path?.ToArray() ?? []; + Countdown = 100; + return true; + } + + private static bool FindByAddress(AtkResNode* node, nint address, out List? path) + { + if (node == null) + { + path = null; + return false; + } + + if ((nint)node == address) + { + path = [(nint)node]; + return true; + } + + if ((int)node->Type >= 1000) + { + var cNode = (AtkComponentNode*)node; + + if (cNode->Component != null) + { + if ((nint)cNode->Component == address) + { + path = [(nint)node]; + return true; + } + + if (FindByAddress(cNode->Component->UldManager.RootNode, address, out path) && path != null) + { + path.Add((nint)node); + return true; + } + } + } + + if (FindByAddress(node->ChildNode, address, out path) && path != null) + { + path.Add((nint)node); + return true; + } + + if (FindByAddress(node->PrevSiblingNode, address, out path) && path != null) + { + return true; + } + + path = null; + return false; + } + + private static void PrintNodeHeaderOnly(AtkResNode* node, bool selected, AtkUnitBase* addon) + { + if (addon == null) + { + return; + } + + if (node == null) + { + return; + } + + var tree = AddonTree.GetOrCreate(addon->NameString); + if (tree == null) + { + return; + } + + ImGui.PushStyleColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1)); + ResNodeTree.GetOrCreate(node, tree).WriteTreeHeading(); + ImGui.PopStyleColor(); + } + + private void PerformSearch(nint address) + { + var unitListBaseAddr = GetUnitListBaseAddr(); + if (unitListBaseAddr == null) + { + return; + } + + for (var i = 0; i < UnitListCount; i++) + { + var unitManager = &unitListBaseAddr[i]; + var safeCount = Math.Min(unitManager->Count, unitManager->Entries.Length); + + for (var j = 0; j < safeCount; j++) + { + var addon = unitManager->Entries[j].Value; + if ((nint)addon == address || FindByAddress(addon, address)) + { + this.uiDebug2.SelectedAddonName = addon->NameString; + return; + } + } + } + } + + /// + /// An found by the Element Selector. + /// + internal struct AddonResult + { + /// The addon itself. + internal AtkUnitBase* Addon; + + /// A list of nodes discovered within this addon by the Element Selector. + internal List Nodes; + + /// The calculated area of the addon's root node. + internal float Area; + + /// + /// Initializes a new instance of the struct. + /// + /// The addon found. + /// A list for documenting nodes found within the addon. + public AddonResult(AtkUnitBase* addon, List nodes) + { + this.Addon = addon; + this.Nodes = nodes; + var rootNode = addon->RootNode; + this.Area = rootNode != null ? rootNode->Width * rootNode->Height * rootNode->ScaleY * rootNode->ScaleX : 0; + } + + /// + public override readonly bool Equals(object? obj) + { + if (obj is not AddonResult ar) + { + return false; + } + + return (nint)this.Addon == (nint)ar.Addon; + } + } + + /// + /// An found by the Element Selector. + /// + internal struct NodeResult + { + /// The node itself. + internal AtkResNode* Node; + + /// A struct representing the perimeter of the node. + internal NodeBounds NodeBounds; + + /// + public override readonly bool Equals(object? obj) + { + if (obj is not NodeResult nr) + { + return false; + } + + return nr.Node == this.Node; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs new file mode 100644 index 000000000..3aef3b6a4 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs @@ -0,0 +1,50 @@ +using System.Numerics; + +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +/// A popout window for an . +/// +internal class AddonPopoutWindow : Window, IDisposable +{ + private readonly AddonTree addonTree; + + /// + /// Initializes a new instance of the class. + /// + /// The AddonTree this popout will show. + /// the window's name. + public AddonPopoutWindow(AddonTree tree, string name) + : base(name) + { + this.addonTree = tree; + this.PositionCondition = ImGuiCond.Once; + + var pos = ImGui.GetMousePos() + new Vector2(50, -50); + var workSize = ImGui.GetMainViewport().WorkSize; + var pos2 = new Vector2(Math.Min(workSize.X - 750, pos.X), Math.Min(workSize.Y - 250, pos.Y)); + + this.Position = pos2; + this.SizeCondition = ImGuiCond.Once; + this.Size = new(700, 200); + this.IsOpen = true; + this.SizeConstraints = new() { MinimumSize = new(100, 100) }; + } + + /// + public override void Draw() + { + ImGui.BeginChild($"{this.WindowName}child", new(-1, -1), true); + this.addonTree.Draw(); + ImGui.EndChild(); + } + + /// + public void Dispose() + { + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs new file mode 100644 index 000000000..b293b734e --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs @@ -0,0 +1,69 @@ +using System.Numerics; + +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Windowing; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static Dalamud.Interface.Internal.UiDebug2.UiDebug2; + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +/// A popout window for a . +/// +internal unsafe class NodePopoutWindow : Window, IDisposable +{ + private readonly ResNodeTree resNodeTree; + + private bool firstDraw = true; + + /// + /// Initializes a new instance of the class. + /// + /// The node tree this window will show. + /// The name of the window. + public NodePopoutWindow(ResNodeTree nodeTree, string windowName) + : base(windowName) + { + this.resNodeTree = nodeTree; + + var pos = ImGui.GetMousePos() + new Vector2(50, -50); + var workSize = ImGui.GetMainViewport().WorkSize; + var pos2 = new Vector2(Math.Min(workSize.X - 750, pos.X), Math.Min(workSize.Y - 250, pos.Y)); + + this.Position = pos2; + this.IsOpen = true; + this.PositionCondition = ImGuiCond.Once; + this.SizeCondition = ImGuiCond.Once; + this.Size = new(700, 200); + this.SizeConstraints = new() { MinimumSize = new(100, 100) }; + } + + private AddonTree AddonTree => this.resNodeTree.AddonTree; + + private AtkResNode* Node => this.resNodeTree.Node; + + /// + public override void Draw() + { + if (this.Node != null && this.AddonTree.ContainsNode(this.Node)) + { + ImGui.BeginChild($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); + ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); + ImGui.EndChild(); + this.firstDraw = false; + } + else + { + Log.Warning($"Popout closed ({this.WindowName}); Node or Addon no longer exists."); + this.IsOpen = false; + this.Dispose(); + } + } + + /// + public void Dispose() + { + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs new file mode 100644 index 000000000..d2510d16b --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -0,0 +1,214 @@ +using System.Collections.Generic; +using System.Numerics; + +using Dalamud.Interface.Components; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static System.StringComparison; +using static Dalamud.Interface.FontAwesomeIcon; + +namespace Dalamud.Interface.Internal.UiDebug2; + +/// +internal unsafe partial class UiDebug2 +{ + /// + /// All unit lists to check for addons. + /// + internal static readonly List UnitListOptions = + [ + new(13, "Loaded"), + new(14, "Focused"), + new(0, "Depth Layer 1"), + new(1, "Depth Layer 2"), + new(2, "Depth Layer 3"), + new(3, "Depth Layer 4"), + new(4, "Depth Layer 5"), + new(5, "Depth Layer 6"), + new(6, "Depth Layer 7"), + new(7, "Depth Layer 8"), + new(8, "Depth Layer 9"), + new(9, "Depth Layer 10"), + new(10, "Depth Layer 11"), + new(11, "Depth Layer 12"), + new(12, "Depth Layer 13"), + new(15, "Units 16"), + new(16, "Units 17"), + new(17, "Units 18"), + ]; + + private string addonNameSearch = string.Empty; + + private bool visFilter; + + /// + /// Gets the base address for all unit lists. + /// + /// The address, if found. + internal static AtkUnitList* GetUnitListBaseAddr() => &((UIModule*)GameGui.GetUIModule())->GetRaptureAtkModule()->RaptureAtkUnitManager.AtkUnitManager.DepthLayerOneList; + + private void DrawSidebar() + { + ImGui.BeginGroup(); + + this.DrawNameSearch(); + this.DrawAddonSelectionList(); + this.elementSelector.DrawInterface(); + + ImGui.EndGroup(); + } + + private void DrawNameSearch() + { + ImGui.BeginChild("###sidebar_nameSearch", new(250, 40), true); + var atkUnitBaseSearch = this.addonNameSearch; + + Vector4? defaultColor = this.visFilter ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1); + if (ImGuiComponents.IconButton("filter", LowVision, defaultColor)) + { + this.visFilter = !this.visFilter; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Filter by visibility"); + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("###atkUnitBaseSearch", "Filter by name", ref atkUnitBaseSearch, 0x20)) + { + this.addonNameSearch = atkUnitBaseSearch; + } + + ImGui.EndChild(); + } + + private void DrawAddonSelectionList() + { + ImGui.BeginChild("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); + + var unitListBaseAddr = GetUnitListBaseAddr(); + + foreach (var unit in UnitListOptions) + { + this.DrawUnitListOption(unitListBaseAddr, unit); + } + + ImGui.EndChild(); + } + + private void DrawUnitListOption(AtkUnitList* unitListBaseAddr, UnitListOption unit) + { + var atkUnitList = &unitListBaseAddr[unit.Index]; + var safeLength = Math.Min(atkUnitList->Count, atkUnitList->Entries.Length); + + var options = new List(); + var totalCount = 0; + var matchCount = 0; + var anyVisible = false; + + var usingFilter = this.visFilter || !string.IsNullOrEmpty(this.addonNameSearch); + + for (var i = 0; i < safeLength; i++) + { + var addon = atkUnitList->Entries[i].Value; + + if (addon == null) + { + continue; + } + + totalCount++; + + if (this.visFilter && !addon->IsVisible) + { + continue; + } + + if (!string.IsNullOrEmpty(this.addonNameSearch) && !addon->NameString.Contains(this.addonNameSearch, InvariantCultureIgnoreCase)) + { + continue; + } + + matchCount++; + anyVisible |= addon->IsVisible; + options.Add(new AddonOption(addon->NameString, addon->IsVisible)); + } + + if (matchCount == 0) + { + return; + } + + ImGui.PushStyleColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}"; + var treePush = ImGui.TreeNodeEx($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); + ImGui.PopStyleColor(); + + if (treePush) + { + foreach (var option in options) + { + ImGui.PushStyleColor(ImGuiCol.Text, option.Visible ? new Vector4(0.1f, 1f, 0.1f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + if (ImGui.Selectable($"{option.Name}##select{option.Name}", this.SelectedAddonName == option.Name)) + { + this.SelectedAddonName = option.Name; + } + + ImGui.PopStyleColor(); + } + + ImGui.TreePop(); + } + } + + /// + /// A struct representing a unit list that can be browed in the sidebar. + /// + internal struct UnitListOption + { + /// The index of the unit list. + internal uint Index; + + /// The name of the unit list. + internal string Name; + + /// + /// Initializes a new instance of the struct. + /// + /// The index of the unit list. + /// The name of the unit list. + internal UnitListOption(uint i, string name) + { + this.Index = i; + this.Name = name; + } + } + + /// + /// A struct representing an addon that can be selected in the sidebar. + /// + internal struct AddonOption + { + /// The name of the addon. + internal string Name; + + /// Whether the addon is visible. + internal bool Visible; + + /// + /// Initializes a new instance of the struct. + /// + /// The name of the addon. + /// Whether the addon is visible. + internal AddonOption(string name, bool visible) + { + this.Name = name; + this.Visible = visible; + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs new file mode 100644 index 000000000..396a84ac9 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; + +using Dalamud.Game.Gui; +using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Windowing; +using Dalamud.Logging.Internal; +using Dalamud.Plugin.Services; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using static ImGuiNET.ImGuiWindowFlags; + +namespace Dalamud.Interface.Internal.UiDebug2; + +// Original version by aers https://github.com/aers/FFXIVUIDebug +// Also incorporates features from Caraxi's fork https://github.com/Caraxi/SimpleTweaksPlugin/blob/main/Debugging/UIDebug.cs + +/// +/// A tool for browsing the contents and structure of UI elements. +/// +internal partial class UiDebug2 : IDisposable +{ + private readonly ElementSelector elementSelector; + + /// + /// Initializes a new instance of the class. + /// + internal UiDebug2() + { + this.elementSelector = new(this); + + GameGui = Service.Get(); + + Log = new ModuleLog("UiDebug2"); + } + + /// + internal static ModuleLog Log { get; set; } = null!; + + /// + internal static IGameGui GameGui { get; set; } = null!; + + /// + /// Gets a collection of instances, each representing an . + /// + internal static Dictionary AddonTrees { get; } = []; + + /// + /// Gets or sets a window system to handle any popout windows for addons or nodes. + /// + internal static WindowSystem PopoutWindows { get; set; } = new("UiDebugPopouts"); + + /// + /// Gets or sets the name of the currently-selected . + /// + internal string? SelectedAddonName { get; set; } + + /// + /// Clears all windows and s. + /// + public void Dispose() + { + foreach (var a in AddonTrees) + { + a.Value.Dispose(); + } + + AddonTrees.Clear(); + PopoutWindows.RemoveAllWindows(); + this.elementSelector.Dispose(); + } + + /// + /// Draws the UiDebug tool's interface and contents. + /// + internal void Draw() + { + PopoutWindows.Draw(); + this.DrawSidebar(); + this.DrawMainPanel(); + } + + private void DrawMainPanel() + { + ImGui.SameLine(); + ImGui.BeginChild("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); + + if (this.elementSelector.Active) + { + this.elementSelector.DrawSelectorOutput(); + } + else + { + if (this.SelectedAddonName != null) + { + var addonTree = AddonTree.GetOrCreate(this.SelectedAddonName); + + if (addonTree == null) + { + this.SelectedAddonName = null; + return; + } + + addonTree.Draw(); + } + } + + ImGui.EndChild(); + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs new file mode 100644 index 000000000..37b2f92cd --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -0,0 +1,204 @@ +using System.Collections.Generic; +using System.Numerics; + +using Dalamud.Interface.Components; +using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Client.Graphics; +using ImGuiNET; + +using static Dalamud.Interface.ColorHelpers; +using static ImGuiNET.ImGuiCol; + +namespace Dalamud.Interface.Internal.UiDebug2.Utility; + +/// +/// Miscellaneous ImGui tools used by . +/// +internal static class Gui +{ + /// + /// Begins a tree node that also displays a colored line to the left while open. + /// + /// The label of the tree. + /// The color of the heading text. + /// A value representing where to begin drawing the left-side line. + /// Whether this tree should default to being open. + /// true if the tree is open. + internal static bool NestedTreePush(string label, Vector4 color, out Vector2 lineStart, bool defOpen = false) + { + ImGui.PushStyleColor(Text, color); + var result = NestedTreePush(label, out lineStart, defOpen); + ImGui.PopStyleColor(); + return result; + } + + /// + internal static bool NestedTreePush(string label, out Vector2 lineStart, bool defOpen = false) + { + var imGuiTreeNodeFlags = ImGuiTreeNodeFlags.SpanFullWidth; + + if (defOpen) + { + imGuiTreeNodeFlags |= ImGuiTreeNodeFlags.DefaultOpen; + } + + var treeNodeEx = ImGui.TreeNodeEx(label, imGuiTreeNodeFlags); + lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); + return treeNodeEx; + } + + /// + /// Completes a NestedTree. + /// + /// The starting position calculated when the tree was pushed. + /// The color of the left-side line. + internal static void NestedTreePop(Vector2 lineStart, Vector4? color = null) + { + var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 }; + + if (lineStart.Y < lineEnd.Y) + { + ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color ?? new(1)), 1); + } + + ImGui.TreePop(); + } + + /// + /// A radio-button-esque input that uses Fontawesome icon buttons. + /// + /// The type of value being set. + /// The label for the inputs. + /// The value being set. + /// A list of all options to create buttons for. + /// A list of the icons to use for each option. + /// true if a button is clicked. + internal static unsafe bool IconSelectInput(string label, ref T val, List options, List icons) + { + var ret = false; + for (var i = 0; i < options.Count; i++) + { + var option = options[i]; + var icon = icons[i]; + + if (i > 0) + { + ImGui.SameLine(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() - ((ImGui.GetFontSize() / -6f) + 7f)); + } + + var color = *ImGui.GetStyleColorVec4(val is not null && val.Equals(option) ? ButtonActive : Button); + + if (ImGuiComponents.IconButton($"{label}{option}{i}", icon, color)) + { + val = option; + ret = true; + } + } + + return ret; + } + + /// + /// Prints field name and its value. + /// + /// The name of the field. + /// The value of the field. + /// Whether to enable click-to-copy. + internal static void PrintFieldValuePair(string fieldName, string value, bool copy = true) + { + ImGui.Text($"{fieldName}:"); + ImGui.SameLine(); + if (copy) + { + ClickToCopyText(value); + } + else + { + ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), value); + } + } + + /// + /// Prints a set of fields and their values. + /// + /// Tuples of fieldnames and values to display. + internal static void PrintFieldValuePairs(params (string FieldName, string Value)[] pairs) + { + for (var i = 0; i < pairs.Length; i++) + { + if (i != 0) + { + ImGui.SameLine(); + } + + PrintFieldValuePair(pairs[i].FieldName, pairs[i].Value, false); + } + } + + /// + internal static void PrintColor(ByteColor color, string fmt) => PrintColor(RgbaUintToVector4(color.RGBA), fmt); + + /// + internal static void PrintColor(Vector3 color, string fmt) => PrintColor(new Vector4(color, 1), fmt); + + /// + /// Prints a text string representing a color, with a backdrop in that color. + /// + /// The color value. + /// The text string to print. + /// Colors the text itself either white or black, depending on the luminosity of the background color. + internal static void PrintColor(Vector4 color, string fmt) + { + static double Luminosity(Vector4 vector4) => + Math.Pow( + (Math.Pow(vector4.X, 2) * 0.299f) + + (Math.Pow(vector4.Y, 2) * 0.587f) + + (Math.Pow(vector4.Z, 2) * 0.114f), + 0.5f) * vector4.W; + + ImGui.PushStyleColor(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)); + ImGui.PushStyleColor(Button, color); + ImGui.PushStyleColor(ButtonActive, color); + ImGui.PushStyleColor(ButtonHovered, color); + ImGui.SmallButton(fmt); + ImGui.PopStyleColor(4); + } + + /// + internal static void ClickToCopyText(string text, string? textCopy = null) + { + ImGui.PushStyleColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1)); + ImGuiHelpers.ClickToCopyText(text, textCopy); + ImGui.PopStyleColor(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip($"{textCopy ?? text}"); + } + } + + /// + /// Draws a tooltip that changes based on the cursor's x-position within the hovered item. + /// + /// The text for each section. + /// true if the item is hovered. + internal static bool SplitTooltip(params string[] tooltips) + { + if (!ImGui.IsItemHovered()) + { + return false; + } + + var mouseX = ImGui.GetMousePos().X; + var minX = ImGui.GetItemRectMin().X; + var maxX = ImGui.GetItemRectMax().X; + var prog = (mouseX - minX) / (maxX - minX); + + var index = (int)Math.Floor(prog * tooltips.Length); + + ImGui.SetTooltip(tooltips[index]); + + return true; + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs new file mode 100644 index 000000000..82bf8f96c --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; +using ImGuiNET; + +using static System.Math; + +namespace Dalamud.Interface.Internal.UiDebug2.Utility; + +/// +/// A struct representing the perimeter of an , accounting for all transformations. +/// +public unsafe struct NodeBounds +{ + /// + /// Initializes a new instance of the struct. + /// + /// The node to calculate the bounds of. + internal NodeBounds(AtkResNode* node) + { + if (node == null) + { + return; + } + + var w = node->Width; + var h = node->Height; + this.Points = w == 0 && h == 0 ? + new() { new(0) } : + new() { new(0), new(w, 0), new(w, h), new(0, h) }; + + this.TransformPoints(node); + } + + /// + /// Initializes a new instance of the struct, containing only a single given point. + /// + /// The point onscreen. + /// The node used to calculate transformations. + internal NodeBounds(Vector2 point, AtkResNode* node) + { + this.Points = [point]; + this.TransformPoints(node); + } + + private List Points { get; set; } = []; + + /// + /// Draws the bounds onscreen. + /// + /// The color of line to use. + /// The thickness of line to use. + /// If there is only a single point to draw, it will be indicated with a circle and dot. + internal readonly void Draw(Vector4 col, int thickness = 1) + { + if (this.Points == null || this.Points.Count == 0) + { + return; + } + + if (this.Points.Count == 1) + { + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, ColorHelpers.RgbaVector4ToUint(col with { W = col.W / 2 }), 12, thickness); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], thickness, ColorHelpers.RgbaVector4ToUint(col), 12, thickness + 1); + } + else + { + var path = new ImVectorWrapper(this.Points.Count); + foreach (var p in this.Points) + { + path.Add(p); + } + + ImGui.GetBackgroundDrawList() + .AddPolyline(ref path[0], path.Length, ColorHelpers.RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); + + path.Dispose(); + } + } + + /// + /// Draws the bounds onscreen, filled in. + /// + /// The fill and border color. + /// The border thickness. + internal readonly void DrawFilled(Vector4 col, int thickness = 1) + { + if (this.Points == null || this.Points.Count == 0) + { + return; + } + + if (this.Points.Count == 1) + { + ImGui.GetBackgroundDrawList() + .AddCircleFilled(this.Points[0], 10, ColorHelpers.RgbaVector4ToUint(col with { W = col.W / 2 }), 12); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, ColorHelpers.RgbaVector4ToUint(col), 12, thickness); + } + else + { + var path = new ImVectorWrapper(this.Points.Count); + foreach (var p in this.Points) + { + path.Add(p); + } + + ImGui.GetBackgroundDrawList() + .AddConvexPolyFilled(ref path[0], path.Length, ColorHelpers.RgbaVector4ToUint(col with { W = col.W / 2 })); + ImGui.GetBackgroundDrawList() + .AddPolyline(ref path[0], path.Length, ColorHelpers.RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); + + path.Dispose(); + } + } + + /// + /// Checks whether the bounds contain a given point. + /// + /// The point to check. + /// True if the point exists within the bounds. + internal readonly bool ContainsPoint(Vector2 p) + { + var count = this.Points.Count; + var inside = false; + + for (var i = 0; i < count; i++) + { + var p1 = this.Points[i]; + var p2 = this.Points[(i + 1) % count]; + + if (p.Y > Min(p1.Y, p2.Y) && + p.Y <= Max(p1.Y, p2.Y) && + p.X <= Max(p1.X, p2.X) && + (p1.X.Equals(p2.X) || p.X <= ((p.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y)) + p1.X)) + { + inside = !inside; + } + } + + return inside; + } + + private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s) + { + var cosR = (float)Cos(r); + var sinR = (float)Sin(r); + var d = (p - o) * s; + + return new(o.X + (d.X * cosR) - (d.Y * sinR), + o.Y + (d.X * sinR) + (d.Y * cosR)); + } + + private void TransformPoints(AtkResNode* transformNode) + { + while (transformNode != null) + { + var offset = new Vector2(transformNode->X, transformNode->Y); + var origin = offset + new Vector2(transformNode->OriginX, transformNode->OriginY); + var rotation = transformNode->Rotation; + var scale = new Vector2(transformNode->ScaleX, transformNode->ScaleY); + + this.Points = this.Points.Select(b => TransformPoint(b + offset, origin, rotation, scale)).ToList(); + + transformNode = transformNode->ParentNode; + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs index d4bea2931..33db51c75 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs @@ -1,11 +1,11 @@ -namespace Dalamud.Interface.Internal.Windows.Data.Widgets; +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget for displaying addon inspector. /// internal class AddonInspectorWidget : IDataWindowWidget { - private UiDebug? addonInspector; + private UiDebug2.UiDebug2? addonInspector; /// public string[]? CommandShortcuts { get; init; } = { "ai", "addoninspector" }; @@ -19,7 +19,7 @@ internal class AddonInspectorWidget : IDataWindowWidget /// public void Load() { - this.addonInspector = new UiDebug(); + this.addonInspector = new UiDebug2.UiDebug2(); if (this.addonInspector is not null) { From e19f9284e502cda31bfa7468ff4a5c018d181cb5 Mon Sep 17 00:00:00 2001 From: ItsBexy Date: Tue, 3 Sep 2024 21:23:50 -0600 Subject: [PATCH 055/375] Implement ImRaii --- .../UiDebug2/Browsing/AddonTree.FieldNames.cs | 22 ++--- .../Internal/UiDebug2/Browsing/Events.cs | 69 +++++++-------- .../UiDebug2/Browsing/NodeTree.Component.cs | 81 +++++++++--------- .../UiDebug2/Browsing/NodeTree.Editor.cs | 13 +-- .../UiDebug2/Browsing/NodeTree.Image.cs | 31 ++++--- .../UiDebug2/Browsing/NodeTree.NineGrid.cs | 3 +- .../UiDebug2/Browsing/NodeTree.Res.cs | 46 +++++++--- .../UiDebug2/Browsing/NodeTree.Text.cs | 32 ++++--- .../Browsing/TimelineTree.KeyGroupColumn.cs | 2 +- .../UiDebug2/Browsing/TimelineTree.cs | 33 ++++---- .../Internal/UiDebug2/ElementSelector.cs | 31 +++---- .../Internal/UiDebug2/Popout.Addon.cs | 5 +- .../Internal/UiDebug2/Popout.Node.cs | 5 +- .../Internal/UiDebug2/UiDebug2.Sidebar.cs | 33 ++++---- .../Interface/Internal/UiDebug2/UiDebug2.cs | 7 +- .../Internal/UiDebug2/Utility/Gui.cs | 84 ++++++------------- .../Internal/UiDebug2/Utility/NodeBounds.cs | 7 +- 17 files changed, 248 insertions(+), 256 deletions(-) diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs index e72de2b23..4baad4753 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -28,10 +28,8 @@ public unsafe partial class AddonTree return null; } - if (!AddonTypeDict.ContainsKey(this.AddonName)) + if (AddonTypeDict.TryAdd(this.AddonName, null)) { - AddonTypeDict.Add(this.AddonName, null); - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) { try @@ -104,13 +102,15 @@ public unsafe partial class AddonTree } } + return; + void ParseExplicitField(nint fieldAddr, FieldInfo field, MemberInfo fieldType, string name) { try { - if (this.FieldNames.TryAdd(fieldAddr, new List(path!) { name }) && fieldType.DeclaringType == baseType) + if (this.FieldNames.TryAdd(fieldAddr, [..path!, name]) && fieldType.DeclaringType == baseType) { - this.PopulateFieldNames(field.GetValue(obj), fieldAddr, new List(path) { name }); + this.PopulateFieldNames(field.GetValue(obj), fieldAddr, [..path, name]); } } catch (Exception ex) @@ -140,12 +140,12 @@ public unsafe partial class AddonTree var itemAddr = fieldAddr + (size * i); var itemName = $"{name}[{i}]"; - this.FieldNames.TryAdd(itemAddr, new List(path!) { itemName }); + this.FieldNames.TryAdd(itemAddr, [..path!, itemName]); var item = Marshal.PtrToStructure(itemAddr, itemType); if (itemType.DeclaringType == baseType) { - this.PopulateFieldNames(item, itemAddr, new List(path) { name }); + this.PopulateFieldNames(item, itemAddr, [..path, itemName]); } } } @@ -176,16 +176,16 @@ public unsafe partial class AddonTree return; } - this.FieldNames.TryAdd(fieldAddr, new List(path!) { name }); - this.FieldNames.TryAdd(pointer, new List(path) { name }); + this.FieldNames.TryAdd(fieldAddr, [..path!, name]); + this.FieldNames.TryAdd(pointer, [..path, name]); - if (itemType?.DeclaringType != baseType || itemType.IsPointer) + if (itemType?.DeclaringType != baseType || itemType!.IsPointer) { return; } var item = Marshal.PtrToStructure(pointer, itemType); - this.PopulateFieldNames(item, pointer, new List(path) { name }); + this.PopulateFieldNames(item, pointer, [..path, name]); } catch (Exception ex) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index bd1efe6c7..d542f5025 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -25,44 +26,44 @@ public static class Events return; } - if (ImGui.TreeNode($"Events##{(nint)node:X}eventTree")) + var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); + if (tree) { - if (ImGui.BeginTable($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg)) + var tab = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg); + + ImGui.TableSetupColumn("#", WidthFixed); + ImGui.TableSetupColumn("Type", WidthFixed); + ImGui.TableSetupColumn("Param", WidthFixed); + ImGui.TableSetupColumn("Flags", WidthFixed); + ImGui.TableSetupColumn("Unk29", WidthFixed); + ImGui.TableSetupColumn("Target", WidthFixed); + ImGui.TableSetupColumn("Listener", WidthFixed); + + ImGui.TableHeadersRow(); + + var i = 0; + while (evt != null) { - ImGui.TableSetupColumn("#", WidthFixed); - ImGui.TableSetupColumn("Type", WidthFixed); - ImGui.TableSetupColumn("Param", WidthFixed); - ImGui.TableSetupColumn("Flags", WidthFixed); - ImGui.TableSetupColumn("Unk29", WidthFixed); - ImGui.TableSetupColumn("Target", WidthFixed); - ImGui.TableSetupColumn("Listener", WidthFixed); - - ImGui.TableHeadersRow(); - - var i = 0; - while (evt != null) - { - ImGui.TableNextColumn(); - ImGui.Text($"{i++}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Type}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Param}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Flags}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Unk29}"); - ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Target:X}"); - ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); - evt = evt->NextEvent; - } - - ImGui.EndTable(); + ImGui.TableNextColumn(); + ImGui.Text($"{i++}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Type}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Param}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Flags}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Unk29}"); + ImGui.TableNextColumn(); + Gui.ClickToCopyText($"{(nint)evt->Target:X}"); + ImGui.TableNextColumn(); + Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); + evt = evt->NextEvent; } - ImGui.TreePop(); + tab.Dispose(); } + + tree.Dispose(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs index 2c95924c5..026933631 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs @@ -14,12 +14,8 @@ namespace Dalamud.Interface.Internal.UiDebug2.Browsing; ///
internal unsafe class ComponentNodeTree : ResNodeTree { - private readonly AtkUldManager* uldManager; - private readonly ComponentType componentType; - private readonly AtkComponentBase* component; - /// /// Initializes a new instance of the class. /// @@ -28,17 +24,21 @@ internal unsafe class ComponentNodeTree : ResNodeTree internal ComponentNodeTree(AtkResNode* node, AddonTree addonTree) : base(node, addonTree) { - this.component = ((AtkComponentNode*)node)->Component; - this.uldManager = &this.component->UldManager; this.NodeType = 0; - this.componentType = ((AtkUldComponentInfo*)this.uldManager->Objects)->ComponentType; + this.componentType = ((AtkUldComponentInfo*)this.UldManager->Objects)->ComponentType; } + private AtkComponentBase* Component => this.CompNode->Component; + + private AtkComponentNode* CompNode => (AtkComponentNode*)this.Node; + + private AtkUldManager* UldManager => &this.Component->UldManager; + /// private protected override string GetHeaderText() { - var childCount = (int)this.uldManager->NodeListCount; - return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.component:X})"; + var childCount = (int)this.UldManager->NodeListCount; + return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.Component:X})"; } /// @@ -57,29 +57,30 @@ internal unsafe class ComponentNodeTree : ResNodeTree private protected override void PrintChildNodes() { base.PrintChildNodes(); - var count = this.uldManager->NodeListCount; - PrintNodeListAsTree(this.uldManager->NodeList, count, $"Node List [{count}]:", this.AddonTree, new(0f, 0.5f, 0.8f, 1f)); + var count = this.UldManager->NodeListCount; + PrintNodeListAsTree(this.UldManager->NodeList, count, $"Node List [{count}]:", this.AddonTree, new(0f, 0.5f, 0.8f, 1f)); } /// private protected override void PrintFieldNames() { this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); - this.PrintFieldName((nint)this.component, new(0f, 0.5f, 0.8f, 1f)); + this.PrintFieldName((nint)this.Component, new(0f, 0.5f, 0.8f, 1f)); } /// private protected override void PrintFieldsForNodeType(bool isEditorOpen = false) { - if (this.component == null) + if (this.Component == null) { return; } + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault switch (this.componentType) { case TextInput: - var textInputComponent = (AtkComponentTextInput*)this.component; + var textInputComponent = (AtkComponentTextInput*)this.Component; ImGui.Text( $"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); ImGui.Text( @@ -97,98 +98,96 @@ internal unsafe class ComponentNodeTree : ResNodeTree break; case List: case TreeList: - var l = (AtkComponentList*)this.component; + var l = (AtkComponentList*)this.Component; if (ImGui.SmallButton("Inc.Selected")) { l->SelectedItemIndex++; } - break; - default: break; } } private void PrintComponentObject() { - PrintFieldValuePair("Component", $"{(nint)this.component:X}"); + PrintFieldValuePair("Component", $"{(nint)this.Component:X}"); ImGui.SameLine(); switch (this.componentType) { case Button: - ShowStruct((AtkComponentButton*)this.component); + ShowStruct((AtkComponentButton*)this.Component); break; case Slider: - ShowStruct((AtkComponentSlider*)this.component); + ShowStruct((AtkComponentSlider*)this.Component); break; case Window: - ShowStruct((AtkComponentWindow*)this.component); + ShowStruct((AtkComponentWindow*)this.Component); break; case CheckBox: - ShowStruct((AtkComponentCheckBox*)this.component); + ShowStruct((AtkComponentCheckBox*)this.Component); break; case GaugeBar: - ShowStruct((AtkComponentGaugeBar*)this.component); + ShowStruct((AtkComponentGaugeBar*)this.Component); break; case RadioButton: - ShowStruct((AtkComponentRadioButton*)this.component); + ShowStruct((AtkComponentRadioButton*)this.Component); break; case TextInput: - ShowStruct((AtkComponentTextInput*)this.component); + ShowStruct((AtkComponentTextInput*)this.Component); break; case Icon: - ShowStruct((AtkComponentIcon*)this.component); + ShowStruct((AtkComponentIcon*)this.Component); break; case NumericInput: - ShowStruct((AtkComponentNumericInput*)this.component); + ShowStruct((AtkComponentNumericInput*)this.Component); break; case List: - ShowStruct((AtkComponentList*)this.component); + ShowStruct((AtkComponentList*)this.Component); break; case TreeList: - ShowStruct((AtkComponentTreeList*)this.component); + ShowStruct((AtkComponentTreeList*)this.Component); break; case DropDownList: - ShowStruct((AtkComponentDropDownList*)this.component); + ShowStruct((AtkComponentDropDownList*)this.Component); break; case ScrollBar: - ShowStruct((AtkComponentScrollBar*)this.component); + ShowStruct((AtkComponentScrollBar*)this.Component); break; case ListItemRenderer: - ShowStruct((AtkComponentListItemRenderer*)this.component); + ShowStruct((AtkComponentListItemRenderer*)this.Component); break; case IconText: - ShowStruct((AtkComponentIconText*)this.component); + ShowStruct((AtkComponentIconText*)this.Component); break; case ComponentType.DragDrop: - ShowStruct((AtkComponentDragDrop*)this.component); + ShowStruct((AtkComponentDragDrop*)this.Component); break; case GuildLeveCard: - ShowStruct((AtkComponentGuildLeveCard*)this.component); + ShowStruct((AtkComponentGuildLeveCard*)this.Component); break; case TextNineGrid: - ShowStruct((AtkComponentTextNineGrid*)this.component); + ShowStruct((AtkComponentTextNineGrid*)this.Component); break; case JournalCanvas: - ShowStruct((AtkComponentJournalCanvas*)this.component); + ShowStruct((AtkComponentJournalCanvas*)this.Component); break; case HoldButton: - ShowStruct((AtkComponentHoldButton*)this.component); + ShowStruct((AtkComponentHoldButton*)this.Component); break; case Portrait: - ShowStruct((AtkComponentPortrait*)this.component); + ShowStruct((AtkComponentPortrait*)this.Component); break; default: - ShowStruct(this.component); + ShowStruct(this.Component); break; } } private void PrintComponentDataObject() { - var componentData = this.component->UldManager.ComponentData; + var componentData = this.Component->UldManager.ComponentData; PrintFieldValuePair("Data", $"{(nint)componentData:X}"); if (componentData != null) diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index ad7c09165..43c85acda 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; -using System.Linq; using System.Numerics; using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -25,11 +26,11 @@ internal unsafe partial class ResNodeTree ///
private protected void DrawNodeEditorTable() { - ImGui.BeginTable($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); + var tab = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); this.DrawEditorRows(); - ImGui.EndTable(); + tab.Dispose(); } /// @@ -289,7 +290,7 @@ internal unsafe partial class NineGridNodeTree /// internal unsafe partial class TextNodeTree { - private static readonly List FontList = Enum.GetValues().ToList(); + private static readonly List FontList = [.. Enum.GetValues()]; private static readonly string[] FontNames = Enum.GetNames(); @@ -370,8 +371,8 @@ internal unsafe partial class TextNodeTree var hAlign = (int)alignment % 3; var vAlign = ((int)alignment - hAlign) / 3; - var hAlignInput = IconSelectInput($"{label}H", ref hAlign, new() { 0, 1, 2 }, new() { AlignLeft, AlignCenter, AlignRight }); - var vAlignInput = IconSelectInput($"{label}V", ref vAlign, new() { 0, 1, 2 }, new() { ArrowsUpToLine, GripLines, ArrowsDownToLine }); + var hAlignInput = IconSelectInput($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]); + var vAlignInput = IconSelectInput($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]); if (hAlignInput || vAlignInput) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs index f3cc69618..de0216b05 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -1,6 +1,8 @@ using System.Numerics; using System.Runtime.InteropServices; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -11,6 +13,7 @@ using static Dalamud.Utility.Util; using static FFXIVClientStructs.FFXIV.Component.GUI.TextureType; using static ImGuiNET.ImGuiTableColumnFlags; using static ImGuiNET.ImGuiTableFlags; +using static ImGuiNET.ImGuiTreeNodeFlags; namespace Dalamud.Interface.Internal.UiDebug2.Browsing; @@ -60,7 +63,9 @@ internal unsafe partial class ImageNodeTree : ResNodeTree return; } - if (NestedTreePush($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", out _)) + var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth); + + if (tree) { PrintFieldValuePairs( ("Texture Type", $"{this.TexData.TexType}"), @@ -93,9 +98,9 @@ internal unsafe partial class ImageNodeTree : ResNodeTree { this.DrawFullTexture(); } - - ImGui.TreePop(); } + + tree.Dispose(); } /// @@ -157,7 +162,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree if (ImGui.IsItemClicked()) { - ImGui.SetClipboardText(ImGui.IsKeyDown(ImGuiKey.ModShift) + ImGui.SetClipboardText( + ImGui.IsKeyDown(ImGuiKey.ModShift) ? $"new Vector4({u}{suffix}, {v}{suffix}, {w}{suffix}, {h}{suffix})" : $"new Vector2({u}{suffix}, {v}{suffix});\nnew Vector2({w}{suffix}, {h}{suffix})"); } @@ -185,7 +191,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree private void PrintPartsTable() { - ImGui.BeginTable($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); + var tab = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); ImGui.TableSetupColumn("Part ID", WidthFixed); ImGui.TableSetupColumn("Part Texture", WidthFixed); ImGui.TableSetupColumn("Coordinates", WidthFixed); @@ -200,17 +206,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree { ImGui.TableNextColumn(); - if (i == this.TexData.PartId) - { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0.85F, 1, 1)); - } - - ImGui.Text($"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}"); - - if (i == this.TexData.PartId) - { - ImGui.PopStyleColor(1); - } + var col = i == this.TexData.PartId ? new Vector4(0, 0.85F, 1, 1) : new(1); + ImGui.TextColored(col, $"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}"); ImGui.TableNextColumn(); @@ -245,7 +242,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree PrintPartCoords(u / tWidth, v / tWidth, (u + width) / tWidth, (v + height) / tHeight, true, true); } - ImGui.EndTable(); + tab.Dispose(); } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs index 3c66d44c3..8f4e8c196 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs @@ -35,8 +35,7 @@ internal unsafe partial class NineGridNodeTree : ImageNodeTree private NineGridOffsets Offsets => new(this.NgNode); /// - private protected override void DrawPartOutline( - uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false) + private protected override void DrawPartOutline(uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false) { var part = this.TexData.PartsList->Parts[partId]; diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index 222ca30d4..efb62dd7a 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -3,6 +3,8 @@ using System.Numerics; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -15,6 +17,9 @@ using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; using static Dalamud.Utility.Util; using static FFXIVClientStructs.FFXIV.Component.GUI.NodeFlags; +using static ImGuiNET.ImGuiCol; +using static ImGuiNET.ImGuiTreeNodeFlags; + namespace Dalamud.Interface.Internal.UiDebug2.Browsing; /// @@ -117,15 +122,25 @@ internal unsafe partial class ResNodeTree : IDisposable return; } - ImGui.PushStyleColor(ImGuiCol.Text, color); - var treeOpened = NestedTreePush($"{label}##{(nint)nodeList:X}", color, out var lineStart); - ImGui.PopStyleColor(); + var c = ImRaii.PushColor(Text, color); + var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth); + c.Pop(); - if (treeOpened) + if (tree) { + var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); + PrintNodeList(nodeList, count, addonTree); - NestedTreePop(lineStart, color); + + var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 }; + + if (lineStart.Y < lineEnd.Y) + { + ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color), 1); + } } + + tree.Dispose(); } /// @@ -250,7 +265,7 @@ internal unsafe partial class ResNodeTree : IDisposable private void PrintTree(uint? index, bool forceOpen = false) { var visible = this.Node->NodeFlags.HasFlag(Visible); - + var label = $"{(index == null ? string.Empty : $"[{index}] ")}[#{this.Node->NodeId}]###{(nint)this.Node:X}nodeTree"; var displayColor = !visible ? new Vector4(0.8f, 0.8f, 0.8f, 1) : this.Node->Color.A == 0 ? new(0.015f, 0.575f, 0.355f, 1) : new(0.1f, 1f, 0.1f, 1f); @@ -260,9 +275,8 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.SetNextItemOpen(true, ImGuiCond.Always); } - ImGui.PushStyleColor(ImGuiCol.Text, displayColor); - - var treePush = NestedTreePush($"{(index == null ? string.Empty : $"[{index}] ")}[#{this.Node->NodeId}]###{(nint)this.Node:X}nodeTree", displayColor, out var lineStart); + var col = ImRaii.PushColor(Text, displayColor); + var tree = ImRaii.TreeNode(label, SpanFullWidth); if (ImGui.IsItemHovered()) { @@ -272,10 +286,11 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.SameLine(); this.WriteTreeHeading(); - ImGui.PopStyleColor(); + col.Pop(); - if (treePush) + if (tree) { + var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); try { PrintFieldValuePair("Node", $"{(nint)this.Node:X}"); @@ -309,8 +324,15 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.TextDisabled($"Couldn't display node!\n\n{ex}"); } - NestedTreePop(lineStart, displayColor); + var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 }; + + if (lineStart.Y < lineEnd.Y) + { + ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(displayColor), 1); + } } + + tree.Dispose(); } private void DrawBasicControls() diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index 8572a5495..b7c01cbc2 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -1,10 +1,12 @@ -using System.Numerics; using System.Runtime.InteropServices; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface.ImGuiSeStringRenderer; +using Dalamud.Interface.Internal.UiDebug2.Utility; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -51,19 +53,19 @@ internal unsafe partial class TextNodeTree : ResNodeTree #pragma warning disable try { - var style = new SeStringDrawParams() + var style = new SeStringDrawParams { - Color = TxtNode->TextColor.RGBA, - EdgeColor = TxtNode->EdgeColor.RGBA, + Color = this.TxtNode->TextColor.RGBA, + EdgeColor = this.TxtNode->EdgeColor.RGBA, ForceEdgeColor = true, EdgeStrength = 1f }; - ImGuiHelpers.SeStringWrapped(NodeText.AsSpan(), style); + ImGuiHelpers.SeStringWrapped(this.NodeText.AsSpan(), style); } catch { - ImGui.Text(Marshal.PtrToStringAnsi(new(NodeText.StringPtr)) ?? ""); + ImGui.Text(Marshal.PtrToStringAnsi(new(this.NodeText.StringPtr)) ?? ""); } #pragma warning restore @@ -81,7 +83,9 @@ internal unsafe partial class TextNodeTree : ResNodeTree private void PrintPayloads() { - if (ImGui.TreeNode($"Text Payloads##{(nint)this.Node:X}")) + var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}"); + + if (tree) { var utf8String = this.NodeText; var seStringBytes = new byte[utf8String.BufUsed]; @@ -100,15 +104,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree { case PayloadType.RawText when payload is TextPayload tp: { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - ImGui.Text("Raw Text: '"); - ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.6f, 0.6f, 0.6f, 1)); - ImGui.Text(tp.Text); - ImGui.PopStyleColor(); - ImGui.SameLine(); - ImGui.PopStyleVar(); - ImGui.Text("'"); + Gui.PrintFieldValuePair("Raw Text", tp.Text ?? string.Empty); break; } @@ -119,8 +115,8 @@ internal unsafe partial class TextNodeTree : ResNodeTree } } } - - ImGui.TreePop(); } + + tree.Dispose(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs index 179197128..e638cca40 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs @@ -5,7 +5,7 @@ using ImGuiNET; namespace Dalamud.Interface.Internal.UiDebug2.Browsing; /// -public partial struct TimelineTree +public readonly partial struct TimelineTree { /// /// An interface for retrieving and printing the contents of a given column in an animation timeline table. diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs index 22fb61872..d34233b16 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Client.Graphics; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -13,6 +15,7 @@ using static Dalamud.Utility.Util; using static FFXIVClientStructs.FFXIV.Component.GUI.NodeType; using static ImGuiNET.ImGuiTableColumnFlags; using static ImGuiNET.ImGuiTableFlags; +using static ImGuiNET.ImGuiTreeNodeFlags; // ReSharper disable SuggestBaseTypeForParameter namespace Dalamud.Interface.Internal.UiDebug2.Browsing; @@ -20,9 +23,9 @@ namespace Dalamud.Interface.Internal.UiDebug2.Browsing; /// /// A struct allowing a node's animation timeline to be printed and browsed. /// -public unsafe partial struct TimelineTree +public readonly unsafe partial struct TimelineTree { - private AtkResNode* node; + private readonly AtkResNode* node; /// /// Initializes a new instance of the struct. @@ -53,7 +56,9 @@ public unsafe partial struct TimelineTree if (count > 0) { - if (NestedTreePush($"Timeline##{(nint)this.node:X}timeline", out _)) + var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth); + + if (tree) { PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}"); @@ -72,12 +77,12 @@ public unsafe partial struct TimelineTree for (var a = 0; a < count; a++) { var animation = this.Resource->Animations[a]; - var isActive = this.ActiveAnimation != null && animation.Equals(*this.ActiveAnimation); + var isActive = this.ActiveAnimation != null && &animation == this.ActiveAnimation; this.PrintAnimation(animation, a, isActive, (nint)(this.NodeTimeline->Resource->Animations + (a * sizeof(AtkTimelineAnimation)))); } - - ImGui.TreePop(); } + + tree.Dispose(); } } @@ -305,11 +310,11 @@ public unsafe partial struct TimelineTree { var columns = this.BuildColumns(animation); - ImGui.PushStyleColor(ImGuiCol.Text, isActive ? new Vector4(1, 0.65F, 0.4F, 1) : new(1)); - var treePush = ImGui.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); - ImGui.PopStyleColor(); + var col = ImRaii.PushColor(ImGuiCol.Text, isActive ? new Vector4(1, 0.65F, 0.4F, 1) : new(1)); + var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); + col.Dispose(); - if (treePush) + if (tree) { PrintFieldValuePair("Animation", $"{address:X}"); @@ -317,7 +322,7 @@ public unsafe partial struct TimelineTree if (columns.Count > 0) { - ImGui.BeginTable($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); + var table = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); foreach (var c in columns) { @@ -339,11 +344,11 @@ public unsafe partial struct TimelineTree } } - ImGui.EndTable(); + table.Dispose(); } - - ImGui.TreePop(); } + + tree.Dispose(); } private List BuildColumns(AtkTimelineAnimation animation) diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index c6cb99037..d29eb98cf 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -6,6 +6,8 @@ using System.Numerics; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.UiDebug2.Browsing; using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -18,6 +20,7 @@ using static Dalamud.Interface.Utility.ImGuiHelpers; using static FFXIVClientStructs.FFXIV.Component.GUI.NodeFlags; using static ImGuiNET.ImGuiCol; using static ImGuiNET.ImGuiWindowFlags; +// ReSharper disable StructLacksIEquatable.Global #pragma warning disable CS0659 @@ -76,10 +79,10 @@ internal unsafe class ElementSelector : IDisposable /// internal void DrawInterface() { - ImGui.BeginChild("###sidebar_elementSelector", new(250, 0), true); + var ch = ImRaii.Child("###sidebar_elementSelector", new(250, 0), true); - ImGui.PushFont(IconFont); - ImGui.PushStyleColor(Text, this.Active ? new Vector4(1, 1, 0.2f, 1) : new(1)); + var f = ImRaii.PushFont(IconFont); + var col = ImRaii.PushColor(Text, this.Active ? new Vector4(1, 1, 0.2f, 1) : new(1)); if (ImGui.Button($"{(char)ObjectUngroup}")) { this.Active = !this.Active; @@ -94,8 +97,8 @@ internal unsafe class ElementSelector : IDisposable } } - ImGui.PopStyleColor(); - ImGui.PopFont(); + col.Pop(); + f.Pop(); if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Element Selector"); @@ -112,7 +115,7 @@ internal unsafe class ElementSelector : IDisposable this.PerformSearch(address); } - ImGui.EndChild(); + ch.Dispose(); } /// @@ -138,9 +141,9 @@ internal unsafe class ElementSelector : IDisposable var mousePos = ImGui.GetMousePos() - MainViewport.Pos; var addonResults = GetAtkUnitBaseAtPosition(mousePos); - ImGui.PushStyleColor(WindowBg, new Vector4(0.5f)); - ImGui.BeginChild("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); - ImGui.BeginGroup(); + var col = ImRaii.PushColor(WindowBg, new Vector4(0.5f)); + var ch = ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); + var g = ImRaii.Group(); Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); ImGui.Spacing(); @@ -201,9 +204,9 @@ internal unsafe class ElementSelector : IDisposable } } - ImGui.EndGroup(); - ImGui.EndChild(); - ImGui.PopStyleColor(); + g.Dispose(); + ch.Dispose(); + col.Pop(); } private static List GetAtkUnitBaseAtPosition(Vector2 position) @@ -381,9 +384,9 @@ internal unsafe class ElementSelector : IDisposable return; } - ImGui.PushStyleColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1)); + var col = ImRaii.PushColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1)); ResNodeTree.GetOrCreate(node, tree).WriteTreeHeading(); - ImGui.PopStyleColor(); + col.Pop(); } private void PerformSearch(nint address) diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs index 3aef3b6a4..436607261 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs @@ -1,6 +1,7 @@ using System.Numerics; using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using ImGuiNET; @@ -38,9 +39,9 @@ internal class AddonPopoutWindow : Window, IDisposable /// public override void Draw() { - ImGui.BeginChild($"{this.WindowName}child", new(-1, -1), true); + var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true); this.addonTree.Draw(); - ImGui.EndChild(); + ch.Dispose(); } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs index b293b734e..d657bbdec 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs @@ -1,6 +1,7 @@ using System.Numerics; using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -49,9 +50,9 @@ internal unsafe class NodePopoutWindow : Window, IDisposable { if (this.Node != null && this.AddonTree.ContainsNode(this.Node)) { - ImGui.BeginChild($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); + var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); - ImGui.EndChild(); + ch.Dispose(); this.firstDraw = false; } else diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs index d2510d16b..45ce27a77 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Numerics; using Dalamud.Interface.Components; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -14,7 +16,7 @@ namespace Dalamud.Interface.Internal.UiDebug2; /// internal unsafe partial class UiDebug2 { - /// + /// /// All unit lists to check for addons. /// internal static readonly List UnitListOptions = @@ -51,18 +53,18 @@ internal unsafe partial class UiDebug2 private void DrawSidebar() { - ImGui.BeginGroup(); + var g = ImRaii.Group(); this.DrawNameSearch(); this.DrawAddonSelectionList(); this.elementSelector.DrawInterface(); - ImGui.EndGroup(); + g.Dispose(); } private void DrawNameSearch() { - ImGui.BeginChild("###sidebar_nameSearch", new(250, 40), true); + var ch = ImRaii.Child("###sidebar_nameSearch", new(250, 40), true); var atkUnitBaseSearch = this.addonNameSearch; Vector4? defaultColor = this.visFilter ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1); @@ -84,12 +86,12 @@ internal unsafe partial class UiDebug2 this.addonNameSearch = atkUnitBaseSearch; } - ImGui.EndChild(); + ch.Dispose(); } private void DrawAddonSelectionList() { - ImGui.BeginChild("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); + var ch = ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); var unitListBaseAddr = GetUnitListBaseAddr(); @@ -98,7 +100,7 @@ internal unsafe partial class UiDebug2 this.DrawUnitListOption(unitListBaseAddr, unit); } - ImGui.EndChild(); + ch.Dispose(); } private void DrawUnitListOption(AtkUnitList* unitListBaseAddr, UnitListOption unit) @@ -144,26 +146,27 @@ internal unsafe partial class UiDebug2 return; } - ImGui.PushStyleColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}"; - var treePush = ImGui.TreeNodeEx($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); - ImGui.PopStyleColor(); - if (treePush) + var col1 = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + var tree = ImRaii.TreeNode($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); + col1.Pop(); + + if (tree) { foreach (var option in options) { - ImGui.PushStyleColor(ImGuiCol.Text, option.Visible ? new Vector4(0.1f, 1f, 0.1f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + var col2 = ImRaii.PushColor(ImGuiCol.Text, option.Visible ? new Vector4(0.1f, 1f, 0.1f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1)); if (ImGui.Selectable($"{option.Name}##select{option.Name}", this.SelectedAddonName == option.Name)) { this.SelectedAddonName = option.Name; } - ImGui.PopStyleColor(); + col2.Pop(); } - - ImGui.TreePop(); } + + tree.Dispose(); } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs index 396a84ac9..afb058641 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Dalamud.Game.Gui; using Dalamud.Interface.Internal.UiDebug2.Browsing; +using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; @@ -43,7 +44,7 @@ internal partial class UiDebug2 : IDisposable internal static IGameGui GameGui { get; set; } = null!; /// - /// Gets a collection of instances, each representing an . + /// Gets a collection of instances, each representing an . /// internal static Dictionary AddonTrees { get; } = []; @@ -85,7 +86,7 @@ internal partial class UiDebug2 : IDisposable private void DrawMainPanel() { ImGui.SameLine(); - ImGui.BeginChild("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); + var ch = ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); if (this.elementSelector.Active) { @@ -107,6 +108,6 @@ internal partial class UiDebug2 : IDisposable } } - ImGui.EndChild(); + ch.Dispose(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index 37b2f92cd..f523bd685 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -3,6 +3,8 @@ using System.Numerics; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using FFXIVClientStructs.FFXIV.Client.Graphics; using ImGuiNET; @@ -16,54 +18,6 @@ namespace Dalamud.Interface.Internal.UiDebug2.Utility; /// internal static class Gui { - /// - /// Begins a tree node that also displays a colored line to the left while open. - /// - /// The label of the tree. - /// The color of the heading text. - /// A value representing where to begin drawing the left-side line. - /// Whether this tree should default to being open. - /// true if the tree is open. - internal static bool NestedTreePush(string label, Vector4 color, out Vector2 lineStart, bool defOpen = false) - { - ImGui.PushStyleColor(Text, color); - var result = NestedTreePush(label, out lineStart, defOpen); - ImGui.PopStyleColor(); - return result; - } - - /// - internal static bool NestedTreePush(string label, out Vector2 lineStart, bool defOpen = false) - { - var imGuiTreeNodeFlags = ImGuiTreeNodeFlags.SpanFullWidth; - - if (defOpen) - { - imGuiTreeNodeFlags |= ImGuiTreeNodeFlags.DefaultOpen; - } - - var treeNodeEx = ImGui.TreeNodeEx(label, imGuiTreeNodeFlags); - lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); - return treeNodeEx; - } - - /// - /// Completes a NestedTree. - /// - /// The starting position calculated when the tree was pushed. - /// The color of the left-side line. - internal static void NestedTreePop(Vector2 lineStart, Vector4? color = null) - { - var lineEnd = lineStart with { Y = ImGui.GetCursorScreenPos().Y - 7 }; - - if (lineStart.Y < lineEnd.Y) - { - ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color ?? new(1)), 1); - } - - ImGui.TreePop(); - } - /// /// A radio-button-esque input that uses Fontawesome icon buttons. /// @@ -150,31 +104,41 @@ internal static class Gui /// Colors the text itself either white or black, depending on the luminosity of the background color. internal static void PrintColor(Vector4 color, string fmt) { + var c = new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) + .Push(Button, color) + .Push(ButtonActive, color) + .Push(ButtonHovered, color); + + ImGui.SmallButton(fmt); + + c.Pop(4); + return; + static double Luminosity(Vector4 vector4) => Math.Pow( (Math.Pow(vector4.X, 2) * 0.299f) + (Math.Pow(vector4.Y, 2) * 0.587f) + (Math.Pow(vector4.Z, 2) * 0.114f), 0.5f) * vector4.W; - - ImGui.PushStyleColor(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)); - ImGui.PushStyleColor(Button, color); - ImGui.PushStyleColor(ButtonActive, color); - ImGui.PushStyleColor(ButtonHovered, color); - ImGui.SmallButton(fmt); - ImGui.PopStyleColor(4); } /// internal static void ClickToCopyText(string text, string? textCopy = null) { - ImGui.PushStyleColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1)); + var c = ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1)); ImGuiHelpers.ClickToCopyText(text, textCopy); - ImGui.PopStyleColor(); + c.Pop(); if (ImGui.IsItemHovered()) { - ImGui.SetTooltip($"{textCopy ?? text}"); + var t = ImRaii.Tooltip(); + var f = ImRaii.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Copy.ToIconString()); + f.Pop(); + ImGui.SameLine(); + ImGui.Text($"{textCopy ?? text}"); + + t.Dispose(); } } @@ -197,7 +161,9 @@ internal static class Gui var index = (int)Math.Floor(prog * tooltips.Length); - ImGui.SetTooltip(tooltips[index]); + var t = ImRaii.Tooltip(); + ImGui.Text(tooltips[index]); + t.Dispose(); return true; } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs index 82bf8f96c..9d2af32e8 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs @@ -28,9 +28,7 @@ public unsafe struct NodeBounds var w = node->Width; var h = node->Height; - this.Points = w == 0 && h == 0 ? - new() { new(0) } : - new() { new(0), new(w, 0), new(w, h), new(0, h) }; + this.Points = w == 0 && h == 0 ? [new(0)] : [new(0), new(w, 0), new(w, h), new(0, h)]; this.TransformPoints(node); } @@ -149,8 +147,7 @@ public unsafe struct NodeBounds var sinR = (float)Sin(r); var d = (p - o) * s; - return new(o.X + (d.X * cosR) - (d.Y * sinR), - o.Y + (d.X * sinR) + (d.Y * cosR)); + return new(o.X + (d.X * cosR) - (d.Y * sinR), o.Y + (d.X * sinR) + (d.Y * cosR)); } private void TransformPoints(AtkResNode* transformNode) From f7e3156a89cf329b5a0ab21e56d4dea233d91ad3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 07:48:52 +0200 Subject: [PATCH 056/375] Bump actions/download-artifact from 2 to 4.1.7 in /.github/workflows (#2039) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c564cc81..ac27bc672 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -75,7 +75,7 @@ jobs: run: | dotnet tool install -g Microsoft.DotNet.ApiCompat.Tool - name: "Download Proposed Artifacts" - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4.1.7 with: name: dalamud-artifact path: .\right @@ -112,7 +112,7 @@ jobs: with: repository: goatcorp/dalamud-distrib token: ${{ secrets.UPDATE_PAT }} - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4.1.7 with: name: dalamud-artifact path: .\scratch From 3f34fb2dd3c4534a2ac884ac70d53bcf3fa9a302 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 12 Sep 2024 22:39:43 -0700 Subject: [PATCH 057/375] ci: Bump upload-artifact (#2040) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac27bc672..669d6255a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,7 +56,7 @@ jobs: bin/Release/Dalamud.*.exe bin/Release/FFXIVClientStructs.dll - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: dalamud-artifact path: bin\Release From 4b4227d1271a7a0e504b83eef9c633f2fd736a3e Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 14 Sep 2024 01:14:11 +0200 Subject: [PATCH 058/375] Update ClientStructs (#2032) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 7c5f04e34..3841b96f6 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 7c5f04e346067f7a316ad9072fb8260122ba80f0 +Subproject commit 3841b96f6aad172aca7e4b8e75a5d89c6d054e50 From 1f74293de4f15ad6f14b44c4582130662cb77c2b Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 15 Sep 2024 02:23:24 +0900 Subject: [PATCH 059/375] Update Lumina and use MacroStringParser (#2033) No longer requires hook and main thread requirements on compiling macro strings. Needs lookup table fixing on Lumina; using reflection to fix for the time being. --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 2 +- Dalamud/Dalamud.csproj | 2 +- .../Payloads/DalamudLinkPayload.cs | 24 ++--- .../Internal/SeStringRenderer.cs | 97 ++----------------- Dalamud/Utility/SeStringExtensions.cs | 38 ++++---- 5 files changed, 40 insertions(+), 123 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index a81ab54fc..5f91a6ac8 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,7 +27,7 @@ - + diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index a0322aab1..7d4910760 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,7 +71,7 @@ - + diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index cbe416625..8b020b111 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -37,17 +37,19 @@ public class DalamudLinkPayload : Payload /// protected override byte[] EncodeImpl() { - return new Lumina.Text.SeStringBuilder() - .BeginMacro(MacroCode.Link) - .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) - .AppendUIntExpression(this.CommandId) - .AppendIntExpression(this.Extra1) - .AppendIntExpression(this.Extra2) - .BeginStringExpression() - .Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString })) - .EndExpression() - .EndMacro() - .ToArray(); + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + var res = ssb.BeginMacro(MacroCode.Link) + .AppendIntExpression((int)EmbeddedInfoType.DalamudLink - 1) + .AppendUIntExpression(this.CommandId) + .AppendIntExpression(this.Extra1) + .AppendIntExpression(this.Extra2) + .BeginStringExpression() + .Append(JsonConvert.SerializeObject(new[] { this.Plugin, this.ExtraString })) + .EndExpression() + .EndMacro() + .ToArray(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); + return res; } /// diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 83c777130..8afc1b473 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -1,4 +1,3 @@ -using System.Buffers; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; @@ -16,18 +15,16 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Client.UI.Misc; using ImGuiNET; using Lumina.Excel.GeneratedSheets2; +using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; using static Dalamud.Game.Text.SeStringHandling.BitmapFontIcon; -using SeStringBuilder = Lumina.Text.SeStringBuilder; - namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; /// Draws SeString. @@ -46,30 +43,12 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// of this placeholder. On its own, usually displayed like [OBJ]. private const int ObjectReplacementCharacter = '\uFFFC'; - /// SeString to return instead, if macro encoder has failed and could not provide us the reason. - private static readonly ReadOnlySeString MacroEncoderEncodeStringError = - new SeStringBuilder() - .BeginMacro(MacroCode.ColorType).AppendIntExpression(508).EndMacro() - .BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(509).EndMacro() - .Append( - ""u8) - .BeginMacro(MacroCode.EdgeColorType).AppendIntExpression(0).EndMacro() - .BeginMacro(MacroCode.ColorType).AppendIntExpression(0).EndMacro() - .ToReadOnlySeString(); - [ServiceManager.ServiceDependency] private readonly GameConfig gameConfig = Service.Get(); /// Cache of compiled SeStrings from . private readonly ConcurrentLru cache = new(1024); - /// Sets the global invalid parameter handler. Used to suppress vsprintf_s from raising. - /// There exists a thread local version of this, but as the game-provided implementation is what - /// effectively is a screaming tool that the game has a bug, it should be safe to fail in any means. - private readonly delegate* unmanaged< - delegate* unmanaged, - delegate* unmanaged> setInvalidParameterHandler; - /// Parsed gfdata.gfd file, containing bitmap font icon lookup table. private readonly GfdFile gfd; @@ -90,14 +69,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService this.colorStackSet = new( dm.Excel.GetSheet() ?? throw new InvalidOperationException("Failed to access UIColor sheet.")); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; - - // SetUnhandledExceptionFilter(who cares); - // _set_purecall_handler(() => *(int*)0 = 0xff14); - // _set_invalid_parameter_handler(() => *(int*)0 = 0xff14); - var f = sigScanner.ScanText( - "ff 15 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8d 0d ?? ?? ?? ?? e8 ?? ?? ?? ??") + 26; - fixed (void* p = &this.setInvalidParameterHandler) - *(nint*)p = *(int*)f + f + 4; } /// Finalizes an instance of the class. @@ -106,72 +77,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); - /// Compiles a SeString from a text macro representation. - /// SeString text macro representation. - /// Compiled SeString. - public ReadOnlySeString Compile(ReadOnlySpan text) - { - // MacroEncoder looks stateful; disallowing calls from off main threads for now. - ThreadSafety.AssertMainThread(); - - var prev = this.setInvalidParameterHandler(&MsvcrtInvalidParameterHandlerDetour); - try - { - using var tmp = new Utf8String(); - RaptureTextModule.Instance()->MacroEncoder.EncodeString(&tmp, text); - return new(tmp.AsSpan().ToArray()); - } - catch (Exception) - { - return MacroEncoderEncodeStringError; - } - finally - { - this.setInvalidParameterHandler(prev); - } - - [UnmanagedCallersOnly] - static void MsvcrtInvalidParameterHandlerDetour(char* a, char* b, char* c, int d, nuint e) => - throw new InvalidOperationException(); - } - - /// Compiles a SeString from a text macro representation. - /// SeString text macro representation. - /// Compiled SeString. - public ReadOnlySeString Compile(ReadOnlySpan text) - { - var len = Encoding.UTF8.GetByteCount(text); - if (len >= 1024) - { - var buf = ArrayPool.Shared.Rent(len + 1); - buf[Encoding.UTF8.GetBytes(text, buf)] = 0; - var res = this.Compile(buf); - ArrayPool.Shared.Return(buf); - return res; - } - else - { - Span buf = stackalloc byte[len + 1]; - buf[Encoding.UTF8.GetBytes(text, buf)] = 0; - return this.Compile(buf); - } - } - /// Compiles and caches a SeString from a text macro representation. /// SeString text macro representation. /// Newline characters will be normalized to newline payloads. /// Compiled SeString. - public ReadOnlySeString CompileAndCache(string text) - { - // MacroEncoder looks stateful; disallowing calls from off main threads for now. - // Note that this is replicated in context.Compile. Only access cache from the main thread. - ThreadSafety.AssertMainThread(); - - return this.cache.GetOrAdd( + public ReadOnlySeString CompileAndCache(string text) => + this.cache.GetOrAdd( text, - static (text, context) => context.Compile(text.ReplaceLineEndings("
")), - this); - } + static text => ReadOnlySeString.FromMacroString( + text.ReplaceLineEndings("
"), + new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError })); /// Compiles and caches a SeString from a text macro representation, and then draws it. /// SeString text macro representation. diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 545a7e8a8..f78cf09ce 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Lumina.Text.Parse; using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; using DSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; @@ -24,44 +24,44 @@ public static class SeStringExtensions /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. - public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) - { - ThreadSafety.AssertMainThread(); - return ssb.Append(Service.Get().Compile(macroString)); - } + [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")] + [Api11ToDo("Remove")] + public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => + ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-16 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. - public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) - { - ThreadSafety.AssertMainThread(); - return ssb.Append(Service.Get().Compile(macroString)); - } + [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")] + [Api11ToDo("Remove")] + public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => + ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) { - ThreadSafety.AssertMainThread(); - return ssb.Append(DSeString.Parse(Service.Get().Compile(macroString))); + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); + ssb.Append(DSeString.Parse(lssb.ToReadOnlySeString().Data.Span)); + LSeStringBuilder.SharedPool.Return(lssb); + return ssb; } /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-16 to compile and append to . /// this for method chaining. - /// Must be called from the main thread. public static DSeStringBuilder AppendMacroString(this DSeStringBuilder ssb, ReadOnlySpan macroString) { - ThreadSafety.AssertMainThread(); - return ssb.Append(DSeString.Parse(Service.Get().Compile(macroString))); + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); + ssb.Append(DSeString.Parse(lssb.ToReadOnlySeString().Data.Span)); + LSeStringBuilder.SharedPool.Return(lssb); + return ssb; } /// From 98f58b3f896cf72a6d581d1a717988e4bcaaf285 Mon Sep 17 00:00:00 2001 From: SlashNephy Date: Mon, 16 Sep 2024 03:20:42 +0900 Subject: [PATCH 060/375] Respect Dalamud chat type setting in changelogs message (#2041) --- Dalamud/Plugin/Internal/PluginManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index f390664b6..6ad81a806 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -318,6 +318,7 @@ internal class PluginManager : IInternalDisposableService new UIForegroundPayload(0), new TextPayload("]"), }), + Type = this.configuration.GeneralChatType, }); foreach (var metadata in updateMetadata) From 4d802a1beed748533eb14f95e1dd97312711b4c0 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:29:15 +0200 Subject: [PATCH 061/375] Update ClientStructs (#2043) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 3841b96f6..6db2841b8 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 3841b96f6aad172aca7e4b8e75a5d89c6d054e50 +Subproject commit 6db2841b818ac2272e7eefad309d10018389eb85 From 73ec12145d086ff92abdf8e26be6c554db1313a2 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Wed, 25 Sep 2024 09:54:50 -0700 Subject: [PATCH 062/375] feat: Add new IPC methods, and docs (#2042) - New methods `HasAction`, `HasFunction`, and `SubscriptionCount` to allow making better decisions about IPC logic. - Some better documentation for the call gates. --- Dalamud/Plugin/Ipc/ICallGateProvider.cs | 106 ++++++------------ Dalamud/Plugin/Ipc/ICallGateSubscriber.cs | 31 +++-- .../Plugin/Ipc/Internal/CallGatePubSubBase.cs | 67 +++++++++-- 3 files changed, 113 insertions(+), 91 deletions(-) diff --git a/Dalamud/Plugin/Ipc/ICallGateProvider.cs b/Dalamud/Plugin/Ipc/ICallGateProvider.cs index cf4c59b2e..1d160d9cb 100644 --- a/Dalamud/Plugin/Ipc/ICallGateProvider.cs +++ b/Dalamud/Plugin/Ipc/ICallGateProvider.cs @@ -4,8 +4,24 @@ using Dalamud.Plugin.Ipc.Internal; namespace Dalamud.Plugin.Ipc; -/// -public interface ICallGateProvider +/// +/// The backing interface for the provider ("server") half of an IPC channel. This interface is used to expose methods +/// to other plugins via RPC, as well as to allow other plugins to subscribe to notifications from this plugin. +/// +public interface ICallGateProvider +{ + /// + public int SubscriptionCount { get; } + + /// + public void UnregisterAction(); + + /// + public void UnregisterFunc(); +} + +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -13,18 +29,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -32,18 +42,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -51,18 +55,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -70,18 +68,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -89,18 +81,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -108,18 +94,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -127,18 +107,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -146,18 +120,12 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); } -/// -public interface ICallGateProvider +/// +public interface ICallGateProvider : ICallGateProvider { /// public void RegisterAction(Action action); @@ -165,12 +133,6 @@ public interface ICallGateProvider /// public void RegisterFunc(Func func); - /// - public void UnregisterAction(); - - /// - public void UnregisterFunc(); - /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); } diff --git a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs index 40d642a61..fd67ec7db 100644 --- a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs +++ b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs @@ -4,8 +4,21 @@ using Dalamud.Plugin.Ipc.Internal; namespace Dalamud.Plugin.Ipc; +/// +/// An interface for all IPC subscribers. +/// +public interface ICallGateSubscriber +{ + + /// + public bool HasAction { get; } + + /// + public bool HasFunction { get; } +} + /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -21,7 +34,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -37,7 +50,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -53,7 +66,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -69,7 +82,7 @@ public interface ICallGateSubscriber } /// -public interface ICallGateSubscriber +public interface ICallGateSubscriber : ICallGateSubscriber { /// public void Subscribe(Action action); @@ -84,7 +97,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// @@ -100,7 +113,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// @@ -116,7 +129,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// @@ -132,7 +145,7 @@ public interface ICallGateSubscriber public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); } -/// +/// : ICallGateSubscriber public interface ICallGateSubscriber { /// diff --git a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs index b6a4e8a61..308457373 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs @@ -16,71 +16,118 @@ internal abstract class CallGatePubSubBase this.Channel = Service.Get().GetOrCreateChannel(name); } + /// + /// Gets a value indicating whether this IPC call gate has an associated Action. Only exposed to + /// s. + /// + public bool HasAction => this.Channel.Action != null; + + /// + /// Gets a value indicating whether this IPC call gate has an associated Function. Only exposed to + /// s. + /// + public bool HasFunction => this.Channel.Func != null; + + /// + /// Gets the count of subscribers listening for messages through this call gate. Only exposed to + /// s, and can be used to determine if messages should be sent through the gate. + /// + public int SubscriptionCount => this.Channel.Subscriptions.Count; + /// /// Gets the underlying channel implementation. /// protected CallGateChannel Channel { get; init; } - + /// - /// Removes a registered Action from inter-plugin communication. + /// Removes the associated Action from this call gate, effectively disabling RPC calls. /// + /// public void UnregisterAction() => this.Channel.Action = null; /// - /// Removes a registered Func from inter-plugin communication. + /// Removes the associated Function from this call gate. /// + /// public void UnregisterFunc() => this.Channel.Func = null; /// - /// Registers an Action for inter-plugin communication. + /// Registers a for use by other plugins via RPC. This Delegate must satisfy the constraints + /// of an type as defined by the interface, meaning they may not return a value and must have + /// the proper number of parameters. /// /// Action to register. + /// + /// private protected void RegisterAction(Delegate action) => this.Channel.Action = action; /// - /// Registers a Func for inter-plugin communication. + /// Registers a for use by other plugins via RPC. This Delegate must satisfy the constraints + /// of a type as defined by the interface, meaning its return type and parameters must + /// match accordingly. /// /// Func to register. + /// + /// private protected void RegisterFunc(Delegate func) => this.Channel.Func = func; /// - /// Subscribe an expression to this registration. + /// Registers a (of type ) that will be called when the providing + /// plugin calls . This method can be used to receive notifications + /// of events or data updates from a specific plugin. /// /// Action to subscribe. + /// private protected void Subscribe(Delegate action) => this.Channel.Subscribe(action); /// - /// Unsubscribe an expression from this registration. + /// Removes a subscription created through . Note that the to be + /// unsubscribed must be the same instance as the one passed in. /// /// Action to unsubscribe. + /// private protected void Unsubscribe(Delegate action) => this.Channel.Unsubscribe(action); /// - /// Invoke an action registered for inter-plugin communication. + /// Executes the Action registered for this IPC call gate via . This method is intended + /// to be called by plugins wishing to access another plugin via RPC. The parameters passed to this method will be + /// passed to the owning plugin, with appropriate serialization for complex data types. Primitive data types will + /// be passed as-is. The target Action will be called on the same thread as the caller. /// /// Action arguments. /// This is thrown when the IPC publisher has not registered an action for calling yet. + /// + /// private protected void InvokeAction(params object?[]? args) => this.Channel.InvokeAction(args); /// - /// Invoke a function registered for inter-plugin communication. + /// Executes the Function registered for this IPC call gate via . This method is intended + /// to be called by plugins wishing to access another plugin via RPC. The parameters passed to this method will be + /// passed to the owning plugin, with appropriate serialization for complex data types. Primitive data types will + /// be passed as-is. The target Action will be called on the same thread as the caller. /// /// Parameter args. /// The return value. /// The return type. /// This is thrown when the IPC publisher has not registered a func for calling yet. + /// + /// private protected TRet InvokeFunc(params object?[]? args) => this.Channel.InvokeFunc(args); /// - /// Invoke all actions that have subscribed to this IPC. + /// Send the given arguments to all subscribers (through ) of this IPC call gate. This method + /// is intended to be used by the provider plugin to notify all subscribers of an event or data update. The + /// parameters passed to this method will be passed to all subscribers, with appropriate serialization for complex + /// data types. Primitive data types will be passed as-is. The subscription actions will be called sequentially in + /// order of registration on the same thread as the caller. /// /// Delegate arguments. private protected void SendMessage(params object?[]? args) From 437e44393198def628d6df03573481dc8c0f9652 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Wed, 25 Sep 2024 12:56:35 -0700 Subject: [PATCH 063/375] fix: Re-muck the API surface to not break existing plugins (#2046) --- Dalamud/Plugin/Ipc/ICallGateProvider.cs | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/Dalamud/Plugin/Ipc/ICallGateProvider.cs b/Dalamud/Plugin/Ipc/ICallGateProvider.cs index 1d160d9cb..154ca88d3 100644 --- a/Dalamud/Plugin/Ipc/ICallGateProvider.cs +++ b/Dalamud/Plugin/Ipc/ICallGateProvider.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Ipc.Internal; +using Dalamud.Utility; #pragma warning disable SA1402 // File may only contain a single type @@ -28,6 +29,14 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(); @@ -41,6 +50,14 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1); @@ -54,6 +71,14 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2); @@ -67,6 +92,14 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3); @@ -80,6 +113,14 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); @@ -93,6 +134,14 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); @@ -106,6 +155,14 @@ public interface ICallGateProvider : ICallGateProv /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); @@ -119,6 +176,14 @@ public interface ICallGateProvider : ICallGate /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); @@ -132,6 +197,14 @@ public interface ICallGateProvider : ICall /// public void RegisterFunc(Func func); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterAction(); + + /// + [Api11ToDo("Remove, it's in the base interface")] + public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); From 3bda9277de18af8a25eecce97fc670558b7043d8 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Thu, 26 Sep 2024 04:34:24 +0200 Subject: [PATCH 064/375] Update ClientStructs (#2045) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6db2841b8..3ea098e4d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6db2841b818ac2272e7eefad309d10018389eb85 +Subproject commit 3ea098e4dacbc49f9a3ee192bd6ae034431f8abd From 431640746596a04b7e2b2b46db6daad213cc11df Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 28 Sep 2024 20:27:46 +0200 Subject: [PATCH 065/375] Update ClientStructs (#2047) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 3ea098e4d..81fb80190 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 3ea098e4dacbc49f9a3ee192bd6ae034431f8abd +Subproject commit 81fb801905b7fe8e02aa40dcf9942e0f707dcc21 From a432fa6b323503b8525224a1655e25468c1e964f Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Sep 2024 14:15:31 +0200 Subject: [PATCH 066/375] build: 10.0.0.15 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 7d4910760..b0b130810 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.14 + 10.0.0.15 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 3c18814a66e7f7165f079932ed078c2580eee7c8 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 29 Sep 2024 10:44:06 -0700 Subject: [PATCH 067/375] Update to newest Serilog --- Dalamud/Dalamud.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index b0b130810..528145d59 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -79,10 +79,10 @@ - - - - + + + + all From 621fd17e1d053c9a29c1b8201eb4447986e0afba Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 29 Sep 2024 10:45:43 -0700 Subject: [PATCH 068/375] Expose Serilog API to plugins --- Dalamud/Logging/ScopedPluginLogService.cs | 10 ++++------ Dalamud/Plugin/Services/IPluginLog.cs | 9 ++++++++- targets/Dalamud.Plugin.targets | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Dalamud/Logging/ScopedPluginLogService.cs b/Dalamud/Logging/ScopedPluginLogService.cs index 54f231e0d..7305aa87b 100644 --- a/Dalamud/Logging/ScopedPluginLogService.cs +++ b/Dalamud/Logging/ScopedPluginLogService.cs @@ -40,6 +40,9 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog this.Logger = loggerConfiguration.CreateLogger(); } + + /// + public ILogger Logger { get; } /// public LogEventLevel MinimumLogLevel @@ -47,12 +50,7 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog get => this.levelSwitch.MinimumLevel; set => this.levelSwitch.MinimumLevel = value; } - - /// - /// Gets a logger that may be exposed to plugins some day. - /// - public ILogger Logger { get; } - + /// public void Fatal(string messageTemplate, params object[] values) => this.Write(LogEventLevel.Fatal, null, messageTemplate, values); diff --git a/Dalamud/Plugin/Services/IPluginLog.cs b/Dalamud/Plugin/Services/IPluginLog.cs index dcba5bb29..38406fd91 100644 --- a/Dalamud/Plugin/Services/IPluginLog.cs +++ b/Dalamud/Plugin/Services/IPluginLog.cs @@ -1,4 +1,5 @@ -using Serilog.Events; +using Serilog; +using Serilog.Events; #pragma warning disable CS1573 // See https://github.com/dotnet/roslyn/issues/40325 @@ -9,6 +10,12 @@ namespace Dalamud.Plugin.Services; /// public interface IPluginLog { + /// + /// Gets a Serilog ILogger instance for this plugin. This is the entrypoint for plugins that wish to use more + /// advanced logging functionality. + /// + public ILogger Logger { get; } + /// /// Gets or sets the minimum log level that will be recorded from this plugin to Dalamud's logs. This may be set /// by either the plugin or by Dalamud itself. diff --git a/targets/Dalamud.Plugin.targets b/targets/Dalamud.Plugin.targets index dc5bec410..10b63c437 100644 --- a/targets/Dalamud.Plugin.targets +++ b/targets/Dalamud.Plugin.targets @@ -22,6 +22,7 @@ + From 8d535af4ade9e1d706390830bf4e6778cf2ee07f Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 29 Sep 2024 10:47:26 -0700 Subject: [PATCH 069/375] remove excess declarations from IPC --- Dalamud/Plugin/Ipc/ICallGateProvider.cs | 74 +------------------------ 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/Dalamud/Plugin/Ipc/ICallGateProvider.cs b/Dalamud/Plugin/Ipc/ICallGateProvider.cs index 154ca88d3..f4e5c76d7 100644 --- a/Dalamud/Plugin/Ipc/ICallGateProvider.cs +++ b/Dalamud/Plugin/Ipc/ICallGateProvider.cs @@ -13,7 +13,7 @@ public interface ICallGateProvider { /// public int SubscriptionCount { get; } - + /// public void UnregisterAction(); @@ -29,14 +29,6 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(); @@ -50,14 +42,6 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1); @@ -71,14 +55,6 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2); @@ -92,14 +68,6 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3); @@ -113,14 +81,6 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); @@ -134,14 +94,6 @@ public interface ICallGateProvider : ICallGateProvider /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); @@ -155,14 +107,6 @@ public interface ICallGateProvider : ICallGateProv /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); @@ -176,14 +120,6 @@ public interface ICallGateProvider : ICallGate /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); @@ -197,14 +133,6 @@ public interface ICallGateProvider : ICall /// public void RegisterFunc(Func func); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterAction(); - - /// - [Api11ToDo("Remove, it's in the base interface")] - public new void UnregisterFunc(); /// public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); From 4d6b8e59efc839206bbbb30deb659203d59cd9e8 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 29 Sep 2024 10:51:00 -0700 Subject: [PATCH 070/375] apply low-hanging fruit for api11todos --- Dalamud/Game/Gui/Dtr/DtrBarEntry.cs | 3 +-- Dalamud/Utility/SeStringExtensions.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index 651f59b1b..8ba46f999 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -85,8 +85,7 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry /// /// Class representing an entry in the server info bar. /// -[Api11ToDo(Api11ToDoAttribute.MakeInternal)] -public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry +internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry { private readonly DalamudConfiguration configuration; diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index f78cf09ce..b89f815bc 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -24,7 +24,7 @@ public static class SeStringExtensions /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . /// this for method chaining. - [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")] + [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.", true)] [Api11ToDo("Remove")] public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); @@ -33,7 +33,7 @@ public static class SeStringExtensions /// Target SeString builder. /// Macro string in UTF-16 to compile and append to . /// this for method chaining. - [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.")] + [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.", true)] [Api11ToDo("Remove")] public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); From 547a9e19fe0b4a65df3ebcbc978aff66dc27b7cc Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Mon, 30 Sep 2024 11:56:14 -0700 Subject: [PATCH 071/375] Re-enable rollups for API11 (#2048) --- .github/workflows/rollup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rollup.yml b/.github/workflows/rollup.yml index 70a8fc7b7..5452d998e 100644 --- a/.github/workflows/rollup.yml +++ b/.github/workflows/rollup.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: branches: - - WORKFLOW_DISABLED_REMOVE_BEFORE_RUNNING + - api11 defaults: run: From 60bb357399d5e48ce6b4ac2c5b5ad4c0e295ac97 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 8 Oct 2024 03:14:33 +0200 Subject: [PATCH 072/375] Update ClientStructs (#2050) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 81fb80190..99ad244e7 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 81fb801905b7fe8e02aa40dcf9942e0f707dcc21 +Subproject commit 99ad244e70e3545d397ebe82f71d6bc0333914ca From 552aafd70dcd0ef8d0353559a287e5938a7cd34a Mon Sep 17 00:00:00 2001 From: ItsBexy Date: Thu, 17 Oct 2024 10:52:22 -0600 Subject: [PATCH 073/375] Misc UiDebug2 Fixes - The widget will now only check the `FFXIVClientStructs` assembly for addon type information. - Updated ImRaii usage - Clarified suppression in `NodeTree.Text.cs` - Restored the original Addon Inspector in the Data window, so that both versions can coexist for the time being --- .../UiDebug2/Browsing/AddonTree.FieldNames.cs | 43 ++--- .../Internal/UiDebug2/Browsing/Events.cs | 65 +++---- .../UiDebug2/Browsing/NodeTree.Editor.cs | 9 +- .../UiDebug2/Browsing/NodeTree.Image.cs | 88 ++++----- .../UiDebug2/Browsing/NodeTree.Res.cs | 16 +- .../UiDebug2/Browsing/NodeTree.Text.cs | 8 +- .../UiDebug2/Browsing/TimelineTree.cs | 63 +++--- .../Internal/UiDebug2/ElementSelector.cs | 182 +++++++++--------- .../Internal/UiDebug2/Popout.Addon.cs | 7 +- .../Internal/UiDebug2/Popout.Node.cs | 9 +- .../Internal/UiDebug2/UiDebug2.Sidebar.cs | 92 +++++---- .../Interface/Internal/UiDebug2/UiDebug2.cs | 40 ++-- .../Internal/UiDebug2/Utility/Gui.cs | 45 +++-- .../Internal/UiDebug2/Utility/NodeBounds.cs | 19 +- .../Internal/Windows/Data/DataWindow.cs | 1 + .../Data/Widgets/AddonInspectorWidget.cs | 4 +- .../Data/Widgets/AddonInspectorWidget2.cs | 35 ++++ 17 files changed, 379 insertions(+), 347 deletions(-) create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs index 4baad4753..49f976a0b 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -16,8 +16,10 @@ public unsafe partial class AddonTree { private static readonly Dictionary AddonTypeDict = []; + private static readonly Assembly? ClientStructs = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(static a => a.GetName().Name == "FFXIVClientStructs"); + /// - /// Gets or sets a collection of names corresponding to pointers documented within the addon struct. + /// Gets or sets a collection of names for field offsets that have been documented in FFXIVClientStructs. /// internal Dictionary> FieldNames { get; set; } = []; @@ -28,28 +30,25 @@ public unsafe partial class AddonTree return null; } - if (AddonTypeDict.TryAdd(this.AddonName, null)) + if (AddonTypeDict.TryAdd(this.AddonName, null) && ClientStructs != null) { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + try { - try + foreach (var t in from t in ClientStructs.GetTypes() + where t.IsPublic + let xivAddonAttr = (Addon?)t.GetCustomAttribute(typeof(Addon), false) + where xivAddonAttr != null + where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName) + select t) { - foreach (var t in from t in a.GetTypes() - where t.IsPublic - let xivAddonAttr = (Addon?)t.GetCustomAttribute(typeof(Addon), false) - where xivAddonAttr != null - where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName) - select t) - { - AddonTypeDict[this.AddonName] = t; - break; - } - } - catch - { - // ignored + AddonTypeDict[this.AddonName] = t; + break; } } + catch + { + // ignored + } } return AddonTypeDict.TryGetValue(this.AddonName, out var result) && result != null ? Marshal.PtrToStructure(new(addon), result) : *addon; @@ -108,7 +107,7 @@ public unsafe partial class AddonTree { try { - if (this.FieldNames.TryAdd(fieldAddr, [..path!, name]) && fieldType.DeclaringType == baseType) + if (this.FieldNames.TryAdd(fieldAddr, [..path, name]) && fieldType.DeclaringType == baseType) { this.PopulateFieldNames(field.GetValue(obj), fieldAddr, [..path, name]); } @@ -140,7 +139,7 @@ public unsafe partial class AddonTree var itemAddr = fieldAddr + (size * i); var itemName = $"{name}[{i}]"; - this.FieldNames.TryAdd(itemAddr, [..path!, itemName]); + this.FieldNames.TryAdd(itemAddr, [..path, itemName]); var item = Marshal.PtrToStructure(itemAddr, itemType); if (itemType.DeclaringType == baseType) @@ -176,10 +175,10 @@ public unsafe partial class AddonTree return; } - this.FieldNames.TryAdd(fieldAddr, [..path!, name]); + this.FieldNames.TryAdd(fieldAddr, [..path, name]); this.FieldNames.TryAdd(pointer, [..path, name]); - if (itemType?.DeclaringType != baseType || itemType!.IsPointer) + if (itemType?.DeclaringType != baseType || itemType.IsPointer) { return; } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index d542f5025..a422e4db6 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -26,44 +26,41 @@ public static class Events return; } - var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); + using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); if (tree) { - var tab = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg); - - ImGui.TableSetupColumn("#", WidthFixed); - ImGui.TableSetupColumn("Type", WidthFixed); - ImGui.TableSetupColumn("Param", WidthFixed); - ImGui.TableSetupColumn("Flags", WidthFixed); - ImGui.TableSetupColumn("Unk29", WidthFixed); - ImGui.TableSetupColumn("Target", WidthFixed); - ImGui.TableSetupColumn("Listener", WidthFixed); - - ImGui.TableHeadersRow(); - - var i = 0; - while (evt != null) + using (ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg)) { - ImGui.TableNextColumn(); - ImGui.Text($"{i++}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Type}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Param}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Flags}"); - ImGui.TableNextColumn(); - ImGui.Text($"{evt->Unk29}"); - ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Target:X}"); - ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); - evt = evt->NextEvent; + ImGui.TableSetupColumn("#", WidthFixed); + ImGui.TableSetupColumn("Type", WidthFixed); + ImGui.TableSetupColumn("Param", WidthFixed); + ImGui.TableSetupColumn("Flags", WidthFixed); + ImGui.TableSetupColumn("Unk29", WidthFixed); + ImGui.TableSetupColumn("Target", WidthFixed); + ImGui.TableSetupColumn("Listener", WidthFixed); + + ImGui.TableHeadersRow(); + + var i = 0; + while (evt != null) + { + ImGui.TableNextColumn(); + ImGui.Text($"{i++}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Type}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Param}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Flags}"); + ImGui.TableNextColumn(); + ImGui.Text($"{evt->Unk29}"); + ImGui.TableNextColumn(); + Gui.ClickToCopyText($"{(nint)evt->Target:X}"); + ImGui.TableNextColumn(); + Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); + evt = evt->NextEvent; + } } - - tab.Dispose(); } - - tree.Dispose(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index 43c85acda..ef30ea151 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -26,11 +26,10 @@ internal unsafe partial class ResNodeTree /// private protected void DrawNodeEditorTable() { - var tab = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); - - this.DrawEditorRows(); - - tab.Dispose(); + using (ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX)) + { + this.DrawEditorRows(); + } } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs index de0216b05..678eec8cd 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -51,8 +51,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree /// /// Draws the texture inside the window, in either of two styles.

- /// Full Imagepresents the texture in full as a spritesheet.
- /// Parts Listpresents the individual parts as rows in a table. + /// Full Image (0)presents the texture in full as a spritesheet.
+ /// Parts List (1)presents the individual parts as rows in a table. ///
private protected void DrawTextureAndParts() { @@ -63,7 +63,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree return; } - var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth); + using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth); if (tree) { @@ -99,8 +99,6 @@ internal unsafe partial class ImageNodeTree : ResNodeTree this.DrawFullTexture(); } } - - tree.Dispose(); } /// @@ -191,58 +189,62 @@ internal unsafe partial class ImageNodeTree : ResNodeTree private void PrintPartsTable() { - var tab = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); - ImGui.TableSetupColumn("Part ID", WidthFixed); - ImGui.TableSetupColumn("Part Texture", WidthFixed); - ImGui.TableSetupColumn("Coordinates", WidthFixed); - - ImGui.TableHeadersRow(); - - var tWidth = this.TexData.Texture->Width; - var tHeight = this.TexData.Texture->Height; - var textureSize = new Vector2(tWidth, tHeight); - - for (ushort i = 0; i < this.TexData.PartCount; i++) + using (ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable)) { - ImGui.TableNextColumn(); + ImGui.TableSetupColumn("Part ID", WidthFixed); + ImGui.TableSetupColumn("Part Texture", WidthFixed); + ImGui.TableSetupColumn("Coordinates", WidthFixed); - var col = i == this.TexData.PartId ? new Vector4(0, 0.85F, 1, 1) : new(1); - ImGui.TextColored(col, $"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}"); + ImGui.TableHeadersRow(); - ImGui.TableNextColumn(); + var tWidth = this.TexData.Texture->Width; + var tHeight = this.TexData.Texture->Height; + var textureSize = new Vector2(tWidth, tHeight); - var part = this.TexData.PartsList->Parts[i]; - var hiRes = this.TexData.HiRes; + for (ushort i = 0; i < this.TexData.PartCount; i++) + { + ImGui.TableNextColumn(); - var u = hiRes ? part.U * 2f : part.U; - var v = hiRes ? part.V * 2f : part.V; - var width = hiRes ? part.Width * 2f : part.Width; - var height = hiRes ? part.Height * 2f : part.Height; + var col = i == this.TexData.PartId ? new Vector4(0, 0.85F, 1, 1) : new(1); + ImGui.TextColored(col, $"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}"); - ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(width, height), new Vector2(u, v) / textureSize, new Vector2(u + width, v + height) / textureSize); + ImGui.TableNextColumn(); - ImGui.TableNextColumn(); + var part = this.TexData.PartsList->Parts[i]; + var hiRes = this.TexData.HiRes; - ImGui.TextColored(!hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Standard:\t"); - ImGui.SameLine(); - var cursX = ImGui.GetCursorPosX(); + var u = hiRes ? part.U * 2f : part.U; + var v = hiRes ? part.V * 2f : part.V; + var width = hiRes ? part.Width * 2f : part.Width; + var height = hiRes ? part.Height * 2f : part.Height; - PrintPartCoords(u / 2f, v / 2f, width / 2f, height / 2f); + ImGui.Image( + new(this.TexData.Texture->D3D11ShaderResourceView), + new(width, height), + new Vector2(u, v) / textureSize, + new Vector2(u + width, v + height) / textureSize); - ImGui.TextColored(hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Hi-Res:\t"); - ImGui.SameLine(); - ImGui.SetCursorPosX(cursX); + ImGui.TableNextColumn(); - PrintPartCoords(u, v, width, height); + ImGui.TextColored(!hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Standard:\t"); + ImGui.SameLine(); + var cursX = ImGui.GetCursorPosX(); - ImGui.Text("UV:\t"); - ImGui.SameLine(); - ImGui.SetCursorPosX(cursX); + PrintPartCoords(u / 2f, v / 2f, width / 2f, height / 2f); - PrintPartCoords(u / tWidth, v / tWidth, (u + width) / tWidth, (v + height) / tHeight, true, true); + ImGui.TextColored(hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Hi-Res:\t"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursX); + + PrintPartCoords(u, v, width, height); + + ImGui.Text("UV:\t"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursX); + + PrintPartCoords(u / tWidth, v / tWidth, (u + width) / tWidth, (v + height) / tHeight, true, true); + } } - - tab.Dispose(); } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index efb62dd7a..366e3d36d 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -90,11 +90,11 @@ internal unsafe partial class ResNodeTree : IDisposable NodeType.ClippingMask => new ClippingMaskNodeTree(node, addonTree), NodeType.Counter => new CounterNodeTree(node, addonTree), NodeType.Collision => new CollisionNodeTree(node, addonTree), - _ => new ResNodeTree(node, addonTree), + _ => new ResNodeTree(node, addonTree) }; /// - /// Prints a list of NodeTree for a given list of nodes. + /// Prints a list of NodeTrees for a given list of nodes. /// /// The address of the start of the list. /// The number of nodes in the list. @@ -122,8 +122,8 @@ internal unsafe partial class ResNodeTree : IDisposable return; } - var c = ImRaii.PushColor(Text, color); - var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth); + using var c = ImRaii.PushColor(Text, color); + using var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth); c.Pop(); if (tree) @@ -139,8 +139,6 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(color), 1); } } - - tree.Dispose(); } /// @@ -275,8 +273,8 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.SetNextItemOpen(true, ImGuiCond.Always); } - var col = ImRaii.PushColor(Text, displayColor); - var tree = ImRaii.TreeNode(label, SpanFullWidth); + using var col = ImRaii.PushColor(Text, displayColor); + using var tree = ImRaii.TreeNode(label, SpanFullWidth); if (ImGui.IsItemHovered()) { @@ -331,8 +329,6 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.GetWindowDrawList().AddLine(lineStart, lineEnd, RgbaVector4ToUint(displayColor), 1); } } - - tree.Dispose(); } private void DrawBasicControls() diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index b7c01cbc2..257ce5aa3 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -50,7 +50,6 @@ internal unsafe partial class TextNodeTree : ResNodeTree ImGui.TextColored(new(1), "Text:"); ImGui.SameLine(); -#pragma warning disable try { var style = new SeStringDrawParams @@ -61,13 +60,14 @@ internal unsafe partial class TextNodeTree : ResNodeTree EdgeStrength = 1f }; +#pragma warning disable SeStringRenderer ImGuiHelpers.SeStringWrapped(this.NodeText.AsSpan(), style); +#pragma warning restore SeStringRenderer } catch { ImGui.Text(Marshal.PtrToStringAnsi(new(this.NodeText.StringPtr)) ?? ""); } -#pragma warning restore PrintFieldValuePairs( ("Font", $"{this.TxtNode->FontType}"), @@ -83,7 +83,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree private void PrintPayloads() { - var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}"); + using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}"); if (tree) { @@ -116,7 +116,5 @@ internal unsafe partial class TextNodeTree : ResNodeTree } } } - - tree.Dispose(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs index d34233b16..111b92597 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -56,7 +56,7 @@ public readonly unsafe partial struct TimelineTree if (count > 0) { - var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth); + using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth); if (tree) { @@ -81,8 +81,6 @@ public readonly unsafe partial struct TimelineTree this.PrintAnimation(animation, a, isActive, (nint)(this.NodeTimeline->Resource->Animations + (a * sizeof(AtkTimelineAnimation)))); } } - - tree.Dispose(); } } @@ -310,45 +308,46 @@ public readonly unsafe partial struct TimelineTree { var columns = this.BuildColumns(animation); - var col = ImRaii.PushColor(ImGuiCol.Text, isActive ? new Vector4(1, 0.65F, 0.4F, 1) : new(1)); - var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); - col.Dispose(); - - if (tree) + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 0.65F, 0.4F, 1), isActive)) { - PrintFieldValuePair("Animation", $"{address:X}"); + using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); - ShowStruct((AtkTimelineAnimation*)address); - - if (columns.Count > 0) + if (tree) { - var table = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); + PrintFieldValuePair("Animation", $"{address:X}"); - foreach (var c in columns) + ShowStruct((AtkTimelineAnimation*)address); + + if (columns.Count > 0) { - ImGui.TableSetupColumn(c.Name, WidthFixed, c.Width); - } - - ImGui.TableHeadersRow(); - - var rows = columns.Select(static c => c.Count).Max(); - - for (var i = 0; i < rows; i++) - { - ImGui.TableNextRow(); - - foreach (var c in columns) + using (ImRaii.Table( + $"##{(nint)this.node}animTable{a}", + columns.Count, + Borders | SizingFixedFit | RowBg | NoHostExtendX)) { - ImGui.TableNextColumn(); - c.PrintValueAt(i); + foreach (var c in columns) + { + ImGui.TableSetupColumn(c.Name, WidthFixed, c.Width); + } + + ImGui.TableHeadersRow(); + + var rows = columns.Select(static c => c.Count).Max(); + + for (var i = 0; i < rows; i++) + { + ImGui.TableNextRow(); + + foreach (var c in columns) + { + ImGui.TableNextColumn(); + c.PrintValueAt(i); + } + } } } - - table.Dispose(); } } - - tree.Dispose(); } private List BuildColumns(AtkTimelineAnimation animation) diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index d29eb98cf..2c6a16893 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -79,43 +79,53 @@ internal unsafe class ElementSelector : IDisposable /// internal void DrawInterface() { - var ch = ImRaii.Child("###sidebar_elementSelector", new(250, 0), true); - - var f = ImRaii.PushFont(IconFont); - var col = ImRaii.PushColor(Text, this.Active ? new Vector4(1, 1, 0.2f, 1) : new(1)); - if (ImGui.Button($"{(char)ObjectUngroup}")) + using (ImRaii.Child("###sidebar_elementSelector", new(250, 0), true)) { - this.Active = !this.Active; - } + using var f = ImRaii.PushFont(IconFont); + using var col = ImRaii.PushColor(Text, new Vector4(1, 1, 0.2f, 1), this.Active); - if (Countdown > 0) - { - Countdown -= 1; - if (Countdown < 0) + if (ImGui.Button($"{(char)ObjectUngroup}")) { - Countdown = 0; + this.Active = !this.Active; + } + + if (Countdown > 0) + { + Countdown -= 1; + if (Countdown < 0) + { + Countdown = 0; + } + } + + col.Pop(); + f.Pop(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Element Selector"); + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 32); + ImGui.InputTextWithHint( + "###addressSearchInput", + "Address Search", + ref this.addressSearchInput, + 18, + ImGuiInputTextFlags.AutoSelectAll); + ImGui.SameLine(); + + if (ImGuiComponents.IconButton("###elemSelectorAddrSearch", Search) && nint.TryParse( + this.addressSearchInput, + NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier, + InvariantInfo, + out var address)) + { + this.PerformSearch(address); } } - - col.Pop(); - f.Pop(); - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Element Selector"); - } - - ImGui.SameLine(); - - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 32); - ImGui.InputTextWithHint("###addressSearchInput", "Address Search", ref this.addressSearchInput, 18, ImGuiInputTextFlags.AutoSelectAll); - ImGui.SameLine(); - - if (ImGuiComponents.IconButton("###elemSelectorAddrSearch", Search) && nint.TryParse(this.addressSearchInput, NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier, InvariantInfo, out var address)) - { - this.PerformSearch(address); - } - - ch.Dispose(); } /// @@ -141,72 +151,71 @@ internal unsafe class ElementSelector : IDisposable var mousePos = ImGui.GetMousePos() - MainViewport.Pos; var addonResults = GetAtkUnitBaseAtPosition(mousePos); - var col = ImRaii.PushColor(WindowBg, new Vector4(0.5f)); - var ch = ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); - var g = ImRaii.Group(); - - Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); - ImGui.Spacing(); - ImGui.Text("RESULTS:\n"); - - var i = 0; - foreach (var a in addonResults) + using var col = ImRaii.PushColor(WindowBg, new Vector4(0.5f)); + using (ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse)) { - var name = a.Addon->NameString; - ImGui.Text($"[Addon] {name}"); - ImGui.Indent(15); - foreach (var n in a.Nodes) + using (ImRaii.Group()) { - var nSelected = i++ == this.index; + Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); + ImGui.Spacing(); + ImGui.Text("RESULTS:\n"); - PrintNodeHeaderOnly(n.Node, nSelected, a.Addon); - - if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + var i = 0; + foreach (var a in addonResults) { - this.Active = false; - - this.uiDebug2.SelectedAddonName = a.Addon->NameString; - - var ptrList = new List { (nint)n.Node }; - - var nextNode = n.Node->ParentNode; - while (nextNode != null) + var name = a.Addon->NameString; + ImGui.Text($"[Addon] {name}"); + ImGui.Indent(15); + foreach (var n in a.Nodes) { - ptrList.Add((nint)nextNode); - nextNode = nextNode->ParentNode; + var nSelected = i++ == this.index; + + PrintNodeHeaderOnly(n.Node, nSelected, a.Addon); + + if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + { + this.Active = false; + + this.uiDebug2.SelectedAddonName = a.Addon->NameString; + + var ptrList = new List { (nint)n.Node }; + + var nextNode = n.Node->ParentNode; + while (nextNode != null) + { + ptrList.Add((nint)nextNode); + nextNode = nextNode->ParentNode; + } + + SearchResults = [.. ptrList]; + Countdown = 100; + Scrolled = false; + } + + if (nSelected) + { + n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1)); + } } - SearchResults = [.. ptrList]; - Countdown = 100; - Scrolled = false; + ImGui.Indent(-15); } - if (nSelected) + if (i != 0) { - n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1)); + this.index -= (int)ImGui.GetIO().MouseWheel; + while (this.index < 0) + { + this.index += i; + } + + while (this.index >= i) + { + this.index -= i; + } } } - - ImGui.Indent(-15); } - - if (i != 0) - { - this.index -= (int)ImGui.GetIO().MouseWheel; - while (this.index < 0) - { - this.index += i; - } - - while (this.index >= i) - { - this.index -= i; - } - } - - g.Dispose(); - ch.Dispose(); - col.Pop(); } private static List GetAtkUnitBaseAtPosition(Vector2 position) @@ -384,9 +393,8 @@ internal unsafe class ElementSelector : IDisposable return; } - var col = ImRaii.PushColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1)); + using var col = ImRaii.PushColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1)); ResNodeTree.GetOrCreate(node, tree).WriteTreeHeading(); - col.Pop(); } private void PerformSearch(nint address) diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs index 436607261..91dfc8067 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs @@ -39,9 +39,10 @@ internal class AddonPopoutWindow : Window, IDisposable /// public override void Draw() { - var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true); - this.addonTree.Draw(); - ch.Dispose(); + using (ImRaii.Child($"{this.WindowName}child", new(-1, -1), true)) + { + this.addonTree.Draw(); + } } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs index d657bbdec..c07806823 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs @@ -50,10 +50,11 @@ internal unsafe class NodePopoutWindow : Window, IDisposable { if (this.Node != null && this.AddonTree.ContainsNode(this.Node)) { - var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); - ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); - ch.Dispose(); - this.firstDraw = false; + using (ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true)) + { + ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); + this.firstDraw = false; + } } else { diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs index 45ce27a77..4c6e08409 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -16,7 +16,7 @@ namespace Dalamud.Interface.Internal.UiDebug2; /// internal unsafe partial class UiDebug2 { - /// + /// /// All unit lists to check for addons. /// internal static readonly List UnitListOptions = @@ -38,7 +38,7 @@ internal unsafe partial class UiDebug2 new(12, "Depth Layer 13"), new(15, "Units 16"), new(16, "Units 17"), - new(17, "Units 18"), + new(17, "Units 18") ]; private string addonNameSearch = string.Empty; @@ -53,54 +53,54 @@ internal unsafe partial class UiDebug2 private void DrawSidebar() { - var g = ImRaii.Group(); - - this.DrawNameSearch(); - this.DrawAddonSelectionList(); - this.elementSelector.DrawInterface(); - - g.Dispose(); + using (ImRaii.Group()) + { + this.DrawNameSearch(); + this.DrawAddonSelectionList(); + this.elementSelector.DrawInterface(); + } } private void DrawNameSearch() { - var ch = ImRaii.Child("###sidebar_nameSearch", new(250, 40), true); - var atkUnitBaseSearch = this.addonNameSearch; - - Vector4? defaultColor = this.visFilter ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1); - if (ImGuiComponents.IconButton("filter", LowVision, defaultColor)) + using (ImRaii.Child("###sidebar_nameSearch", new(250, 40), true)) { - this.visFilter = !this.visFilter; + var atkUnitBaseSearch = this.addonNameSearch; + + Vector4? defaultColor = this.visFilter ? new(0.0f, 0.8f, 0.2f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1); + if (ImGuiComponents.IconButton("filter", LowVision, defaultColor)) + { + this.visFilter = !this.visFilter; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Filter by visibility"); + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("###atkUnitBaseSearch", "Filter by name", ref atkUnitBaseSearch, 0x20)) + { + this.addonNameSearch = atkUnitBaseSearch; + } } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Filter by visibility"); - } - - ImGui.SameLine(); - - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.InputTextWithHint("###atkUnitBaseSearch", "Filter by name", ref atkUnitBaseSearch, 0x20)) - { - this.addonNameSearch = atkUnitBaseSearch; - } - - ch.Dispose(); } private void DrawAddonSelectionList() { - var ch = ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); - - var unitListBaseAddr = GetUnitListBaseAddr(); - - foreach (var unit in UnitListOptions) + using (ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar)) { - this.DrawUnitListOption(unitListBaseAddr, unit); - } + var unitListBaseAddr = GetUnitListBaseAddr(); - ch.Dispose(); + foreach (var unit in UnitListOptions) + { + this.DrawUnitListOption(unitListBaseAddr, unit); + } + + ; + } } private void DrawUnitListOption(AtkUnitList* unitListBaseAddr, UnitListOption unit) @@ -148,25 +148,23 @@ internal unsafe partial class UiDebug2 var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}"; - var col1 = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); - var tree = ImRaii.TreeNode($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); + using var col1 = 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(); if (tree) { foreach (var option in options) { - var col2 = ImRaii.PushColor(ImGuiCol.Text, option.Visible ? new Vector4(0.1f, 1f, 0.1f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1)); - if (ImGui.Selectable($"{option.Name}##select{option.Name}", this.SelectedAddonName == option.Name)) + using (ImRaii.PushColor(ImGuiCol.Text, option.Visible ? new Vector4(0.1f, 1f, 0.1f, 1f) : new Vector4(0.6f, 0.6f, 0.6f, 1))) { - this.SelectedAddonName = option.Name; + if (ImGui.Selectable($"{option.Name}##select{option.Name}", this.SelectedAddonName == option.Name)) + { + this.SelectedAddonName = option.Name; + } } - - col2.Pop(); } } - - tree.Dispose(); } /// diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs index afb058641..17c64ddca 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs @@ -31,17 +31,13 @@ internal partial class UiDebug2 : IDisposable internal UiDebug2() { this.elementSelector = new(this); - - GameGui = Service.Get(); - - Log = new ModuleLog("UiDebug2"); } /// - internal static ModuleLog Log { get; set; } = null!; + internal static ModuleLog Log { get; set; } = new("UiDebug2"); /// - internal static IGameGui GameGui { get; set; } = null!; + internal static IGameGui GameGui { get; set; } = Service.Get(); /// /// Gets a collection of instances, each representing an . @@ -86,28 +82,28 @@ internal partial class UiDebug2 : IDisposable private void DrawMainPanel() { ImGui.SameLine(); - var ch = ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); - if (this.elementSelector.Active) + using (ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar)) { - this.elementSelector.DrawSelectorOutput(); - } - else - { - if (this.SelectedAddonName != null) + if (this.elementSelector.Active) { - var addonTree = AddonTree.GetOrCreate(this.SelectedAddonName); - - if (addonTree == null) + this.elementSelector.DrawSelectorOutput(); + } + else + { + if (this.SelectedAddonName != null) { - this.SelectedAddonName = null; - return; - } + var addonTree = AddonTree.GetOrCreate(this.SelectedAddonName); - addonTree.Draw(); + if (addonTree == null) + { + this.SelectedAddonName = null; + return; + } + + addonTree.Draw(); + } } } - - ch.Dispose(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index f523bd685..c203090ec 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -24,8 +24,8 @@ internal static class Gui /// The type of value being set. /// The label for the inputs. /// The value being set. - /// A list of all options to create buttons for. - /// A list of the icons to use for each option. + /// A list of all options. + /// A list of icons corresponding to the options. /// true if a button is clicked. internal static unsafe bool IconSelectInput(string label, ref T val, List options, List icons) { @@ -104,14 +104,11 @@ internal static class Gui /// Colors the text itself either white or black, depending on the luminosity of the background color. internal static void PrintColor(Vector4 color, string fmt) { - var c = new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) - .Push(Button, color) - .Push(ButtonActive, color) - .Push(ButtonHovered, color); + using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)).Push(Button, color).Push(ButtonActive, color).Push(ButtonHovered, color)) + { + ImGui.SmallButton(fmt); + } - ImGui.SmallButton(fmt); - - c.Pop(4); return; static double Luminosity(Vector4 vector4) => @@ -125,20 +122,21 @@ internal static class Gui /// internal static void ClickToCopyText(string text, string? textCopy = null) { - var c = ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1)); - ImGuiHelpers.ClickToCopyText(text, textCopy); - c.Pop(); + using (ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1))) + { + ImGuiHelpers.ClickToCopyText(text, textCopy); + } if (ImGui.IsItemHovered()) { - var t = ImRaii.Tooltip(); - var f = ImRaii.PushFont(UiBuilder.IconFont); - ImGui.Text(FontAwesomeIcon.Copy.ToIconString()); - f.Pop(); - ImGui.SameLine(); - ImGui.Text($"{textCopy ?? text}"); - - t.Dispose(); + using (ImRaii.Tooltip()) + { + using var f = ImRaii.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Copy.ToIconString()); + f.Pop(); + ImGui.SameLine(); + ImGui.Text($"{textCopy ?? text}"); + } } } @@ -161,9 +159,10 @@ internal static class Gui var index = (int)Math.Floor(prog * tooltips.Length); - var t = ImRaii.Tooltip(); - ImGui.Text(tooltips[index]); - t.Dispose(); + using (ImRaii.Tooltip()) + { + ImGui.Text(tooltips[index]); + } return true; } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs index 9d2af32e8..cffd676f7 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs @@ -7,6 +7,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; using static System.Math; +using static Dalamud.Interface.ColorHelpers; namespace Dalamud.Interface.Internal.UiDebug2.Utility; @@ -61,8 +62,8 @@ public unsafe struct NodeBounds if (this.Points.Count == 1) { - ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, ColorHelpers.RgbaVector4ToUint(col with { W = col.W / 2 }), 12, thickness); - ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], thickness, ColorHelpers.RgbaVector4ToUint(col), 12, thickness + 1); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12, thickness); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], thickness, RgbaVector4ToUint(col), 12, thickness + 1); } else { @@ -73,7 +74,7 @@ public unsafe struct NodeBounds } ImGui.GetBackgroundDrawList() - .AddPolyline(ref path[0], path.Length, ColorHelpers.RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); + .AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); path.Dispose(); } @@ -94,8 +95,8 @@ public unsafe struct NodeBounds if (this.Points.Count == 1) { ImGui.GetBackgroundDrawList() - .AddCircleFilled(this.Points[0], 10, ColorHelpers.RgbaVector4ToUint(col with { W = col.W / 2 }), 12); - ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, ColorHelpers.RgbaVector4ToUint(col), 12, thickness); + .AddCircleFilled(this.Points[0], 10, RgbaVector4ToUint(col with { W = col.W / 2 }), 12); + ImGui.GetBackgroundDrawList().AddCircle(this.Points[0], 10, RgbaVector4ToUint(col), 12, thickness); } else { @@ -106,9 +107,9 @@ public unsafe struct NodeBounds } ImGui.GetBackgroundDrawList() - .AddConvexPolyFilled(ref path[0], path.Length, ColorHelpers.RgbaVector4ToUint(col with { W = col.W / 2 })); + .AddConvexPolyFilled(ref path[0], path.Length, RgbaVector4ToUint(col with { W = col.W / 2 })); ImGui.GetBackgroundDrawList() - .AddPolyline(ref path[0], path.Length, ColorHelpers.RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); + .AddPolyline(ref path[0], path.Length, RgbaVector4ToUint(col), ImDrawFlags.Closed, thickness); path.Dispose(); } @@ -147,7 +148,9 @@ public unsafe struct NodeBounds var sinR = (float)Sin(r); var d = (p - o) * s; - return new(o.X + (d.X * cosR) - (d.Y * sinR), o.Y + (d.X * sinR) + (d.Y * cosR)); + return new( + o.X + (d.X * cosR) - (d.Y * sinR), + o.Y + (d.X * sinR) + (d.Y * cosR)); } private void TransformPoints(AtkResNode* transformNode) diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 8aeeb7f7b..2449cab1c 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -21,6 +21,7 @@ internal class DataWindow : Window, IDisposable private readonly IDataWindowWidget[] modules = { new AddonInspectorWidget(), + new AddonInspectorWidget2(), new AddonLifecycleWidget(), new AddonWidget(), new AddressesWidget(), diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs index 33db51c75..e11404dec 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget.cs @@ -5,7 +5,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class AddonInspectorWidget : IDataWindowWidget { - private UiDebug2.UiDebug2? addonInspector; + private UiDebug? addonInspector; /// public string[]? CommandShortcuts { get; init; } = { "ai", "addoninspector" }; @@ -19,7 +19,7 @@ internal class AddonInspectorWidget : IDataWindowWidget /// public void Load() { - this.addonInspector = new UiDebug2.UiDebug2(); + this.addonInspector = new UiDebug(); if (this.addonInspector is not null) { diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs new file mode 100644 index 000000000..6cd6ecb0b --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonInspectorWidget2.cs @@ -0,0 +1,35 @@ +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying addon inspector. +/// +internal class AddonInspectorWidget2 : IDataWindowWidget +{ + private UiDebug2.UiDebug2? addonInspector2; + + /// + public string[]? CommandShortcuts { get; init; } = ["ai2", "addoninspector2"]; + + /// + public string DisplayName { get; init; } = "Addon Inspector v2 (Testing)"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.addonInspector2 = new UiDebug2.UiDebug2(); + + if (this.addonInspector2 is not null) + { + this.Ready = true; + } + } + + /// + public void Draw() + { + this.addonInspector2?.Draw(); + } +} From 0b9af0e3f43c42dedd29c3d19371fbaacc762caa Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sun, 20 Oct 2024 19:59:03 -0700 Subject: [PATCH 074/375] Update to Lumina 5 (new Excel parsing) (#2022) * Refactor and upgrade to new excel design * Obsolete ExcelResolver and use only RowRef * Better benchmarking for Lumina * Add custom game-supported RSV provider * Refactor and move Lazy and nullable/cached row objects to RowRefs * Convert IRSVProvider to delegate, resolve strings by default * Split IExcelRow into IExcelSubrow * Extra lumina documentation * Minor RSV CS fixes * Fix UIGlowPayload warning * Fix rebase * Update to Lumina 5 --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 +- Dalamud/Dalamud.csproj | 4 +- Dalamud/Data/DataManager.cs | 18 ++-- Dalamud/Data/LuminaUtils.cs | 22 +++++ Dalamud/Data/RsvResolver.cs | 51 +++++++++++ .../ClientState/Aetherytes/AetheryteEntry.cs | 9 +- Dalamud/Game/ClientState/Buddy/BuddyMember.cs | 16 ++-- Dalamud/Game/ClientState/ClientState.cs | 2 +- Dalamud/Game/ClientState/Fates/Fate.cs | 11 +-- .../ClientState/JobGauge/Types/SMNGauge.cs | 4 +- .../Objects/SubKinds/PlayerCharacter.cs | 22 ++--- .../ClientState/Objects/Types/Character.cs | 28 ++++--- Dalamud/Game/ClientState/Party/PartyMember.cs | 16 ++-- .../ClientState/Resolvers/ExcelResolver{T}.cs | 38 --------- Dalamud/Game/ClientState/Statuses/Status.cs | 6 +- Dalamud/Game/Gui/ContextMenu/MenuItem.cs | 2 +- .../Game/Gui/ContextMenu/MenuTargetDefault.cs | 7 +- .../PartyFinder/Types/JobFlagsExtensions.cs | 2 +- .../PartyFinder/Types/PartyFinderListing.cs | 36 ++++---- .../UniversalisMarketBoardUploader.cs | 6 +- .../Game/Network/Internal/NetworkHandlers.cs | 14 ++-- .../Structures/InfoProxy/CharacterData.cs | 22 ++--- Dalamud/Game/Text/SeStringHandling/Payload.cs | 7 -- .../Payloads/AutoTranslatePayload.cs | 84 +++++++++---------- .../SeStringHandling/Payloads/ItemPayload.cs | 34 +++----- .../Payloads/MapLinkPayload.cs | 34 +++----- .../Payloads/PlayerPayload.cs | 16 ++-- .../SeStringHandling/Payloads/QuestPayload.cs | 14 ++-- .../Payloads/StatusPayload.cs | 14 ++-- .../Payloads/UIForegroundPayload.cs | 22 ++--- .../Payloads/UIGlowPayload.cs | 17 ++-- .../Game/Text/SeStringHandling/SeString.cs | 19 ++--- .../Internal/SeStringColorStackSet.cs | 2 +- .../Internal/SeStringRenderer.cs | 2 +- .../Windows/Data/Widgets/AetherytesWidget.cs | 4 +- .../Windows/Data/Widgets/GaugeWidget.cs | 4 +- .../Windows/Data/Widgets/ObjectTableWidget.cs | 6 +- .../Widgets/SeStringRendererTestWidget.cs | 24 +++--- .../Windows/Data/Widgets/UIColorWidget.cs | 52 +++++++----- .../AgingSteps/ContextMenuAgingStep.cs | 41 +++++---- .../SelfTest/AgingSteps/LuminaAgingStep.cs | 38 ++++++--- .../Windows/SelfTest/SelfTestWindow.cs | 8 +- Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 2 +- Dalamud/Plugin/Services/IClientState.cs | 2 +- Dalamud/Plugin/Services/IDataManager.cs | 33 ++++++-- Dalamud/Utility/MapUtil.cs | 10 +-- Dalamud/Utility/SeStringExtensions.cs | 20 ++++- Dalamud/Utility/Util.cs | 12 +-- lib/FFXIVClientStructs | 2 +- 49 files changed, 460 insertions(+), 403 deletions(-) create mode 100644 Dalamud/Data/LuminaUtils.cs create mode 100644 Dalamud/Data/RsvResolver.cs delete mode 100644 Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 5f91a6ac8..6cc92cbb5 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 528145d59..a577f130b 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 78fa83b00..d017bf85a 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -11,6 +11,7 @@ using Dalamud.Utility.Timing; using Lumina; using Lumina.Data; using Lumina.Excel; + using Newtonsoft.Json; using Serilog; @@ -28,12 +29,15 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager { private readonly Thread luminaResourceThread; private readonly CancellationTokenSource luminaCancellationTokenSource; + private readonly RsvResolver rsvResolver; [ServiceManager.ServiceConstructor] private DataManager(Dalamud dalamud) { this.Language = (ClientLanguage)dalamud.StartInfo.Language; + this.rsvResolver = new(); + try { Log.Verbose("Starting data load..."); @@ -44,11 +48,8 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager { LoadMultithreaded = true, CacheFileResources = true, -#if NEVER // Lumina bug PanicOnSheetChecksumMismatch = true, -#else - PanicOnSheetChecksumMismatch = false, -#endif + RsvResolver = this.rsvResolver.TryResolve, DefaultExcelLanguage = this.Language.ToLumina(), }; @@ -129,12 +130,12 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager #region Lumina Wrappers /// - public ExcelSheet? GetExcelSheet() where T : ExcelRow - => this.Excel.GetSheet(); + public ExcelSheet GetExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow + => this.Excel.GetSheet(language?.ToLumina(), name); /// - public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow - => this.Excel.GetSheet(language.ToLumina()); + public SubrowExcelSheet GetSubrowExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelSubrow + => this.Excel.GetSubrowSheet(language?.ToLumina(), name); /// public FileResource? GetFile(string path) @@ -170,6 +171,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager { this.luminaCancellationTokenSource.Cancel(); this.GameData.Dispose(); + this.rsvResolver.Dispose(); } private class LauncherTroubleshootingInfo diff --git a/Dalamud/Data/LuminaUtils.cs b/Dalamud/Data/LuminaUtils.cs new file mode 100644 index 000000000..6da67f426 --- /dev/null +++ b/Dalamud/Data/LuminaUtils.cs @@ -0,0 +1,22 @@ +using Lumina.Excel; + +namespace Dalamud.Data; + +/// +/// A helper class to easily resolve Lumina data within Dalamud. +/// +internal static class LuminaUtils +{ + private static ExcelModule Module => Service.Get().Excel; + + /// + /// Initializes a new instance of the class using the default . + /// + /// The type of Lumina sheet to resolve. + /// The id of the row to resolve. + /// A new object. + public static RowRef CreateRef(uint rowId) where T : struct, IExcelRow + { + return new(Module, rowId); + } +} diff --git a/Dalamud/Data/RsvResolver.cs b/Dalamud/Data/RsvResolver.cs new file mode 100644 index 000000000..3f507ff1d --- /dev/null +++ b/Dalamud/Data/RsvResolver.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + +using Dalamud.Hooking; +using Dalamud.Logging.Internal; +using Dalamud.Memory; +using FFXIVClientStructs.FFXIV.Client.LayoutEngine; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Data; + +/// +/// Provides functionality for resolving RSV strings. +/// +internal sealed unsafe class RsvResolver : IDisposable +{ + private static readonly ModuleLog Log = new("RsvProvider"); + + private readonly Hook addRsvStringHook; + + /// + /// Initializes a new instance of the class. + /// + public RsvResolver() + { + this.addRsvStringHook = Hook.FromAddress((nint)LayoutWorld.MemberFunctionPointers.AddRsvString, this.AddRsvStringDetour); + + this.addRsvStringHook.Enable(); + } + + private Dictionary Lookup { get; } = []; + + /// Attemps to resolve an RSV string. + /// + public bool TryResolve(ReadOnlySeString rsvString, out ReadOnlySeString resolvedString) => + this.Lookup.TryGetValue(rsvString, out resolvedString); + + /// + public void Dispose() + { + this.addRsvStringHook.Dispose(); + } + + private bool AddRsvStringDetour(LayoutWorld* @this, byte* rsvString, byte* resolvedString, nuint resolvedStringSize) + { + var rsv = new ReadOnlySeString(MemoryHelper.ReadRawNullTerminated((nint)rsvString)); + var resolved = new ReadOnlySeString(new ReadOnlySpan(resolvedString, (int)resolvedStringSize).ToArray()); + Log.Debug($"Resolving RSV \"{rsv}\" to \"{resolved}\"."); + this.Lookup[rsv] = resolved; + return this.addRsvStringHook.Original(@this, rsvString, resolvedString, resolvedStringSize); + } +} diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs index 058d6c0c2..244989476 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs @@ -1,6 +1,9 @@ -using Dalamud.Game.ClientState.Resolvers; +using Dalamud.Data; + using FFXIVClientStructs.FFXIV.Client.Game.UI; +using Lumina.Excel; + namespace Dalamud.Game.ClientState.Aetherytes; /// @@ -56,7 +59,7 @@ public interface IAetheryteEntry /// /// Gets the Aetheryte data related to this aetheryte. /// - ExcelResolver AetheryteData { get; } + RowRef AetheryteData { get; } } /// @@ -103,5 +106,5 @@ internal sealed class AetheryteEntry : IAetheryteEntry public bool IsApartment => this.data.IsApartment; /// - public ExcelResolver AetheryteData => new(this.AetheryteId); + public RowRef AetheryteData => LuminaUtils.CreateRef(this.AetheryteId); } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs index a650a7b44..025de611d 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs @@ -1,6 +1,8 @@ +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; + +using Lumina.Excel; namespace Dalamud.Game.ClientState.Buddy; @@ -45,17 +47,17 @@ public interface IBuddyMember /// /// Gets the Mount data related to this buddy. It should only be used with companion buddies. /// - ExcelResolver MountData { get; } + RowRef MountData { get; } /// /// Gets the Pet data related to this buddy. It should only be used with pet buddies. /// - ExcelResolver PetData { get; } + RowRef PetData { get; } /// /// Gets the Trust data related to this buddy. It should only be used with battle buddies. /// - ExcelResolver TrustData { get; } + RowRef TrustData { get; } } /// @@ -94,13 +96,13 @@ internal unsafe class BuddyMember : IBuddyMember public uint DataID => this.Struct->DataId; /// - public ExcelResolver MountData => new(this.DataID); + public RowRef MountData => LuminaUtils.CreateRef(this.DataID); /// - public ExcelResolver PetData => new(this.DataID); + public RowRef PetData => LuminaUtils.CreateRef(this.DataID); /// - public ExcelResolver TrustData => new(this.DataID); + public RowRef TrustData => LuminaUtils.CreateRef(this.DataID); private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 9dd999860..0fad33e15 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -19,7 +19,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Action = System.Action; diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 2d32cc04c..9b41ac758 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -1,10 +1,11 @@ using System.Numerics; using Dalamud.Data; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; +using Lumina.Excel; + namespace Dalamud.Game.ClientState.Fates; /// @@ -20,7 +21,7 @@ public interface IFate : IEquatable /// /// Gets game data linked to this Fate. /// - Lumina.Excel.GeneratedSheets.Fate GameData { get; } + RowRef GameData { get; } /// /// Gets the time this started. @@ -105,7 +106,7 @@ public interface IFate : IEquatable /// /// Gets the territory this is located in. /// - ExcelResolver TerritoryType { get; } + RowRef TerritoryType { get; } /// /// Gets the address of this Fate in memory. @@ -185,7 +186,7 @@ internal unsafe partial class Fate : IFate public ushort FateId => this.Struct->FateId; /// - public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId); + public RowRef GameData => LuminaUtils.CreateRef(this.FateId); /// public int StartTimeEpoch => this.Struct->StartTimeEpoch; @@ -238,5 +239,5 @@ internal unsafe partial class Fate : IFate /// /// Gets the territory this is located in. /// - public ExcelResolver TerritoryType => new(this.Struct->TerritoryId); + public RowRef TerritoryType => LuminaUtils.CreateRef(this.Struct->TerritoryId); } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index aa7e27fd2..81be0e762 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -29,13 +29,13 @@ public unsafe class SMNGauge : JobGaugeBase /// /// Gets the summon that will return after the current summon expires. - /// This maps to the sheet. + /// This maps to the sheet. /// public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; /// /// Gets the summon glam for the . - /// This maps to the sheet. + /// This maps to the sheet. /// public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs index 87fbf39c3..2fd723071 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs @@ -1,12 +1,8 @@ -using System.Numerics; - -using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Data; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; -using Dalamud.Game.ClientState.Statuses; -using Dalamud.Game.Text.SeStringHandling; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.ClientState.Objects.SubKinds; @@ -16,14 +12,14 @@ namespace Dalamud.Game.ClientState.Objects.SubKinds; public interface IPlayerCharacter : IBattleChara { /// - /// Gets the current world of the character. + /// Gets the current world of the character. /// - ExcelResolver CurrentWorld { get; } + RowRef CurrentWorld { get; } /// - /// Gets the home world of the character. + /// Gets the home world of the character. /// - ExcelResolver HomeWorld { get; } + RowRef HomeWorld { get; } } /// @@ -42,10 +38,10 @@ internal unsafe class PlayerCharacter : BattleChara, IPlayerCharacter } /// - public ExcelResolver CurrentWorld => new(this.Struct->CurrentWorld); + public RowRef CurrentWorld => LuminaUtils.CreateRef(this.Struct->CurrentWorld); /// - public ExcelResolver HomeWorld => new(this.Struct->HomeWorld); + public RowRef HomeWorld => LuminaUtils.CreateRef(this.Struct->HomeWorld); /// /// Gets the target actor ID of the PlayerCharacter. diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index 72f6a9950..b04b54301 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -1,10 +1,12 @@ using System.Runtime.CompilerServices; +using Dalamud.Data; using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -using Lumina.Excel.GeneratedSheets; + +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.ClientState.Objects.Types; @@ -61,7 +63,7 @@ public interface ICharacter : IGameObject /// /// Gets the ClassJob of this Chara. /// - public ExcelResolver ClassJob { get; } + public RowRef ClassJob { get; } /// /// Gets the level of this Chara. @@ -87,7 +89,7 @@ public interface ICharacter : IGameObject /// /// Gets the current online status of the character. /// - public ExcelResolver OnlineStatus { get; } + public RowRef OnlineStatus { get; } /// /// Gets the status flags. @@ -97,14 +99,14 @@ public interface ICharacter : IGameObject /// /// Gets the current mount for this character. Will be null if the character doesn't have a mount. /// - public ExcelResolver? CurrentMount { get; } + public RowRef? CurrentMount { get; } /// /// Gets the current minion summoned for this character. Will be null if the character doesn't have a minion. /// This method *will* return information about a spawned (but invisible) minion, e.g. if the character is riding a /// mount. /// - public ExcelResolver? CurrentMinion { get; } + public RowRef? CurrentMinion { get; } } /// @@ -150,7 +152,7 @@ internal unsafe class Character : GameObject, ICharacter public byte ShieldPercentage => this.Struct->CharacterData.ShieldValue; /// - public ExcelResolver ClassJob => new(this.Struct->CharacterData.ClassJob); + public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->CharacterData.ClassJob); /// public byte Level => this.Struct->CharacterData.Level; @@ -170,7 +172,7 @@ internal unsafe class Character : GameObject, ICharacter public uint NameId => this.Struct->NameId; /// - public ExcelResolver OnlineStatus => new(this.Struct->CharacterData.OnlineStatus); + public RowRef OnlineStatus => LuminaUtils.CreateRef(this.Struct->CharacterData.OnlineStatus); /// /// Gets the status flags. @@ -186,28 +188,28 @@ internal unsafe class Character : GameObject, ICharacter (this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None); /// - public ExcelResolver? CurrentMount + public RowRef? CurrentMount { get { if (this.Struct->IsNotMounted()) return null; // just for safety. var mountId = this.Struct->Mount.MountId; - return mountId == 0 ? null : new ExcelResolver(mountId); + return mountId == 0 ? null : LuminaUtils.CreateRef(mountId); } } /// - public ExcelResolver? CurrentMinion + public RowRef? CurrentMinion { get { if (this.Struct->CompanionObject != null) - return new ExcelResolver(this.Struct->CompanionObject->BaseId); + return LuminaUtils.CreateRef(this.Struct->CompanionObject->BaseId); // this is only present if a minion is summoned but hidden (e.g. the player's on a mount). var hiddenCompanionId = this.Struct->CompanionData.CompanionId; - return hiddenCompanionId == 0 ? null : new ExcelResolver(hiddenCompanionId); + return hiddenCompanionId == 0 ? null : LuminaUtils.CreateRef(hiddenCompanionId); } } diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index 34cd31dec..b764431f5 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -1,13 +1,15 @@ using System.Numerics; using System.Runtime.CompilerServices; +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.ClientState.Statuses; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; +using Lumina.Excel; + namespace Dalamud.Game.ClientState.Party; /// @@ -71,12 +73,12 @@ public interface IPartyMember /// /// Gets the territory this party member is located in. /// - ExcelResolver Territory { get; } + RowRef Territory { get; } /// /// Gets the World this party member resides in. /// - ExcelResolver World { get; } + RowRef World { get; } /// /// Gets the displayname of this party member. @@ -91,7 +93,7 @@ public interface IPartyMember /// /// Gets the classjob of this party member. /// - ExcelResolver ClassJob { get; } + RowRef ClassJob { get; } /// /// Gets the level of this party member. @@ -169,12 +171,12 @@ internal unsafe class PartyMember : IPartyMember /// /// Gets the territory this party member is located in. /// - public ExcelResolver Territory => new(this.Struct->TerritoryType); + public RowRef Territory => LuminaUtils.CreateRef(this.Struct->TerritoryType); /// /// Gets the World this party member resides in. /// - public ExcelResolver World => new(this.Struct->HomeWorld); + public RowRef World => LuminaUtils.CreateRef(this.Struct->HomeWorld); /// /// Gets the displayname of this party member. @@ -189,7 +191,7 @@ internal unsafe class PartyMember : IPartyMember /// /// Gets the classjob of this party member. /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); + public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->ClassJob); /// /// Gets the level of this party member. diff --git a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs deleted file mode 100644 index 04003d9aa..000000000 --- a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Dalamud.Data; - -using Lumina.Excel; - -namespace Dalamud.Game.ClientState.Resolvers; - -/// -/// This object resolves a rowID within an Excel sheet. -/// -/// The type of Lumina sheet to resolve. -public class ExcelResolver where T : ExcelRow -{ - /// - /// Initializes a new instance of the class. - /// - /// The ID of the classJob. - internal ExcelResolver(uint id) - { - this.Id = id; - } - - /// - /// Gets the ID to be resolved. - /// - public uint Id { get; } - - /// - /// Gets GameData linked to this excel row. - /// - public T? GameData => Service.Get().GetExcelSheet()?.GetRow(this.Id); - - /// - /// Gets GameData linked to this excel row with the specified language. - /// - /// The language. - /// The ExcelRow in the specified language. - public T? GetWithLanguage(ClientLanguage language) => Service.Get().GetExcelSheet(language)?.GetRow(this.Id); -} diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index ad1a24b6a..f09d13fb3 100644 --- a/Dalamud/Game/ClientState/Statuses/Status.cs +++ b/Dalamud/Game/ClientState/Statuses/Status.cs @@ -1,6 +1,8 @@ +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; + +using Lumina.Excel; namespace Dalamud.Game.ClientState.Statuses; @@ -31,7 +33,7 @@ public unsafe class Status /// /// Gets the GameData associated with this status. /// - public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver(this.Struct->StatusId).GameData; + public RowRef GameData => LuminaUtils.CreateRef(this.Struct->StatusId); /// /// Gets the parameter value of the status. diff --git a/Dalamud/Game/Gui/ContextMenu/MenuItem.cs b/Dalamud/Game/Gui/ContextMenu/MenuItem.cs index 9b7cc2bc1..df1cb54a7 100644 --- a/Dalamud/Game/Gui/ContextMenu/MenuItem.cs +++ b/Dalamud/Game/Gui/ContextMenu/MenuItem.cs @@ -1,7 +1,7 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.ContextMenu; diff --git a/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs b/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs index 26365ab14..f09b3e105 100644 --- a/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs +++ b/Dalamud/Game/Gui/ContextMenu/MenuTargetDefault.cs @@ -1,11 +1,12 @@ +using Dalamud.Data; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Network.Structures.InfoProxy; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.ContextMenu; @@ -46,7 +47,7 @@ public sealed unsafe class MenuTargetDefault : MenuTarget /// /// Gets the home world id of the target. /// - public ExcelResolver TargetHomeWorld => new((uint)this.Context->TargetHomeWorldId); + public RowRef TargetHomeWorld => LuminaUtils.CreateRef((uint)this.Context->TargetHomeWorldId); /// /// Gets the currently targeted character. Only shows up for specific targets, like friends, party finder listings, or party members. diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs index ba72021ba..1c78c871b 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs @@ -1,5 +1,5 @@ using Dalamud.Plugin.Services; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.PartyFinder.Types; diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs index 3461841ab..09de07e0d 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs @@ -5,7 +5,8 @@ using Dalamud.Data; using Dalamud.Game.Gui.PartyFinder.Internal; using Dalamud.Game.Text.SeStringHandling; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Gui.PartyFinder.Types; @@ -48,7 +49,7 @@ public interface IPartyFinderListing /// /// Gets a list of the classes/jobs that are currently present in the party. /// - IReadOnlyCollection> JobsPresent { get; } + IReadOnlyCollection> JobsPresent { get; } /// /// Gets the ID assigned to this listing by the game's server. @@ -73,17 +74,17 @@ public interface IPartyFinderListing /// /// Gets the world that this listing was created on. /// - Lazy World { get; } + RowRef World { get; } /// /// Gets the home world of the listing's host. /// - Lazy HomeWorld { get; } + RowRef HomeWorld { get; } /// /// Gets the current world of the listing's host. /// - Lazy CurrentWorld { get; } + RowRef CurrentWorld { get; } /// /// Gets the Party Finder category this listing is listed under. @@ -98,7 +99,7 @@ public interface IPartyFinderListing /// /// Gets the duty this listing is for. May be null for non-duty listings. /// - Lazy Duty { get; } + RowRef Duty { get; } /// /// Gets the type of duty this listing is for. @@ -216,12 +217,12 @@ internal class PartyFinderListing : IPartyFinderListing this.ContentId = listing.ContentId; this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); - this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World)); - this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld)); - this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld)); + this.World = LuminaUtils.CreateRef(listing.World); + this.HomeWorld = LuminaUtils.CreateRef(listing.HomeWorld); + this.CurrentWorld = LuminaUtils.CreateRef(listing.CurrentWorld); this.Category = (DutyCategory)listing.Category; this.RawDuty = listing.Duty; - this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty)); + this.Duty = LuminaUtils.CreateRef(listing.Duty); this.DutyType = (DutyType)listing.DutyType; this.BeginnersWelcome = listing.BeginnersWelcome == 1; this.SecondsRemaining = listing.SecondsRemaining; @@ -231,10 +232,7 @@ internal class PartyFinderListing : IPartyFinderListing this.SlotsFilled = listing.NumSlotsFilled; this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp; this.JobsPresent = listing.JobsPresent - .Select(id => new Lazy( - () => id == 0 - ? null - : dataManager.GetExcelSheet().GetRow(id))) + .Select(id => LuminaUtils.CreateRef(id)) .ToArray(); } @@ -251,13 +249,13 @@ internal class PartyFinderListing : IPartyFinderListing public SeString Description { get; } /// - public Lazy World { get; } + public RowRef World { get; } /// - public Lazy HomeWorld { get; } + public RowRef HomeWorld { get; } /// - public Lazy CurrentWorld { get; } + public RowRef CurrentWorld { get; } /// public DutyCategory Category { get; } @@ -266,7 +264,7 @@ internal class PartyFinderListing : IPartyFinderListing public ushort RawDuty { get; } /// - public Lazy Duty { get; } + public RowRef Duty { get; } /// public DutyType DutyType { get; } @@ -314,7 +312,7 @@ internal class PartyFinderListing : IPartyFinderListing public IReadOnlyCollection RawJobsPresent => this.jobsPresent; /// - public IReadOnlyCollection> JobsPresent { get; } + public IReadOnlyCollection> JobsPresent { get; } #region Indexers diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index 7892e8a04..a62de369b 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -46,7 +46,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader var uploadObject = new UniversalisItemUploadRequest { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0, UploaderId = uploader.ToString(), ItemId = request.Listings.FirstOrDefault()?.CatalogId ?? 0, Listings = [], @@ -120,7 +120,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader var taxUploadObject = new UniversalisTaxUploadRequest { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0, UploaderId = clientState.LocalContentId.ToString(), TaxData = new UniversalisTaxData { @@ -158,7 +158,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader return; var itemId = purchaseHandler.CatalogId; - var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0; + var worldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0; // ==================================================================================== diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 0e87880ec..ad8dbb600 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -16,7 +16,7 @@ using Dalamud.Hooking; using Dalamud.Networking.Http; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.UI.Info; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Serilog; namespace Dalamud.Game.Network.Internal; @@ -282,21 +282,17 @@ internal unsafe class NetworkHandlers : IInternalDisposableService if (this.configuration.DutyFinderTaskbarFlash) Util.FlashWindow(); - var cfConditionSheet = Service.Get().GetExcelSheet()!; - var cfCondition = cfConditionSheet.GetRow(conditionId); + var cfCondition = LuminaUtils.CreateRef(conditionId); - if (cfCondition == null) + if (!cfCondition.IsValid) { Log.Error("CFC key {ConditionId} not in Lumina data", conditionId); return result; } - var cfcName = cfCondition.Name.ToDalamudString(); + var cfcName = cfCondition.Value.Name.ToDalamudString(); if (cfcName.Payloads.Count == 0) - { cfcName = "Duty Roulette"; - cfCondition.Image = 112324; - } Task.Run(() => { @@ -308,7 +304,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Service.GetNullable()?.Print(b.Build()); } - this.CfPop.InvokeSafely(cfCondition); + this.CfPop.InvokeSafely(cfCondition.Value); }).ContinueWith( task => Log.Error(task.Exception, "CfPop.Invoke failed"), TaskContinuationOptions.OnlyOnFaulted); diff --git a/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs b/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs index 96de1c3b2..fcb46b0c3 100644 --- a/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs +++ b/Dalamud/Game/Network/Structures/InfoProxy/CharacterData.cs @@ -1,11 +1,11 @@ using System.Collections.Generic; -using Dalamud.Game.ClientState.Resolvers; -using Dalamud.Memory; +using Dalamud.Data; using FFXIVClientStructs.FFXIV.Client.UI.Info; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Game.Network.Structures.InfoProxy; @@ -92,15 +92,15 @@ public unsafe class CharacterData /// /// Gets the applicable statues of the character. /// - public IReadOnlyList> Statuses + public IReadOnlyList> Statuses { get { - var statuses = new List>(); + var statuses = new List>(); for (var i = 0; i < 64; i++) { if ((this.StatusMask & (1UL << i)) != 0) - statuses.Add(new((uint)i)); + statuses.Add(LuminaUtils.CreateRef((uint)i)); } return statuses; @@ -125,22 +125,22 @@ public unsafe class CharacterData /// /// Gets the current world of the character. /// - public ExcelResolver CurrentWorld => new(this.Struct->CurrentWorld); + public RowRef CurrentWorld => LuminaUtils.CreateRef(this.Struct->CurrentWorld); /// /// Gets the home world of the character. /// - public ExcelResolver HomeWorld => new(this.Struct->HomeWorld); + public RowRef HomeWorld => LuminaUtils.CreateRef(this.Struct->HomeWorld); /// /// Gets the location of the character. /// - public ExcelResolver Location => new(this.Struct->Location); + public RowRef Location => LuminaUtils.CreateRef(this.Struct->Location); /// /// Gets the grand company of the character. /// - public ExcelResolver GrandCompany => new((uint)this.Struct->GrandCompany); + public RowRef GrandCompany => LuminaUtils.CreateRef((uint)this.Struct->GrandCompany); /// /// Gets the primary client language of the character. @@ -178,7 +178,7 @@ public unsafe class CharacterData /// /// Gets the job of the character. /// - public ExcelResolver ClassJob => new(this.Struct->Job); + public RowRef ClassJob => LuminaUtils.CreateRef(this.Struct->Job); /// /// Gets the name of the character. diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index b876598de..a38a8271d 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -38,13 +38,6 @@ public abstract partial class Payload /// public bool Dirty { get; protected set; } = true; - /// - /// Gets the Lumina instance to use for any necessary data lookups. - /// - [JsonIgnore] - // TODO: We should refactor this. It should not be possible to get IDataManager through here. - protected IDataManager DataResolver => Service.Get(); - /// /// Decodes a binary representation of a payload into its corresponding nice object payload. /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index afc1cdc01..b038deb6f 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -2,7 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel.Sheets; +using Lumina.Text.ReadOnly; + using Newtonsoft.Json; using Serilog; @@ -106,28 +110,28 @@ public class AutoTranslatePayload : Payload, ITextProvider this.Key = GetInteger(reader); } + private static ReadOnlySeString ResolveTextCommand(TextCommand command) + { + // TextCommands prioritize the `Alias` field, if it not empty + // Example for this is /rangerpose2l which becomes /blackrangerposeb in chat + return !command.Alias.IsEmpty ? command.Alias : command.Command; + } + private string Resolve() { string value = null; - var sheet = this.DataResolver.GetExcelSheet(); + var excelModule = Service.Get().Excel; + var completionSheet = excelModule.GetSheet(); - Completion row = null; - try - { - // try to get the row in the Completion table itself, because this is 'easiest' - // The row may not exist at all (if the Key is for another table), or it could be the wrong row - // (again, if it's meant for another table) - row = sheet.GetRow(this.Key); - } - catch - { - } // don't care, row will be null + // try to get the row in the Completion table itself, because this is 'easiest' + // The row may not exist at all (if the Key is for another table), or it could be the wrong row + // (again, if it's meant for another table) - if (row?.Group == this.Group) + if (completionSheet.GetRowOrDefault(this.Key) is { } completion && completion.Group == this.Group) { // if the row exists in this table and the group matches, this is actually the correct data - value = row.Text; + value = completion.Text.ExtractText(); } else { @@ -135,34 +139,34 @@ public class AutoTranslatePayload : Payload, ITextProvider { // we need to get the linked table and do the lookup there instead // in this case, there will only be one entry for this group id - row = sheet.First(r => r.Group == this.Group); + var row = completionSheet.First(r => r.Group == this.Group); // many of the names contain valid id ranges after the table name, but we don't need those - var actualTableName = row.LookupTable.RawString.Split('[')[0]; + var actualTableName = row.LookupTable.ExtractText().Split('[')[0]; var name = actualTableName switch { - "Action" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "ActionComboRoute" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "BuddyAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "ClassJob" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "Companion" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Singular, - "CraftAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "GeneralAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "GuardianDeity" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "MainCommand" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "Mount" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Singular, - "Pet" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "PetAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "PetMirage" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "PlaceName" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, - "Race" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Masculine, - "TextCommand" => this.ResolveTextCommand(), - "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Masculine, - "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "Action" => excelModule.GetSheet().GetRow(this.Key).Name, + "ActionComboRoute" => excelModule.GetSheet().GetRow(this.Key).Name, + "BuddyAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "ClassJob" => excelModule.GetSheet().GetRow(this.Key).Name, + "Companion" => excelModule.GetSheet().GetRow(this.Key).Singular, + "CraftAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "GeneralAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "GuardianDeity" => excelModule.GetSheet().GetRow(this.Key).Name, + "MainCommand" => excelModule.GetSheet().GetRow(this.Key).Name, + "Mount" => excelModule.GetSheet().GetRow(this.Key).Singular, + "Pet" => excelModule.GetSheet().GetRow(this.Key).Name, + "PetAction" => excelModule.GetSheet().GetRow(this.Key).Name, + "PetMirage" => excelModule.GetSheet().GetRow(this.Key).Name, + "PlaceName" => excelModule.GetSheet().GetRow(this.Key).Name, + "Race" => excelModule.GetSheet().GetRow(this.Key).Masculine, + "TextCommand" => AutoTranslatePayload.ResolveTextCommand(excelModule.GetSheet().GetRow(this.Key)), + "Tribe" => excelModule.GetSheet().GetRow(this.Key).Masculine, + "Weather" => excelModule.GetSheet().GetRow(this.Key).Name, _ => throw new Exception(actualTableName), }; - value = name; + value = name.ExtractText(); } catch (Exception e) { @@ -172,12 +176,4 @@ public class AutoTranslatePayload : Payload, ITextProvider return value; } - - private Lumina.Text.SeString ResolveTextCommand() - { - // TextCommands prioritize the `Alias` field, if it not empty - // Example for this is /rangerpose2l which becomes /blackrangerposeb in chat - var result = this.DataResolver.GetExcelSheet().GetRow(this.Key); - return result.Alias.Payloads.Count > 0 ? result.Alias : result.Command; - } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index e3415b2dc..c31707ff2 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -3,9 +3,10 @@ using System.IO; using System.Linq; using System.Text; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; -using Serilog; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -14,8 +15,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class ItemPayload : Payload { - private Item? item; - // mainly to allow overriding the name (for things like owo) // TODO: even though this is present in some item links, it may not really have a use at all // For things like owo, changing the text payload is probably correct, whereas changing the @@ -131,27 +130,13 @@ public class ItemPayload : Payload public uint RawItemId => this.rawItemId; /// - /// Gets the underlying Lumina Item represented by this payload. + /// Gets the underlying Lumina data represented by this payload. This is either a Item or EventItem . /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Item? Item - { - get - { - // TODO(goat): This should be revamped/removed on an API level change. - if (this.Kind == ItemKind.EventItem) - { - Log.Warning("Event items cannot be fetched from the ItemPayload"); - return null; - } - - this.item ??= this.DataResolver.GetExcelSheet()!.GetRow(this.ItemId); - return this.item; - } - } + public RowRef Item => + this.Kind == ItemKind.EventItem + ? (RowRef)LuminaUtils.CreateRef(this.ItemId) + : (RowRef)LuminaUtils.CreateRef(this.ItemId); /// /// Gets a value indicating whether or not this item link is for a high-quality version of the item. @@ -183,7 +168,8 @@ public class ItemPayload : Payload /// public override string ToString() { - return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}"; + var name = this.displayName ?? (this.Item.GetValueOrDefault()?.Name ?? this.Item.GetValueOrDefault()?.Name)?.ExtractText(); + return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {name}"; } /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs index 7d975b347..7b672d07a 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,11 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class MapLinkPayload : Payload { - private Map map; - private TerritoryType territoryType; - private string placeNameRegion; - private string placeName; - [JsonProperty] private uint territoryTypeId; @@ -38,8 +36,8 @@ public class MapLinkPayload : Payload // this fudge is necessary basically to ensure we don't shift down a full tenth // because essentially values are truncated instead of rounded, so 3.09999f will become // 3.0f and not 3.1f - this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetX); - this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetY); + this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.Value.SizeFactor, this.Map.Value.OffsetX); + this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.Value.SizeFactor, this.Map.Value.OffsetY); } /// @@ -72,20 +70,14 @@ public class MapLinkPayload : Payload /// /// Gets the Map specified for this map link. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); + public RowRef Map => LuminaUtils.CreateRef(this.mapId); /// /// Gets the TerritoryType specified for this map link. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); + public RowRef TerritoryType => LuminaUtils.CreateRef(this.territoryTypeId); /// /// Gets the internal x-coordinate for this map position. @@ -102,13 +94,13 @@ public class MapLinkPayload : Payload /// /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. /// - public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor, this.Map.OffsetX); + public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.Value.SizeFactor, this.Map.Value.OffsetX); /// /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. /// [JsonIgnore] - public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor, this.Map.OffsetY); + public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.Value.SizeFactor, this.Map.Value.OffsetY); // there is no Z; it's purely in the text payload where applicable @@ -143,18 +135,18 @@ public class MapLinkPayload : Payload /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". /// [JsonIgnore] - public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; + public string PlaceNameRegion => this.TerritoryType.Value.PlaceNameRegion.Value.Name.ExtractText(); /// /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". /// [JsonIgnore] - public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; + public string PlaceName => this.TerritoryType.Value.PlaceName.Value.Name.ExtractText(); /// /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. /// - public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; + public string DataString => $"m:{this.territoryTypeId},{this.mapId},{this.RawX},{this.RawY}"; /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index b9d6bc896..07a13e5a3 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Text; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -12,8 +15,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class PlayerPayload : Payload { - private World world; - [JsonProperty] private uint serverId; @@ -43,11 +44,8 @@ public class PlayerPayload : Payload /// /// Gets the Lumina object representing the player's home server. /// - /// - /// Value is evaluated lazily and cached. - /// [JsonIgnore] - public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); + public RowRef World => LuminaUtils.CreateRef(this.serverId); /// /// Gets or sets the player's displayed name. This does not contain the server name. @@ -72,7 +70,7 @@ public class PlayerPayload : Payload /// The world name will always be present. /// [JsonIgnore] - public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; + public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.ValueNullable?.Name}"; /// public override PayloadType Type => PayloadType.Player; @@ -80,7 +78,7 @@ public class PlayerPayload : Payload /// public override string ToString() { - return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; + return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.ValueNullable?.Name}"; } /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs index e5b9e635e..19d494d8a 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class QuestPayload : Payload { - private Quest quest; - [JsonProperty] private uint questId; @@ -40,16 +41,13 @@ public class QuestPayload : Payload /// /// Gets the underlying Lumina Quest represented by this payload. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); + public RowRef Quest => LuminaUtils.CreateRef(this.questId); /// public override string ToString() { - return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; + return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest.ValueNullable?.Name.ExtractText() ?? "QUEST NOT FOUND"}"; } /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs index 3e10f7659..d102dfab6 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class StatusPayload : Payload { - private Status status; - [JsonProperty] private uint statusId; @@ -40,16 +41,13 @@ public class StatusPayload : Payload /// /// Gets the Lumina Status object represented by this payload. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); + public RowRef Status => LuminaUtils.CreateRef(this.statusId); /// public override string ToString() { - return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; + return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.ValueNullable?.Name}"; } /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index 1cd96a81a..995c20211 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class UIForegroundPayload : Payload { - private UIColor color; - [JsonProperty] private ushort colorKey; @@ -51,11 +52,8 @@ public class UIForegroundPayload : Payload /// /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + public RowRef UIColor => LuminaUtils.CreateRef(this.colorKey); /// /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. @@ -63,15 +61,11 @@ public class UIForegroundPayload : Payload [JsonIgnore] public ushort ColorKey { - get - { - return this.colorKey; - } + get => this.colorKey; set { this.colorKey = value; - this.color = null; this.Dirty = true; } } @@ -80,13 +74,13 @@ public class UIForegroundPayload : Payload /// Gets the Red/Green/Blue/Alpha values for this foreground color, encoded as a typical hex color. /// [JsonIgnore] - public uint RGBA => this.UIColor.UIForeground; + public uint RGBA => this.UIColor.Value.UIForeground; /// /// Gets the ABGR value for this foreground color, as ImGui requires it in PushColor. /// [JsonIgnore] - public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.UIForeground); + public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIForeground); /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index 68ed983ff..3049ccac3 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.IO; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Data; + +using Lumina.Excel; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// public class UIGlowPayload : Payload { - private UIColor color; - [JsonProperty] private ushort colorKey; @@ -57,7 +58,6 @@ public class UIGlowPayload : Payload set { this.colorKey = value; - this.color = null; this.Dirty = true; } } @@ -71,22 +71,19 @@ public class UIGlowPayload : Payload /// Gets the Red/Green/Blue/Alpha values for this glow color, encoded as a typical hex color. /// [JsonIgnore] - public uint RGBA => this.UIColor.UIGlow; + public uint RGBA => this.UIColor.Value.UIGlow; /// /// Gets the ABGR value for this glow color, as ImGui requires it in PushColor. /// [JsonIgnore] - public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.UIGlow); + public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIGlow); /// /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. /// - /// - /// The value is evaluated lazily and cached. - /// [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + public RowRef UIColor => LuminaUtils.CreateRef(this.colorKey); /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index 3b83aed0c..baae181e3 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -7,7 +7,7 @@ using System.Text; using Dalamud.Data; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Utility; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling; @@ -200,12 +200,12 @@ public class SeString case ItemPayload.ItemKind.Normal: case ItemPayload.ItemKind.Collectible: case ItemPayload.ItemKind.Hq: - var item = data.GetExcelSheet()?.GetRow(itemId); - displayName = item?.Name; + var item = data.GetExcelSheet()?.GetRowOrDefault(itemId); + displayName = item?.Name.ExtractText(); rarity = item?.Rarity ?? 1; break; case ItemPayload.ItemKind.EventItem: - displayName = data.GetExcelSheet()?.GetRow(itemId)?.Name; + displayName = data.GetExcelSheet()?.GetRowOrDefault(itemId)?.Name.ExtractText(); break; default: throw new ArgumentOutOfRangeException(nameof(kind), kind, null); @@ -251,7 +251,7 @@ public class SeString /// An SeString containing all the payloads necessary to display an item link in the chat log. public static SeString CreateItemLink(Item item, bool isHq, string? displayNameOverride = null) { - return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name); + return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name.ExtractText()); } /// @@ -360,15 +360,14 @@ public class SeString var mapSheet = data.GetExcelSheet(); var matches = data.GetExcelSheet() - .Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant()) - .ToArray(); + .Where(row => row.Name.ExtractText().Equals(placeName, StringComparison.InvariantCultureIgnoreCase)); foreach (var place in matches) { - var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); - if (map != null && map.TerritoryType.Row != 0) + var map = mapSheet.Cast().FirstOrDefault(row => row!.Value.PlaceName.RowId == place.RowId); + if (map.HasValue && map.Value.TerritoryType.RowId != 0) { - return CreateMapLinkWithInstance(map.TerritoryType.Row, map.RowId, instance, xCoord, yCoord, fudgeFactor); + return CreateMapLinkWithInstance(map.Value.TerritoryType.RowId, map.Value.RowId, instance, xCoord, yCoord, fudgeFactor); } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs index 5a7e87e1b..ddff55923 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.Text; using Lumina.Excel; -using Lumina.Excel.GeneratedSheets2; +using Lumina.Excel.Sheets; using Lumina.Text.Expressions; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 8afc1b473..9d62de193 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -18,7 +18,7 @@ using FFXIVClientStructs.FFXIV.Client.UI; using ImGuiNET; -using Lumina.Excel.GeneratedSheets2; +using Lumina.Excel.Sheets; using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs index 606c49c49..9a3b231e5 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AetherytesWidget.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState.Aetherytes; +using Dalamud.Game.ClientState.Aetherytes; using ImGuiNET; @@ -56,7 +56,7 @@ internal class AetherytesWidget : IDataWindowWidget ImGui.TextUnformatted($"{i}"); ImGui.TableNextColumn(); // Name - ImGui.TextUnformatted($"{info.AetheryteData.GameData?.PlaceName.Value?.Name}"); + ImGui.TextUnformatted($"{info.AetheryteData.ValueNullable?.PlaceName.ValueNullable?.Name}"); ImGui.TableNextColumn(); // ID ImGui.TextUnformatted($"{info.AetheryteId}"); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs index c16cc34e1..f30585406 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/GaugeWidget.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState; +using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.JobGauge; using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.Utility; @@ -39,7 +39,7 @@ internal class GaugeWidget : IDataWindowWidget return; } - var jobID = player.ClassJob.Id; + var jobID = player.ClassJob.RowId; JobGaugeBase? gauge = jobID switch { 19 => jobGauges.Get(), diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs index 963138bec..761dc49a8 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ObjectTableWidget.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Objects; @@ -56,8 +56,8 @@ internal class ObjectTableWidget : IDataWindowWidget { stateString += $"ObjectTableLen: {objectTable.Length}\n"; stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n"; - stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData?.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; - stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData?.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; + stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.ValueNullable?.Name : clientState.LocalPlayer.CurrentWorld.RowId.ToString())}\n"; + stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.ValueNullable?.Name : clientState.LocalPlayer.HomeWorld.RowId.ToString())}\n"; stateString += $"LocalCID: {clientState.LocalContentId:X}\n"; stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n"; stateString += $"TerritoryType: {clientState.TerritoryType}\n\n"; diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index 17aba0c71..f4b76942f 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Numerics; using System.Text; @@ -16,7 +16,8 @@ using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using Lumina.Excel.GeneratedSheets2; +using Lumina.Excel; +using Lumina.Excel.Sheets; using Lumina.Text; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -33,7 +34,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; private ImVectorWrapper testStringBuffer; private string testString = string.Empty; - private Addon[]? addons; + private ExcelSheet addons; private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; @@ -53,7 +54,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget public void Load() { this.style = new() { GetEntity = this.GetEntity }; - this.addons = null; + this.addons = Service.Get().GetExcelSheet(); this.logkind = null; this.testString = string.Empty; this.interactable = this.useEntity = true; @@ -155,9 +156,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (this.logkind is null) { var tt = new SeStringBuilder(); - foreach (var uc in Service.Get().GetExcelSheet()!) + foreach (var uc in Service.Get().GetExcelSheet()) { - var ucsp = uc.Format.AsReadOnly().AsSpan(); + var ucsp = uc.Format.AsSpan(); if (ucsp.IsEmpty) continue; @@ -184,7 +185,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.CollapsingHeader("Addon Table")) { - this.addons ??= Service.Get().GetExcelSheet()!.ToArray(); if (ImGui.BeginTable("Addon Sheet", 3)) { ImGui.TableSetupScrollFreeze(0, 1); @@ -197,25 +197,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget ImGui.TableHeadersRow(); var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - clipper.Begin(this.addons.Length); + clipper.Begin(this.addons.Count); while (clipper.Step()) { for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { + var row = this.addons.GetRowAt(i); + ImGui.TableNextRow(); ImGui.PushID(i); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted($"{this.addons[i].RowId}"); + ImGui.TextUnformatted($"{row.RowId}"); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGuiHelpers.SeStringWrapped(this.addons[i].Text.AsReadOnly(), this.style); + ImGuiHelpers.SeStringWrapped(row.Text, this.style); ImGui.TableNextColumn(); if (ImGui.Button("Print to Chat")) - Service.Get().Print(this.addons[i].Text.ToDalamudString()); + Service.Get().Print(row.Text.ToDalamudString()); ImGui.PopID(); } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs index 575bce23c..6b57f7a6a 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Linq; using System.Numerics; using System.Text; @@ -12,7 +12,8 @@ using Dalamud.Storage.Assets; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel; +using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -21,7 +22,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class UiColorWidget : IDataWindowWidget { - private UIColor[]? colors; + private ExcelSheet colors; /// public string[]? CommandShortcuts { get; init; } = ["uicolor"]; @@ -36,15 +37,12 @@ internal class UiColorWidget : IDataWindowWidget public void Load() { this.Ready = true; - this.colors = null; + this.colors = Service.Get().GetExcelSheet(); } /// public unsafe void Draw() { - this.colors ??= Service.Get().GetExcelSheet()?.ToArray(); - if (this.colors is null) return; - Service.Get().CompileAndDrawWrapped( "· Color notation is #" + "RR" + @@ -73,12 +71,24 @@ internal class UiColorWidget : IDataWindowWidget ImGui.TableHeadersRow(); var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - clipper.Begin(this.colors.Length, ImGui.GetFrameHeightWithSpacing()); + clipper.Begin(this.colors.Count, ImGui.GetFrameHeightWithSpacing()); while (clipper.Step()) { for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - var id = this.colors[i].RowId; + var row = this.colors.GetRowAt(i); + UIColor? adjacentRow = null; + if (i + 1 < this.colors.Count) + { + var adjRow = this.colors.GetRowAt(i + 1); + if (adjRow.RowId == row.RowId + 1) + { + adjacentRow = adjRow; + } + } + + var id = row.RowId; + ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -88,33 +98,33 @@ internal class UiColorWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col1"); - if (this.DrawColorColumn(this.colors[i].UIForeground) && - i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) - DrawEdgePreview(id, this.colors[i].UIForeground, this.colors[i + 1].UIForeground); + if (this.DrawColorColumn(row.UIForeground) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.UIForeground, adjacentRow.Value.UIForeground); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col2"); - if (this.DrawColorColumn(this.colors[i].UIGlow) && - i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) - DrawEdgePreview(id, this.colors[i].UIGlow, this.colors[i + 1].UIGlow); + if (this.DrawColorColumn(row.UIGlow) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.UIGlow, adjacentRow.Value.UIGlow); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col3"); - if (this.DrawColorColumn(this.colors[i].Unknown2) && - i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) - DrawEdgePreview(id, this.colors[i].Unknown2, this.colors[i + 1].Unknown2); + if (this.DrawColorColumn(row.Unknown0) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.Unknown0, adjacentRow.Value.Unknown0); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.PushID($"row{id}_col4"); - if (this.DrawColorColumn(this.colors[i].Unknown3) && - i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1) - DrawEdgePreview(id, this.colors[i].Unknown3, this.colors[i + 1].Unknown3); + if (this.DrawColorColumn(row.Unknown1) && + adjacentRow.HasValue) + DrawEdgePreview(id, row.Unknown1, adjacentRow.Value.Unknown1); ImGui.PopID(); } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs index 3bc91088d..5bcd1845c 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs @@ -7,10 +7,9 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.Gui.ContextMenu; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Utility; using ImGuiNET; using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Serilog; namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; @@ -45,9 +44,9 @@ internal class ContextMenuAgingStep : IAgingStep { var contextMenu = Service.Get(); var dataMgr = Service.Get(); - this.itemSheet = dataMgr.GetExcelSheet()!; - this.materiaSheet = dataMgr.GetExcelSheet()!; - this.stainSheet = dataMgr.GetExcelSheet()!; + this.itemSheet = dataMgr.GetExcelSheet(); + this.materiaSheet = dataMgr.GetExcelSheet(); + this.stainSheet = dataMgr.GetExcelSheet(); ImGui.Text(this.currentSubStep.ToString()); @@ -83,7 +82,7 @@ internal class ContextMenuAgingStep : IAgingStep case SubStep.TestDefault: if (this.targetCharacter is { } character) { - ImGui.Text($"Did you click \"{character.Name}\" ({character.ClassJob.GameData!.Abbreviation.ToDalamudString()})?"); + ImGui.Text($"Did you click \"{character.Name}\" ({character.ClassJob.Value.Abbreviation.ExtractText()})?"); if (ImGui.Button("Yes")) this.currentSubStep++; @@ -146,7 +145,7 @@ internal class ContextMenuAgingStep : IAgingStep var targetItem = (a.Target as MenuTargetInventory)!.TargetItem; if (targetItem is { } item) { - name = (this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty); + name = (this.itemSheet.GetRowOrDefault(item.ItemId)?.Name.ExtractText() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty); count = item.Quantity; } else @@ -194,7 +193,7 @@ internal class ContextMenuAgingStep : IAgingStep { var b = new StringBuilder(); b.AppendLine($"Target: {targetDefault.TargetName}"); - b.AppendLine($"Home World: {targetDefault.TargetHomeWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({targetDefault.TargetHomeWorld.Id})"); + b.AppendLine($"Home World: {targetDefault.TargetHomeWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({targetDefault.TargetHomeWorld.RowId})"); b.AppendLine($"Content Id: 0x{targetDefault.TargetContentId:X8}"); b.AppendLine($"Object Id: 0x{targetDefault.TargetObjectId:X8}"); Log.Verbose(b.ToString()); @@ -209,20 +208,20 @@ internal class ContextMenuAgingStep : IAgingStep b.AppendLine($"Content Id: 0x{character.ContentId:X8}"); b.AppendLine($"FC Tag: {character.FCTag}"); - b.AppendLine($"Job: {character.ClassJob.GameData?.Abbreviation.ToDalamudString() ?? "Unknown"} ({character.ClassJob.Id})"); - b.AppendLine($"Statuses: {string.Join(", ", character.Statuses.Select(s => s.GameData?.Name.ToDalamudString() ?? s.Id.ToString()))}"); - b.AppendLine($"Home World: {character.HomeWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.HomeWorld.Id})"); - b.AppendLine($"Current World: {character.CurrentWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.CurrentWorld.Id})"); + b.AppendLine($"Job: {character.ClassJob.ValueNullable?.Abbreviation.ExtractText() ?? "Unknown"} ({character.ClassJob.RowId})"); + b.AppendLine($"Statuses: {string.Join(", ", character.Statuses.Select(s => s.ValueNullable?.Name.ExtractText() ?? s.RowId.ToString()))}"); + b.AppendLine($"Home World: {character.HomeWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.HomeWorld.RowId})"); + b.AppendLine($"Current World: {character.CurrentWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.CurrentWorld.RowId})"); b.AppendLine($"Is From Other Server: {character.IsFromOtherServer}"); b.Append("Location: "); - if (character.Location.GameData is { } location) - b.Append($"{location.PlaceNameRegion.Value?.Name.ToDalamudString() ?? "Unknown"}/{location.PlaceNameZone.Value?.Name.ToDalamudString() ?? "Unknown"}/{location.PlaceName.Value?.Name.ToDalamudString() ?? "Unknown"}"); + if (character.Location.ValueNullable is { } location) + b.Append($"{location.PlaceNameRegion.ValueNullable?.Name.ExtractText() ?? "Unknown"}/{location.PlaceNameZone.ValueNullable?.Name.ExtractText() ?? "Unknown"}/{location.PlaceName.ValueNullable?.Name.ExtractText() ?? "Unknown"}"); else b.Append("Unknown"); - b.AppendLine($" ({character.Location.Id})"); + b.AppendLine($" ({character.Location.RowId})"); - b.AppendLine($"Grand Company: {character.GrandCompany.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.GrandCompany.Id})"); + b.AppendLine($"Grand Company: {character.GrandCompany.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.GrandCompany.RowId})"); b.AppendLine($"Client Language: {character.ClientLanguage}"); b.AppendLine($"Languages: {string.Join(", ", character.Languages)}"); b.AppendLine($"Gender: {character.Gender}"); @@ -241,7 +240,7 @@ internal class ContextMenuAgingStep : IAgingStep if (targetInventory.TargetItem is { } item) { var b = new StringBuilder(); - b.AppendLine($"Item: {(item.IsEmpty ? "None" : this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString())} ({item.ItemId})"); + b.AppendLine($"Item: {(item.IsEmpty ? "None" : this.itemSheet.GetRowOrDefault(item.ItemId)?.Name.ExtractText())} ({item.ItemId})"); b.AppendLine($"Container: {item.ContainerType}"); b.AppendLine($"Slot: {item.InventorySlot}"); b.AppendLine($"Quantity: {item.Quantity}"); @@ -259,7 +258,7 @@ internal class ContextMenuAgingStep : IAgingStep Log.Verbose($"{materiaId} {materiaGrade}"); if (this.materiaSheet.GetRow(materiaId) is { } materia && materia.Item[materiaGrade].Value is { } materiaItem) - materias.Add($"{materiaItem.Name.ToDalamudString()}"); + materias.Add($"{materiaItem.Name.ExtractText()}"); else materias.Add($"Unknown (Id: {materiaId}, Grade: {materiaGrade})"); } @@ -275,7 +274,7 @@ internal class ContextMenuAgingStep : IAgingStep var stainId = item.Stains[i]; if (stainId != 0) { - var stainName = this.stainSheet.GetRow(stainId)?.Name.ToDalamudString().ToString() ?? "Unknown"; + var stainName = this.stainSheet.GetRowOrDefault(stainId)?.Name.ExtractText() ?? "Unknown"; b.AppendLine($" Stain {i + 1}: {stainName} ({stainId})"); } else @@ -285,13 +284,13 @@ internal class ContextMenuAgingStep : IAgingStep } if (item.Stains[0] != 0) - b.AppendLine($"{this.stainSheet.GetRow(item.Stains[0])?.Name.ToDalamudString() ?? "Unknown"} ({item.Stains[0]})"); + b.AppendLine($"{this.stainSheet.GetRowOrDefault(item.Stains[0])?.Name.ExtractText() ?? "Unknown"} ({item.Stains[0]})"); else b.AppendLine("None"); b.Append("Glamoured Item: "); if (item.GlamourId != 0) - b.AppendLine($"{this.itemSheet.GetRow(item.GlamourId)?.Name.ToDalamudString() ?? "Unknown"} ({item.GlamourId})"); + b.AppendLine($"{this.itemSheet.GetRowOrDefault(item.GlamourId)?.Name.ExtractText() ?? "Unknown"} ({item.GlamourId})"); else b.AppendLine("None"); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs index a07b21e54..0f411b8f1 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - using Dalamud.Data; using Dalamud.Utility; using Lumina.Excel; @@ -11,31 +8,46 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; /// Test setup for Lumina. /// /// ExcelRow to run test on. -internal class LuminaAgingStep : IAgingStep - where T : ExcelRow +/// Whether or not the sheet is large. If it is large, the self test will iterate through the full sheet in one frame and benchmark the time taken. +internal class LuminaAgingStep(bool isLargeSheet) : IAgingStep + where T : struct, IExcelRow { private int step = 0; - private List rows; + private ExcelSheet rows; /// - public string Name => "Test Lumina"; + public string Name => $"Test Lumina ({typeof(T).Name})"; /// public SelfTestStepResult RunStep() { - var dataManager = Service.Get(); + this.rows ??= Service.Get().GetExcelSheet(); - this.rows ??= dataManager.GetExcelSheet().ToList(); + if (isLargeSheet) + { + var i = 0; + T currentRow = default; + foreach (var row in this.rows) + { + i++; + currentRow = row; + } - Util.ShowObject(this.rows[this.step]); + Util.ShowObject(currentRow); + return SelfTestStepResult.Pass; + } + else + { + Util.ShowObject(this.rows.GetRowAt(this.step)); - this.step++; - return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + this.step++; + return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } } /// public void CleanUp() { - // ignored + this.step = 0; } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 51c9b35f6..3b3670228 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -9,7 +9,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Logging.Internal; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.SelfTest; @@ -39,7 +39,11 @@ internal class SelfTestWindow : Window new GamepadStateAgingStep(), new ChatAgingStep(), new HoverAgingStep(), - new LuminaAgingStep(), + new LuminaAgingStep(true), + new LuminaAgingStep(true), + new LuminaAgingStep(true), + new LuminaAgingStep(true), + new LuminaAgingStep(false), new AddonLifecycleAgingStep(), new PartyFinderAgingStep(), new HandledExceptionAgingStep(), diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 34641fe4b..59f6b23c1 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -599,7 +599,7 @@ internal class LocalPlugin : IAsyncDisposable // Changes to Lumina should be upstreamed if feasible, and if there is a desire to re-add unpinned Lumina we // will need to put this behind some kind of feature flag somewhere. config.SharedAssemblies.Add((typeof(Lumina.GameData).Assembly.GetName(), true)); - config.SharedAssemblies.Add((typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName(), true)); + config.SharedAssemblies.Add((typeof(Lumina.Excel.Sheets.Addon).Assembly.GetName(), true)); } private void EnsureLoader() diff --git a/Dalamud/Plugin/Services/IClientState.cs b/Dalamud/Plugin/Services/IClientState.cs index 6b0555f23..f7c462d09 100644 --- a/Dalamud/Plugin/Services/IClientState.cs +++ b/Dalamud/Plugin/Services/IClientState.cs @@ -61,7 +61,7 @@ public interface IClientState /// /// Event that gets fired when a duty is ready. /// - public event Action CfPop; + public event Action CfPop; /// /// Gets the language of the client. diff --git a/Dalamud/Plugin/Services/IDataManager.cs b/Dalamud/Plugin/Services/IDataManager.cs index cead130aa..65c51a9fb 100644 --- a/Dalamud/Plugin/Services/IDataManager.cs +++ b/Dalamud/Plugin/Services/IDataManager.cs @@ -5,7 +5,9 @@ using Dalamud.Game; using Lumina; using Lumina.Data; +using Lumina.Data.Structs.Excel; using Lumina.Excel; +using Lumina.Excel.Exceptions; namespace Dalamud.Plugin.Services; @@ -37,17 +39,38 @@ public interface IDataManager /// /// Get an with the given Excel sheet row type. /// + /// Language of the sheet to get. Leave or empty to use the default language. + /// Explicitly provide the name of the sheet to get. Leave to use 's sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets. /// The excel sheet type to get. /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet() where T : ExcelRow; + /// + /// If the sheet type you want has subrows, use instead. + /// + /// Sheet name was not specified neither via 's nor . + /// does not have a valid . + /// Sheet does not exist. + /// Sheet had a mismatched column hash. + /// Sheet does not support nor . + /// Sheet was not a . + public ExcelSheet GetExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow; /// - /// Get an with the given Excel sheet row type with a specified language. + /// Get a with the given Excel sheet row type. /// - /// Language of the sheet to get. + /// Language of the sheet to get. Leave or empty to use the default language. + /// Explicitly provide the name of the sheet to get. Leave to use 's sheet name. Explicit names are necessary for quest/dungeon/cutscene sheets. /// The excel sheet type to get. - /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow; + /// The , giving access to game rows. + /// + /// If the sheet type you want has only rows, use instead. + /// + /// Sheet name was not specified neither via 's nor . + /// does not have a valid . + /// Sheet does not exist. + /// Sheet had a mismatched column hash. + /// Sheet does not support nor . + /// Sheet was not a . + public SubrowExcelSheet GetSubrowExcelSheet(ClientLanguage? language = null, string? name = null) where T : struct, IExcelSubrow; /// /// Get a with the given path. diff --git a/Dalamud/Utility/MapUtil.cs b/Dalamud/Utility/MapUtil.cs index 221f9cb55..3e72add9c 100644 --- a/Dalamud/Utility/MapUtil.cs +++ b/Dalamud/Utility/MapUtil.cs @@ -1,11 +1,11 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Data; using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; namespace Dalamud.Utility; @@ -149,9 +149,7 @@ public static class MapUtil if (agentMap == null || agentMap->CurrentMapId == 0) throw new InvalidOperationException("Could not determine active map - data may not be loaded yet?"); - var territoryTransient = Service.Get() - .GetExcelSheet()! - .GetRow(agentMap->CurrentTerritoryId); + var territoryTransient = LuminaUtils.CreateRef(agentMap->CurrentTerritoryId); return WorldToMap( go.Position, @@ -161,7 +159,7 @@ public static class MapUtil */ -agentMap->CurrentOffsetX, -agentMap->CurrentOffsetY, - territoryTransient?.OffsetZ ?? 0, + territoryTransient.ValueNullable?.OffsetZ ?? 0, (uint)agentMap->CurrentMapSizeFactor, correctZOffset); } diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index b89f815bc..34ebb1450 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,4 +1,6 @@ -using Lumina.Text.Parse; +using Lumina.Text.Parse; + +using Lumina.Text.ReadOnly; using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; using DSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; @@ -20,6 +22,22 @@ public static class SeStringExtensions /// The re-parsed Dalamud SeString. public static DSeString ToDalamudString(this LSeString originalString) => DSeString.Parse(originalString.RawData); + /// + /// Convert a Lumina ReadOnlySeString into a Dalamud SeString. + /// This conversion re-parses the string. + /// + /// The original Lumina ReadOnlySeString. + /// The re-parsed Dalamud SeString. + public static DSeString ToDalamudString(this ReadOnlySeString originalString) => DSeString.Parse(originalString.Data.Span); + + /// + /// Convert a Lumina ReadOnlySeStringSpan into a Dalamud SeString. + /// This conversion re-parses the string. + /// + /// The original Lumina ReadOnlySeStringSpan. + /// The re-parsed Dalamud SeString. + public static DSeString ToDalamudString(this ReadOnlySeStringSpan originalString) => DSeString.Parse(originalString.Data); + /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index d8e05716e..e37afb765 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -21,7 +21,7 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Support; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Serilog; using TerraFX.Interop.Windows; using Windows.Win32.Storage.FileSystem; @@ -813,7 +813,7 @@ public static class Util var names = data.GetExcelSheet(ClientLanguage.English)!; var rng = new Random(); - return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString; + return names.GetRowAt(rng.Next(0, names.Count - 1)).Singular.ExtractText(); } /// @@ -891,13 +891,13 @@ public static class Util if (actor is ICharacter chara) { actorString += - $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.GameData?.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.ValueNullable?.Name : chara.ClassJob.RowId.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; } if (actor is IPlayerCharacter pc) { actorString += - $" HomeWorld: {(resolveGameData ? pc.HomeWorld.GameData?.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.GameData?.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + $" HomeWorld: {(resolveGameData ? pc.HomeWorld.ValueNullable?.Name : pc.HomeWorld.RowId.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.ValueNullable?.Name : pc.CurrentWorld.RowId.ToString())} FC: {pc.CompanyTag}\n"; } ImGui.TextUnformatted(actorString); @@ -925,13 +925,13 @@ public static class Util if (actor is Character chara) { actorString += - $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.GameData?.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + $" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.ValueNullable?.Name : chara.ClassJob.RowId.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; } if (actor is PlayerCharacter pc) { actorString += - $" HomeWorld: {(resolveGameData ? pc.HomeWorld.GameData?.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.GameData?.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + $" HomeWorld: {(resolveGameData ? pc.HomeWorld.ValueNullable?.Name : pc.HomeWorld.RowId.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.ValueNullable?.Name : pc.CurrentWorld.RowId.ToString())} FC: {pc.CompanyTag}\n"; } ImGui.TextUnformatted(actorString); diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 81fb80190..7c5f04e34 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 81fb801905b7fe8e02aa40dcf9942e0f707dcc21 +Subproject commit 7c5f04e346067f7a316ad9072fb8260122ba80f0 From a8244a9114949a1b5595d4aaffab0f4f0c34e60a Mon Sep 17 00:00:00 2001 From: Blooym <19539165+Blooym@users.noreply.github.com> Date: Mon, 21 Oct 2024 05:38:15 +0200 Subject: [PATCH 075/375] chore: fix a very small typo (#2058) --- .../Internal/Windows/Settings/Tabs/SettingsTabAbout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs index d32b37cdf..6a231eeaf 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs @@ -140,7 +140,7 @@ Dale Arcane Disgea Risu Tom -Blyoom +Blooym Valk From 8fc93c4c525cf377ed16ccb29c72047cb98bf160 Mon Sep 17 00:00:00 2001 From: ItsBexy Date: Thu, 24 Oct 2024 16:37:04 -0600 Subject: [PATCH 076/375] Switch to `TextUnformatted()` Plus other noodling with GUI business. I considered adding some of the helper features I use here to `ImGuiComponents`, but it would have quickly started expanding into a separate project of its own, and I didn't really want to entangle that with this PR right now. --- .../Internal/UiDebug2/Browsing/AddonTree.cs | 2 +- .../Internal/UiDebug2/Browsing/Events.cs | 11 +- .../UiDebug2/Browsing/NodeTree.Component.cs | 14 +- .../UiDebug2/Browsing/NodeTree.Editor.cs | 4 +- .../UiDebug2/Browsing/NodeTree.Res.cs | 4 +- .../UiDebug2/Browsing/NodeTree.Text.cs | 8 +- .../Browsing/TimelineTree.KeyGroupColumn.cs | 2 +- .../UiDebug2/Browsing/TimelineTree.cs | 2 +- .../Internal/UiDebug2/ElementSelector.cs | 130 +++++++++--------- .../Internal/UiDebug2/UiDebug2.Sidebar.cs | 2 - .../Internal/UiDebug2/Utility/Gui.cs | 40 ++++-- 11 files changed, 116 insertions(+), 103 deletions(-) diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs index 6d6377530..2823a2058 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs @@ -114,7 +114,7 @@ public unsafe partial class AddonTree : IDisposable var isVisible = addon->IsVisible; - ImGui.Text($"{this.AddonName}"); + ImGui.TextUnformatted($"{this.AddonName}"); ImGui.SameLine(); ImGui.SameLine(); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index a422e4db6..6869763c8 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -27,6 +27,7 @@ public static class Events } using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); + if (tree) { using (ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg)) @@ -45,15 +46,15 @@ public static class Events while (evt != null) { ImGui.TableNextColumn(); - ImGui.Text($"{i++}"); + ImGui.TextUnformatted($"{i++}"); ImGui.TableNextColumn(); - ImGui.Text($"{evt->Type}"); + ImGui.TextUnformatted($"{evt->Type}"); ImGui.TableNextColumn(); - ImGui.Text($"{evt->Param}"); + ImGui.TextUnformatted($"{evt->Param}"); ImGui.TableNextColumn(); - ImGui.Text($"{evt->Flags}"); + ImGui.TextUnformatted($"{evt->Flags}"); ImGui.TableNextColumn(); - ImGui.Text($"{evt->Unk29}"); + ImGui.TextUnformatted($"{evt->Unk29}"); ImGui.TableNextColumn(); Gui.ClickToCopyText($"{(nint)evt->Target:X}"); ImGui.TableNextColumn(); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs index 026933631..d9fcf52cc 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs @@ -81,19 +81,19 @@ internal unsafe class ComponentNodeTree : ResNodeTree { case TextInput: var textInputComponent = (AtkComponentTextInput*)this.Component; - ImGui.Text( + ImGui.TextUnformatted( $"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); - ImGui.Text( + ImGui.TextUnformatted( $"InputBase Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); - ImGui.Text( + ImGui.TextUnformatted( $"Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText01.StringPtr))}"); - ImGui.Text( + ImGui.TextUnformatted( $"Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText02.StringPtr))}"); - ImGui.Text( + ImGui.TextUnformatted( $"Text3: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText03.StringPtr))}"); - ImGui.Text( + ImGui.TextUnformatted( $"Text4: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText04.StringPtr))}"); - ImGui.Text( + ImGui.TextUnformatted( $"Text5: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText05.StringPtr))}"); break; case List: diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index ef30ea151..6cb178bd7 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -370,8 +370,8 @@ internal unsafe partial class TextNodeTree var hAlign = (int)alignment % 3; var vAlign = ((int)alignment - hAlign) / 3; - var hAlignInput = IconSelectInput($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]); - var vAlignInput = IconSelectInput($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]); + var hAlignInput = IconButtonSelect($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]); + var vAlignInput = IconButtonSelect($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]); if (hAlignInput || vAlignInput) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index 366e3d36d..a333940c1 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -90,7 +90,7 @@ internal unsafe partial class ResNodeTree : IDisposable NodeType.ClippingMask => new ClippingMaskNodeTree(node, addonTree), NodeType.Counter => new CounterNodeTree(node, addonTree), NodeType.Collision => new CollisionNodeTree(node, addonTree), - _ => new ResNodeTree(node, addonTree) + _ => new ResNodeTree(node, addonTree), }; /// @@ -163,7 +163,7 @@ internal unsafe partial class ResNodeTree : IDisposable /// internal void WriteTreeHeading() { - ImGui.Text(this.GetHeaderText()); + ImGui.TextUnformatted(this.GetHeaderText()); this.PrintFieldNames(); } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index 257ce5aa3..d9b4e7986 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -57,7 +57,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree Color = this.TxtNode->TextColor.RGBA, EdgeColor = this.TxtNode->EdgeColor.RGBA, ForceEdgeColor = true, - EdgeStrength = 1f + EdgeStrength = 1f, }; #pragma warning disable SeStringRenderer @@ -66,7 +66,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree } catch { - ImGui.Text(Marshal.PtrToStringAnsi(new(this.NodeText.StringPtr)) ?? ""); + ImGui.TextUnformatted(Marshal.PtrToStringAnsi(new(this.NodeText.StringPtr)) ?? string.Empty); } PrintFieldValuePairs( @@ -98,7 +98,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree for (var i = 0; i < seString.Payloads.Count; i++) { var payload = seString.Payloads[i]; - ImGui.Text($"[{i}]"); + ImGui.TextUnformatted($"[{i}]"); ImGui.SameLine(); switch (payload.Type) { @@ -110,7 +110,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree default: { - ImGui.Text(payload.ToString()); + ImGui.TextUnformatted(payload.ToString()); break; } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs index e638cca40..2ba416b4f 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.KeyGroupColumn.cs @@ -66,7 +66,7 @@ public readonly partial struct TimelineTree /// The default print function, if none is specified. /// /// The value to print. - public static void PlainTextCell(T value) => ImGui.Text($"{value}"); + public static void PlainTextCell(T value) => ImGui.TextUnformatted($"{value}"); /// /// Adds a value to this column. diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs index 111b92597..8e21f4030 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -138,7 +138,7 @@ public readonly unsafe partial struct TimelineTree return; } - var rotColumn = new KeyGroupColumn("Rotation", static r => ImGui.Text($"{r * (180d / Math.PI):F1}°")); + var rotColumn = new KeyGroupColumn("Rotation", static r => ImGui.TextUnformatted($"{r * (180d / Math.PI):F1}°")); for (var f = 0; f < keyGroup.KeyFrameCount; f++) { diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index 2c6a16893..7f603fdac 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -81,26 +81,26 @@ internal unsafe class ElementSelector : IDisposable { using (ImRaii.Child("###sidebar_elementSelector", new(250, 0), true)) { - using var f = ImRaii.PushFont(IconFont); - using var col = ImRaii.PushColor(Text, new Vector4(1, 1, 0.2f, 1), this.Active); - - if (ImGui.Button($"{(char)ObjectUngroup}")) + using (ImRaii.PushFont(IconFont)) { - this.Active = !this.Active; - } - - if (Countdown > 0) - { - Countdown -= 1; - if (Countdown < 0) + using (ImRaii.PushColor(Text, new Vector4(1, 1, 0.2f, 1), this.Active)) { - Countdown = 0; + if (ImGui.Button($"{(char)ObjectUngroup}")) + { + this.Active = !this.Active; + } + + if (Countdown > 0) + { + Countdown -= 1; + if (Countdown < 0) + { + Countdown = 0; + } + } } } - col.Pop(); - f.Pop(); - if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Element Selector"); @@ -151,67 +151,69 @@ internal unsafe class ElementSelector : IDisposable var mousePos = ImGui.GetMousePos() - MainViewport.Pos; var addonResults = GetAtkUnitBaseAtPosition(mousePos); - using var col = ImRaii.PushColor(WindowBg, new Vector4(0.5f)); - using (ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse)) + using (ImRaii.PushColor(WindowBg, new Vector4(0.5f))) { - using (ImRaii.Group()) + using (ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse)) { - Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); - ImGui.Spacing(); - ImGui.Text("RESULTS:\n"); - - var i = 0; - foreach (var a in addonResults) + using (ImRaii.Group()) { - var name = a.Addon->NameString; - ImGui.Text($"[Addon] {name}"); - ImGui.Indent(15); - foreach (var n in a.Nodes) + Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); + ImGui.Spacing(); + ImGui.Text("RESULTS:\n"); + + var i = 0; + foreach (var a in addonResults) { - var nSelected = i++ == this.index; - - PrintNodeHeaderOnly(n.Node, nSelected, a.Addon); - - if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left)) + var name = a.Addon->NameString; + ImGui.TextUnformatted($"[Addon] {name}"); + ImGui.Indent(15); + foreach (var n in a.Nodes) { - this.Active = false; + var nSelected = i++ == this.index; - this.uiDebug2.SelectedAddonName = a.Addon->NameString; + PrintNodeHeaderOnly(n.Node, nSelected, a.Addon); - var ptrList = new List { (nint)n.Node }; - - var nextNode = n.Node->ParentNode; - while (nextNode != null) + if (nSelected && ImGui.IsMouseClicked(ImGuiMouseButton.Left)) { - ptrList.Add((nint)nextNode); - nextNode = nextNode->ParentNode; + this.Active = false; + + this.uiDebug2.SelectedAddonName = a.Addon->NameString; + + var ptrList = new List { (nint)n.Node }; + + var nextNode = n.Node->ParentNode; + while (nextNode != null) + { + ptrList.Add((nint)nextNode); + nextNode = nextNode->ParentNode; + } + + SearchResults = [.. ptrList]; + Countdown = 100; + Scrolled = false; } - SearchResults = [.. ptrList]; - Countdown = 100; - Scrolled = false; + if (nSelected) + { + n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1)); + } } - if (nSelected) + ImGui.Indent(-15); + } + + if (i != 0) + { + this.index -= (int)ImGui.GetIO().MouseWheel; + while (this.index < 0) { - n.NodeBounds.DrawFilled(new(1, 1, 0.2f, 1)); + this.index += i; } - } - ImGui.Indent(-15); - } - - if (i != 0) - { - this.index -= (int)ImGui.GetIO().MouseWheel; - while (this.index < 0) - { - this.index += i; - } - - while (this.index >= i) - { - this.index -= i; + while (this.index >= i) + { + this.index -= i; + } } } } @@ -393,8 +395,10 @@ internal unsafe class ElementSelector : IDisposable return; } - using var col = ImRaii.PushColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1)); - ResNodeTree.GetOrCreate(node, tree).WriteTreeHeading(); + using (ImRaii.PushColor(Text, selected ? new Vector4(1, 1, 0.2f, 1) : new(0.6f, 0.6f, 0.6f, 1))) + { + ResNodeTree.GetOrCreate(node, tree).WriteTreeHeading(); + } } private void PerformSearch(nint address) diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs index 4c6e08409..4253f13bc 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -98,8 +98,6 @@ internal unsafe partial class UiDebug2 { this.DrawUnitListOption(unitListBaseAddr, unit); } - - ; } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index c203090ec..954e5cb72 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Numerics; using Dalamud.Interface.Components; -using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.Graphics; @@ -27,20 +26,19 @@ internal static class Gui /// A list of all options. /// A list of icons corresponding to the options. /// true if a button is clicked. - internal static unsafe bool IconSelectInput(string label, ref T val, List options, List icons) + internal static unsafe bool IconButtonSelect(string label, ref T val, List options, List icons) { var ret = false; + for (var i = 0; i < options.Count; i++) { - var option = options[i]; - var icon = icons[i]; - if (i > 0) { - ImGui.SameLine(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() - ((ImGui.GetFontSize() / -6f) + 7f)); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); } + var option = options[i]; + var icon = icons.Count > i ? icons[i] : FontAwesomeIcon.Question; var color = *ImGui.GetStyleColorVec4(val is not null && val.Equals(option) ? ButtonActive : Button); if (ImGuiComponents.IconButton($"{label}{option}{i}", icon, color)) @@ -61,7 +59,7 @@ internal static class Gui /// Whether to enable click-to-copy. internal static void PrintFieldValuePair(string fieldName, string value, bool copy = true) { - ImGui.Text($"{fieldName}:"); + ImGui.TextUnformatted($"{fieldName}:"); ImGui.SameLine(); if (copy) { @@ -119,25 +117,37 @@ internal static class Gui 0.5f) * vector4.W; } - /// + /// + /// Print out text that can be copied when clicked. + /// + /// The text to show. + /// The text to copy when clicked. internal static void ClickToCopyText(string text, string? textCopy = null) { using (ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1))) { - ImGuiHelpers.ClickToCopyText(text, textCopy); + textCopy ??= text; + ImGui.TextUnformatted($"{text}"); } if (ImGui.IsItemHovered()) { using (ImRaii.Tooltip()) { - using var f = ImRaii.PushFont(UiBuilder.IconFont); - ImGui.Text(FontAwesomeIcon.Copy.ToIconString()); - f.Pop(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); + } + ImGui.SameLine(); - ImGui.Text($"{textCopy ?? text}"); + ImGui.TextUnformatted($"{textCopy}"); } } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText($"{textCopy}"); + } } /// @@ -161,7 +171,7 @@ internal static class Gui using (ImRaii.Tooltip()) { - ImGui.Text(tooltips[index]); + ImGui.TextUnformatted(tooltips[index]); } return true; From 82fe5a387be1023e46dde4a503be4e2acd0fa279 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 1 Nov 2024 08:54:42 -0700 Subject: [PATCH 077/375] fix: Register DataManager as a service --- Dalamud/Interface/Internal/DalamudInterface.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 793dfddd9..cdebdfb2e 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using CheapLoc; using Dalamud.Configuration.Internal; using Dalamud.Console; +using Dalamud.Data; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Keys; @@ -56,6 +57,7 @@ internal class DalamudInterface : IInternalDisposableService private readonly Dalamud dalamud; private readonly DalamudConfiguration configuration; private readonly InterfaceManager interfaceManager; + private readonly DataManager dataManager; private readonly ChangelogWindow changelogWindow; private readonly ColorDemoWindow colorDemoWindow; @@ -97,6 +99,7 @@ internal class DalamudInterface : IInternalDisposableService DalamudConfiguration configuration, FontAtlasFactory fontAtlasFactory, InterfaceManager interfaceManager, + DataManager dataManager, PluginImageCache pluginImageCache, DalamudAssetManager dalamudAssetManager, Game.Framework framework, @@ -108,6 +111,7 @@ internal class DalamudInterface : IInternalDisposableService this.dalamud = dalamud; this.configuration = configuration; this.interfaceManager = interfaceManager; + this.dataManager = dataManager; this.WindowSystem = new WindowSystem("DalamudCore"); From 5fd43bf1027f3381177c68c825c8e94bbf34fba8 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 1 Nov 2024 08:55:53 -0700 Subject: [PATCH 078/375] deps: Bump Lumina to 5.2.0 --- Dalamud/Dalamud.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index a577f130b..f93a81b27 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all @@ -136,16 +136,16 @@ - + - + - + - + From cffc123abd67f1610f629764d94a6ffe19a49516 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 1 Nov 2024 09:22:24 -0700 Subject: [PATCH 079/375] deps: bump coreplugin lumina --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 6cc92cbb5..6edea55bf 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all From b570564258fdba95d3712b4163b67eda743d66a1 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 1 Nov 2024 09:54:55 -0700 Subject: [PATCH 080/375] fix: re-add invalid attributes per msbuild???? --- Dalamud/Dalamud.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index f93a81b27..79d7a64f8 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -136,16 +136,16 @@ - + - + - + - + From f39abd9dd22d7de950b1c7aea43836b4247b20cb Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 4 Nov 2024 14:39:26 +0100 Subject: [PATCH 081/375] fix: don't refer to CS assembly by name --- .../Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs index 49f976a0b..51b572985 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -16,7 +16,7 @@ public unsafe partial class AddonTree { private static readonly Dictionary AddonTypeDict = []; - private static readonly Assembly? ClientStructs = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(static a => a.GetName().Name == "FFXIVClientStructs"); + private static readonly Assembly? ClientStructsAssembly = typeof(Addon).Assembly; /// /// Gets or sets a collection of names for field offsets that have been documented in FFXIVClientStructs. @@ -30,11 +30,11 @@ public unsafe partial class AddonTree return null; } - if (AddonTypeDict.TryAdd(this.AddonName, null) && ClientStructs != null) + if (AddonTypeDict.TryAdd(this.AddonName, null) && ClientStructsAssembly != null) { try { - foreach (var t in from t in ClientStructs.GetTypes() + foreach (var t in from t in ClientStructsAssembly.GetTypes() where t.IsPublic let xivAddonAttr = (Addon?)t.GetCustomAttribute(typeof(Addon), false) where xivAddonAttr != null From 0c87717ffca5a55875f821db7526284b3c67a0be Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:43:38 +0100 Subject: [PATCH 082/375] [master] Update ClientStructs (#2054) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 99ad244e7..72b7fa138 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 99ad244e70e3545d397ebe82f71d6bc0333914ca +Subproject commit 72b7fa138c782966bf150fe63d8f4149e62ed9cd From 8956bd54a93a4466955996a82889f3a29c0738e8 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 4 Nov 2024 15:01:09 +0100 Subject: [PATCH 083/375] fix some warnings --- Dalamud/Game/Addon/Lifecycle/AddonEvent.cs | 2 +- Dalamud/Game/ClientState/Fates/Fate.cs | 12 +++++++++++- Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs | 4 ++-- Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs | 2 +- Dalamud/Interface/Internal/UiDebug.cs | 6 +++--- .../Internal/UiDebug2/Browsing/NodeTree.Image.cs | 6 +++--- Dalamud/Interface/Windowing/Window.cs | 6 +++--- Dalamud/Plugin/Ipc/ICallGateSubscriber.cs | 1 - Dalamud/Utility/StringExtensions.cs | 4 +++- 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs index 91b9dd51f..5fd0ac964 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs @@ -52,7 +52,7 @@ public enum AddonEvent PostDraw, /// - /// An event that is fired immediately before an addon is finalized via and + /// An event that is fired immediately before an addon is finalized via and /// destroyed. After this event, the addon will destruct its UI node data as well as free any allocated memory. /// This event can be used for cleanup and tracking tasks. /// diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 2d32cc04c..eab4229eb 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -4,6 +4,7 @@ using Dalamud.Data; using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; +using Dalamud.Utility; namespace Dalamud.Game.ClientState.Fates; @@ -70,7 +71,13 @@ public interface IFate : IEquatable /// /// Gets a value indicating whether or not this has a EXP bonus. /// + [Api11ToDo("Remove this")] bool HasExpBonus { get; } + + /// + /// Gets a value indicating whether or not this has a EXP bonus. + /// + bool HasBonus { get; } /// /// Gets the icon id of this . @@ -215,7 +222,10 @@ internal unsafe partial class Fate : IFate public byte Progress => this.Struct->Progress; /// - public bool HasExpBonus => this.Struct->IsExpBonus; + public bool HasExpBonus => this.Struct->IsBonus; + + /// + public bool HasBonus => this.Struct->IsBonus; /// public uint IconId => this.Struct->IconId; diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index b8a4a9bd8..2d5633bcb 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -127,7 +127,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// /// Gets a pointer to the NamePlate addon's number array entries as a struct. /// - internal AddonNamePlate.NamePlateIntArrayData* NumberStruct { get; private set; } + internal AddonNamePlate.AddonNamePlateNumberArray* NumberStruct { get; private set; } /// /// Gets or sets a value indicating whether any handler in the current context has instantiated a part builder. @@ -142,7 +142,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext { this.Addon = (AddonNamePlate*)args.Addon; this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex]; - this.NumberStruct = (AddonNamePlate.NamePlateIntArrayData*)this.NumberData->IntArray; + this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray; this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex]; this.HasParts = false; diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 99429d932..7b547a153 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -490,7 +490,7 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler private AddonNamePlate.NamePlateObject* NamePlateObject => &this.context.Addon->NamePlateObjectArray[this.NamePlateIndex]; - private AddonNamePlate.NamePlateIntArrayData.NamePlateObjectIntArrayData* ObjectData => + private AddonNamePlate.AddonNamePlateNumberArray.NamePlateObjectIntArrayData* ObjectData => this.context.NumberStruct->ObjectData.GetPointer(this.ArrayIndex); /// diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index f1a025d93..88294fdee 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -300,7 +300,7 @@ internal unsafe class UiDebug { ImGui.Image( new IntPtr(kernelTexture->D3D11ShaderResourceView), - new Vector2(kernelTexture->Width, kernelTexture->Height)); + new Vector2(kernelTexture->ActualWidth, kernelTexture->ActualHeight)); ImGui.TreePop(); } } @@ -312,8 +312,8 @@ internal unsafe class UiDebug ImGui.Image( new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2( - textureInfo->AtkTexture.KernelTexture->Width, - textureInfo->AtkTexture.KernelTexture->Height)); + textureInfo->AtkTexture.KernelTexture->ActualWidth, + textureInfo->AtkTexture.KernelTexture->ActualHeight)); ImGui.TreePop(); } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs index 678eec8cd..4929fa1d9 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -172,7 +172,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree var cursorScreenPos = ImGui.GetCursorScreenPos(); var cursorLocalPos = ImGui.GetCursorPos(); - ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(this.TexData.Texture->Width, this.TexData.Texture->Height)); + ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(this.TexData.Texture->ActualWidth, this.TexData.Texture->ActualHeight)); for (uint p = 0; p < this.TexData.PartsList->PartCount; p++) { @@ -197,8 +197,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree ImGui.TableHeadersRow(); - var tWidth = this.TexData.Texture->Width; - var tHeight = this.TexData.Texture->Height; + var tWidth = this.TexData.Texture->ActualWidth; + var tHeight = this.TexData.Texture->ActualHeight; var textureSize = new Vector2(tWidth, tHeight); for (ushort i = 0; i < this.TexData.PartCount; i++) diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index d2a51235d..bcceb9311 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -287,7 +287,7 @@ public abstract class Window this.IsFocused = false; - if (doSoundEffects && !this.DisableWindowSounds) UIModule.PlaySound(this.OnCloseSfxId, 0, 0, 0); + if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnCloseSfxId, 0, 0, 0); } return; @@ -307,7 +307,7 @@ public abstract class Window this.internalLastIsOpen = this.internalIsOpen; this.OnOpen(); - if (doSoundEffects && !this.DisableWindowSounds) UIModule.PlaySound(this.OnOpenSfxId, 0, 0, 0); + if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnOpenSfxId, 0, 0, 0); } this.PreDraw(); @@ -347,7 +347,7 @@ public abstract class Window } catch (Exception ex) { - Log.Error(ex, $"Error during Draw(): {this.WindowName}"); + Log.Error(ex, "Error during Draw(): {WindowName}", this.WindowName); } } diff --git a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs index fd67ec7db..9bb1d0550 100644 --- a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs +++ b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs @@ -9,7 +9,6 @@ namespace Dalamud.Plugin.Ipc; /// public interface ICallGateSubscriber { - /// public bool HasAction { get; } diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index 02dfdafbf..9f097bb09 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -2,6 +2,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; +using FFXIVClientStructs.FFXIV.Client.UI; + namespace Dalamud.Utility; /// @@ -40,7 +42,7 @@ public static class StringExtensions public static bool IsValidCharacterName(this string value, bool includeLegacy = true) { if (string.IsNullOrEmpty(value)) return false; - if (!FFXIVClientStructs.FFXIV.Client.UI.UIModule.IsPlayerCharacterName(value)) return false; + if (!UIGlobals.IsValidPlayerCharacterName(value)) return false; return includeLegacy || value.Length <= 21; } } From f3bd83fbe9530b1bc8d00530fb2e55e99ece0f2c Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:20:54 +0100 Subject: [PATCH 084/375] repos: warn on missing testing API (#2056) * repos: warn on missing testing API * repos: reword missing testing API warning --- Dalamud/Plugin/Internal/Types/PluginRepository.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Dalamud/Plugin/Internal/Types/PluginRepository.cs b/Dalamud/Plugin/Internal/Types/PluginRepository.cs index 2f63070c3..c5e703e4b 100644 --- a/Dalamud/Plugin/Internal/Types/PluginRepository.cs +++ b/Dalamud/Plugin/Internal/Types/PluginRepository.cs @@ -205,6 +205,13 @@ internal class PluginRepository return false; } + if (manifest.TestingAssemblyVersion != null && + manifest.TestingAssemblyVersion > manifest.AssemblyVersion && + manifest.TestingDalamudApiLevel == null) + { + Log.Warning("The plugin {PluginName} in {RepoLink} has a testing version available, but it lacks an associated testing API. The 'TestingDalamudApiLevel' property is required.", manifest.InternalName, this.PluginMasterUrl); + } + return true; } From 9a0bc50e2396c450e8bec14589dd5be1d09008e9 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:21:07 +0900 Subject: [PATCH 085/375] Use hooks instead of lifecycle for NamePlateGui (#2060) --- Dalamud/Game/Gui/NamePlate/NamePlateGui.cs | 254 ++++++++++++++---- .../NamePlate/NamePlateGuiAddressResolver.cs | 20 ++ .../Gui/NamePlate/NamePlateUpdateContext.cs | 17 +- Dalamud/Plugin/Services/INamePlateGui.cs | 19 ++ 4 files changed, 244 insertions(+), 66 deletions(-) create mode 100644 Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs index 28e2c36eb..2def0ea00 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs @@ -1,14 +1,16 @@ using System.Collections.Generic; using System.Runtime.InteropServices; -using Dalamud.Game.Addon.Lifecycle; -using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState.Objects; +using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using Serilog; namespace Dalamud.Game.Gui.NamePlate; @@ -38,38 +40,44 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui /// internal static readonly nint EmptyStringPointer = CreateEmptyStringPointer(); - [ServiceManager.ServiceDependency] - private readonly AddonLifecycle addonLifecycle = Service.Get(); - [ServiceManager.ServiceDependency] private readonly GameGui gameGui = Service.Get(); [ServiceManager.ServiceDependency] private readonly ObjectTable objectTable = Service.Get(); - private readonly AddonLifecycleEventListener preRequestedUpdateListener; + private readonly NamePlateGuiAddressResolver address; + + private readonly Hook onRequestedUpdateHook; private NamePlateUpdateContext? context; private NamePlateUpdateHandler[] updateHandlers = []; [ServiceManager.ServiceConstructor] - private NamePlateGui() + private unsafe NamePlateGui(TargetSigScanner sigScanner) { - this.preRequestedUpdateListener = new AddonLifecycleEventListener( - AddonEvent.PreRequestedUpdate, - "NamePlate", - this.OnPreRequestedUpdate); + this.address = new NamePlateGuiAddressResolver(); + this.address.Setup(sigScanner); - this.addonLifecycle.RegisterListener(this.preRequestedUpdateListener); + this.onRequestedUpdateHook = Hook.FromAddress( + this.address.OnRequestedUpdate, + this.OnRequestedUpdateDetour); + this.onRequestedUpdateHook.Enable(); } /// public event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdate; + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdate; + /// public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate; + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdate; + /// public unsafe void RequestRedraw() { @@ -91,7 +99,7 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui /// void IInternalDisposableService.DisposeService() { - this.addonLifecycle.UnregisterListener(this.preRequestedUpdateListener); + this.onRequestedUpdateHook.Dispose(); } /// @@ -144,65 +152,124 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui this.updateHandlers = handlers.ToArray(); } - private void OnPreRequestedUpdate(AddonEvent type, AddonArgs args) + private unsafe void OnRequestedUpdateDetour( + AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { - if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null) - { - return; - } + var calledOriginal = false; - var reqArgs = (AddonRequestedUpdateArgs)args; - if (this.context == null) + try { - this.context = new NamePlateUpdateContext(this.objectTable, reqArgs); - this.CreateHandlers(this.context); - } - else - { - this.context.ResetState(reqArgs); - } - - var activeNamePlateCount = this.context.ActiveNamePlateCount; - if (activeNamePlateCount == 0) - return; - - var activeHandlers = this.updateHandlers[..activeNamePlateCount]; - - if (this.context.IsFullUpdate) - { - foreach (var handler in activeHandlers) + if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null && this.OnPostDataUpdate == null && + this.OnPostNamePlateUpdate == null) { - handler.ResetState(); + return; } - this.OnDataUpdate?.Invoke(this.context, activeHandlers); - this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers); - if (this.context.HasParts) - this.ApplyBuilders(activeHandlers); - } - else - { - var udpatedHandlers = new List(activeNamePlateCount); - foreach (var handler in activeHandlers) + if (this.context == null) { - handler.ResetState(); - if (handler.IsUpdating) - udpatedHandlers.Add(handler); + this.context = new NamePlateUpdateContext(this.objectTable); + this.CreateHandlers(this.context); } - if (this.OnDataUpdate is not null) + this.context.ResetState(addon, numberArrayData, stringArrayData); + + var activeNamePlateCount = this.context!.ActiveNamePlateCount; + if (activeNamePlateCount == 0) + return; + + var activeHandlers = this.updateHandlers[..activeNamePlateCount]; + + if (this.context.IsFullUpdate) { + foreach (var handler in activeHandlers) + { + handler.ResetState(); + } + this.OnDataUpdate?.Invoke(this.context, activeHandlers); - this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers); + this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers); + if (this.context.HasParts) this.ApplyBuilders(activeHandlers); + + try + { + calledOriginal = true; + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } + + this.OnPostNamePlateUpdate?.Invoke(this.context, activeHandlers); + this.OnPostDataUpdate?.Invoke(this.context, activeHandlers); } - else if (udpatedHandlers.Count != 0) + else { - var changedHandlersSpan = udpatedHandlers.ToArray().AsSpan(); - this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers); - if (this.context.HasParts) - this.ApplyBuilders(changedHandlersSpan); + var updatedHandlers = new List(activeNamePlateCount); + foreach (var handler in activeHandlers) + { + handler.ResetState(); + if (handler.IsUpdating) + updatedHandlers.Add(handler); + } + + if (this.OnDataUpdate is not null) + { + this.OnDataUpdate?.Invoke(this.context, activeHandlers); + this.OnNamePlateUpdate?.Invoke(this.context, updatedHandlers); + + if (this.context.HasParts) + this.ApplyBuilders(activeHandlers); + + try + { + calledOriginal = true; + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } + + this.OnPostNamePlateUpdate?.Invoke(this.context, updatedHandlers); + this.OnPostDataUpdate?.Invoke(this.context, activeHandlers); + } + else if (updatedHandlers.Count != 0) + { + this.OnNamePlateUpdate?.Invoke(this.context, updatedHandlers); + + if (this.context.HasParts) + this.ApplyBuilders(updatedHandlers); + + try + { + calledOriginal = true; + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } + + this.OnPostNamePlateUpdate?.Invoke(this.context, updatedHandlers); + this.OnPostDataUpdate?.Invoke(this.context, activeHandlers); + } + } + } + finally + { + if (!calledOriginal) + { + try + { + this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData); + } + catch (Exception e) + { + Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate."); + } } } } @@ -217,6 +284,17 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui } } } + + private void ApplyBuilders(List handlers) + { + foreach (var handler in handlers) + { + if (handler.PartsContainer is { } container) + { + container.ApplyBuilders(handler); + } + } + } } /// @@ -239,6 +317,7 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate { if (this.OnNamePlateUpdateScoped == null) this.parentService.OnNamePlateUpdate += this.OnNamePlateUpdateForward; + this.OnNamePlateUpdateScoped += value; } @@ -250,6 +329,25 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate } } + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdate + { + add + { + if (this.OnPostNamePlateUpdateScoped == null) + this.parentService.OnPostNamePlateUpdate += this.OnPostNamePlateUpdateForward; + + this.OnPostNamePlateUpdateScoped += value; + } + + remove + { + this.OnPostNamePlateUpdateScoped -= value; + if (this.OnPostNamePlateUpdateScoped == null) + this.parentService.OnPostNamePlateUpdate -= this.OnPostNamePlateUpdateForward; + } + } + /// public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate { @@ -257,6 +355,7 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate { if (this.OnDataUpdateScoped == null) this.parentService.OnDataUpdate += this.OnDataUpdateForward; + this.OnDataUpdateScoped += value; } @@ -268,10 +367,33 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate } } + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdate + { + add + { + if (this.OnPostDataUpdateScoped == null) + this.parentService.OnPostDataUpdate += this.OnPostDataUpdateForward; + + this.OnPostDataUpdateScoped += value; + } + + remove + { + this.OnPostDataUpdateScoped -= value; + if (this.OnPostDataUpdateScoped == null) + this.parentService.OnPostDataUpdate -= this.OnPostDataUpdateForward; + } + } + private event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdateScoped; + private event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdateScoped; + private event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdateScoped; + private event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdateScoped; + /// public void RequestRedraw() { @@ -284,8 +406,14 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate this.parentService.OnNamePlateUpdate -= this.OnNamePlateUpdateForward; this.OnNamePlateUpdateScoped = null; + this.parentService.OnPostNamePlateUpdate -= this.OnPostNamePlateUpdateForward; + this.OnPostNamePlateUpdateScoped = null; + this.parentService.OnDataUpdate -= this.OnDataUpdateForward; this.OnDataUpdateScoped = null; + + this.parentService.OnPostDataUpdate -= this.OnPostDataUpdateForward; + this.OnPostDataUpdateScoped = null; } private void OnNamePlateUpdateForward( @@ -294,9 +422,21 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate this.OnNamePlateUpdateScoped?.Invoke(context, handlers); } + private void OnPostNamePlateUpdateForward( + INamePlateUpdateContext context, IReadOnlyList handlers) + { + this.OnPostNamePlateUpdateScoped?.Invoke(context, handlers); + } + private void OnDataUpdateForward( INamePlateUpdateContext context, IReadOnlyList handlers) { this.OnDataUpdateScoped?.Invoke(context, handlers); } + + private void OnPostDataUpdateForward( + INamePlateUpdateContext context, IReadOnlyList handlers) + { + this.OnPostDataUpdateScoped?.Invoke(context, handlers); + } } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs new file mode 100644 index 000000000..450e1fa9f --- /dev/null +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs @@ -0,0 +1,20 @@ +namespace Dalamud.Game.Gui.NamePlate; + +/// +/// An address resolver for the class. +/// +internal class NamePlateGuiAddressResolver : BaseAddressResolver +{ + /// + /// Gets the address of the AddonNamePlate OnRequestedUpdate method. We need to use a hook for this because + /// AddonNamePlate.Show calls OnRequestedUpdate directly, bypassing the AddonLifecycle callsite hook. + /// + public IntPtr OnRequestedUpdate { get; private set; } + + /// + protected override void Setup64Bit(ISigScanner sig) + { + this.OnRequestedUpdate = sig.ScanText( + "4C 8B DC 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B 40 20"); + } +} diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index 2d5633bcb..876c4c2e0 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -1,4 +1,3 @@ -using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState.Objects; using FFXIVClientStructs.FFXIV.Client.UI; @@ -54,13 +53,11 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// Initializes a new instance of the class. /// /// An object table. - /// The addon lifecycle arguments for the update request. - internal NamePlateUpdateContext(ObjectTable objectTable, AddonRequestedUpdateArgs args) + internal NamePlateUpdateContext(ObjectTable objectTable) { this.ObjectTable = objectTable; this.RaptureAtkModule = FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkModule.Instance(); this.Ui3DModule = UIModule.Instance()->GetUI3DModule(); - this.ResetState(args); } /// @@ -137,13 +134,15 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// /// Resets the state of the context based on the provided addon lifecycle arguments. /// - /// The addon lifecycle arguments for the update request. - internal void ResetState(AddonRequestedUpdateArgs args) + /// A pointer to the addon. + /// A pointer to the global number array data struct. + /// A pointer to the global string array data struct. + public void ResetState(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { - this.Addon = (AddonNamePlate*)args.Addon; - this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex]; + this.Addon = (AddonNamePlate*)addon; + this.NumberData = numberArrayData[NamePlateGui.NumberArrayIndex]; this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray; - this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex]; + this.StringData = stringArrayData[NamePlateGui.StringArrayIndex]; this.HasParts = false; this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount; diff --git a/Dalamud/Plugin/Services/INamePlateGui.cs b/Dalamud/Plugin/Services/INamePlateGui.cs index 713d9120b..eb2579bae 100644 --- a/Dalamud/Plugin/Services/INamePlateGui.cs +++ b/Dalamud/Plugin/Services/INamePlateGui.cs @@ -26,6 +26,15 @@ public interface INamePlateGui /// event OnPlateUpdateDelegate? OnNamePlateUpdate; + /// + /// An event which fires after nameplate data is updated and at least one nameplate had important updates. The + /// subscriber is provided with a list of handlers for nameplates with important updates. + /// + /// + /// Fires before . + /// + event OnPlateUpdateDelegate? OnPostNamePlateUpdate; + /// /// An event which fires when nameplate data is updated. The subscriber is provided with a list of handlers for all /// nameplates. @@ -36,6 +45,16 @@ public interface INamePlateGui /// event OnPlateUpdateDelegate? OnDataUpdate; + /// + /// An event which fires after nameplate data is updated. The subscriber is provided with a list of handlers for all + /// nameplates. + /// + /// + /// This event is likely to fire every frame even when no nameplates are actually updated, so in most cases + /// is preferred. Fires after . + /// + event OnPlateUpdateDelegate? OnPostDataUpdate; + /// /// Requests that all nameplates should be redrawn on the following frame. /// From 74ab9191d3a7ef3508285a3884ebf18e5401c216 Mon Sep 17 00:00:00 2001 From: srkizer Date: Tue, 5 Nov 2024 00:06:42 +0900 Subject: [PATCH 086/375] Add ITextureProvider.ConvertToKernelTexture (#2003) * Add ITextureProvider.ConvertToKernelTexture Lets you obtain an instance of Kernel::Texture from IDalamudTextureWrap. * Docs wip --- Dalamud/Interface/Internal/UiDebug.cs | 28 ++++++++ .../Textures/ISharedImmediateTexture.cs | 9 ++- .../TextureManager.FromExistingTexture.cs | 69 ++++++++++++++++++- .../Internal/TextureManagerPluginScoped.cs | 4 ++ Dalamud/Plugin/Services/ITextureProvider.cs | 17 ++++- .../Services/ITextureReadbackProvider.cs | 3 + 6 files changed, 125 insertions(+), 5 deletions(-) diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 88294fdee..2e8f4416b 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -1,7 +1,9 @@ using System.Numerics; +using Dalamud.Game; using Dalamud.Game.Gui; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; +using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; using Dalamud.Utility; @@ -317,6 +319,32 @@ internal unsafe class UiDebug ImGui.TreePop(); } } + + if (ImGui.Button($"Replace with a random image##{(ulong)textureInfo:X}")) + { + var texm = Service.Get(); + texm.Shared + .GetFromGame( + Random.Shared.Next(0, 1) == 0 + ? $"ui/loadingimage/-nowloading_base{Random.Shared.Next(1, 33)}.tex" + : $"ui/loadingimage/-nowloading_base{Random.Shared.Next(1, 33)}_hr1.tex") + .RentAsync() + .ContinueWith( + r => Service.Get().RunOnFrameworkThread( + () => + { + if (!r.IsCompletedSuccessfully) + return; + + using (r.Result) + { + textureInfo->AtkTexture.ReleaseTexture(); + textureInfo->AtkTexture.KernelTexture = + texm.ConvertToKernelTexture(r.Result); + textureInfo->AtkTexture.TextureType = TextureType.KernelTexture; + } + })); + } } } else diff --git a/Dalamud/Interface/Textures/ISharedImmediateTexture.cs b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs index 591b9846c..0ceb92171 100644 --- a/Dalamud/Interface/Textures/ISharedImmediateTexture.cs +++ b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs @@ -25,7 +25,8 @@ public interface ISharedImmediateTexture /// s may be cached, but the performance benefit will be minimal. /// Calling outside the main thread will fail. /// This function does not throw. - /// will be ignored. + /// will be ignored, including the cases when the returned texture wrap + /// is passed to a function with leaveWrapOpen parameter. /// If the texture is unavailable for any reason, then the returned instance of /// will point to an empty texture instead. /// @@ -42,7 +43,8 @@ public interface ISharedImmediateTexture /// s may be cached, but the performance benefit will be minimal. /// Calling outside the main thread will fail. /// This function does not throw. - /// will be ignored. + /// will be ignored, including the cases when the returned texture wrap + /// is passed to a function with leaveWrapOpen parameter. /// If the texture is unavailable for any reason, then will be returned. /// [return: NotNullIfNotNull(nameof(defaultWrap))] @@ -59,7 +61,8 @@ public interface ISharedImmediateTexture /// s may be cached, but the performance benefit will be minimal. /// Calling outside the main thread will fail. /// This function does not throw. - /// on the returned will be ignored. + /// on the returned will be ignored, including + /// the cases when the returned texture wrap is passed to a function with leaveWrapOpen parameter. /// /// Thrown when called outside the UI thread. bool TryGetWrap([NotNullWhen(true)] out IDalamudTextureWrap? texture, out Exception? exception); diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs index a744114e8..2ce96e59d 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps.Internal; using Dalamud.Plugin.Internal.Types; @@ -10,6 +9,8 @@ using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.TerraFxCom; +using Lumina.Data.Files; + using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -18,6 +19,72 @@ namespace Dalamud.Interface.Textures.Internal; /// Service responsible for loading and disposing ImGui texture wraps. internal sealed partial class TextureManager { + /// + unsafe nint ITextureProvider.ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen) => + (nint)this.ConvertToKernelTexture(wrap, leaveWrapOpen); + + /// + public unsafe FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture* ConvertToKernelTexture( + IDalamudTextureWrap wrap, + bool leaveWrapOpen = false) + { + using var wrapAux = new WrapAux(wrap, leaveWrapOpen); + + var flags = TexFile.Attribute.TextureType2D; + if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_IMMUTABLE) + flags |= TexFile.Attribute.Immutable; + if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_DYNAMIC) + flags |= TexFile.Attribute.ReadWrite; + if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) != 0) + flags |= TexFile.Attribute.CpuRead; + if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET) != 0) + flags |= TexFile.Attribute.TextureRenderTarget; + if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_DEPTH_STENCIL) != 0) + flags |= TexFile.Attribute.TextureDepthStencil; + if (wrapAux.Desc.ArraySize != 1) + throw new NotSupportedException("TextureArray2D is currently not supported."); + + var gtex = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture.CreateTexture2D( + (int)wrapAux.Desc.Width, + (int)wrapAux.Desc.Height, + (byte)wrapAux.Desc.MipLevels, + (uint)TexFile.TextureFormat.Null, // instructs the game to skip preprocessing it seems + (uint)flags, + 0); + + // Kernel::Texture owns these resources. We're passing the ownership to them. + wrapAux.TexPtr->AddRef(); + wrapAux.SrvPtr->AddRef(); + + // Not sure this is needed + var ltf = wrapAux.Desc.Format switch + { + DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TexFile.TextureFormat.R32G32B32A32F, + DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TexFile.TextureFormat.R16G16B16A16F, + DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TexFile.TextureFormat.R32G32F, + DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TexFile.TextureFormat.R16G16F, + DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TexFile.TextureFormat.R32F, + DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TexFile.TextureFormat.D24S8, + DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TexFile.TextureFormat.D16, + DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TexFile.TextureFormat.A8, + DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TexFile.TextureFormat.BC1, + DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TexFile.TextureFormat.BC2, + DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TexFile.TextureFormat.BC3, + DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TexFile.TextureFormat.BC5, + DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TexFile.TextureFormat.B4G4R4A4, + DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TexFile.TextureFormat.B5G5R5A1, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TexFile.TextureFormat.B8G8R8A8, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TexFile.TextureFormat.B8G8R8X8, + DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TexFile.TextureFormat.BC7, + _ => TexFile.TextureFormat.Null, + }; + gtex->TextureFormat = (FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.TextureFormat)ltf; + + gtex->D3D11Texture2D = wrapAux.TexPtr; + gtex->D3D11ShaderResourceView = wrapAux.SrvPtr; + return gtex; + } + /// bool ITextureProvider.IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) => this.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat); diff --git a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs index 68e2dde47..c62ad61b4 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs @@ -134,6 +134,10 @@ internal sealed class TextureManagerPluginScoped : $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name})"; } + /// + public unsafe nint ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false) => + (nint)this.ManagerOrThrow.ConvertToKernelTexture(wrap, leaveWrapOpen); + /// public IDalamudTextureWrap CreateEmpty( RawImageSpecification specs, diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs index d914b1091..ff13f11f1 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.Windows.Data.Widgets; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; @@ -281,4 +280,20 @@ public interface ITextureProvider /// true if supported. /// This function does not throw exceptions. bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat); + + /// Converts an existing instance to a new instance of + /// which can be used to supply a custom + /// texture onto an in-game addon (UI element.) + /// Instance of to convert. + /// Whether to leave non-disposed when the returned + /// completes. + /// Address of the new . + /// See PrintTextureInfo in for an example + /// of replacing the texture of an image node. + /// + /// If the returned kernel texture is to be destroyed, call the fourth function in its vtable, by calling + /// or + /// ((delegate* unmanaged<nint, void>)(*(nint**)ptr)[3](ptr). + /// + nint ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false); } diff --git a/Dalamud/Plugin/Services/ITextureReadbackProvider.cs b/Dalamud/Plugin/Services/ITextureReadbackProvider.cs index 309be103a..b41ded41f 100644 --- a/Dalamud/Plugin/Services/ITextureReadbackProvider.cs +++ b/Dalamud/Plugin/Services/ITextureReadbackProvider.cs @@ -22,6 +22,9 @@ public interface ITextureReadbackProvider /// /// The length of the returned RawData may not match /// * . + /// may not be the minimal value required to represent the texture + /// bitmap data. For example, if a texture is 4x4 B8G8R8A8, the minimal pitch would be 32, but the function may + /// return 64 instead. /// This function may throw an exception. /// Task<(RawImageSpecification Specification, byte[] RawData)> GetRawImageAsync( From 620a80275538b083ea59e48430d67b29a1414441 Mon Sep 17 00:00:00 2001 From: Xpahtalo <106037495+Xpahtalo@users.noreply.github.com> Date: Mon, 4 Nov 2024 07:13:46 -0800 Subject: [PATCH 087/375] Add additional warning to plugin config reset modal (#2005) Co-authored-by: goat <16760685+goaaats@users.noreply.github.com> --- .../PluginInstaller/PluginInstallerWindow.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 40753a20d..61f4bd1fc 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -98,6 +98,7 @@ internal class PluginInstallerWindow : Window, IDisposable private bool deletePluginConfigWarningModalDrawing = true; private bool deletePluginConfigWarningModalOnNextFrame = false; + private bool deletePluginConfigWarningModalExplainTesting = false; private string deletePluginConfigWarningModalPluginName = string.Empty; private TaskCompletionSource? deletePluginConfigWarningModalTaskCompletionSource; @@ -732,7 +733,7 @@ internal class PluginInstallerWindow : Window, IDisposable } } } - + private void DrawFooter() { var configuration = Service.Get(); @@ -972,10 +973,11 @@ internal class PluginInstallerWindow : Window, IDisposable } } - private Task ShowDeletePluginConfigWarningModal(string pluginName) + private Task ShowDeletePluginConfigWarningModal(string pluginName, bool explainTesting = false) { this.deletePluginConfigWarningModalOnNextFrame = true; this.deletePluginConfigWarningModalPluginName = pluginName; + this.deletePluginConfigWarningModalExplainTesting = explainTesting; this.deletePluginConfigWarningModalTaskCompletionSource = new TaskCompletionSource(); return this.deletePluginConfigWarningModalTaskCompletionSource.Task; } @@ -986,6 +988,13 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.BeginPopupModal(modalTitle, ref this.deletePluginConfigWarningModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) { + if (this.deletePluginConfigWarningModalExplainTesting) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); + ImGui.Text(Locs.DeletePluginConfigWarningModal_ExplainTesting()); + ImGui.PopStyleColor(); + } + ImGui.Text(Locs.DeletePluginConfigWarningModal_Body(this.deletePluginConfigWarningModalPluginName)); ImGui.Spacing(); @@ -2898,7 +2907,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.MenuItem(Locs.PluginContext_DeletePluginConfigReload)) { - this.ShowDeletePluginConfigWarningModal(plugin.Manifest.Name).ContinueWith(t => + this.ShowDeletePluginConfigWarningModal(plugin.Manifest.Name, optIn != null).ContinueWith(t => { var shouldDelete = t.Result; @@ -4263,7 +4272,9 @@ internal class PluginInstallerWindow : Window, IDisposable public static string DeletePluginConfigWarningModal_Title => Loc.Localize("InstallerDeletePluginConfigWarning", "Warning###InstallerDeletePluginConfigWarning"); - public static string DeletePluginConfigWarningModal_Body(string pluginName) => Loc.Localize("InstallerDeletePluginConfigWarningBody", "Are you sure you want to delete all data and configuration for {0}?").Format(pluginName); + public static string DeletePluginConfigWarningModal_ExplainTesting() => Loc.Localize("InstallerDeletePluginConfigWarningExplainTesting", "Do not select this option if you are only trying to disable testing!"); + + public static string DeletePluginConfigWarningModal_Body(string pluginName) => Loc.Localize("InstallerDeletePluginConfigWarningBody", "Are you sure you want to delete all data and configuration for {0}?\nYou will lose all of your settings for this plugin.").Format(pluginName); public static string DeletePluginConfirmWarningModal_Yes => Loc.Localize("InstallerDeletePluginConfigWarningYes", "Yes"); From 2a9ee760c64254f85e31a6c2b069c2fae19c0e93 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Mon, 4 Nov 2024 16:28:06 +0100 Subject: [PATCH 088/375] Add IGameInventory.GetInventoryItems (#2008) --- Dalamud/Game/Inventory/GameInventory.cs | 3 +++ Dalamud/Plugin/Services/IGameInventory.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index 0ddf0dcaf..02412c551 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -403,6 +403,9 @@ internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInve /// public event IGameInventory.InventoryChangedDelegate? ItemMergedExplicit; + /// + public ReadOnlySpan GetInventoryItems(GameInventoryType type) => GameInventoryItem.GetReadOnlySpanOfInventory(type); + /// void IInternalDisposableService.DisposeService() { diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs index a1b1114d7..0dff1ff03 100644 --- a/Dalamud/Plugin/Services/IGameInventory.cs +++ b/Dalamud/Plugin/Services/IGameInventory.cs @@ -103,4 +103,11 @@ public interface IGameInventory /// public event InventoryChangedDelegate ItemMergedExplicit; + + /// + /// Gets all item slots of the specified inventory type. + /// + /// The type of inventory to get the items for. + /// A read-only span of all items in the specified inventory type. + public ReadOnlySpan GetInventoryItems(GameInventoryType type); } From 3435c346b66ac4d341789482adb31412b75ccea9 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Mon, 4 Nov 2024 08:18:31 -0800 Subject: [PATCH 089/375] feat: Add Dalamud Version to the Title Screen (#1939) --- .../Interface/Internal/DalamudInterface.cs | 7 ++- .../Internal/Windows/TitleScreenMenuWindow.cs | 58 ++++++++++++++++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 793dfddd9..96395ac26 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using CheapLoc; using Dalamud.Configuration.Internal; using Dalamud.Console; +using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Keys; @@ -103,7 +104,8 @@ internal class DalamudInterface : IInternalDisposableService ClientState clientState, TitleScreenMenu titleScreenMenu, GameGui gameGui, - ConsoleManager consoleManager) + ConsoleManager consoleManager, + AddonLifecycle addonLifecycle) { this.dalamud = dalamud; this.configuration = configuration; @@ -129,7 +131,8 @@ internal class DalamudInterface : IInternalDisposableService framework, gameGui, titleScreenMenu, - consoleManager) { IsOpen = false }; + consoleManager, + addonLifecycle) { IsOpen = false }; this.changelogWindow = new ChangelogWindow( this.titleScreenMenuWindow, fontAtlasFactory, diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index c22dc76d3..588153b85 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -5,8 +5,12 @@ using System.Numerics; using Dalamud.Configuration.Internal; using Dalamud.Console; using Dalamud.Game; +using Dalamud.Game.Addon.Lifecycle; +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; @@ -14,10 +18,15 @@ using Dalamud.Interface.Textures.TextureWraps; 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; namespace Dalamud.Interface.Internal.Windows; @@ -39,7 +48,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable private readonly IFontAtlas privateAtlas; private readonly Lazy myFontHandle; private readonly Lazy shadeTexture; - + private readonly AddonLifecycleEventListener versionStringListener; + private readonly Dictionary shadeEasings = new(); private readonly Dictionary moveEasings = new(); private readonly Dictionary logoEasings = new(); @@ -49,7 +59,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable private InOutCubic? fadeOutEasing; private State state = State.Hide; - + /// /// Initializes a new instance of the class. /// @@ -61,6 +71,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable /// An instance of . /// An instance of . /// An instance of . + /// An instance of . public TitleScreenMenuWindow( ClientState clientState, DalamudConfiguration configuration, @@ -69,7 +80,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable Framework framework, GameGui gameGui, TitleScreenMenu titleScreenMenu, - ConsoleManager consoleManager) + ConsoleManager consoleManager, + AddonLifecycle addonLifecycle) : base( "TitleScreenMenuOverlay", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | @@ -109,6 +121,10 @@ internal class TitleScreenMenuWindow : Window, IDisposable framework.Update += this.FrameworkOnUpdate; this.scopedFinalizer.Add(() => framework.Update -= this.FrameworkOnUpdate); + + this.versionStringListener = new AddonLifecycleEventListener(AddonEvent.PreDraw, "_TitleRevision", this.OnVersionStringDraw); + addonLifecycle.RegisterListener(this.versionStringListener); + this.scopedFinalizer.Add(() => addonLifecycle.UnregisterListener(this.versionStringListener)); } private enum State @@ -414,5 +430,41 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.IsOpen = false; } + private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args) + { + if (args is not AddonDrawArgs setupArgs) return; + + var addon = (AtkUnitBase*)setupArgs.Addon; + var textNode = addon->GetTextNodeById(3); + + // look and feel init. should be harmless to set. + textNode->TextFlags |= (byte)TextFlags.MultiLine; + textNode->AlignmentType = AlignmentType.TopLeft; + + if (!this.configuration.ShowTsm || !this.showTsm.Value) + { + textNode->NodeText.SetString(addon->AtkValues[1].String); + return; + } + + var pm = Service.GetNullable(); + + var pluginCount = pm?.InstalledPlugins.Count(c => c.State == PluginState.Loaded) ?? 0; + + var titleVersionText = new SeStringBuilder() + .AddText(addon->AtkValues[1].GetValueAsString()) + .AddText("\n\n") + .AddUiForeground(SeIconChar.BoxedLetterD.ToIconString(), 539) + .AddUiForeground(561) + .AddText($" Dalamud: {Util.GetScmVersion()}") + .AddText($" - {pluginCount} {(pluginCount != 1 ? "plugins" : "plugin")} loaded") + .AddUiForegroundOff(); + + if (pm?.SafeMode ?? false) + titleVersionText.AddUiForeground(" [SAFE MODE]", 17); + + textNode->NodeText.SetString(titleVersionText.Build().EncodeWithNullTerminator()); + } + private void TitleScreenMenuEntryListChange() => this.privateAtlas.BuildFontsAsync(); } From d08520b24534261e966a795dccf844d988622127 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 4 Nov 2024 17:48:34 +0100 Subject: [PATCH 090/375] make version text not-red, since that feels like it should indicate errors --- Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 588153b85..471422eb8 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -454,11 +454,11 @@ internal class TitleScreenMenuWindow : Window, IDisposable var titleVersionText = new SeStringBuilder() .AddText(addon->AtkValues[1].GetValueAsString()) .AddText("\n\n") + .AddUiGlow(10) .AddUiForeground(SeIconChar.BoxedLetterD.ToIconString(), 539) - .AddUiForeground(561) + .AddUiGlowOff() .AddText($" Dalamud: {Util.GetScmVersion()}") - .AddText($" - {pluginCount} {(pluginCount != 1 ? "plugins" : "plugin")} loaded") - .AddUiForegroundOff(); + .AddText($" - {pluginCount} {(pluginCount != 1 ? "plugins" : "plugin")} loaded"); if (pm?.SafeMode ?? false) titleVersionText.AddUiForeground(" [SAFE MODE]", 17); From 66c24b630a96b2fbcc95eefbdf7e052034569ded Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 4 Nov 2024 22:59:24 +0100 Subject: [PATCH 091/375] dark uiglow looks better --- Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 471422eb8..e568ff972 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -454,7 +454,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable var titleVersionText = new SeStringBuilder() .AddText(addon->AtkValues[1].GetValueAsString()) .AddText("\n\n") - .AddUiGlow(10) + .AddUiGlow(701) .AddUiForeground(SeIconChar.BoxedLetterD.ToIconString(), 539) .AddUiGlowOff() .AddText($" Dalamud: {Util.GetScmVersion()}") From fcbad5000a1fd7cf4d063f5b8b253e769f022115 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 4 Nov 2024 23:14:10 +0100 Subject: [PATCH 092/375] add hint about disabling version text to TSM setting --- .../Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs index a582761ba..61ab00936 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs @@ -122,7 +122,7 @@ public class SettingsTabLook : SettingsTab new SettingsEntry( Loc.Localize("DalamudSettingToggleTsm", "Show title screen menu"), - Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen."), + Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen.\nDisabling this will also hide the Dalamud version text on the title screen."), c => c.ShowTsm, (v, c) => c.ShowTsm = v), From 30d56e4d112131efecc169e30448d0c5463119dc Mon Sep 17 00:00:00 2001 From: wolfcomp <4028289+wolfcomp@users.noreply.github.com> Date: Tue, 5 Nov 2024 06:14:51 +0100 Subject: [PATCH 093/375] fix all warnings and breaking changes for CS (#2061) --- Dalamud/Game/ClientState/Fates/Fate.cs | 10 ++++++++++ Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs | 4 ++-- Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs | 4 ++-- Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +- Dalamud/Interface/Internal/UiDebug.cs | 6 +++--- .../Internal/Windows/Data/Widgets/FateTableWidget.cs | 2 +- .../SelfTest/AgingSteps/ContextMenuAgingStep.cs | 2 +- Dalamud/Interface/Windowing/Window.cs | 4 ++-- Dalamud/Utility/StringExtensions.cs | 2 +- lib/FFXIVClientStructs | 2 +- 10 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 9b41ac758..7b6f110a3 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -71,8 +71,14 @@ public interface IFate : IEquatable /// /// Gets a value indicating whether or not this has a EXP bonus. /// + [Obsolete("Use HasBonus instead")] bool HasExpBonus { get; } + /// + /// Gets a value indicating whether or not this has a bonus. + /// + bool HasBonus { get; } + /// /// Gets the icon id of this . /// @@ -216,8 +222,12 @@ internal unsafe partial class Fate : IFate public byte Progress => this.Struct->Progress; /// + [Obsolete("Use HasBonus instead")] public bool HasExpBonus => this.Struct->IsExpBonus; + /// + public bool HasBonus => this.Struct->IsBonus; + /// public uint IconId => this.Struct->IconId; diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index b8a4a9bd8..2d5633bcb 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -127,7 +127,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// /// Gets a pointer to the NamePlate addon's number array entries as a struct. /// - internal AddonNamePlate.NamePlateIntArrayData* NumberStruct { get; private set; } + internal AddonNamePlate.AddonNamePlateNumberArray* NumberStruct { get; private set; } /// /// Gets or sets a value indicating whether any handler in the current context has instantiated a part builder. @@ -142,7 +142,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext { this.Addon = (AddonNamePlate*)args.Addon; this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex]; - this.NumberStruct = (AddonNamePlate.NamePlateIntArrayData*)this.NumberData->IntArray; + this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray; this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex]; this.HasParts = false; diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 99429d932..170fea687 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -490,7 +490,7 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler private AddonNamePlate.NamePlateObject* NamePlateObject => &this.context.Addon->NamePlateObjectArray[this.NamePlateIndex]; - private AddonNamePlate.NamePlateIntArrayData.NamePlateObjectIntArrayData* ObjectData => + private AddonNamePlate.AddonNamePlateNumberArray.NamePlateObjectIntArrayData* ObjectData => this.context.NumberStruct->ObjectData.GetPointer(this.ArrayIndex); /// diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 3844da15d..53aa9a9d9 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -63,7 +63,7 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets the quantity of items in this item stack. /// - public uint Quantity => this.InternalItem.Quantity; + public int Quantity => this.InternalItem.Quantity; /// /// Gets the spiritbond of this item. diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index f1a025d93..88294fdee 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -300,7 +300,7 @@ internal unsafe class UiDebug { ImGui.Image( new IntPtr(kernelTexture->D3D11ShaderResourceView), - new Vector2(kernelTexture->Width, kernelTexture->Height)); + new Vector2(kernelTexture->ActualWidth, kernelTexture->ActualHeight)); ImGui.TreePop(); } } @@ -312,8 +312,8 @@ internal unsafe class UiDebug ImGui.Image( new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2( - textureInfo->AtkTexture.KernelTexture->Width, - textureInfo->AtkTexture.KernelTexture->Height)); + textureInfo->AtkTexture.KernelTexture->ActualWidth, + textureInfo->AtkTexture.KernelTexture->ActualHeight)); ImGui.TreePop(); } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs index 77c05cb46..1a43f2b2d 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs @@ -153,7 +153,7 @@ internal class FateTableWidget : IDataWindowWidget } ImGui.TableNextColumn(); // HasExpBonus - ImGui.TextUnformatted(fate.HasExpBonus.ToString()); + ImGui.TextUnformatted(fate.HasBonus.ToString()); ImGui.TableNextColumn(); // Position DrawCopyableText(fate.Position.ToString(), "Click to copy Position"); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs index 5bcd1845c..f08eccd96 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs @@ -141,7 +141,7 @@ internal class ContextMenuAgingStep : IAgingStep OnClicked = (IMenuItemClickedArgs a) => { SeString name; - uint count; + int count; var targetItem = (a.Target as MenuTargetInventory)!.TargetItem; if (targetItem is { } item) { diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index d2a51235d..e507750dc 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -287,7 +287,7 @@ public abstract class Window this.IsFocused = false; - if (doSoundEffects && !this.DisableWindowSounds) UIModule.PlaySound(this.OnCloseSfxId, 0, 0, 0); + if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnCloseSfxId); } return; @@ -307,7 +307,7 @@ public abstract class Window this.internalLastIsOpen = this.internalIsOpen; this.OnOpen(); - if (doSoundEffects && !this.DisableWindowSounds) UIModule.PlaySound(this.OnOpenSfxId, 0, 0, 0); + if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnOpenSfxId); } this.PreDraw(); diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index 02dfdafbf..a63109167 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -40,7 +40,7 @@ public static class StringExtensions public static bool IsValidCharacterName(this string value, bool includeLegacy = true) { if (string.IsNullOrEmpty(value)) return false; - if (!FFXIVClientStructs.FFXIV.Client.UI.UIModule.IsPlayerCharacterName(value)) return false; + if (!FFXIVClientStructs.FFXIV.Client.UI.UIGlobals.IsValidPlayerCharacterName(value)) return false; return includeLegacy || value.Length <= 21; } } diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 7c5f04e34..e59323cf3 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 7c5f04e346067f7a316ad9072fb8260122ba80f0 +Subproject commit e59323cf39e6acffd385006c156fa0105c255b54 From fea5b3b563ba9b42b6d03881ac27d6c95ef75800 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 5 Nov 2024 21:15:09 +0100 Subject: [PATCH 094/375] Safely call events in DalamudPluginInterface (#2063) --- Dalamud/Plugin/DalamudPluginInterface.cs | 31 ++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index ecd2e0799..d5cf360af 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -526,13 +526,40 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa /// If this plugin was affected by the change. internal void NotifyActivePluginsChanged(PluginListInvalidationKind kind, bool affectedThisPlugin) { - this.ActivePluginsChanged?.Invoke(kind, affectedThisPlugin); + if (this.ActivePluginsChanged is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(kind, affectedThisPlugin); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } } private void OnLocalizationChanged(string langCode) { this.UiLanguage = langCode; - this.LanguageChanged?.Invoke(langCode); + + if (this.LanguageChanged is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(langCode); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } } private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) From d4735980c8998dea7c01729ce356c0fa68d16b33 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 5 Nov 2024 22:37:45 +0100 Subject: [PATCH 095/375] [api11] Use Conditions address from CS (#2064) --- Dalamud/Game/ClientState/ClientStateAddressResolver.cs | 7 ------- Dalamud/Game/ClientState/Conditions/Condition.cs | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 5a5351a38..625271d2a 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -50,11 +50,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// public IntPtr KeyboardStateIndexArray { get; private set; } - /// - /// Gets the address of the condition flag array. - /// - public IntPtr ConditionFlags { get; private set; } - // Functions /// @@ -93,8 +88,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; - this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 8B D3 E8 ?? ?? ?? ?? 32 C0 48 83 C4 20"); - this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); } } diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 6fee02fd6..8f39df46f 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -28,10 +28,9 @@ internal sealed class Condition : IInternalDisposableService, ICondition private bool isDisposed; [ServiceManager.ServiceConstructor] - private Condition(ClientState clientState) + private unsafe Condition() { - var resolver = clientState.AddressResolver; - this.Address = resolver.ConditionFlags; + this.Address = (nint)FFXIVClientStructs.FFXIV.Client.Game.Conditions.Instance(); // Initialization for (var i = 0; i < MaxConditionEntries; i++) From c88e0086723a72564c07dfb7446bc6e55f9d179b Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Mon, 11 Nov 2024 08:23:54 -0800 Subject: [PATCH 096/375] chore: Bump to API version 11 proper --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 79d7a64f8..b3f1cf30a 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 10.0.0.15 + 11.0.0.0 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From c7facaf072099a98708d56256a7163cf3bbdf215 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Mon, 11 Nov 2024 17:28:40 +0100 Subject: [PATCH 097/375] Rewrite parts of ClientState (#2065) - Completely relies on hooks now, instead of the Framework.Update loop - Support for Logout codes --- Dalamud/Game/ClientState/ClientState.cs | 165 +++++++++++------- .../ClientState/ClientStateAddressResolver.cs | 7 + .../AgingSteps/LogoutEventAgingStep.cs | 2 +- .../Internal/AutoUpdate/AutoUpdateManager.cs | 4 +- Dalamud/Plugin/Services/IClientState.cs | 9 +- 5 files changed, 120 insertions(+), 67 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 0fad33e15..51f25fb5c 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -14,6 +14,7 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Application.Network; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.UI; @@ -37,16 +38,12 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private readonly ClientStateAddressResolver address; private readonly Hook setupTerritoryTypeHook; private readonly Hook uiModuleHandlePacketHook; - - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); + private readonly Hook processPacketPlayerSetupHook; + private readonly Hook onLogoutHook; [ServiceManager.ServiceDependency] private readonly NetworkHandlers networkHandlers = Service.Get(); - private bool lastConditionNone = true; - private bool lastFramePvP; - [ServiceManager.ServiceConstructor] private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) { @@ -62,18 +59,22 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); - - this.framework.Update += this.FrameworkOnOnUpdateEvent; + this.processPacketPlayerSetupHook = Hook.FromAddress(this.address.ProcessPacketPlayerSetup, this.ProcessPacketPlayerSetupDetour); + this.onLogoutHook = Hook.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour); this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; this.setupTerritoryTypeHook.Enable(); this.uiModuleHandlePacketHook.Enable(); + this.processPacketPlayerSetupHook.Enable(); + this.onLogoutHook.Enable(); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType); + private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); + /// public event Action? TerritoryChanged; @@ -87,7 +88,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public event Action? Login; /// - public event Action? Logout; + public event IClientState.LogoutDelegate? Logout; /// public event Action? EnterPvP; @@ -165,23 +166,46 @@ internal sealed class ClientState : IInternalDisposableService, IClientState { this.setupTerritoryTypeHook.Dispose(); this.uiModuleHandlePacketHook.Dispose(); - this.framework.Update -= this.FrameworkOnOnUpdateEvent; + this.processPacketPlayerSetupHook.Dispose(); + this.onLogoutHook.Dispose(); this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; } private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType) { + Log.Debug("TerritoryType changed: {0}", territoryType); + this.TerritoryType = territoryType; this.TerritoryChanged?.InvokeSafely(territoryType); - Log.Debug("TerritoryType changed: {0}", territoryType); + var rowRef = LuminaUtils.CreateRef(territoryType); + if (rowRef.IsValid) + { + var isPvP = rowRef.Value.IsPvpZone; + if (isPvP != this.IsPvP) + { + this.IsPvP = isPvP; + this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; + + if (this.IsPvP) + { + Log.Debug("EnterPvP"); + this.EnterPvP?.InvokeSafely(); + } + else + { + Log.Debug("LeavePvP"); + this.LeavePvP?.InvokeSafely(); + } + } + } this.setupTerritoryTypeHook.Original(eventFramework, territoryType); } private unsafe void UIModuleHandlePacketDetour(UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet) { - this.uiModuleHandlePacketHook!.Original(thisPtr, type, uintParam, packet); + this.uiModuleHandlePacketHook.Original(thisPtr, type, uintParam, packet); switch (type) { @@ -226,59 +250,74 @@ internal sealed class ClientState : IInternalDisposableService, IClientState } } + private unsafe void ProcessPacketPlayerSetupDetour(nint a1, nint packet) + { + // Call original first, so everything is set up. + this.processPacketPlayerSetupHook.Original(a1, packet); + + var gameGui = Service.GetNullable(); + + try + { + Log.Debug("Login"); + this.IsLoggedIn = true; + this.Login?.InvokeSafely(); + gameGui?.ResetUiHideState(); + this.lifecycle.ResetLogout(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during ProcessPacketPlayerSetupDetour"); + } + } + + private unsafe void OnLogoutDetour(LogoutCallbackInterface* thisPtr, LogoutCallbackInterface.LogoutParams* logoutParams) + { + var gameGui = Service.GetNullable(); + + if (logoutParams != null) + { + try + { + var type = logoutParams->Type; + var code = logoutParams->Code; + + Log.Debug("Logout: Type {type}, Code {code}", type, code); + + this.IsLoggedIn = false; + + if (this.Logout is { } callback) + { + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(type, code); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", action.Method); + } + } + } + + gameGui?.ResetUiHideState(); + + this.lifecycle.SetLogout(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during OnLogoutDetour"); + } + } + + this.onLogoutHook.Original(thisPtr, logoutParams); + } + private void NetworkHandlersOnCfPop(ContentFinderCondition e) { this.CfPop?.InvokeSafely(e); } - - private void FrameworkOnOnUpdateEvent(IFramework framework1) - { - var condition = Service.GetNullable(); - var gameGui = Service.GetNullable(); - var data = Service.GetNullable(); - - if (condition == null || gameGui == null || data == null) - return; - - if (condition.Any() && this.lastConditionNone && this.LocalPlayer != null) - { - Log.Debug("Is login"); - this.lastConditionNone = false; - this.IsLoggedIn = true; - this.Login?.InvokeSafely(); - gameGui.ResetUiHideState(); - - this.lifecycle.ResetLogout(); - } - - if (!condition.Any() && this.lastConditionNone == false) - { - Log.Debug("Is logout"); - this.lastConditionNone = true; - this.IsLoggedIn = false; - this.Logout?.InvokeSafely(); - gameGui.ResetUiHideState(); - - this.lifecycle.SetLogout(); - } - - this.IsPvP = GameMain.IsInPvPArea(); - this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; - - if (this.IsPvP != this.lastFramePvP) - { - this.lastFramePvP = this.IsPvP; - - if (this.IsPvP) - { - this.EnterPvP?.InvokeSafely(); - } - else - { - this.LeavePvP?.InvokeSafely(); - } - } - } } /// @@ -322,7 +361,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat public event Action? Login; /// - public event Action? Logout; + public event IClientState.LogoutDelegate? Logout; /// public event Action? EnterPvP; @@ -394,7 +433,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat private void LoginForward() => this.Login?.Invoke(); - private void LogoutForward() => this.Logout?.Invoke(); + private void LogoutForward(int type, int code) => this.Logout?.Invoke(type, code); private void EnterPvPForward() => this.EnterPvP?.Invoke(); diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 625271d2a..6b46ffc0d 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -57,6 +57,11 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// public IntPtr SetupTerritoryType { get; private set; } + /// + /// Gets the address of the method which sets up the player. + /// + public IntPtr ProcessPacketPlayerSetup { get; private set; } + /// /// Gets the address of the method which polls the gamepads for data. /// Called every frame, even when `Enable Gamepad` is off in the settings. @@ -82,6 +87,8 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA"); + this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); + // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. // lea rcx, ds:1DB9F74h[rax*4] KeyboardState // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs index 1869dd108..723182111 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs @@ -50,7 +50,7 @@ internal class LogoutEventAgingStep : IAgingStep } } - private void ClientStateOnOnLogout() + private void ClientStateOnOnLogout(int type, int code) { this.hasPassed = true; } diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index 6404846f7..c25ec4ee4 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -89,7 +89,7 @@ internal class AutoUpdateManager : IServiceType t => { t.Result.Login += this.OnLogin; - t.Result.Logout += this.OnLogout; + t.Result.Logout += (int type, int code) => this.OnLogout(); }); Service.GetAsync().ContinueWith(t => { t.Result.Update += this.OnUpdate; }); diff --git a/Dalamud/Plugin/Services/IClientState.cs b/Dalamud/Plugin/Services/IClientState.cs index f7c462d09..bac2b3e3f 100644 --- a/Dalamud/Plugin/Services/IClientState.cs +++ b/Dalamud/Plugin/Services/IClientState.cs @@ -22,6 +22,13 @@ public interface IClientState /// The level of the corresponding ClassJob. public delegate void LevelChangeDelegate(uint classJobId, uint level); + /// + /// A delegate type used for the event. + /// + /// The type of logout. + /// The success/failure code. + public delegate void LogoutDelegate(int type, int code); + /// /// Event that gets fired when the current Territory changes. /// @@ -46,7 +53,7 @@ public interface IClientState /// /// Event that fires when a character is logging out. /// - public event Action Logout; + public event LogoutDelegate Logout; /// /// Event that fires when a character is entering PvP. From 167e76911edabb318b3781ab5788ec9ae50001ca Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 11 Nov 2024 08:39:22 -0800 Subject: [PATCH 098/375] bump clientstructs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index e59323cf3..751645811 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit e59323cf39e6acffd385006c156fa0105c255b54 +Subproject commit 7516458116708bdcf8f72848c7692f312be3b40c From 084f8b55e77c50dd2d01710783676a48ca66500a Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 11 Nov 2024 08:54:57 -0800 Subject: [PATCH 099/375] fix cs breaks --- Dalamud/Game/Addon/Events/PluginEventController.cs | 2 +- .../Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs | 4 ++-- Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/Addon/Events/PluginEventController.cs b/Dalamud/Game/Addon/Events/PluginEventController.cs index d33e2e253..403a812db 100644 --- a/Dalamud/Game/Addon/Events/PluginEventController.cs +++ b/Dalamud/Game/Addon/Events/PluginEventController.cs @@ -165,7 +165,7 @@ internal unsafe class PluginEventController : IDisposable { var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey; var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address; - var eventTypeMatches = currentEvent->Type == eventType; + var eventTypeMatches = currentEvent->State.EventType == eventType; if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs index 51b572985..8affa1eac 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -16,7 +16,7 @@ public unsafe partial class AddonTree { private static readonly Dictionary AddonTypeDict = []; - private static readonly Assembly? ClientStructsAssembly = typeof(Addon).Assembly; + private static readonly Assembly? ClientStructsAssembly = typeof(AddonAttribute).Assembly; /// /// Gets or sets a collection of names for field offsets that have been documented in FFXIVClientStructs. @@ -36,7 +36,7 @@ public unsafe partial class AddonTree { foreach (var t in from t in ClientStructsAssembly.GetTypes() where t.IsPublic - let xivAddonAttr = (Addon?)t.GetCustomAttribute(typeof(Addon), false) + let xivAddonAttr = (AddonAttribute?)t.GetCustomAttribute(typeof(AddonAttribute), false) where xivAddonAttr != null where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName) select t) diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index 6869763c8..0c3a947dd 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -36,7 +36,7 @@ public static class Events ImGui.TableSetupColumn("Type", WidthFixed); ImGui.TableSetupColumn("Param", WidthFixed); ImGui.TableSetupColumn("Flags", WidthFixed); - ImGui.TableSetupColumn("Unk29", WidthFixed); + ImGui.TableSetupColumn("StateFlags1", WidthFixed); ImGui.TableSetupColumn("Target", WidthFixed); ImGui.TableSetupColumn("Listener", WidthFixed); @@ -48,13 +48,13 @@ public static class Events ImGui.TableNextColumn(); ImGui.TextUnformatted($"{i++}"); ImGui.TableNextColumn(); - ImGui.TextUnformatted($"{evt->Type}"); + ImGui.TextUnformatted($"{evt->State.EventType}"); ImGui.TableNextColumn(); ImGui.TextUnformatted($"{evt->Param}"); ImGui.TableNextColumn(); - ImGui.TextUnformatted($"{evt->Flags}"); + ImGui.TextUnformatted($"{evt->State.StateFlags}"); ImGui.TableNextColumn(); - ImGui.TextUnformatted($"{evt->Unk29}"); + ImGui.TextUnformatted($"{evt->State.UnkFlags1}"); ImGui.TableNextColumn(); Gui.ClickToCopyText($"{(nint)evt->Target:X}"); ImGui.TableNextColumn(); From c0f05614c67d7755a751d6ec082be9614bc589e0 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 12 Nov 2024 17:20:29 +0100 Subject: [PATCH 100/375] [api11] Some code cleanup and signature replacements (#2066) * Remove unused code from ChatHandlers * Replace sigs in DalamudAtkTweaks * Resolve LocalContentId by using PlayerState.ContentId * Resolve BuddyList address via UIState.Buddy * Resolve ObjectTable address via GameObjectManager * Resolve FateTable address via FateManager * Resolve GroupManager address via GroupManager * Resolve JobGauges address via JobGaugeManager.CurrentGauge * Simplify ItemHover/Out event * Resolve ToggleUiHide address via RaptureAtkModule.SetUiVisibility * Resolve PopulateItemLinkObject via InventoryItem.Copy * Add byte[].AsPointer extension * Resolve addresses used by ToastGui via UIModule functions * Use Length from Span as ObjectTableLength * Replace OpenMapWithMapLink with CS call * Resolve FrameworkAddressResolver with CS vtable * Drop unnecessary ToArray in HandlePrintMessage * Clean up event calls in HandlePrintMessageDetour * Simplify LocalContentId further This pointer can't be null, because it's part of the .data section. * Compare SeStrings in FlyTextGui with SequenceEqual * Use CS types in FlyTextGuis internal code * Simplify reading SeStrings internally * Remove AsPointer again * Resolve Number/StringArray by type in NamePlateGui * Fix crashes in HandlePrintMessageDetour * Resolve InteractableLinkClicked with LogViewer.HandleLinkClick --- Dalamud/Game/ChatHandlers.cs | 91 +------- Dalamud/Game/ClientState/Buddy/BuddyList.cs | 18 +- Dalamud/Game/ClientState/ClientState.cs | 5 +- .../ClientState/ClientStateAddressResolver.cs | 44 ---- Dalamud/Game/ClientState/Fates/FateTable.cs | 44 +--- .../Game/ClientState/JobGauge/JobGauges.cs | 12 +- .../Game/ClientState/Objects/ObjectTable.cs | 65 +++--- .../ClientState/Objects/Types/Character.cs | 2 +- .../ClientState/Objects/Types/GameObject.cs | 2 +- Dalamud/Game/ClientState/Party/PartyList.cs | 10 +- Dalamud/Game/ClientState/Party/PartyMember.cs | 2 +- Dalamud/Game/Framework.cs | 36 +-- Dalamud/Game/FrameworkAddressResolver.cs | 40 ---- Dalamud/Game/Gui/ChatGui.cs | 182 +++++++-------- Dalamud/Game/Gui/ChatGuiAddressResolver.cs | 28 --- Dalamud/Game/Gui/FlyText/FlyTextGui.cs | 104 ++++----- Dalamud/Game/Gui/GameGui.cs | 147 +++---------- Dalamud/Game/Gui/GameGuiAddressResolver.cs | 18 -- Dalamud/Game/Gui/NamePlate/NamePlateGui.cs | 27 +-- .../Gui/NamePlate/NamePlateUpdateContext.cs | 4 +- Dalamud/Game/Gui/Toast/ToastGui.cs | 208 ++++++------------ .../Game/Gui/Toast/ToastGuiAddressResolver.cs | 30 --- Dalamud/Game/Internal/DalamudAtkTweaks.cs | 45 ++-- .../Windows/Data/Widgets/BuddyListWidget.cs | 4 +- Dalamud/Plugin/Services/IChatGui.cs | 2 +- 25 files changed, 343 insertions(+), 827 deletions(-) delete mode 100644 Dalamud/Game/FrameworkAddressResolver.cs delete mode 100644 Dalamud/Game/Gui/ChatGuiAddressResolver.cs delete mode 100644 Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index a41c6ff7a..c40744ca4 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -1,22 +1,14 @@ -using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using CheapLoc; + using Dalamud.Configuration.Internal; -using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Gui; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Windows; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal; using Dalamud.Utility; @@ -27,49 +19,10 @@ namespace Dalamud.Game; /// Chat events and public helper functions. /// [ServiceManager.EarlyLoadedService] -internal class ChatHandlers : IServiceType +internal partial class ChatHandlers : IServiceType { - private static readonly ModuleLog Log = new("CHATHANDLER"); + private static readonly ModuleLog Log = new("ChatHandlers"); - private readonly Dictionary retainerSaleRegexes = new() - { - { - ClientLanguage.Japanese, - new Regex[] - { - new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.English, - new Regex[] - { - new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.German, - new Regex[] - { - new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled), - new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.French, - new Regex[] - { - new Regex(@"^Un servant a vendu (?.+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled), - } - }, - }; - - private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); - - [ServiceManager.ServiceDependency] - private readonly Dalamud dalamud = Service.Get(); - [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -92,6 +45,9 @@ internal class ChatHandlers : IServiceType /// public bool IsAutoUpdateComplete { get; private set; } + [GeneratedRegex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled)] + private static partial Regex CompiledUrlRegex(); + private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) { var textVal = message.TextValue; @@ -100,7 +56,7 @@ internal class ChatHandlers : IServiceType this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) { // This seems to be in the user block list - let's not show it - Log.Debug("Blocklist triggered"); + Log.Debug("Filtered a message that contained a muted word"); isHandled = true; return; } @@ -127,41 +83,10 @@ internal class ChatHandlers : IServiceType return; #endif - if (type == XivChatType.RetainerSale) - { - foreach (var regex in this.retainerSaleRegexes[(ClientLanguage)this.dalamud.StartInfo.Language]) - { - var matchInfo = regex.Match(message.TextValue); - - // we no longer really need to do/validate the item matching since we read the id from the byte array - // but we'd be checking the main match anyway - var itemInfo = matchInfo.Groups["item"]; - if (!itemInfo.Success) - continue; - - var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload; - if (itemLink == default) - { - Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode())); - break; - } - - Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); - - var valueInfo = matchInfo.Groups["value"]; - // not sure if using a culture here would work correctly, so just strip symbols instead - if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue)) - continue; - - // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ)); - break; - } - } - var messageCopy = message; var senderCopy = sender; - var linkMatch = this.urlRegex.Match(message.TextValue); + var linkMatch = CompiledUrlRegex().Match(message.TextValue); if (linkMatch.Value.Length > 0) this.LastLink = linkMatch.Value; } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index db9b8e562..84cfd24a3 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -1,14 +1,12 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using FFXIVClientStructs.FFXIV.Client.Game.UI; namespace Dalamud.Game.ClientState.Buddy; @@ -28,14 +26,9 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] private BuddyList() { - this.address = this.clientState.AddressResolver; - - Log.Verbose($"Buddy list address {Util.DescribeAddress(this.address.BuddyList)}"); } /// @@ -76,14 +69,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList } } - /// - /// Gets the address of the buddy list. - /// - internal IntPtr BuddyListAddress => this.address.BuddyList; - - private static int BuddyMemberSize { get; } = Marshal.SizeOf(); - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; + private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => &UIState.Instance()->Buddy; /// public IBuddyMember? this[int index] diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 51f25fb5c..750ba34c5 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -17,6 +17,7 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Application.Network; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Event; +using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; @@ -111,7 +112,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState get { var agentMap = AgentMap.Instance(); - return agentMap != null ? AgentMap.Instance()->CurrentMapId : 0; + return agentMap != null ? agentMap->CurrentMapId : 0; } } @@ -119,7 +120,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public IPlayerCharacter? LocalPlayer => Service.GetNullable()?[0] as IPlayerCharacter; /// - public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); + public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId; /// public bool IsLoggedIn { get; private set; } diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 6b46ffc0d..cc635f784 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -7,39 +7,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver { // Static offsets - /// - /// Gets the address of the actor table. - /// - public IntPtr ObjectTable { get; private set; } - - /// - /// Gets the address of the buddy list. - /// - public IntPtr BuddyList { get; private set; } - - /// - /// Gets the address of a pointer to the fate table. - /// - /// - /// This is a static address to a pointer, not the address of the table itself. - /// - public IntPtr FateTablePtr { get; private set; } - - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManager { get; private set; } - - /// - /// Gets the address of the local content id. - /// - public IntPtr LocalContentId { get; private set; } - - /// - /// Gets the address of job gauge data. - /// - public IntPtr JobGaugeData { get; private set; } - /// /// Gets the address of the keyboard state. /// @@ -74,17 +41,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ??"); - - this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04"); - - this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F1 44 0F B7 41"); - - this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 77 71"); - - this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 0D ?? ?? ?? ?? 48 8D 57 08"); - this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8; - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA"); this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index abd5bac41..1bf557ad5 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager; namespace Dalamud.Game.ClientState.Fates; @@ -20,55 +19,34 @@ namespace Dalamud.Game.ClientState.Fates; #pragma warning restore SA1015 internal sealed partial class FateTable : IServiceType, IFateTable { - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] - private FateTable(ClientState clientState) + private FateTable() { - this.address = clientState.AddressResolver; - - Log.Verbose($"Fate table address {Util.DescribeAddress(this.address.FateTablePtr)}"); } /// - public IntPtr Address => this.address.FateTablePtr; + public unsafe IntPtr Address => (nint)CSFateManager.Instance(); /// public unsafe int Length { get { - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) + var fateManager = CSFateManager.Instance(); + if (fateManager == null) return 0; // Sonar used this to check if the table was safe to read - if (Struct->FateDirector == null) + if (fateManager->FateDirector == null) return 0; - if (Struct->Fates.First == null || Struct->Fates.Last == null) + if (fateManager->Fates.First == null || fateManager->Fates.Last == null) return 0; - return Struct->Fates.Count; + return fateManager->Fates.Count; } } - /// - /// Gets the address of the Fate table. - /// - internal unsafe IntPtr FateTableAddress - { - get - { - if (this.address.FateTablePtr == IntPtr.Zero) - return IntPtr.Zero; - - return *(IntPtr*)this.address.FateTablePtr; - } - } - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; - /// public IFate? this[int index] { @@ -99,11 +77,11 @@ internal sealed partial class FateTable : IServiceType, IFateTable if (index >= this.Length) return IntPtr.Zero; - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) + var fateManager = CSFateManager.Instance(); + if (fateManager == null) return IntPtr.Zero; - return (IntPtr)this.Struct->Fates[index].Value; + return (IntPtr)fateManager->Fates[index].Value; } /// diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index e0ae986c5..5a734ed87 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -5,9 +5,8 @@ using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using CSJobGaugeManager = FFXIVClientStructs.FFXIV.Client.Game.JobGaugeManager; namespace Dalamud.Game.ClientState.JobGauge; @@ -21,18 +20,15 @@ namespace Dalamud.Game.ClientState.JobGauge; #pragma warning restore SA1015 internal class JobGauges : IServiceType, IJobGauges { - private Dictionary cache = new(); + private Dictionary cache = []; [ServiceManager.ServiceConstructor] - private JobGauges(ClientState clientState) + private JobGauges() { - this.Address = clientState.AddressResolver.JobGaugeData; - - Log.Verbose($"JobGaugeData address {Util.DescribeAddress(this.Address)}"); } /// - public IntPtr Address { get; } + public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->CurrentGauge); /// public T Get() where T : JobGaugeBase diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index c0b9c6e12..8ea1b582f 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -7,15 +7,17 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using FFXIVClientStructs.Interop; + using Microsoft.Extensions.ObjectPool; -using Serilog; - using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; +using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager; namespace Dalamud.Game.ClientState.Objects; @@ -29,10 +31,12 @@ namespace Dalamud.Game.ClientState.Objects; #pragma warning restore SA1015 internal sealed partial class ObjectTable : IServiceType, IObjectTable { - private const int ObjectTableLength = 599; + private static readonly ModuleLog Log = new("ObjectTable"); + + private static int objectTableLength; private readonly ClientState clientState; - private readonly CachedEntry[] cachedObjectTable = new CachedEntry[ObjectTableLength]; + private readonly CachedEntry[] cachedObjectTable; private readonly ObjectPool multiThreadedEnumerators = new DefaultObjectPoolProvider().Create(); @@ -46,29 +50,30 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable { this.clientState = clientState; - var nativeObjectTableAddress = (CSGameObject**)this.clientState.AddressResolver.ObjectTable; + var nativeObjectTable = CSGameObjectManager.Instance()->Objects.IndexSorted; + objectTableLength = nativeObjectTable.Length; + + this.cachedObjectTable = new CachedEntry[objectTableLength]; for (var i = 0; i < this.cachedObjectTable.Length; i++) - this.cachedObjectTable[i] = new(nativeObjectTableAddress, i); + this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i)); for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++) this.frameworkThreadEnumerators[i] = new(this, i); - - Log.Verbose($"Object table address {Util.DescribeAddress(this.clientState.AddressResolver.ObjectTable)}"); } /// - public nint Address + public unsafe nint Address { get { _ = this.WarnMultithreadedUsage(); - return this.clientState.AddressResolver.ObjectTable; + return (nint)(&CSGameObjectManager.Instance()->Objects); } } /// - public int Length => ObjectTableLength; + public int Length => objectTableLength; /// public IGameObject? this[int index] @@ -77,7 +82,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable { _ = this.WarnMultithreadedUsage(); - return index is >= ObjectTableLength or < 0 ? null : this.cachedObjectTable[index].Update(); + return (index >= objectTableLength || index < 0) ? null : this.cachedObjectTable[index].Update(); } } @@ -120,7 +125,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable { _ = this.WarnMultithreadedUsage(); - return index is < 0 or >= ObjectTableLength ? nint.Zero : (nint)this.cachedObjectTable[index].Address; + return (index >= objectTableLength || index < 0) ? nint.Zero : (nint)this.cachedObjectTable[index].Address; } /// @@ -172,33 +177,21 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable } /// Stores an object table entry, with preallocated concrete types. - internal readonly unsafe struct CachedEntry + /// Initializes a new instance of the struct. + /// A pointer to the object table entry this entry should be pointing to. + internal readonly unsafe struct CachedEntry(Pointer* gameObjectPtr) { - private readonly CSGameObject** gameObjectPtrPtr; - private readonly PlayerCharacter playerCharacter; - private readonly BattleNpc battleNpc; - private readonly Npc npc; - private readonly EventObj eventObj; - private readonly GameObject gameObject; - - /// Initializes a new instance of the struct. - /// The object table that this entry should be pointing to. - /// The slot index inside the table. - public CachedEntry(CSGameObject** ownerTable, int slot) - { - this.gameObjectPtrPtr = ownerTable + slot; - this.playerCharacter = new(nint.Zero); - this.battleNpc = new(nint.Zero); - this.npc = new(nint.Zero); - this.eventObj = new(nint.Zero); - this.gameObject = new(nint.Zero); - } + private readonly PlayerCharacter playerCharacter = new(nint.Zero); + private readonly BattleNpc battleNpc = new(nint.Zero); + private readonly Npc npc = new(nint.Zero); + private readonly EventObj eventObj = new(nint.Zero); + private readonly GameObject gameObject = new(nint.Zero); /// Gets the address of the underlying native object. May be null. public CSGameObject* Address { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => *this.gameObjectPtrPtr; + get => gameObjectPtr->Value; } /// Updates and gets the wrapped game object pointed by this struct. @@ -284,11 +277,11 @@ internal sealed partial class ObjectTable public bool MoveNext() { - if (this.index == ObjectTableLength) + if (this.index == objectTableLength) return false; var cache = this.owner!.cachedObjectTable.AsSpan(); - for (this.index++; this.index < ObjectTableLength; this.index++) + for (this.index++; this.index < objectTableLength; this.index++) { if (cache[this.index].Update() is { } ao) { diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index b04b54301..a91ecc230 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -161,7 +161,7 @@ internal unsafe class Character : GameObject, ICharacter public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray(); /// - public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6); + public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag); /// /// Gets the target object ID of the character. diff --git a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs index f9fd87bf4..4209100f0 100644 --- a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs +++ b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs @@ -197,7 +197,7 @@ internal partial class GameObject internal unsafe partial class GameObject : IGameObject { /// - public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64); + public SeString Name => SeString.Parse(this.Struct->Name); /// public ulong GameObjectId => this.Struct->GetGameObjectId(); diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index 4fbd8fdfb..a016a8211 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -6,9 +6,8 @@ using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Serilog; +using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager; namespace Dalamud.Game.ClientState.Party; @@ -28,14 +27,9 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] private PartyList() { - this.address = this.clientState.AddressResolver; - - Log.Verbose($"Group manager address {Util.DescribeAddress(this.address.GroupManager)}"); } /// @@ -48,7 +42,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0; /// - public IntPtr GroupManagerAddress => this.address.GroupManager; + public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance(); /// public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]); diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index b764431f5..cf620a7ef 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -181,7 +181,7 @@ internal unsafe class PartyMember : IPartyMember /// /// Gets the displayname of this party member. /// - public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref Struct->Name[0]), 0x40); + public SeString Name => SeString.Parse(this.Struct->Name); /// /// Gets the sex of this party member. diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 07942f780..82c7f5f6c 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -16,6 +15,8 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using CSFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework; + namespace Dalamud.Game; /// @@ -31,11 +32,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework private readonly Stopwatch updateStopwatch = new(); private readonly HitchDetector hitchDetector; - private readonly Hook updateHook; - private readonly Hook destroyHook; + private readonly Hook updateHook; + private readonly Hook destroyHook; - private readonly FrameworkAddressResolver addressResolver; - [ServiceManager.ServiceDependency] private readonly GameLifecycle lifecycle = Service.Get(); @@ -51,13 +50,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework private ulong tickCounter; [ServiceManager.ServiceConstructor] - private Framework(TargetSigScanner sigScanner) + private unsafe Framework() { this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch); - this.addressResolver = new FrameworkAddressResolver(); - this.addressResolver.Setup(sigScanner); - this.frameworkDestroy = new(); this.frameworkThreadTaskScheduler = new(); this.FrameworkThreadTaskFactory = new( @@ -66,23 +62,13 @@ internal sealed class Framework : IInternalDisposableService, IFramework TaskContinuationOptions.None, this.frameworkThreadTaskScheduler); - this.updateHook = Hook.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate); - this.destroyHook = Hook.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy); + this.updateHook = Hook.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Tick, this.HandleFrameworkUpdate); + this.destroyHook = Hook.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Destroy, this.HandleFrameworkDestroy); this.updateHook.Enable(); this.destroyHook.Enable(); } - /// - /// A delegate type used during the native Framework::destroy. - /// - /// The native Framework address. - /// A value indicating if the call was successful. - public delegate bool OnRealDestroyDelegate(IntPtr framework); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate bool OnUpdateDetour(IntPtr framework); - /// public event IFramework.OnUpdateDelegate? Update; @@ -390,7 +376,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework } } - private bool HandleFrameworkUpdate(IntPtr framework) + private unsafe bool HandleFrameworkUpdate(CSFramework* thisPtr) { this.frameworkThreadTaskScheduler.BoundThread ??= Thread.CurrentThread; @@ -483,10 +469,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework this.hitchDetector.Stop(); original: - return this.updateHook.OriginalDisposeSafe(framework); + return this.updateHook.OriginalDisposeSafe(thisPtr); } - private bool HandleFrameworkDestroy(IntPtr framework) + private unsafe bool HandleFrameworkDestroy(CSFramework* thisPtr) { this.frameworkDestroy.Cancel(); this.DispatchUpdateEvents = false; @@ -504,7 +490,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework ServiceManager.WaitForServiceUnload(); Log.Information("Framework::Destroy OK!"); - return this.destroyHook.OriginalDisposeSafe(framework); + return this.destroyHook.OriginalDisposeSafe(thisPtr); } } diff --git a/Dalamud/Game/FrameworkAddressResolver.cs b/Dalamud/Game/FrameworkAddressResolver.cs deleted file mode 100644 index 8ea371f2c..000000000 --- a/Dalamud/Game/FrameworkAddressResolver.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Dalamud.Game; - -/// -/// The address resolver for the class. -/// -internal sealed class FrameworkAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address for the function that is called once the Framework is destroyed. - /// - public IntPtr DestroyAddress { get; private set; } - - /// - /// Gets the address for the function that is called once the Framework is free'd. - /// - public IntPtr FreeAddress { get; private set; } - - /// - /// Gets the function that is called every tick. - /// - public IntPtr TickAddress { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.SetupFramework(sig); - } - - private void SetupFramework(ISigScanner scanner) - { - this.DestroyAddress = - scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF"); - - this.FreeAddress = - scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ?? 48 85 C9"); - - this.TickAddress = - scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??"); - } -} diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 6779f32a8..2482c36b4 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -11,11 +11,19 @@ using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; -using Dalamud.Memory; using Dalamud.Plugin.Services; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.System.String; +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; namespace Dalamud.Game.Gui; @@ -27,14 +35,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui { private static readonly ModuleLog Log = new("ChatGui"); - private readonly ChatGuiAddressResolver address; - private readonly Queue chatQueue = new(); private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); private readonly Hook printMessageHook; - private readonly Hook populateItemLinkHook; - private readonly Hook interactableLinkClickedHook; + private readonly Hook inventoryItemCopyHook; + private readonly Hook handleLinkClickHook; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -42,29 +48,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private ImmutableDictionary<(string PluginName, uint CommandId), Action>? dalamudLinkHandlersCopy; [ServiceManager.ServiceConstructor] - private ChatGui(TargetSigScanner sigScanner) + private ChatGui() { - this.address = new ChatGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.printMessageHook = Hook.FromAddress((nint)RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); - this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); - this.interactableLinkClickedHook = Hook.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); + this.printMessageHook = Hook.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); + this.inventoryItemCopyHook = Hook.FromAddress(InventoryItem.Addresses.Copy.Value, this.InventoryItemCopyDetour); + this.handleLinkClickHook = Hook.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour); this.printMessageHook.Enable(); - this.populateItemLinkHook.Enable(); - this.interactableLinkClickedHook.Enable(); + this.inventoryItemCopyHook.Enable(); + this.handleLinkClickHook.Enable(); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, byte silent); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr); - /// public event IChatGui.OnMessageDelegate? ChatMessage; @@ -78,7 +75,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled; /// - public int LastLinkedItemId { get; private set; } + public uint LastLinkedItemId { get; private set; } /// public byte LastLinkedItemFlags { get; private set; } @@ -106,8 +103,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui void IInternalDisposableService.DisposeService() { this.printMessageHook.Dispose(); - this.populateItemLinkHook.Dispose(); - this.interactableLinkClickedHook.Dispose(); + this.inventoryItemCopyHook.Dispose(); + this.handleLinkClickHook.Dispose(); } /// @@ -275,21 +272,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui }); } - private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) + private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) { + this.inventoryItemCopyHook.Original(thisPtr, otherPtr); + try { - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + this.LastLinkedItemId = otherPtr->ItemId; + this.LastLinkedItemFlags = (byte)otherPtr->Flags; - this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8); - this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14); - - // Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}"); + // Log.Verbose($"InventoryItemCopyDetour {thisPtr} {otherPtr} - linked:{this.LastLinkedItemId}"); } catch (Exception ex) { - Log.Error(ex, "Exception onPopulateItemLink hook."); - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + Log.Error(ex, "Exception in InventoryItemCopyHook"); } } @@ -299,58 +295,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui try { - var originalSenderData = sender->AsSpan().ToArray(); - var originalMessageData = message->AsSpan().ToArray(); + var parsedSender = SeString.Parse(sender->AsSpan()); + var parsedMessage = SeString.Parse(message->AsSpan()); - var parsedSender = SeString.Parse(originalSenderData); - var parsedMessage = SeString.Parse(originalMessageData); + var terminatedSender = parsedSender.EncodeWithNullTerminator(); + var terminatedMessage = parsedMessage.EncodeWithNullTerminator(); // Call events var isHandled = false; - var invocationList = this.CheckMessageHandled!.GetInvocationList(); - foreach (var @delegate in invocationList) + if (this.CheckMessageHandled is { } handledCallback) { - try - { - var messageHandledDelegate = @delegate as IChatGui.OnCheckMessageHandledDelegate; - messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); - } - catch (Exception e) - { - Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name); - } - } - - if (!isHandled) - { - invocationList = this.ChatMessage!.GetInvocationList(); - foreach (var @delegate in invocationList) + foreach (var action in handledCallback.GetInvocationList().Cast()) { try { - var messageHandledDelegate = @delegate as IChatGui.OnMessageDelegate; - messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); + action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); } catch (Exception e) { - Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name); + Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", action.Method); } } } - var possiblyModifiedSenderData = parsedSender.Encode(); - var possiblyModifiedMessageData = parsedMessage.Encode(); - - if (!Util.FastByteArrayCompare(originalSenderData, possiblyModifiedSenderData)) + if (!isHandled && this.ChatMessage is { } callback) { - Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(originalSenderData)} -> {parsedSender}"); + foreach (var action in callback.GetInvocationList().Cast()) + { + try + { + action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled); + } + catch (Exception e) + { + Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", action.Method); + } + } + } + + var possiblyModifiedSenderData = parsedSender.EncodeWithNullTerminator(); + var possiblyModifiedMessageData = parsedMessage.EncodeWithNullTerminator(); + + if (!terminatedSender.SequenceEqual(possiblyModifiedSenderData)) + { + Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(terminatedSender)} -> {parsedSender}"); sender->SetString(possiblyModifiedSenderData); } - if (!Util.FastByteArrayCompare(originalMessageData, possiblyModifiedMessageData)) + if (!terminatedMessage.SequenceEqual(possiblyModifiedMessageData)) { - Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(originalMessageData)} -> {parsedMessage}"); + Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(terminatedMessage)} -> {parsedMessage}"); message->SetString(possiblyModifiedMessageData); } @@ -374,42 +369,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui return messageId; } - private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) + private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData) { + if ((Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink) + { + this.handleLinkClickHook.Original(thisPtr, linkData); + return; + } + + Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); + + var sb = LuminaSeStringBuilder.SharedPool.Get(); try { - var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1); + var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload); - if (interactableType != Payload.EmbeddedInfoType.DalamudLink) + // read until link terminator + foreach (var payload in seStringSpan) { - this.interactableLinkClickedHook.Original(managerPtr, messagePtr); - return; + sb.Append(payload); + + if (payload.Type == ReadOnlySePayloadType.Macro && + payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link && + payload.TryGetExpression(out var expr1) && + expr1.TryGetInt(out var expr1Val) && + expr1Val == (int)LinkMacroPayloadType.Terminator) + { + break; + } } - Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); + var seStr = SeString.Parse(sb.ToArray()); + if (seStr.Payloads.Count == 0 || seStr.Payloads[0] is not DalamudLinkPayload link) + return; - var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10); - var seStr = MemoryHelper.ReadSeStringNullTerminated(payloadPtr); - var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator); - var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads; - if (payloads.Count == 0) return; - var linkPayload = payloads[0]; - if (linkPayload is DalamudLinkPayload link) + if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value)) { - if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value)) - { - Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); - value.Invoke(link.CommandId, new SeString(payloads)); - } - else - { - Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); - } + Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); + value.Invoke(link.CommandId, seStr); + } + else + { + Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); } } catch (Exception ex) { - Log.Error(ex, "Exception on InteractableLinkClicked hook"); + Log.Error(ex, "Exception in HandleLinkClickDetour"); + } + finally + { + LuminaSeStringBuilder.SharedPool.Return(sb); } } } @@ -451,7 +461,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled; /// - public int LastLinkedItemId => this.chatGuiService.LastLinkedItemId; + public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId; /// public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags; diff --git a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs deleted file mode 100644 index 366e79fc6..000000000 --- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Dalamud.Game.Gui; - -/// -/// The address resolver for the class. -/// -internal sealed class ChatGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native PopulateItemLinkObject method. - /// - public IntPtr PopulateItemLinkObject { get; private set; } - - /// - /// Gets the address of the native InteractableLinkClicked method. - /// - public IntPtr InteractableLinkClicked { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); - - // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0 - this.PopulateItemLinkObject = sig.ScanText("E8 ?? ?? ?? ?? 8B 4E FC"); - - this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 4B ?? E8 ?? ?? ?? ?? 33 D2"); - } -} diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index 623bc51b3..b01f8c244 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -8,6 +9,9 @@ using Dalamud.IoC.Internal; using Dalamud.Memory; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + using Serilog; namespace Dalamud.Game.Gui.FlyText; @@ -29,7 +33,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui private readonly Hook createFlyTextHook; [ServiceManager.ServiceConstructor] - private FlyTextGui(TargetSigScanner sigScanner) + private unsafe FlyTextGui(TargetSigScanner sigScanner) { this.Address = new FlyTextGuiAddressResolver(); this.Address.Setup(sigScanner); @@ -43,29 +47,29 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui /// /// Private delegate for the native CreateFlyText function's hook. /// - private delegate IntPtr CreateFlyTextDelegate( - IntPtr addonFlyText, + private unsafe delegate nint CreateFlyTextDelegate( + AtkUnitBase* thisPtr, FlyTextKind kind, int val1, int val2, - IntPtr text2, + byte* text2, uint color, uint icon, uint damageTypeIcon, - IntPtr text1, + byte* text1, float yOffset); /// /// Private delegate for the native AddFlyText function pointer. /// - private delegate void AddFlyTextDelegate( - IntPtr addonFlyText, + private unsafe delegate void AddFlyTextDelegate( + AtkUnitBase* thisPtr, uint actorIndex, uint messageMax, - IntPtr numbers, + NumberArrayData* numberArrayData, uint offsetNum, uint offsetNumMax, - IntPtr strings, + StringArrayData* stringArrayData, uint offsetStr, uint offsetStrMax, int unknown); @@ -87,26 +91,16 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon) { // Known valid flytext region within the atk arrays - var numIndex = 30; - var strIndex = 27; var numOffset = 161u; var strOffset = 28u; - // Get the UI module and flytext addon pointers - var gameGui = Service.GetNullable(); - if (gameGui == null) - return; - - var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); - var flytext = gameGui.GetAddonByName("_FlyText"); - - if (ui == null || flytext == IntPtr.Zero) + var flytext = RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText"); + if (flytext == null) return; // Get the number and string arrays we need - var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; - var numArray = atkArrayDataHolder._NumberArrays[numIndex]; - var strArray = atkArrayDataHolder._StringArrays[strIndex]; + var numArray = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.FlyText); + var strArray = AtkStage.Instance()->GetStringArrayData(StringArrayType.FlyText); // Write the values to the arrays using a known valid flytext region numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section @@ -120,44 +114,35 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui numArray->IntArray[numOffset + 8] = 0; // Unknown numArray->IntArray[numOffset + 9] = 0; // Unknown, has something to do with yOffset - strArray->SetValue((int)strOffset + 0, text1.Encode(), false, true, false); - strArray->SetValue((int)strOffset + 1, text2.Encode(), false, true, false); + strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false); + strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false); this.addFlyTextNative( flytext, actorIndex, 1, - (IntPtr)numArray, + numArray, numOffset, 10, - (IntPtr)strArray, + strArray, strOffset, 2, 0); } - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private IntPtr CreateFlyTextDetour( - IntPtr addonFlyText, + private unsafe nint CreateFlyTextDetour( + AtkUnitBase* thisPtr, FlyTextKind kind, int val1, int val2, - IntPtr text2, + byte* text2, uint color, uint icon, uint damageTypeIcon, - IntPtr text1, + byte* text1, float yOffset) { - var retVal = IntPtr.Zero; + var retVal = nint.Zero; try { Log.Verbose("[FlyText] Enter CreateFlyText detour!"); @@ -167,19 +152,19 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui var tmpKind = kind; var tmpVal1 = val1; var tmpVal2 = val2; - var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1); - var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2); + var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1); + var tmpText2 = text2 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text2); var tmpColor = color; var tmpIcon = icon; var tmpDamageTypeIcon = damageTypeIcon; var tmpYOffset = yOffset; - var cmpText1 = tmpText1.ToString(); - var cmpText2 = tmpText2.ToString(); + var originalText1 = tmpText1.EncodeWithNullTerminator(); + var originalText2 = tmpText2.EncodeWithNullTerminator(); - Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " + + Log.Verbose($"[FlyText] Called with addonFlyText({(nint)thisPtr:X}) " + $"kind({kind}) val1({val1}) val2({val2}) damageTypeIcon({damageTypeIcon}) " + - $"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " + + $"text1({(nint)text1:X}, \"{tmpText1}\") text2({(nint)text2:X}, \"{tmpText2}\") " + $"color({color:X}) icon({icon}) yOffset({yOffset})"); Log.Verbose("[FlyText] Calling flytext events!"); this.FlyTextCreated?.Invoke( @@ -204,12 +189,15 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui return IntPtr.Zero; } + var maybeModifiedText1 = tmpText1.EncodeWithNullTerminator(); + var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator(); + // Check if any values have changed var dirty = tmpKind != kind || tmpVal1 != val1 || tmpVal2 != val2 || - tmpText1.ToString() != cmpText1 || - tmpText2.ToString() != cmpText2 || + !maybeModifiedText1.SequenceEqual(originalText1) || + !maybeModifiedText2.SequenceEqual(originalText2) || tmpDamageTypeIcon != damageTypeIcon || tmpColor != color || tmpIcon != icon || @@ -219,28 +207,26 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui if (!dirty) { Log.Verbose("[FlyText] Calling flytext with original args."); - return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, + return this.createFlyTextHook.Original(thisPtr, kind, val1, val2, text2, color, icon, damageTypeIcon, text1, yOffset); } - var terminated1 = Terminate(tmpText1.Encode()); - var terminated2 = Terminate(tmpText2.Encode()); - var pText1 = Marshal.AllocHGlobal(terminated1.Length); - var pText2 = Marshal.AllocHGlobal(terminated2.Length); - Marshal.Copy(terminated1, 0, pText1, terminated1.Length); - Marshal.Copy(terminated2, 0, pText2, terminated2.Length); + var pText1 = Marshal.AllocHGlobal(maybeModifiedText1.Length); + var pText2 = Marshal.AllocHGlobal(maybeModifiedText2.Length); + Marshal.Copy(maybeModifiedText1, 0, pText1, maybeModifiedText1.Length); + Marshal.Copy(maybeModifiedText2, 0, pText2, maybeModifiedText2.Length); Log.Verbose("[FlyText] Allocated and set strings."); retVal = this.createFlyTextHook.Original( - addonFlyText, + thisPtr, tmpKind, tmpVal1, tmpVal2, - pText2, + (byte*)pText2, tmpColor, tmpIcon, tmpDamageTypeIcon, - pText1, + (byte*)pText1, tmpYOffset); Log.Verbose("[FlyText] Returned from original. Delaying free task."); diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 80463a119..74e0a8df3 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -1,4 +1,3 @@ -using System.Numerics; using System.Runtime.InteropServices; using Dalamud.Game.Text.SeStringHandling.Payloads; @@ -18,7 +17,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Common.Component.BGCollision; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using SharpDX; using Vector2 = System.Numerics.Vector2; using Vector3 = System.Numerics.Vector3; @@ -33,21 +31,19 @@ namespace Dalamud.Game.Gui; internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui { private static readonly ModuleLog Log = new("GameGui"); - + + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + private readonly GameGuiAddressResolver address; private readonly Hook setGlobalBgmHook; - private readonly Hook handleItemHoverHook; - private readonly Hook handleItemOutHook; private readonly Hook handleActionHoverHook; private readonly Hook handleActionOutHook; private readonly Hook handleImmHook; - private readonly Hook toggleUiHideHook; + private readonly Hook setUiVisibilityHook; private readonly Hook utf8StringFromSequenceHook; - private GetUIMapObjectDelegate? getUIMapObject; - private OpenMapWithFlagDelegate? openMapWithFlag; - [ServiceManager.ServiceConstructor] private GameGui(TargetSigScanner sigScanner) { @@ -57,32 +53,27 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui Log.Verbose("===== G A M E G U I ====="); Log.Verbose($"GameGuiManager address {Util.DescribeAddress(this.address.BaseAddress)}"); Log.Verbose($"SetGlobalBgm address {Util.DescribeAddress(this.address.SetGlobalBgm)}"); - Log.Verbose($"HandleItemHover address {Util.DescribeAddress(this.address.HandleItemHover)}"); - Log.Verbose($"HandleItemOut address {Util.DescribeAddress(this.address.HandleItemOut)}"); Log.Verbose($"HandleImm address {Util.DescribeAddress(this.address.HandleImm)}"); this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - this.handleItemHoverHook = Hook.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour); - this.handleItemOutHook = Hook.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour); - this.handleActionHoverHook = Hook.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); - this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour); + this.setUiVisibilityHook = Hook.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour); this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour); this.setGlobalBgmHook.Enable(); - this.handleItemHoverHook.Enable(); - this.handleItemOutHook.Enable(); this.handleImmHook.Enable(); - this.toggleUiHideHook.Enable(); + this.setUiVisibilityHook.Enable(); this.handleActionHoverHook.Enable(); this.handleActionOutHook.Enable(); this.utf8StringFromSequenceHook.Enable(); + + this.framework.Update += this.FrameworkUpdate; } // Hooked delegates @@ -90,21 +81,9 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] - private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); @@ -113,9 +92,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, bool uiVisible); /// public event EventHandler? UiHideToggled; @@ -137,33 +113,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// public bool OpenMapWithMapLink(MapLinkPayload mapLink) - { - var uiModule = this.GetUIModule(); - - if (uiModule == IntPtr.Zero) - { - Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()"); - return false; - } - - this.getUIMapObject ??= this.address.GetVirtualFunction(uiModule, 0, 8); - - var uiMapObjectPtr = this.getUIMapObject(uiModule); - - if (uiMapObjectPtr == IntPtr.Zero) - { - Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()"); - return false; - } - - this.openMapWithFlag ??= this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63); - - var mapLinkString = mapLink.DataString; - - Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); - - return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); - } + => RaptureAtkModule.Instance()->OpenMapWithMapLink(mapLink.DataString); /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) @@ -311,11 +261,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// void IInternalDisposableService.DisposeService() { + this.framework.Update -= this.FrameworkUpdate; + this.setGlobalBgmHook.Dispose(); - this.handleItemHoverHook.Dispose(); - this.handleItemOutHook.Dispose(); this.handleImmHook.Dispose(); - this.toggleUiHideHook.Dispose(); + this.setUiVisibilityHook.Dispose(); this.handleActionHoverHook.Dispose(); this.handleActionOutHook.Dispose(); this.utf8StringFromSequenceHook.Dispose(); @@ -359,51 +309,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return retVal; } - private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4); - - if (retVal.ToInt64() == 22) - { - var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); - this.HoveredItem = itemId; - - this.HoveredItemChanged?.InvokeSafely(this, itemId); - - Log.Verbose($"HoverItemId:{itemId} this:{hoverState.ToInt64()}"); - } - - return retVal; - } - - private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4); - - if (a3 != IntPtr.Zero && a4 == 1) - { - var a3Val = Marshal.ReadByte(a3, 0x8); - - if (a3Val == 255) - { - this.HoveredItem = 0ul; - - try - { - this.HoveredItemChanged?.Invoke(this, 0ul); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } - - Log.Verbose("HoverItemId: 0"); - } - } - - return retVal; - } - private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) { this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); @@ -445,16 +350,14 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return retVal; } - private IntPtr ToggleUiHideDetour(IntPtr thisPtr, bool unknownByte) + private unsafe void SetUiVisibilityDetour(RaptureAtkModule* thisPtr, bool uiVisible) { - var result = this.toggleUiHideHook.Original(thisPtr, unknownByte); + this.setUiVisibilityHook.Original(thisPtr, uiVisible); this.GameUiHidden = !RaptureAtkModule.Instance()->IsUiVisible; this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden); - Log.Debug("UiHide toggled: {0}", this.GameUiHidden); - - return result; + Log.Debug("GameUiHidden: {0}", this.GameUiHidden); } private char HandleImmDetour(IntPtr framework, char a2, byte a3) @@ -477,6 +380,24 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe? } + + private unsafe void FrameworkUpdate(IFramework framework) + { + var agentItemDetail = AgentItemDetail.Instance(); + if (agentItemDetail != null) + { + var itemId = agentItemDetail->ItemId; + + if (this.HoveredItem != itemId) + { + Log.Verbose($"HoveredItem changed: {itemId}"); + + this.HoveredItem = itemId; + + this.HoveredItemChanged?.InvokeSafely(this, itemId); + } + } + } } /// diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 5b02a2d09..a742541ea 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -15,16 +15,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// public IntPtr SetGlobalBgm { get; private set; } - /// - /// Gets the address of the native HandleItemHover method. - /// - public IntPtr HandleItemHover { get; private set; } - - /// - /// Gets the address of the native HandleItemOut method. - /// - public IntPtr HandleItemOut { get; private set; } - /// /// Gets the address of the native HandleActionHover method. /// @@ -40,11 +30,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// public IntPtr HandleImm { get; private set; } - /// - /// Gets the address of the native ToggleUiHide method. - /// - public IntPtr ToggleUiHide { get; private set; } - /// /// Gets the address of the native Utf8StringFromSequence method. /// @@ -54,13 +39,10 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver protected override void Setup64Bit(ISigScanner sig) { this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); - this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 6C 24 48 48 8B 74 24 50 4C 89 B7 08 01 00 00"); - this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); - this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 44 0F B6 81"); this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8"); } } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs index 2def0ea00..32192ad21 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Objects; @@ -20,16 +20,6 @@ namespace Dalamud.Game.Gui.NamePlate; [ServiceManager.EarlyLoadedService] internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui { - /// - /// The index for the number array used by the NamePlate addon. - /// - public const int NumberArrayIndex = 5; - - /// - /// The index for the string array used by the NamePlate addon. - /// - public const int StringArrayIndex = 4; - /// /// The index for of the FullUpdate entry in the NamePlate number array. /// @@ -81,18 +71,11 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui /// public unsafe void RequestRedraw() { - var addon = this.gameGui.GetAddonByName("NamePlate"); - if (addon != 0) + var addon = (AddonNamePlate*)this.gameGui.GetAddonByName("NamePlate"); + if (addon != null) { - var raptureAtkModule = RaptureAtkModule.Instance(); - if (raptureAtkModule == null) - { - return; - } - - ((AddonNamePlate*)addon)->DoFullUpdate = 1; - var namePlateNumberArrayData = raptureAtkModule->AtkArrayDataHolder.NumberArrays[NumberArrayIndex]; - namePlateNumberArrayData->SetValue(NumberArrayFullUpdateIndex, 1); + addon->DoFullUpdate = 1; + AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate)->SetValue(NumberArrayFullUpdateIndex, 1); } } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index 876c4c2e0..fef3f9a86 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -140,9 +140,9 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext public void ResetState(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { this.Addon = (AddonNamePlate*)addon; - this.NumberData = numberArrayData[NamePlateGui.NumberArrayIndex]; + this.NumberData = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate); this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray; - this.StringData = stringArrayData[NamePlateGui.StringArrayIndex]; + this.StringData = AtkStage.Instance()->GetStringArrayData(StringArrayType.NamePlate); this.HasParts = false; this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount; diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 0895d1b6b..6b55b3408 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -5,8 +5,11 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Memory; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; + namespace Dalamud.Game.Gui.Toast; /// @@ -17,8 +20,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui { private const uint QuestToastCheckmarkMagic = 60081; - private readonly ToastGuiAddressResolver address; - private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new(); private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new(); private readonly Queue errorQueue = new(); @@ -30,16 +31,12 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui /// /// Initializes a new instance of the class. /// - /// Sig scanner to use. [ServiceManager.ServiceConstructor] - private ToastGui(TargetSigScanner sigScanner) + private unsafe ToastGui() { - this.address = new ToastGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour); - this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour); - this.showErrorToastHook = Hook.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour); + this.showNormalToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowWideText, this.HandleNormalToastDetour); + this.showQuestToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowText, this.HandleQuestToastDetour); + this.showErrorToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowErrorText, this.HandleErrorToastDetour); this.showNormalToastHook.Enable(); this.showQuestToastHook.Enable(); @@ -48,16 +45,16 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui #region Marshal delegates - private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId); + private unsafe delegate void ShowNormalToastDelegate(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId); - private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); + private unsafe delegate void ShowQuestToastDelegate(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); - private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe); + private unsafe delegate void ShowErrorToastDelegate(UIModule* thisPtr, byte* text, byte respectsHidingMaybe); #endregion #region Events - + /// public event IToastGui.OnNormalToastDelegate? Toast; @@ -102,32 +99,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui this.ShowError(message); } } - - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private SeString ParseString(IntPtr text) - { - var bytes = new List(); - unsafe - { - var ptr = (byte*)text; - while (*ptr != 0) - { - bytes.Add(*ptr); - ptr += 1; - } - } - - // call events - return SeString.Parse(bytes.ToArray()); - } } /// @@ -149,36 +120,30 @@ internal sealed partial class ToastGui this.normalQueue.Enqueue((message.Encode(), options)); } - private void ShowNormal(byte[] bytes, ToastOptions? options = null) + private unsafe void ShowNormal(byte[] bytes, ToastOptions? options = null) { options ??= new ToastOptions(); - var manager = Service.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + fixed (byte* ptr = bytes.NullTerminate()) { - fixed (byte* ptr = terminated) - { - this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); - } + this.HandleNormalToastDetour( + UIModule.Instance(), + ptr, + 5, + (byte)options.Position, + (byte)options.Speed, + 0); } } - private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId) + private unsafe void HandleNormalToastDetour(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId) { - if (text == IntPtr.Zero) - { - return IntPtr.Zero; - } + if (text == null) + return; // call events var isHandled = false; - var str = this.ParseString(text); + var str = MemoryHelper.ReadSeStringNullTerminated((nint)text); var options = new ToastOptions { Position = (ToastPosition)isTop, @@ -189,18 +154,17 @@ internal sealed partial class ToastGui // do nothing if handled if (isHandled) - { - return IntPtr.Zero; - } + return; - var terminated = Terminate(str.Encode()); - - unsafe + fixed (byte* ptr = str.EncodeWithNullTerminator()) { - fixed (byte* message = terminated) - { - return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId); - } + this.showNormalToastHook.Original( + thisPtr, + ptr, + layer, + (byte)(options.Position == ToastPosition.Top ? 1 : 0), + (byte)(options.Speed == ToastSpeed.Fast ? 1 : 0), + logMessageId); } } } @@ -224,45 +188,33 @@ internal sealed partial class ToastGui this.questQueue.Enqueue((message.Encode(), options)); } - private void ShowQuest(byte[] bytes, QuestToastOptions? options = null) + private unsafe void ShowQuest(byte[] bytes, QuestToastOptions? options = null) { options ??= new QuestToastOptions(); - var manager = Service.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - var (ioc1, ioc2) = this.DetermineParameterOrder(options); - unsafe + fixed (byte* ptr = bytes.NullTerminate()) { - fixed (byte* ptr = terminated) - { - this.HandleQuestToastDetour( - manager!.Value, - (int)options.Position, - (IntPtr)ptr, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } + this.HandleQuestToastDetour( + UIModule.Instance(), + (int)options.Position, + ptr, + ioc1, + (byte)(options.PlaySound ? 1 : 0), + ioc2, + 0); } } - private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) + private unsafe void HandleQuestToastDetour(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) { - if (text == IntPtr.Zero) - { - return 0; - } + if (text == null) + return; // call events var isHandled = false; - var str = this.ParseString(text); + var str = SeString.Parse(text); var options = new QuestToastOptions { Position = (QuestToastPosition)position, @@ -275,27 +227,20 @@ internal sealed partial class ToastGui // do nothing if handled if (isHandled) - { - return 0; - } - - var terminated = Terminate(str.Encode()); + return; var (ioc1, ioc2) = this.DetermineParameterOrder(options); - unsafe + fixed (byte* ptr = str.EncodeWithNullTerminator()) { - fixed (byte* message = terminated) - { - return this.showQuestToastHook.Original( - manager, - (int)options.Position, - (IntPtr)message, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } + this.showQuestToastHook.Original( + UIModule.Instance(), + (int)options.Position, + ptr, + ioc1, + (byte)(options.PlaySound ? 1 : 0), + ioc2, + 0); } } @@ -324,51 +269,32 @@ internal sealed partial class ToastGui this.errorQueue.Enqueue(message.Encode()); } - private void ShowError(byte[] bytes) + private unsafe void ShowError(byte[] bytes) { - var manager = Service.GetNullable()?.GetUIModule(); - if (manager == null) - return; - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + fixed (byte* ptr = bytes.NullTerminate()) { - fixed (byte* ptr = terminated) - { - this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0); - } + this.HandleErrorToastDetour(UIModule.Instance(), ptr, 0); } } - private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe) + private unsafe void HandleErrorToastDetour(UIModule* thisPtr, byte* text, byte respectsHidingMaybe) { - if (text == IntPtr.Zero) - { - return 0; - } + if (text == null) + return; // call events var isHandled = false; - var str = this.ParseString(text); + var str = SeString.Parse(text); this.ErrorToast?.Invoke(ref str, ref isHandled); // do nothing if handled if (isHandled) - { - return 0; - } + return; - var terminated = Terminate(str.Encode()); - - unsafe + fixed (byte* ptr = str.EncodeWithNullTerminator()) { - fixed (byte* message = terminated) - { - return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe); - } + this.showErrorToastHook.Original(thisPtr, ptr, respectsHidingMaybe); } } } diff --git a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs deleted file mode 100644 index 0a8775540..000000000 --- a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// An address resolver for the class. -/// -internal class ToastGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native ShowNormalToast method. - /// - public IntPtr ShowNormalToast { get; private set; } - - /// - /// Gets the address of the native ShowQuestToast method. - /// - public IntPtr ShowQuestToast { get; private set; } - - /// - /// Gets the address of the ShowErrorToast method. - /// - public IntPtr ShowErrorToast { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); - this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??"); - this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0"); - } -} diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 39ff7c241..c147f76e6 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -6,9 +6,11 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; using Dalamud.Interface.Internal; using Dalamud.Interface.Windowing; +using Dalamud.Logging.Internal; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; -using Serilog; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; @@ -20,12 +22,14 @@ namespace Dalamud.Game.Internal; [ServiceManager.EarlyLoadedService] internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService { + private static readonly ModuleLog Log = new("DalamudAtkTweaks"); + private readonly Hook hookAgentHudOpenSystemMenu; // TODO: Make this into events in Framework.Gui - private readonly Hook hookUiModuleRequestMainCommand; + private readonly Hook hookUiModuleExecuteMainCommand; - private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -44,12 +48,8 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CF 4C 89 B4 24 B8 08 00 00"); this.hookAgentHudOpenSystemMenu = Hook.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); - - var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); - this.hookUiModuleRequestMainCommand = Hook.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - - var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 54 41 57"); - this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); + this.hookUiModuleExecuteMainCommand = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour); + this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour); this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins"); this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); @@ -57,18 +57,14 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService // this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; this.hookAgentHudOpenSystemMenu.Enable(); - this.hookUiModuleRequestMainCommand.Enable(); + this.hookUiModuleExecuteMainCommand.Enable(); this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); } /// Finalizes an instance of the class. ~DalamudAtkTweaks() => this.Dispose(false); - private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); - - private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); - - private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); + private delegate void AgentHudOpenSystemMenuPrototype(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize); /// void IInternalDisposableService.DisposeService() => this.Dispose(true); @@ -81,7 +77,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService if (disposing) { this.hookAgentHudOpenSystemMenu.Dispose(); - this.hookUiModuleRequestMainCommand.Dispose(); + this.hookUiModuleExecuteMainCommand.Dispose(); this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); // this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; @@ -116,22 +112,19 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService } */ - private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) + private void AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", MemoryHelper.ReadSeStringAsString(out _, new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); - - // "SendHotkey" // 3 == Close - if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && this.configuration.IsFocusManagementEnabled) + if (eventType == AtkEventType.InputReceived && WindowSystem.HasAnyWindowSystemFocus && atkEventData != null && *(int*)atkEventData == 3 && this.configuration.IsFocusManagementEnabled) { Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return IntPtr.Zero; + return; } - return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg); + this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, eventType, eventParam, atkEvent, atkEventData); } - private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) + private void AgentHudOpenSystemMenuDetour(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize) { if (WindowSystem.HasAnyWindowSystemFocus && this.configuration.IsFocusManagementEnabled) { @@ -213,7 +206,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); } - private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) + private unsafe void UiModuleExecuteMainCommandDetour(UIModule* thisPtr, uint commandId) { var dalamudInterface = Service.GetNullable(); @@ -226,7 +219,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService dalamudInterface?.OpenSettings(); break; default: - this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); + this.hookUiModuleExecuteMainCommand.Original(thisPtr, commandId); break; } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs index c35280f92..961d3c3c0 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs @@ -1,4 +1,4 @@ -using Dalamud.Game.ClientState.Buddy; +using Dalamud.Game.ClientState.Buddy; using Dalamud.Utility; using ImGuiNET; @@ -32,8 +32,6 @@ internal class BuddyListWidget : IDataWindowWidget var buddyList = Service.Get(); ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - - ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); { var member = buddyList.CompanionBuddy; if (member == null) diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 09f485ac2..42bbd6b06 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -72,7 +72,7 @@ public interface IChatGui /// /// Gets the ID of the last linked item. /// - public int LastLinkedItemId { get; } + public uint LastLinkedItemId { get; } /// /// Gets the flags of the last linked item. From 67e6bcac61844e7e9a897f1b4f5cadf347b8d47a Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Tue, 12 Nov 2024 08:23:22 -0800 Subject: [PATCH 101/375] fix: XivChatType respects the configured chat channel if not set (#2001) --- Dalamud/Game/Gui/ChatGui.cs | 4 +++- Dalamud/Game/Text/XivChatEntry.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 2482c36b4..36e27995d 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -168,8 +168,10 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui var sender = Utf8String.FromSequence(chat.Name.Encode()); var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode()); + + var targetChannel = chat.Type ?? this.configuration.GeneralChatType; - this.HandlePrintMessageDetour(RaptureLogModule.Instance(), chat.Type, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); + this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); sender->Dtor(true); message->Dtor(true); diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index 25f752054..eb40d6636 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -10,7 +10,7 @@ public sealed class XivChatEntry /// /// Gets or sets the type of entry. /// - public XivChatType Type { get; set; } = XivChatType.Debug; + public XivChatType? Type { get; set; } /// /// Gets or sets the message timestamp. From 57cc2f36f9aeb084774089bfe20467f1f242289d Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 12 Nov 2024 11:27:23 -0500 Subject: [PATCH 102/375] feat: Encourage developers to use DLLs for devPlugins (#1958) * Add file dialog to add a dev plugin * Require dev plugins to be paths to the DLL * Only allow .dlls in the dev plugin setting entry * Update dev plugin location hint * update wording --------- Co-authored-by: KazWolfe --- .../Windows/Settings/SettingsEntry.cs | 7 ++ .../Internal/Windows/Settings/SettingsTab.cs | 8 ++ .../Windows/Settings/SettingsWindow.cs | 26 ++++--- .../Widgets/DevPluginsSettingsEntry.cs | 76 +++++++++++++------ Dalamud/Plugin/Internal/PluginManager.cs | 6 +- 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs index a72cae024..46013b72c 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsEntry.cs @@ -40,6 +40,13 @@ public abstract class SettingsEntry /// public abstract void Draw(); + /// + /// Called after the draw function and when the style overrides are removed. + /// + public virtual void PostDraw() + { + } + /// /// Function to be called when the tab is opened. /// diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs index d06fe0fb6..bd4a702f5 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsTab.cs @@ -44,6 +44,14 @@ public abstract class SettingsTab : IDisposable ImGuiHelpers.ScaledDummy(15); } + public virtual void PostDraw() + { + foreach (var settingsEntry in this.Entries) + { + settingsEntry.PostDraw(); + } + } + public virtual void Load() { foreach (var settingsEntry in this.Entries) diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs index 5e3857f42..c678dff10 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs @@ -154,15 +154,23 @@ internal class SettingsWindow : Window } // Don't add padding for the about tab(credits) - using var padding = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, new Vector2(2, 2), - settingsTab is not SettingsTabAbout); - using var borderColor = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.ChildBg)); - using var tabChild = ImRaii.Child( - $"###settings_scrolling_{settingsTab.Title}", - new Vector2(-1, -1), - true); - if (tabChild) - settingsTab.Draw(); + { + using var padding = ImRaii.PushStyle( + ImGuiStyleVar.WindowPadding, + new Vector2(2, 2), + settingsTab is not SettingsTabAbout); + using var borderColor = ImRaii.PushColor( + ImGuiCol.Border, + ImGui.GetColorU32(ImGuiCol.ChildBg)); + using var tabChild = ImRaii.Child( + $"###settings_scrolling_{settingsTab.Title}", + new Vector2(-1, -1), + true); + if (tabChild) + settingsTab.Draw(); + } + + settingsTab.PostDraw(); } else if (settingsTab.IsOpen) { diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs index cfb1ff39f..fe4462ce2 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs @@ -10,6 +10,7 @@ using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; +using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Internal; @@ -25,6 +26,7 @@ public class DevPluginsSettingsEntry : SettingsEntry private bool devPluginLocationsChanged; private string devPluginTempLocation = string.Empty; private string devPluginLocationAddError = string.Empty; + private FileDialogManager fileDialogManager = new(); public DevPluginsSettingsEntry() { @@ -68,7 +70,23 @@ public class DevPluginsSettingsEntry : SettingsEntry } } - ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add dev plugin load locations.\nThese can be either the directory or DLL path.")); + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add dev plugin load locations.\nThis must be a path to the plugin DLL.")); + + var locationSelect = Loc.Localize("DalamudDevPluginLocationSelect", "Select Dev Plugin DLL"); + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Folder, locationSelect)) + { + this.fileDialogManager.OpenFileDialog( + locationSelect, + ".dll", + (result, path) => + { + if (result) + { + this.devPluginTempLocation = path; + this.AddDevPlugin(); + } + }); + } ImGuiHelpers.ScaledDummy(5); @@ -167,26 +185,7 @@ public class DevPluginsSettingsEntry : SettingsEntry ImGui.NextColumn(); if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) - { - this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); - Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); - } - else if (!ValidDevPluginPath(this.devPluginTempLocation)) - { - this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginInvalid", "The entered value is not a valid path to a potential Dev Plugin.\nDid you mean to enter it as a custom plugin repository in the fields below instead?"); - Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); - } - else - { - this.devPluginLocations.Add(new DevPluginLocationSettings - { - Path = this.devPluginTempLocation.Replace("\"", string.Empty), - IsEnabled = true, - }); - this.devPluginLocationsChanged = true; - this.devPluginTempLocation = string.Empty; - } + this.AddDevPlugin(); } ImGui.Columns(1); @@ -197,6 +196,39 @@ public class DevPluginsSettingsEntry : SettingsEntry } } + private void AddDevPlugin() + { + if (this.devPluginLocations.Any( + r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) + { + this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); + Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + } + else if (!ValidDevPluginPath(this.devPluginTempLocation)) + { + this.devPluginLocationAddError = Loc.Localize( + "DalamudDevPluginInvalid", + "The entered value is not a valid path to a potential Dev Plugin.\nDid you mean to enter it as a custom plugin repository in the fields below instead?"); + Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + } + else + { + this.devPluginLocations.Add( + new DevPluginLocationSettings + { + Path = this.devPluginTempLocation.Replace("\"", string.Empty), + IsEnabled = true, + }); + this.devPluginLocationsChanged = true; + this.devPluginTempLocation = string.Empty; + } + } + + public override void PostDraw() + { + this.fileDialogManager.Draw(); + } + private static bool ValidDevPluginPath(string path) - => Path.IsPathRooted(path) && (Path.GetExtension(path) == ".dll" || !Path.Exists(path) || Directory.Exists(path)); + => Path.IsPathRooted(path) && Path.GetExtension(path) == ".dll"; } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 6ad81a806..ddb59c027 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -772,11 +772,7 @@ internal class PluginManager : IInternalDisposableService Log.Verbose("Scanning dev plugins at {Path}", setting.Path); - if (Directory.Exists(setting.Path)) - { - devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); - } - else if (File.Exists(setting.Path)) + if (File.Exists(setting.Path)) { devDllFiles.Add(new FileInfo(setting.Path)); } From 401af52c40c60eae040eca5ad380d27f6e987a3a Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 08:31:41 -0800 Subject: [PATCH 103/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 751645811..9fe41b4d7 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 7516458116708bdcf8f72848c7692f312be3b40c +Subproject commit 9fe41b4d7e4255652e31cebaf00261ade5bbd618 From 486192662ddb453ffcfe8822ea6c34ddd2ccf0d7 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 08:40:10 -0800 Subject: [PATCH 104/375] New SetupTerritoryType sig Co-authored-by: Ottermandias <70807659+ottermandias@users.noreply.github.com> --- Dalamud/Game/ClientState/ClientStateAddressResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index cc635f784..c7d7837a4 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -41,7 +41,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA"); + this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC ?? 48 8B D9 48 89 6C 24"); this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); From 10fc7656d3da9cfea28bbecce360c37196569bc2 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 10:26:21 -0800 Subject: [PATCH 105/375] bump cs, ts name fix --- Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs | 4 ++-- lib/FFXIVClientStructs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index e568ff972..faf99aa2a 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -432,9 +432,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable private unsafe void OnVersionStringDraw(AddonEvent ev, AddonArgs args) { - if (args is not AddonDrawArgs setupArgs) return; + if (args is not AddonDrawArgs drawArgs) return; - var addon = (AtkUnitBase*)setupArgs.Addon; + var addon = (AtkUnitBase*)drawArgs.Addon; var textNode = addon->GetTextNodeById(3); // look and feel init. should be harmless to set. diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 9fe41b4d7..f069f71f5 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 9fe41b4d7e4255652e31cebaf00261ade5bbd618 +Subproject commit f069f71f558664f6f8113f996a4828616deba6e0 From dc3818f42b4267ba29d72e31853d75cdb706585f Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 14:47:16 -0800 Subject: [PATCH 106/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index f069f71f5..3028a2a2a 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit f069f71f558664f6f8113f996a4828616deba6e0 +Subproject commit 3028a2a2ad45043374956d5560992974e312cb69 From c6b0db069e561520b0ab1c5561c95a1cffd99698 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 16:07:28 -0800 Subject: [PATCH 107/375] fix: InventoryItem#Copy is a vfunc now --- Dalamud/Game/Gui/ChatGui.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 36e27995d..fefc82790 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -51,7 +51,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private ChatGui() { this.printMessageHook = Hook.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); - this.inventoryItemCopyHook = Hook.FromAddress(InventoryItem.Addresses.Copy.Value, this.InventoryItemCopyDetour); + this.inventoryItemCopyHook = Hook.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour); this.handleLinkClickHook = Hook.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour); this.printMessageHook.Enable(); From 979bac5d7b08f0de8bf1a634366bf1039f01564d Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 13 Nov 2024 01:18:37 +0100 Subject: [PATCH 108/375] fix nonstandard sizeof() usage --- Dalamud.Boot/utils.cpp | 2 +- Dalamud.Boot/utils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index 65018add4..bbe47db82 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -103,7 +103,7 @@ bool utils::loaded_module::find_imported_function_pointer(const char* pcszDllNam ppFunctionAddress = nullptr; // This span might be too long in terms of meaningful data; it only serves to prevent accessing memory outsides boundaries. - for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof IMAGE_IMPORT_DESCRIPTOR)) { + for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR))) { // Having all zero values signals the end of the table. We didn't find anything. if (!importDescriptor.OriginalFirstThunk && !importDescriptor.TimeDateStamp && !importDescriptor.ForwarderChain && !importDescriptor.FirstThunk) diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index f10e277c0..eef405b26 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -121,7 +121,7 @@ namespace utils { memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect); template&& std::is_standard_layout_v>> - memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof T, dwNewProtect) {} + memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof(T), dwNewProtect) {} template memory_tenderizer(std::span s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {} From e2e11d5f05d499ce92ac7bb4dc0e8d8482dc94e7 Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 13 Nov 2024 01:18:37 +0100 Subject: [PATCH 109/375] fix nonstandard sizeof() usage --- Dalamud.Boot/utils.cpp | 2 +- Dalamud.Boot/utils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index 65018add4..bbe47db82 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -103,7 +103,7 @@ bool utils::loaded_module::find_imported_function_pointer(const char* pcszDllNam ppFunctionAddress = nullptr; // This span might be too long in terms of meaningful data; it only serves to prevent accessing memory outsides boundaries. - for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof IMAGE_IMPORT_DESCRIPTOR)) { + for (const auto& importDescriptor : span_as(directory.VirtualAddress, directory.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR))) { // Having all zero values signals the end of the table. We didn't find anything. if (!importDescriptor.OriginalFirstThunk && !importDescriptor.TimeDateStamp && !importDescriptor.ForwarderChain && !importDescriptor.FirstThunk) diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index f10e277c0..eef405b26 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -121,7 +121,7 @@ namespace utils { memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect); template&& std::is_standard_layout_v>> - memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof T, dwNewProtect) {} + memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof(T), dwNewProtect) {} template memory_tenderizer(std::span s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {} From fd6d52d33b94b769bf61c506b436c5e5c0e5e159 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 20:23:53 -0800 Subject: [PATCH 110/375] Extend IconBrowserWidget range Resolves #2074. Co-authored-by: ItsBexy <103910869+itsbexy@users.noreply.github.com> --- .../Windows/Data/Widgets/IconBrowserWidget.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs index 886f5cb19..ff34574b5 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs @@ -17,6 +17,8 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// public class IconBrowserWidget : IDataWindowWidget { + private const int MaxIconId = 250_000; + private Vector2 iconSize = new(64.0f, 64.0f); private Vector2 editIconSize = new(64.0f, 64.0f); @@ -24,7 +26,7 @@ public class IconBrowserWidget : IDataWindowWidget private Task>? iconIdsTask; private int startRange; - private int stopRange = 200000; + private int stopRange = MaxIconId; private bool showTooltipImage; private Vector2 mouseDragStart; @@ -53,8 +55,8 @@ public class IconBrowserWidget : IDataWindowWidget { var texm = Service.Get(); - var result = new List<(int ItemId, string Path)>(200000); - for (var iconId = 0; iconId < 200000; iconId++) + var result = new List<(int ItemId, string Path)>(MaxIconId); + for (var iconId = 0; iconId < MaxIconId; iconId++) { // // Remove range 170,000 -> 180,000 by default, this specific range causes exceptions. // if (iconId is >= 170000 and < 180000) @@ -119,12 +121,19 @@ public class IconBrowserWidget : IDataWindowWidget ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputInt("##StartRange", ref this.startRange, 0, 0)) + { + this.startRange = Math.Clamp(this.startRange, 0, MaxIconId); this.valueRange = null; + } + ImGui.NextColumn(); ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputInt("##StopRange", ref this.stopRange, 0, 0)) + { + this.stopRange = Math.Clamp(this.stopRange, 0, MaxIconId); this.valueRange = null; + } ImGui.NextColumn(); ImGui.Checkbox("Show Image in Tooltip", ref this.showTooltipImage); From 18363882a7d1f350578364341298fc57cfecccb7 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 12 Nov 2024 21:00:32 -0800 Subject: [PATCH 111/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 3028a2a2a..039aae133 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 3028a2a2ad45043374956d5560992974e312cb69 +Subproject commit 039aae133cc8bed940e85d7bb02e40d3ad6b3435 From d6ac69ff6bc7b03e324c38bed963dd25cac068c8 Mon Sep 17 00:00:00 2001 From: Blair Date: Thu, 14 Nov 2024 02:15:24 +1000 Subject: [PATCH 112/375] Update Lumina to 5.2.1 and Lumina.Excel to 7.1.0 (#2075) --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 ++-- Dalamud/Dalamud.csproj | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 6edea55bf..449d3db68 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index b3f1cf30a..8e912fe8f 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all @@ -160,7 +160,7 @@ - + @@ -168,7 +168,7 @@ ??? - + From f1570cc16899982a691990dba0084c1cea8eac08 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 13 Nov 2024 17:15:57 +0100 Subject: [PATCH 113/375] Update DalamudPackager in .targets (#2076) Goat updated DalamudPackager and released it as Version 11.0.0: https://github.com/goatcorp/DalamudPackager/commit/c4348c30e74ba92225289bb21b7414993614befc On Nuget: https://www.nuget.org/packages/DalamudPackager --- targets/Dalamud.Plugin.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/Dalamud.Plugin.targets b/targets/Dalamud.Plugin.targets index 10b63c437..da897c252 100644 --- a/targets/Dalamud.Plugin.targets +++ b/targets/Dalamud.Plugin.targets @@ -14,7 +14,7 @@ - + From 605246997654a679fd12afdb18fe9936395d7486 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Wed, 13 Nov 2024 08:23:17 -0800 Subject: [PATCH 114/375] bumpy da cs --- Dalamud/Utility/Util.cs | 24 +----------------------- lib/FFXIVClientStructs | 2 +- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index e37afb765..15327a66c 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -162,29 +162,7 @@ public static class Util } /// - /// Gets the amount of commits in the current branch, or null if undetermined. - /// - /// The amount of commits in the current branch. - [Obsolete($"Planned for removal in API 11. Use {nameof(GetScmVersion)} for version tracking.")] - public static int? GetGitCommitCount() - { - if (gitCommitCountInternal != null) - return gitCommitCountInternal.Value; - - var asm = typeof(Util).Assembly; - var attrs = asm.GetCustomAttributes(); - - var value = attrs.First(a => a.Key == "GitCommitCount").Value; - if (value == null) - return null; - - gitCommitCountInternal = int.Parse(value); - return gitCommitCountInternal.Value; - } - - /// - /// Gets the git hash value from the assembly - /// or null if it cannot be found. + /// Gets the git hash value from the assembly or null if it cannot be found. /// /// The git hash of the assembly. public static string? GetGitHashClientStructs() diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 039aae133..78c39ef13 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 039aae133cc8bed940e85d7bb02e40d3ad6b3435 +Subproject commit 78c39ef1318525d766cdf31fd9a32e6f1c7cb453 From 8668ea0d595bcd5bb23455874d4445e3e391b774 Mon Sep 17 00:00:00 2001 From: Infi Date: Wed, 13 Nov 2024 22:43:42 +0100 Subject: [PATCH 115/375] Fix universalis upload with itemId 0 (#2077) --- .../MarketBoardItemRequest.cs | 5 +++++ .../UniversalisMarketBoardUploader.cs | 4 ++-- .../Game/Network/Internal/NetworkHandlers.cs | 20 ++++++++++++------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs index 7af647867..a32a92b13 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs @@ -30,6 +30,11 @@ internal class MarketBoardItemRequest /// public uint AmountToArrive { get; private set; } + /// + /// Gets or sets the offered catalog id used in this listing. + /// + public uint CatalogId { get; internal set; } + /// /// Gets the offered item listings. /// diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index a62de369b..5215cf030 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -48,7 +48,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader { WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0, UploaderId = uploader.ToString(), - ItemId = request.Listings.FirstOrDefault()?.CatalogId ?? 0, + ItemId = request.CatalogId, Listings = [], Sales = [], }; @@ -106,7 +106,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader // ==================================================================================== - Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.Listings.FirstOrDefault()?.CatalogId ?? 0); + Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId); } /// diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index ad8dbb600..2017be4bf 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -367,7 +367,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService })); } - private IObservable> OnMarketBoardSalesBatch( + private IObservable<(uint CatalogId, List Sales)> OnMarketBoardSalesBatch( IObservable start) { var historyObservable = this.MbHistoryObservable.Publish().RefCount(); @@ -390,6 +390,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService // When a start packet is observed, begin observing a window of history packets. // We should only get one packet, which the window closing function ensures. // This packet is flattened to its sale entries and emitted. + uint catalogId = 0; return historyObservable .Do(LogHistoryObserved) .Window(start, UntilBatchEnd) @@ -398,9 +399,12 @@ internal unsafe class NetworkHandlers : IInternalDisposableService new List(), (agg, next) => { + catalogId = next.CatalogId; + agg.AddRange(next.InternalHistoryListings); return agg; - })); + })) + .Select(o => (catalogId, o)); } private IDisposable HandleMarketBoardItemRequest() @@ -411,7 +415,8 @@ internal unsafe class NetworkHandlers : IInternalDisposableService } var startObservable = this.MbItemRequestObservable - .Where(request => request.Ok).Do(LogStartObserved) + .Where(request => request.Ok) + .Do(LogStartObserved) .Publish() .RefCount(); return Observable.When( @@ -432,10 +437,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private void UploadMarketBoardData( MarketBoardItemRequest request, - ICollection sales, + (uint CatalogId, ICollection Sales) sales, ICollection listings) { - var catalogId = listings.FirstOrDefault()?.CatalogId ?? 0; + var catalogId = sales.CatalogId; if (listings.Count != request.AmountToArrive) { Log.Error( @@ -448,10 +453,11 @@ internal unsafe class NetworkHandlers : IInternalDisposableService "Market Board request resolved, starting upload: item#{CatalogId} listings#{ListingsObserved} sales#{SalesObserved}", catalogId, listings.Count, - sales.Count); + sales.Sales.Count); + request.CatalogId = catalogId; request.Listings.AddRange(listings); - request.History.AddRange(sales); + request.History.AddRange(sales.Sales); Task.Run(() => this.uploader.Upload(request)) .ContinueWith( From 9d4bc95f7d648ec6d986a1eb7d6b620d421b434c Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 14 Nov 2024 01:08:07 +0100 Subject: [PATCH 116/375] initial net9 port --- Dalamud.Common/Dalamud.Common.csproj | 1 - Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 2 -- Dalamud.Injector/Dalamud.Injector.csproj | 4 ---- Dalamud.Test/Dalamud.Test.csproj | 8 -------- Dalamud/Dalamud.csproj | 4 ---- Directory.Build.props | 10 +++++++++- build/build.csproj | 1 - global.json | 2 +- tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj | 4 ---- 9 files changed, 10 insertions(+), 26 deletions(-) diff --git a/Dalamud.Common/Dalamud.Common.csproj b/Dalamud.Common/Dalamud.Common.csproj index 594e09021..54a182210 100644 --- a/Dalamud.Common/Dalamud.Common.csproj +++ b/Dalamud.Common/Dalamud.Common.csproj @@ -1,7 +1,6 @@ - net8.0 enable enable diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 449d3db68..1cdfda74e 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -1,9 +1,7 @@ Dalamud.CorePlugin - net8.0-windows x64 - 10.0 true false false diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 1ff29ea66..7b20c3d21 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -1,11 +1,7 @@ - net8.0 win-x64 - x64 - x64;AnyCPU - 10.0 diff --git a/Dalamud.Test/Dalamud.Test.csproj b/Dalamud.Test/Dalamud.Test.csproj index 28e326238..c6c8f8e8a 100644 --- a/Dalamud.Test/Dalamud.Test.csproj +++ b/Dalamud.Test/Dalamud.Test.csproj @@ -1,13 +1,5 @@ - - net8.0-windows - win-x64 - x64 - x64;AnyCPU - 11.0 - - Dalamud.Test Dalamud.Test diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 8e912fe8f..b844edd31 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -1,10 +1,6 @@ - net8.0-windows - x64 - x64;AnyCPU - 12.0 True diff --git a/Directory.Build.props b/Directory.Build.props index 0c5af2e37..4c039e532 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,13 @@ - + + + + net9.0-windows + x64 + x64;AnyCPU + 13.0 + + diff --git a/build/build.csproj b/build/build.csproj index 219b668bd..9222d33ab 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -1,7 +1,6 @@  Exe - net8.0 disable IDE0002;IDE0051;IDE1006;CS0649;CS0169 diff --git a/global.json b/global.json index 7e8286fbe..ab1a4a2ec 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "9.0.0", "rollForward": "latestMinor", "allowPrerelease": true } diff --git a/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj b/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj index 5701e706f..aa6cabedc 100644 --- a/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj +++ b/tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj @@ -2,10 +2,6 @@ Exe - net8.0-windows - x64 - x64;AnyCPU - 12.0 enable enable From 6efa41d33db980e9e56b59c1407428768f676267 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 14 Nov 2024 01:08:39 +0100 Subject: [PATCH 117/375] use net9 SDK in CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 669d6255a..e1fbcac9b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: uses: microsoft/setup-msbuild@v1.0.2 - uses: actions/setup-dotnet@v3 with: - dotnet-version: '8.0.100' + dotnet-version: '9.0.100' - name: Define VERSION run: | $env:COMMIT = $env:GITHUB_SHA.Substring(0, 7) From a5622f30401b4bf174f5d78d8233717da6f40c68 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 14 Nov 2024 01:16:51 +0100 Subject: [PATCH 118/375] possibly work around nuke .NET9 issue --- build/build.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/build/build.csproj b/build/build.csproj index 9222d33ab..37a4d3252 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -11,5 +11,6 @@ + From 3a6312422ce2fcc46a9c185d48bb4897e090ced8 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Wed, 13 Nov 2024 19:44:35 -0800 Subject: [PATCH 119/375] Move HandleActionHover to CS Co-authored-by: Infi --- Dalamud/Game/Gui/GameGui.cs | 2 +- Dalamud/Game/Gui/GameGuiAddressResolver.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 74e0a8df3..b21613a06 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -57,7 +57,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - this.handleActionHoverHook = Hook.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); + this.handleActionHoverHook = Hook.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour); this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index a742541ea..5a404fb2a 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -15,11 +15,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// public IntPtr SetGlobalBgm { get; private set; } - /// - /// Gets the address of the native HandleActionHover method. - /// - public IntPtr HandleActionHover { get; private set; } - /// /// Gets the address of the native HandleActionOut method. /// @@ -39,7 +34,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver protected override void Setup64Bit(ISigScanner sig) { this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); - this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); From 4e76669779de5d95ed93f9443e2c5bd43c478787 Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 14 Nov 2024 06:56:00 +0100 Subject: [PATCH 120/375] Add new city to the upload (#2078) --- .../Universalis/Types/UniversalisTaxData.cs | 6 ++++++ .../Universalis/UniversalisMarketBoardUploader.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs index 72f54773d..2277920a7 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs @@ -48,4 +48,10 @@ internal class UniversalisTaxData /// [JsonProperty("sharlayan")] public uint Sharlayan { get; set; } + + /// + /// Gets or sets Tuliyollal's current tax rate. + /// + [JsonProperty("tuliyollal")] + public uint Tuliyollal { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index 5215cf030..9528a9b50 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -131,6 +131,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader Kugane = taxRates.KuganeTax, Crystarium = taxRates.CrystariumTax, Sharlayan = taxRates.SharlayanTax, + Tuliyollal = taxRates.TuliyollalTax, }, }; From 7c6ed6de76646f8ef0cf47ebdb5abb5828b40d41 Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 14 Nov 2024 06:56:30 +0100 Subject: [PATCH 121/375] Use more of the CS version for ActionDetailHover (#2079) * - Use CS for delegate - Sync HoverActionKind with CS version * Undo changes in favor of #2080 --- Dalamud/Game/Gui/GameGui.cs | 34 ++++----- Dalamud/Game/Gui/HoverActionKind.cs | 107 +++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 18 deletions(-) diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index b21613a06..85ac36a66 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -92,7 +92,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); - + /// public event EventHandler? UiHideToggled; @@ -138,7 +138,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui inView = false; return false; } - + pCoords *= MathF.Abs(1.0f / pCoords.W); screenPos = new Vector2(pCoords.X, pCoords.Y); @@ -166,7 +166,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui worldPos = default; return false; } - + var camera = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CameraManager.Instance()->CurrentCamera; if (camera == null) { @@ -221,7 +221,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); - + /// public IntPtr FindAgentInterface(IntPtr addonPtr) { @@ -422,13 +422,13 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.gameGuiService.HoveredItemChanged += this.HoveredItemForward; this.gameGuiService.HoveredActionChanged += this.HoveredActionForward; } - + /// public event EventHandler? UiHideToggled; - + /// public event EventHandler? HoveredItemChanged; - + /// public event EventHandler? HoveredActionChanged; @@ -444,7 +444,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public HoveredAction HoveredAction => this.gameGuiService.HoveredAction; - + /// void IInternalDisposableService.DisposeService() { @@ -456,7 +456,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui this.HoveredItemChanged = null; this.HoveredActionChanged = null; } - + /// public bool OpenMapWithMapLink(MapLinkPayload mapLink) => this.gameGuiService.OpenMapWithMapLink(mapLink); @@ -464,7 +464,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) => this.gameGuiService.WorldToScreen(worldPos, out screenPos); - + /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos, out bool inView) => this.gameGuiService.WorldToScreen(worldPos, out screenPos, out inView); @@ -476,26 +476,26 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui /// public IntPtr GetUIModule() => this.gameGuiService.GetUIModule(); - + /// public IntPtr GetAddonByName(string name, int index = 1) => this.gameGuiService.GetAddonByName(name, index); - + /// public IntPtr FindAgentInterface(string addonName) => this.gameGuiService.FindAgentInterface(addonName); - + /// - public unsafe IntPtr FindAgentInterface(void* addon) + public unsafe IntPtr FindAgentInterface(void* addon) => this.gameGuiService.FindAgentInterface(addon); /// - public IntPtr FindAgentInterface(IntPtr addonPtr) + public IntPtr FindAgentInterface(IntPtr addonPtr) => this.gameGuiService.FindAgentInterface(addonPtr); private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled); - + private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId); - + private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction); } diff --git a/Dalamud/Game/Gui/HoverActionKind.cs b/Dalamud/Game/Gui/HoverActionKind.cs index 4c7ae9166..ef8fe6400 100644 --- a/Dalamud/Game/Gui/HoverActionKind.cs +++ b/Dalamud/Game/Gui/HoverActionKind.cs @@ -16,6 +16,11 @@ public enum HoverActionKind /// Action = 28, + /// + /// A crafting action is hovered. + /// + CraftingAction = 29, + /// /// A general action is hovered. /// @@ -24,7 +29,7 @@ public enum HoverActionKind /// /// A companion order type of action is hovered. /// - CompanionOrder = 31, + CompanionOrder = 31, // Game Term: BuddyOrder /// /// A main command type of action is hovered. @@ -36,6 +41,11 @@ public enum HoverActionKind /// ExtraCommand = 33, + /// + /// A companion action is hovered. + /// + Companion = 34, + /// /// A pet order type of action is hovered. /// @@ -45,4 +55,99 @@ public enum HoverActionKind /// A trait is hovered. /// Trait = 36, + + /// + /// A buddy action is hovered. + /// + BuddyAction = 37, + + /// + /// A company action is hovered. + /// + CompanyAction = 38, + + /// + /// A mount is hovered. + /// + Mount = 39, + + /// + /// A chocobo race action is hovered. + /// + ChocoboRaceAction = 40, + + /// + /// A chocobo race item is hovered. + /// + ChocoboRaceItem = 41, + + /// + /// A deep dungeon equipment is hovered. + /// + DeepDungeonEquipment = 42, + + /// + /// A deep dungeon equipment 2 is hovered. + /// + DeepDungeonEquipment2 = 43, + + /// + /// A deep dungeon item is hovered. + /// + DeepDungeonItem = 44, + + /// + /// A quick chat is hovered. + /// + QuickChat = 45, + + /// + /// An action combo route is hovered. + /// + ActionComboRoute = 46, + + /// + /// A pvp trait is hovered. + /// + PvPSelectTrait = 47, + + /// + /// A squadron action is hovered. + /// + BgcArmyAction = 48, + + /// + /// A perform action is hovered. + /// + Perform = 49, + + /// + /// A deep dungeon magic stone is hovered. + /// + DeepDungeonMagicStone = 50, + + /// + /// A deep dungeon demiclone is hovered. + /// + DeepDungeonDemiclone = 51, + + /// + /// An eureka magia action is hovered. + /// + EurekaMagiaAction = 52, + + /// + /// An island sanctuary temporary item is hovered. + /// + MYCTemporaryItem = 53, + + /// + /// An ornament is hovered. + /// + Ornament = 54, + + /// + /// Glasses are hovered. + /// + Glasses = 55, } From 097f85eff6386557d1e6a29de3cec9a3d1b1d6e1 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 14 Nov 2024 08:29:28 -0800 Subject: [PATCH 122/375] Move more things to ClientStructs (#2080) * GameGui uses CS methods now Co-authored-by: Infi * Shove even more things over to CS * Clean up NetworkHandlers too * bump cs so things build at least --------- Co-authored-by: Infi --- .../AddonEventManagerAddressResolver.cs | 2 +- Dalamud/Game/ClientState/ClientState.cs | 12 ++- .../ClientState/ClientStateAddressResolver.cs | 11 +-- .../Game/Config/GameConfigAddressResolver.cs | 2 +- .../DutyState/DutyStateAddressResolver.cs | 2 +- Dalamud/Game/Gui/FlyText/FlyTextGui.cs | 73 +++---------------- .../Gui/FlyText/FlyTextGuiAddressResolver.cs | 29 -------- Dalamud/Game/Gui/GameGui.cs | 39 ++++------ Dalamud/Game/Gui/GameGuiAddressResolver.cs | 17 +---- .../PartyFinder/PartyFinderAddressResolver.cs | 18 ----- .../Game/Gui/PartyFinder/PartyFinderGui.cs | 21 ++---- Dalamud/Game/Internal/DalamudAtkTweaks.cs | 6 +- Dalamud/Game/Network/GameNetwork.cs | 30 ++++---- .../Network/GameNetworkAddressResolver.cs | 10 +-- .../Game/Network/Internal/NetworkHandlers.cs | 59 +++++++-------- .../NetworkHandlersAddressResolver.cs | 49 +------------ lib/FFXIVClientStructs | 2 +- 17 files changed, 96 insertions(+), 286 deletions(-) delete mode 100644 Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs delete mode 100644 Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs diff --git a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs index 927ed87ab..415e1b169 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs @@ -16,6 +16,6 @@ internal class AddonEventManagerAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner scanner) { - this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); + this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS } } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 750ba34c5..6ceea4c6b 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -37,7 +37,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private readonly GameLifecycle lifecycle; private readonly ClientStateAddressResolver address; - private readonly Hook setupTerritoryTypeHook; + private readonly Hook setupTerritoryTypeHook; private readonly Hook uiModuleHandlePacketHook; private readonly Hook processPacketPlayerSetupHook; private readonly Hook onLogoutHook; @@ -56,9 +56,10 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language; - Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}"); + var setTerritoryTypeAddr = EventFramework.Addresses.SetTerritoryTypeId.Value; + Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(setTerritoryTypeAddr)}"); - this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); + this.setupTerritoryTypeHook = Hook.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour); this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); this.processPacketPlayerSetupHook = Hook.FromAddress(this.address.ProcessPacketPlayerSetup, this.ProcessPacketPlayerSetupDetour); this.onLogoutHook = Hook.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour); @@ -70,10 +71,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.processPacketPlayerSetupHook.Enable(); this.onLogoutHook.Enable(); } - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType); - + private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); /// diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index c7d7837a4..d44275ef8 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -19,11 +19,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver // Functions - /// - /// Gets the address of the method which sets the territory type. - /// - public IntPtr SetupTerritoryType { get; private set; } - /// /// Gets the address of the method which sets up the player. /// @@ -41,9 +36,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC ?? 48 8B D9 48 89 6C 24"); - - this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); + this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); // not in cs struct // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. // lea rcx, ds:1DB9F74h[rax*4] KeyboardState @@ -51,6 +44,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; - this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); + this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); // unnamed in cs } } diff --git a/Dalamud/Game/Config/GameConfigAddressResolver.cs b/Dalamud/Game/Config/GameConfigAddressResolver.cs index c171932a9..2491c4033 100644 --- a/Dalamud/Game/Config/GameConfigAddressResolver.cs +++ b/Dalamud/Game/Config/GameConfigAddressResolver.cs @@ -13,6 +13,6 @@ internal sealed class GameConfigAddressResolver : BaseAddressResolver /// protected override void Setup64Bit(ISigScanner scanner) { - this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); + this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); // unnamed in CS } } diff --git a/Dalamud/Game/DutyState/DutyStateAddressResolver.cs b/Dalamud/Game/DutyState/DutyStateAddressResolver.cs index 01a0d39b7..1bca93efb 100644 --- a/Dalamud/Game/DutyState/DutyStateAddressResolver.cs +++ b/Dalamud/Game/DutyState/DutyStateAddressResolver.cs @@ -16,6 +16,6 @@ internal class DutyStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. protected override void Setup64Bit(ISigScanner sig) { - this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); + this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); // unnamed in cs } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index b01f8c244..cbf166cfc 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -23,62 +23,21 @@ namespace Dalamud.Game.Gui.FlyText; internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui { /// - /// The native function responsible for adding fly text to the UI. See . + /// The hook that fires when the game creates a fly text element. /// - private readonly AddFlyTextDelegate addFlyTextNative; - - /// - /// The hook that fires when the game creates a fly text element. See . - /// - private readonly Hook createFlyTextHook; + private readonly Hook createFlyTextHook; [ServiceManager.ServiceConstructor] private unsafe FlyTextGui(TargetSigScanner sigScanner) { - this.Address = new FlyTextGuiAddressResolver(); - this.Address.Setup(sigScanner); - - this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); - this.createFlyTextHook = Hook.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour); + this.createFlyTextHook = Hook.FromAddress(AddonFlyText.Addresses.CreateFlyText.Value, this.CreateFlyTextDetour); this.createFlyTextHook.Enable(); } - /// - /// Private delegate for the native CreateFlyText function's hook. - /// - private unsafe delegate nint CreateFlyTextDelegate( - AtkUnitBase* thisPtr, - FlyTextKind kind, - int val1, - int val2, - byte* text2, - uint color, - uint icon, - uint damageTypeIcon, - byte* text1, - float yOffset); - - /// - /// Private delegate for the native AddFlyText function pointer. - /// - private unsafe delegate void AddFlyTextDelegate( - AtkUnitBase* thisPtr, - uint actorIndex, - uint messageMax, - NumberArrayData* numberArrayData, - uint offsetNum, - uint offsetNumMax, - StringArrayData* stringArrayData, - uint offsetStr, - uint offsetStrMax, - int unknown); - /// public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated; - private FlyTextGuiAddressResolver Address { get; } - /// /// Disposes of managed and unmanaged resources. /// @@ -94,7 +53,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui var numOffset = 161u; var strOffset = 28u; - var flytext = RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText"); + var flytext = (AddonFlyText*)RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText"); if (flytext == null) return; @@ -116,23 +75,13 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false); strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false); - - this.addFlyTextNative( - flytext, - actorIndex, - 1, - numArray, - numOffset, - 10, - strArray, - strOffset, - 2, - 0); + + flytext->AddFlyText(actorIndex, 1, numArray, numOffset, 10, strArray, strOffset, 2, 0); } private unsafe nint CreateFlyTextDetour( - AtkUnitBase* thisPtr, - FlyTextKind kind, + AddonFlyText* thisPtr, + int kind, int val1, int val2, byte* text2, @@ -149,7 +98,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui var handled = false; - var tmpKind = kind; + var tmpKind = (FlyTextKind)kind; var tmpVal1 = val1; var tmpVal2 = val2; var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1); @@ -193,7 +142,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator(); // Check if any values have changed - var dirty = tmpKind != kind || + var dirty = (int)tmpKind != kind || tmpVal1 != val1 || tmpVal2 != val2 || !maybeModifiedText1.SequenceEqual(originalText1) || @@ -219,7 +168,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui retVal = this.createFlyTextHook.Original( thisPtr, - tmpKind, + (int)tmpKind, tmpVal1, tmpVal2, (byte*)pText2, diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs b/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs deleted file mode 100644 index aa7c8026f..000000000 --- a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Dalamud.Game.Gui.FlyText; - -/// -/// An address resolver for the class. -/// -internal class FlyTextGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native AddFlyText method, which occurs - /// when the game adds fly text elements to the UI. Multiple fly text - /// elements can be added in a single AddFlyText call. - /// - public IntPtr AddFlyText { get; private set; } - - /// - /// Gets the address of the native CreateFlyText method, which occurs - /// when the game creates a new fly text element. This method is called - /// once per fly text element, and can be called multiple times per - /// AddFlyText call. - /// - public IntPtr CreateFlyText { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7"); - this.CreateFlyText = sig.ScanText("E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B 18"); - } -} diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 85ac36a66..54f4253cd 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -38,11 +38,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui private readonly GameGuiAddressResolver address; private readonly Hook setGlobalBgmHook; - private readonly Hook handleActionHoverHook; - private readonly Hook handleActionOutHook; + private readonly Hook handleActionHoverHook; + private readonly Hook handleActionOutHook; private readonly Hook handleImmHook; private readonly Hook setUiVisibilityHook; - private readonly Hook utf8StringFromSequenceHook; + private readonly Hook utf8StringFromSequenceHook; [ServiceManager.ServiceConstructor] private GameGui(TargetSigScanner sigScanner) @@ -57,14 +57,14 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - this.handleActionHoverHook = Hook.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour); - this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); + this.handleActionHoverHook = Hook.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour); + this.handleActionOutHook = Hook.FromAddress((nint)AgentActionDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleActionOutDetour); this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); this.setUiVisibilityHook = Hook.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour); - this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour); + this.utf8StringFromSequenceHook = Hook.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour); this.setGlobalBgmHook.Enable(); this.handleImmHook.Enable(); @@ -77,19 +77,10 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui } // Hooked delegates - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen); - + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); @@ -309,24 +300,24 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return retVal; } - private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) + private void HandleActionHoverDetour(AgentActionDetail* hoverState, ActionKind actionKind, uint actionId, int a4, byte a5) { this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); - this.HoveredAction.ActionKind = actionKind; + this.HoveredAction.ActionKind = (HoverActionKind)actionKind; this.HoveredAction.BaseActionID = actionId; - this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); + this.HoveredAction.ActionID = hoverState->ActionId; this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); - Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{hoverState.ToInt64():X}"); + Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{(nint)hoverState:X}"); } - private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) + private AtkValue* HandleActionOutDetour(AgentActionDetail* agentActionDetail, AtkValue* a2, AtkValue* a3, uint a4, ulong a5) { - var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); + var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4, a5); - if (a3 != IntPtr.Zero && a4 == 1) + if (a3 != null && a4 == 1) { - var a3Val = Marshal.ReadByte(a3, 0x8); + var a3Val = a3->Int; if (a3Val == 255) { diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 5a404fb2a..bdd579c7e 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -15,28 +15,15 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// public IntPtr SetGlobalBgm { get; private set; } - /// - /// Gets the address of the native HandleActionOut method. - /// - public IntPtr HandleActionOut { get; private set; } - /// /// Gets the address of the native HandleImm method. /// public IntPtr HandleImm { get; private set; } - /// - /// Gets the address of the native Utf8StringFromSequence method. - /// - public IntPtr Utf8StringFromSequence { get; private set; } - /// protected override void Setup64Bit(ISigScanner sig) { - this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); - this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); - this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); - - this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8"); + this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); // unnamed in CS + this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); // unnamed in CS } } diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs deleted file mode 100644 index 0b267694c..000000000 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Dalamud.Game.Gui.PartyFinder; - -/// -/// The address resolver for the class. -/// -internal class PartyFinderAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native ReceiveListing method. - /// - public IntPtr ReceiveListing { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC ?? 48 8B D9 4C 8B FA"); - } -} diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index ef4055b29..0b25a87be 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -7,6 +7,8 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI.Info; + using Serilog; namespace Dalamud.Game.Gui.PartyFinder; @@ -15,12 +17,11 @@ namespace Dalamud.Game.Gui.PartyFinder; /// This class handles interacting with the native PartyFinder window. /// [ServiceManager.EarlyLoadedService] -internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui +internal sealed unsafe class PartyFinderGui : IInternalDisposableService, IPartyFinderGui { - private readonly PartyFinderAddressResolver address; private readonly nint memory; - private readonly Hook receiveListingHook; + private readonly Hook receiveListingHook; /// /// Initializes a new instance of the class. @@ -29,18 +30,12 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG [ServiceManager.ServiceConstructor] private PartyFinderGui(TargetSigScanner sigScanner) { - this.address = new PartyFinderAddressResolver(); - this.address.Setup(sigScanner); - this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); - this.receiveListingHook = Hook.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour); + this.receiveListingHook = Hook.FromAddress(InfoProxyCrossRealm.Addresses.ReceiveListing.Value, this.HandleReceiveListingDetour); this.receiveListingHook.Enable(); } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ReceiveListingDelegate(nint managerPtr, nint data); - /// public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing; @@ -61,18 +56,18 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG } } - private void HandleReceiveListingDetour(nint managerPtr, nint data) + private void HandleReceiveListingDetour(InfoProxyCrossRealm* infoProxy, nint packet) { try { - this.HandleListingEvents(data); + this.HandleListingEvents(packet); } catch (Exception ex) { Log.Error(ex, "Exception on ReceiveListing hook."); } - this.receiveListingHook.Original(managerPtr, data); + this.receiveListingHook.Original(infoProxy, packet); } private void HandleListingEvents(nint data) diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index c147f76e6..7834ab58f 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -24,7 +24,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService { private static readonly ModuleLog Log = new("DalamudAtkTweaks"); - private readonly Hook hookAgentHudOpenSystemMenu; + private readonly Hook hookAgentHudOpenSystemMenu; // TODO: Make this into events in Framework.Gui private readonly Hook hookUiModuleExecuteMainCommand; @@ -45,9 +45,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService [ServiceManager.ServiceConstructor] private DalamudAtkTweaks(TargetSigScanner sigScanner) { - var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CF 4C 89 B4 24 B8 08 00 00"); - - this.hookAgentHudOpenSystemMenu = Hook.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); + this.hookAgentHudOpenSystemMenu = Hook.FromAddress(AgentHUD.Addresses.OpenSystemMenu.Value, this.AgentHudOpenSystemMenuDetour); this.hookUiModuleExecuteMainCommand = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour); this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour); diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index b51232ece..5022eb93d 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -6,6 +6,9 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Network; + using Serilog; namespace Dalamud.Game.Network; @@ -14,10 +17,10 @@ namespace Dalamud.Game.Network; /// This class handles interacting with game network events. /// [ServiceManager.EarlyLoadedService] -internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork +internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetwork { private readonly GameNetworkAddressResolver address; - private readonly Hook processZonePacketDownHook; + private readonly Hook processZonePacketDownHook; private readonly Hook processZonePacketUpHook; private readonly HitchDetector hitchDetectorUp; @@ -25,11 +28,9 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); - - private IntPtr baseAddress; - + [ServiceManager.ServiceConstructor] - private GameNetwork(TargetSigScanner sigScanner) + private unsafe GameNetwork(TargetSigScanner sigScanner) { this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch); this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch); @@ -37,20 +38,19 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork this.address = new GameNetworkAddressResolver(); this.address.Setup(sigScanner); + var onReceivePacketAddress = (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket; + Log.Verbose("===== G A M E N E T W O R K ====="); - Log.Verbose($"ProcessZonePacketDown address {Util.DescribeAddress(this.address.ProcessZonePacketDown)}"); + Log.Verbose($"OnReceivePacket address {Util.DescribeAddress(onReceivePacketAddress)}"); Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}"); - this.processZonePacketDownHook = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); + this.processZonePacketDownHook = Hook.FromAddress(onReceivePacketAddress, this.ProcessZonePacketDownDetour); this.processZonePacketUpHook = Hook.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); this.processZonePacketDownHook.Enable(); this.processZonePacketUpHook.Enable(); } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); @@ -64,10 +64,8 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork this.processZonePacketUpHook.Dispose(); } - private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) + private void ProcessZonePacketDownDetour(PacketDispatcher* dispatcher, uint targetId, IntPtr dataPtr) { - this.baseAddress = a; - this.hitchDetectorDown.Start(); // Go back 0x10 to get back to the start of the packet header @@ -78,7 +76,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork // Call events this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); + this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10); } catch (Exception ex) { @@ -96,7 +94,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); + this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10); } this.hitchDetectorDown.Stop(); diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs index 69b97c59d..de92f7c10 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -5,11 +5,6 @@ namespace Dalamud.Game.Network; /// internal sealed class GameNetworkAddressResolver : BaseAddressResolver { - /// - /// Gets the address of the ProcessZonePacketDown method. - /// - public IntPtr ProcessZonePacketDown { get; private set; } - /// /// Gets the address of the ProcessZonePacketUp method. /// @@ -18,9 +13,6 @@ internal sealed class GameNetworkAddressResolver : BaseAddressResolver /// protected override void Setup64Bit(ISigScanner sig) { - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); - this.ProcessZonePacketDown = sig.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2"); - this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); + this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); // unnamed in cs } } diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 2017be4bf..a25444f10 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -15,6 +15,9 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; using Dalamud.Networking.Http; using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using FFXIVClientStructs.FFXIV.Client.Network; using FFXIVClientStructs.FFXIV.Client.UI.Info; using Lumina.Excel.Sheets; using Serilog; @@ -35,13 +38,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private readonly NetworkHandlersAddressResolver addressResolver; - private readonly Hook cfPopHook; - private readonly Hook mbPurchaseHook; - private readonly Hook mbHistoryHook; + private readonly Hook cfPopHook; + private readonly Hook mbPurchaseHook; + private readonly Hook mbHistoryHook; private readonly Hook customTalkHook; // used for marketboard taxes - private readonly Hook mbItemRequestStartHook; - private readonly Hook mbOfferingsHook; - private readonly Hook mbSendPurchaseRequestHook; + private readonly Hook mbItemRequestStartHook; + private readonly Hook mbOfferingsHook; + private readonly Hook mbSendPurchaseRequestHook; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -134,14 +137,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.handleMarketBoardPurchaseHandler = this.HandleMarketBoardPurchaseHandler(); this.mbPurchaseHook = - Hook.FromAddress( - this.addressResolver.MarketBoardPurchasePacketHandler, + Hook.FromAddress( + PacketDispatcher.Addresses.HandleMarketBoardPurchasePacket.Value, this.MarketPurchasePacketDetour); this.mbPurchaseHook.Enable(); this.mbHistoryHook = - Hook.FromAddress( - this.addressResolver.MarketBoardHistoryPacketHandler, + Hook.FromAddress( + InfoProxyItemSearch.Addresses.ProcessItemHistory.Value, this.MarketHistoryPacketDetour); this.mbHistoryHook.Enable(); @@ -151,22 +154,22 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.CustomTalkReceiveResponseDetour); this.customTalkHook.Enable(); - this.mbItemRequestStartHook = Hook.FromAddress( - this.addressResolver.MarketBoardItemRequestStartPacketHandler, + this.mbItemRequestStartHook = Hook.FromAddress( + PacketDispatcher.Addresses.HandleMarketBoardItemRequestStartPacket.Value, this.MarketItemRequestStartDetour); this.mbItemRequestStartHook.Enable(); - this.mbOfferingsHook = Hook.FromAddress( - this.addressResolver.InfoProxyItemSearchAddPage, + this.mbOfferingsHook = Hook.FromAddress( + (nint)InfoProxyItemSearch.StaticVirtualTablePointer->AddPage, this.MarketBoardOfferingsDetour); this.mbOfferingsHook.Enable(); - this.mbSendPurchaseRequestHook = Hook.FromAddress( - this.addressResolver.BuildMarketBoardPurchaseHandlerPacket, + this.mbSendPurchaseRequestHook = Hook.FromAddress( + InfoProxyItemSearch.Addresses.SendPurchaseRequestPacket.Value, this.MarketBoardSendPurchaseRequestDetour); this.mbSendPurchaseRequestHook.Enable(); - this.cfPopHook = Hook.FromAddress(this.addressResolver.CfPopPacketHandler, this.CfPopDetour); + this.cfPopHook = Hook.FromAddress(PublicContentDirector.Addresses.HandleEnterContentInfoPacket.Value, this.CfPopDetour); this.cfPopHook.Enable(); } @@ -183,8 +186,6 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private delegate byte MarketBoardSendPurchaseRequestPacket(InfoProxyItemSearch* infoProxy); - private delegate nint CfPopDelegate(nint packetData); - /// /// Event which gets fired when a duty is ready. /// @@ -263,7 +264,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.cfPopHook.Dispose(); } - private unsafe nint CfPopDetour(nint packetData) + private unsafe nint CfPopDetour(PublicContentDirector.EnterContentInfoPacket* packetData) { var result = this.cfPopHook.OriginalDisposeSafe(packetData); @@ -529,7 +530,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService return this.configuration.IsMbCollect; } - private nint MarketPurchasePacketDetour(nint a1, nint packetData) + private void MarketPurchasePacketDetour(PacketDispatcher* a1, nint packetData) { try { @@ -540,10 +541,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketPurchasePacketHandler threw an exception"); } - return this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData); + this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData); } - private nint MarketHistoryPacketDetour(nint a1, nint packetData, uint a3, char a4) + private void MarketHistoryPacketDetour(InfoProxyItemSearch* a1, nint packetData) { try { @@ -554,7 +555,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketHistoryPacketDetour threw an exception"); } - return this.mbHistoryHook.OriginalDisposeSafe(a1, packetData, a3, a4); + this.mbHistoryHook.OriginalDisposeSafe(a1, packetData); } private void CustomTalkReceiveResponseDetour(nuint a1, ushort eventId, byte responseId, uint* args, byte argCount) @@ -573,7 +574,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount); } - private nint MarketItemRequestStartDetour(nint a1, nint packetRef) + private void MarketItemRequestStartDetour(PacketDispatcher* a1, nint packetRef) { try { @@ -584,10 +585,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketItemRequestStartDetour threw an exception"); } - return this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef); + this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef); } - private byte MarketBoardOfferingsDetour(nint a1, nint packetRef) + private void MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetRef) { try { @@ -598,10 +599,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketBoardOfferingsDetour threw an exception"); } - return this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef); + this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef); } - private byte MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch) + private bool MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch) { try { diff --git a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs index 166b4d67a..18c48b67d 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs @@ -5,62 +5,17 @@ /// internal class NetworkHandlersAddressResolver : BaseAddressResolver { - /// - /// Gets or sets the pointer to the method responsible for handling CfPop packets. - /// - public nint CfPopPacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the method responsible for handling market board history. In this case, we are - /// sigging the packet handler method directly. - /// - public nint MarketBoardHistoryPacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the method responsible for processing the market board purchase packet. In this - /// case, we are sigging the packet handler method directly. - /// - public nint MarketBoardPurchasePacketHandler { get; set; } - /// /// Gets or sets the pointer to the method responsible for custom talk events. Necessary for marketboard tax data, /// as this isn't really exposed anywhere else. /// public nint CustomTalkEventResponsePacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the method responsible for the marketboard ItemRequestStart packet. - /// - public nint MarketBoardItemRequestStartPacketHandler { get; set; } - - /// - /// Gets or sets the pointer to the InfoProxyItemSearch.AddPage method, used to load market data. - /// - public nint InfoProxyItemSearchAddPage { get; set; } - - /// - /// Gets or sets the pointer to the method inside InfoProxyItemSearch that is responsible for building and sending - /// a purchase request packet. - /// - public nint BuildMarketBoardPurchaseHandlerPacket { get; set; } /// protected override void Setup64Bit(ISigScanner scanner) { - this.CfPopPacketHandler = scanner.ScanText("40 53 57 48 83 EC 78 48 8B D9 48 8D 0D"); - - // TODO: I know this is a CC. I want things working for now. (KW) - this.MarketBoardHistoryPacketHandler = scanner.ScanText( - "40 53 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 85 C0 74 2F 4C 8B 00 48 8B C8 41 FF 90 18 01 00 00 48 8B C8 BA 0B 00 00 00 E8 ?? ?? ?? ?? 48 85 C0 74 10 48 8B D3 48 8B C8 48 83 C4 20 5B E9 ?? ?? ?? ?? 48 83 C4 20 5B C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 40 53"); - this.MarketBoardPurchasePacketHandler = - scanner.ScanText("40 55 56 41 56 48 8B EC 48 83 EC ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 ?? 48 8B 0D ?? ?? ?? ?? 4C 8B F2"); this.CustomTalkEventResponsePacketHandler = - scanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); - this.MarketBoardItemRequestStartPacketHandler = - scanner.ScanText("48 89 5C 24 08 57 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B FA E8 ?? ?? ?? ?? 48 8B D8 48 85 C0 74 4A"); - this.InfoProxyItemSearchAddPage = - scanner.ScanText("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54"); - this.BuildMarketBoardPurchaseHandlerPacket = - scanner.ScanText("40 53 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B D9 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B D0 48 85 C0 0F 84 ?? ?? ?? ?? 8B 8B"); + scanner.ScanText( + "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); // unnamed in CS } } diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 78c39ef13..121f2f8d8 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 78c39ef1318525d766cdf31fd9a32e6f1c7cb453 +Subproject commit 121f2f8d82bce8632e79790a002d5e1ac17a635c From 913fe25cc562119e5fe864b02efc7c2197c4f111 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 08:47:32 -0800 Subject: [PATCH 123/375] InventoryItem size bump --- Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 53aa9a9d9..145ddf841 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -18,7 +18,7 @@ public unsafe struct GameInventoryItem : IEquatable [FieldOffset(0)] internal readonly InventoryItem InternalItem; - private const int StructSizeInBytes = 0x40; + private const int StructSizeInBytes = 0x48; /// /// The view of the backing data, in . From 76df39f7b3e105d9c479ee3d59453267903dfc63 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 10:36:32 -0800 Subject: [PATCH 124/375] bump cs again --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 121f2f8d8..8ed804220 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 121f2f8d82bce8632e79790a002d5e1ac17a635c +Subproject commit 8ed8042205701aa5a1eb32c0fe437e006202fb06 From 7feeae99101b5e729cfbc2d279c238fbc70c2e81 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 15:21:07 -0800 Subject: [PATCH 125/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 8ed804220..6cc4f1c12 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 8ed8042205701aa5a1eb32c0fe437e006202fb06 +Subproject commit 6cc4f1c12c37785101a262cceac249753db3b1f5 From b483b63bf2d82327580c6e5f0c784a7fae7db897 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 15:34:56 -0800 Subject: [PATCH 126/375] GameInventoryItem matches StructSize from CS --- Dalamud/Game/Inventory/GameInventoryItem.cs | 20 +++++--------------- lib/FFXIVClientStructs | 2 +- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 145ddf841..fb27346e0 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -9,7 +9,7 @@ namespace Dalamud.Game.Inventory; /// /// Dalamud wrapper around a ClientStructs InventoryItem. /// -[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)] +[StructLayout(LayoutKind.Explicit, Size = InventoryItem.StructSize)] public unsafe struct GameInventoryItem : IEquatable { /// @@ -17,22 +17,12 @@ public unsafe struct GameInventoryItem : IEquatable /// [FieldOffset(0)] internal readonly InventoryItem InternalItem; - - private const int StructSizeInBytes = 0x48; - + /// /// The view of the backing data, in . /// [FieldOffset(0)] - private fixed ulong dataUInt64[StructSizeInBytes / 0x8]; - - static GameInventoryItem() - { - Debug.Assert( - sizeof(InventoryItem) == StructSizeInBytes, - $"Definition of {nameof(InventoryItem)} has been changed. " + - $"Update {nameof(StructSizeInBytes)} to {sizeof(InventoryItem)} to accommodate for the size change."); - } + private fixed ulong dataUInt64[InventoryItem.StructSize / 0x8]; /// /// Initializes a new instance of the struct. @@ -157,7 +147,7 @@ public unsafe struct GameInventoryItem : IEquatable /// true if the current object is equal to the parameter; otherwise, false. public readonly bool Equals(in GameInventoryItem other) { - for (var i = 0; i < StructSizeInBytes / 8; i++) + for (var i = 0; i < InventoryItem.StructSize / 8; i++) { if (this.dataUInt64[i] != other.dataUInt64[i]) return false; @@ -173,7 +163,7 @@ public unsafe struct GameInventoryItem : IEquatable public override int GetHashCode() { var k = 0x5a8447b91aff51b4UL; - for (var i = 0; i < StructSizeInBytes / 8; i++) + for (var i = 0; i < InventoryItem.StructSize / 8; i++) k ^= this.dataUInt64[i]; return unchecked((int)(k ^ (k >> 32))); } diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6cc4f1c12..81ea903b7 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6cc4f1c12c37785101a262cceac249753db3b1f5 +Subproject commit 81ea903b7dc90bd07a38703738fa7f84b1ed0775 From 94f16ac16e3e44c822665641983966239e96eff6 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 15 Nov 2024 00:36:10 +0100 Subject: [PATCH 127/375] Alias ClientState.IsLoggedIn to AgentLobby.IsLoggedIn (#2082) --- Dalamud/Game/ClientState/ClientState.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 6ceea4c6b..364466cce 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Runtime.InteropServices; using Dalamud.Data; using Dalamud.Game.ClientState.Conditions; @@ -71,7 +70,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.processPacketPlayerSetupHook.Enable(); this.onLogoutHook.Enable(); } - + private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); /// @@ -121,7 +120,14 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId; /// - public bool IsLoggedIn { get; private set; } + public unsafe bool IsLoggedIn + { + get + { + var agentLobby = AgentLobby.Instance(); + return agentLobby != null && agentLobby->IsLoggedIn; + } + } /// public bool IsPvP { get; private set; } @@ -259,7 +265,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState try { Log.Debug("Login"); - this.IsLoggedIn = true; this.Login?.InvokeSafely(); gameGui?.ResetUiHideState(); this.lifecycle.ResetLogout(); @@ -283,8 +288,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState Log.Debug("Logout: Type {type}, Code {code}", type, code); - this.IsLoggedIn = false; - if (this.Logout is { } callback) { foreach (var action in callback.GetInvocationList().Cast()) From ee63f6087796c69db922b849008169f561d10e20 Mon Sep 17 00:00:00 2001 From: ItsBexy <103910869+ItsBexy@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:36:27 -0700 Subject: [PATCH 128/375] Update UIDebug2, ImGuiComponents, ImGuiHelpers (#2081) * Update ImGuiComponents & ImGuiHelpers Took some helper functions created for `UiDebug2`, and incorporated them into `ImGuiComponents` / `ImGuiHelpers` instead - `IconButton()` (and its various overloads) now includes an optional size parameter - `IconButtonSelect()` has been added, allowing a row or grid of IconButtons to serve as a radio-like input - `HelpMarker()` now includes an optional color parameter - `ClickToCopyText()` now includes an optional color parameter, and includes the `FontAwesome.Copy` icon in the tooltip. - Implemented ImRaii in these files These changes are intended not to break any existing calls in plugins or within Dalamud itself. * Fix ambiguous overloads * UiDebug2 Updates - Fixed XY coordinate display - Added AtkValue table to AddonTree display - Restored old behaviour wherein the Addon display initially only shows the Root node and a collapsed node list - The above should also fix the Element Selector / Search behaviour, allowing it to scroll correctly to the searched node - Now displays field offsets for any node/component whose pointer exists in the addon struct - Tidied up node tree headers by removing memory addresses (they're still readable in the opened tree, of course) --- .../ImGuiComponents.ColorPickerWithPalette.cs | 8 +- .../ImGuiComponents.DisabledButton.cs | 54 ++-- .../Components/ImGuiComponents.HelpMarker.cs | 39 ++- .../Components/ImGuiComponents.IconButton.cs | 284 +++++++++++------- .../ImGuiComponents.IconButtonSelect.cs | 87 ++++++ .../ImGuiComponents.TextWithLabel.cs | 10 +- .../ImGuiComponents.ToggleSwitch.cs | 7 +- .../UiDebug2/Browsing/AddonTree.AtkValues.cs | 124 ++++++++ .../UiDebug2/Browsing/AddonTree.FieldNames.cs | 12 + .../Internal/UiDebug2/Browsing/AddonTree.cs | 53 ++-- .../Internal/UiDebug2/Browsing/Events.cs | 8 +- .../UiDebug2/Browsing/NodeTree.Component.cs | 38 ++- .../UiDebug2/Browsing/NodeTree.Editor.cs | 5 +- .../UiDebug2/Browsing/NodeTree.Res.cs | 55 +++- .../Internal/UiDebug2/ElementSelector.cs | 2 +- .../Internal/UiDebug2/Utility/Gui.cs | 99 ++---- .../Internal/UiDebug2/Utility/NodeBounds.cs | 10 +- Dalamud/Interface/Utility/ImGuiHelpers.cs | 32 +- 18 files changed, 649 insertions(+), 278 deletions(-) create mode 100644 Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs create mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs diff --git a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs index aa707aecb..e2f68eab2 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs @@ -1,6 +1,8 @@ using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -41,7 +43,9 @@ public static partial class ImGuiComponents ImGui.OpenPopup($"###ColorPickerPopup{id}"); } - if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) + using var popup = ImRaii.Popup($"###ColorPickerPopup{id}"); + + if (popup) { if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) { @@ -61,8 +65,6 @@ public static partial class ImGuiComponents ImGui.SameLine(); } } - - ImGui.EndPopup(); } return selectedColor; diff --git a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs index 907ad0aeb..ab2ed4724 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs @@ -1,5 +1,7 @@ using System.Numerics; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -21,17 +23,16 @@ public static partial class ImGuiComponents /// Indicator if button is clicked. public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { - ImGui.PushFont(UiBuilder.IconFont); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var text = icon.ToIconString(); + if (id.HasValue) + { + text = $"{text}##{id}"; + } - var text = icon.ToIconString(); - if (id.HasValue) - text = $"{text}##{id}"; - - var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); - - ImGui.PopFont(); - - return button; + return DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); + } } /// @@ -45,31 +46,28 @@ public static partial class ImGuiComponents /// Indicator if button is clicked. public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { + using var col = new ImRaii.Color(); + if (defaultColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + { + col.Push(ImGuiCol.Button, defaultColor.Value); + } if (activeColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + { + col.Push(ImGuiCol.ButtonActive, activeColor.Value); + } if (hoveredColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + { + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); + } var style = ImGui.GetStyle(); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); - var button = ImGui.Button(labelWithId); - - ImGui.PopStyleVar(); - - if (defaultColor.HasValue) - ImGui.PopStyleColor(); - - if (activeColor.HasValue) - ImGui.PopStyleColor(); - - if (hoveredColor.HasValue) - ImGui.PopStyleColor(); - - return button; + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, style.Alpha * alphaMult)) + { + return ImGui.Button(labelWithId); + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index f0ecf2f2f..3392136d1 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -1,3 +1,7 @@ +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Common.Math; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -18,17 +22,32 @@ public static partial class ImGuiComponents /// /// The text to display on hover. /// The icon to use. - public static void HelpMarker(string helpText, FontAwesomeIcon icon) + /// The color of the icon. + public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) { + using var col = new ImRaii.Color(); + + if (color.HasValue) + { + col.Push(ImGuiCol.TextDisabled, color.Value); + } + ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.TextDisabled(icon.ToIconString()); - ImGui.PopFont(); - if (!ImGui.IsItemHovered()) return; - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextDisabled(icon.ToIconString()); + } + + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f)) + { + ImGui.TextUnformatted(helpText); + } + } + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index dc2c99608..5e64fe463 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -1,6 +1,8 @@ using System.Numerics; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -15,8 +17,26 @@ public static partial class ImGuiComponents /// /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon) - => IconButton(icon, null, null, null); + public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector2 size) => IconButton(icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. @@ -24,8 +44,28 @@ public static partial class ImGuiComponents /// The ID of the button. /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); + public static bool IconButton(int id, FontAwesomeIcon icon) => IconButton(id, icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector2 size) => IconButton(id, icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. @@ -33,51 +73,45 @@ public static partial class ImGuiComponents /// The ID of the button. /// The icon for the button. /// Indicator if button is clicked. - public static bool IconButton(string id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); + public static bool IconButton(string id, FontAwesomeIcon icon) => IconButton(id, icon, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(string id, FontAwesomeIcon icon, Vector2 size) + => IconButton(id, icon, null, null, null, size); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// Indicator if button is clicked. + public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) + => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size); /// /// IconButton component to use an icon as a button. /// /// Text already containing the icon string. /// Indicator if button is clicked. - public static bool IconButton(string iconText) - => IconButton(iconText, null, null, null); + public static bool IconButton(string iconText) => IconButton(iconText, null); /// /// IconButton component to use an icon as a button. /// - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. + /// Text already containing the icon string. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor); + public static bool IconButton(string iconText, Vector2 size) => IconButton(iconText, null, null, null, size); /// /// IconButton component to use an icon as a button with color options. @@ -86,62 +120,72 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon. /// Indicator if button is clicked. - public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { - var numColors = 0; + using var col = new ImRaii.Color(); if (defaultColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + col.Push(ImGuiCol.Button, defaultColor.Value); } if (activeColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonActive, activeColor.Value); } if (hoveredColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); + } + + if (size.HasValue) + { + size *= ImGuiHelpers.GlobalScale; } var icon = iconText; - if (icon.Contains("#")) - icon = icon[..icon.IndexOf("#", StringComparison.Ordinal)]; + if (icon.Contains('#')) + { + icon = icon[..icon.IndexOf('#', StringComparison.Ordinal)]; + } - ImGui.PushID(iconText); + bool button; - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon); - ImGui.PopFont(); - - var dl = ImGui.GetWindowDrawList(); - var cursor = ImGui.GetCursorScreenPos(); - - // Draw an ImGui button with the icon and text - var buttonWidth = iconSize.X + (ImGui.GetStyle().FramePadding.X * 2); - var buttonHeight = ImGui.GetFrameHeight(); - var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight)); - - // Draw the icon on the window drawlist - var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y); - - ImGui.PushFont(UiBuilder.IconFont); - dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon); - ImGui.PopFont(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var iconSize = ImGui.CalcTextSize(icon); + var cursor = ImGui.GetCursorScreenPos(); - ImGui.PopID(); + var width = size is { X: not 0 } ? size.Value.X : iconSize.X + (ImGui.GetStyle().FramePadding.X * 2); + var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight(); - if (numColors > 0) - ImGui.PopStyleColor(numColors); + var buttonSize = new Vector2(width, height); + + using (ImRaii.PushId(iconText)) + { + button = ImGui.Button(string.Empty, buttonSize); + } + + var iconPos = cursor + ((buttonSize - iconSize) / 2f); + + ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon); + } return button; } + /// + /// IconButton component to use an icon as a button with color options. + /// + /// Icon to show. + /// Text to show. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text. + /// Indicator if button is clicked. + public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector2 size) => IconButtonWithText(icon, text, null, null, null, size); + /// /// IconButton component to use an icon as a button with color options. /// @@ -150,61 +194,72 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. + /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text. /// Indicator if button is clicked. - public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { - var numColors = 0; + using var col = new ImRaii.Color(); if (defaultColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + col.Push(ImGuiCol.Button, defaultColor.Value); } if (activeColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonActive, activeColor.Value); } if (hoveredColor.HasValue) { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; + col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value); } - ImGui.PushID(text); + if (size.HasValue) + { + size *= ImGuiHelpers.GlobalScale; + } + + bool button; + + Vector2 iconSize; + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + iconSize = ImGui.CalcTextSize(icon.ToIconString()); + } + + var textStr = text; + if (textStr.Contains('#')) + { + textStr = textStr[..textStr.IndexOf('#', StringComparison.Ordinal)]; + } + + var framePadding = ImGui.GetStyle().FramePadding; + var iconPadding = 3 * ImGuiHelpers.GlobalScale; - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - ImGui.PopFont(); - - var textSize = ImGui.CalcTextSize(text); - var dl = ImGui.GetWindowDrawList(); var cursor = ImGui.GetCursorScreenPos(); - var iconPadding = 3 * ImGuiHelpers.GlobalScale; - - // Draw an ImGui button with the icon and text - var buttonWidth = iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; - var buttonHeight = ImGui.GetFrameHeight(); - var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight)); - - // Draw the icon on the window drawlist - var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y); - - ImGui.PushFont(UiBuilder.IconFont); - dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); - ImGui.PopFont(); - - // Draw the text on the window drawlist - var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + ImGui.GetStyle().FramePadding.Y); - dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), text); + using (ImRaii.PushId(text)) + { + var textSize = ImGui.CalcTextSize(textStr); - ImGui.PopID(); + var width = size is { X: not 0 } ? size.Value.X : iconSize.X + textSize.X + (framePadding.X * 2) + iconPadding; + var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight(); - if (numColors > 0) - ImGui.PopStyleColor(numColors); + button = ImGui.Button(string.Empty, new Vector2(width, height)); + } + + var iconPos = cursor + framePadding; + var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + framePadding.Y); + + var dl = ImGui.GetWindowDrawList(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString()); + } + + dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), textStr); return button; } @@ -217,16 +272,15 @@ public static partial class ImGuiComponents /// Width. internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) { - ImGui.PushFont(UiBuilder.IconFont); - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - ImGui.PopFont(); - - var textSize = ImGui.CalcTextSize(text); - var dl = ImGui.GetWindowDrawList(); - var cursor = ImGui.GetCursorScreenPos(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - var iconPadding = 3 * ImGuiHelpers.GlobalScale; - - return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; + var textSize = ImGui.CalcTextSize(text); + + var iconPadding = 3 * ImGuiHelpers.GlobalScale; + + return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs new file mode 100644 index 000000000..3f9c469bb --- /dev/null +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.Components; + +public static partial class ImGuiComponents +{ + /// + /// A radio-like input that uses icon buttons. + /// + /// The type of the value being set. + /// Text that will be used to generate individual labels for the buttons. + /// The value to set. + /// The icons that will be displayed on each button. + /// The options that each button will apply. + /// Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row). + /// Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// The default color of the button range. + /// The color of the actively-selected button. + /// The color of the buttons when hovered. + /// True if any button is clicked. + internal static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + { + var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair(icon, value)); + return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor); + } + + /// + /// A radio-like input that uses icon buttons. + /// + /// The type of the value being set. + /// Text that will be used to generate individual labels for the buttons. + /// The value to set. + /// A list of all icon/option pairs. + /// Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row). + /// Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon. + /// The default color of the button range. + /// The color of the actively-selected button. + /// The color of the buttons when hovered. + /// True if any button is clicked. + internal static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + { + defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button); + activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive); + hoveredColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonHovered); + + var result = false; + + var innerSpacing = ImGui.GetStyle().ItemInnerSpacing; + var y = ImGui.GetCursorPosY(); + + var optArr = options.ToArray(); + for (var i = 0; i < optArr.Length; i++) + { + if (i > 0) + { + if (columns == 0 || i % columns != 0) + { + ImGui.SameLine(0, innerSpacing.X); + } + else + { + y += (buttonSize is { Y: not 0 } ? buttonSize.Value.Y * ImGuiHelpers.GlobalScale : ImGui.GetFrameHeight()) + innerSpacing.Y; + + ImGui.SetCursorPosY(y); + } + } + + optArr[i].Deconstruct(out var icon, out var option); + + var selected = val is not null && val.Equals(option); + + if (IconButton($"{label}{option}{i}", icon, selected ? activeColor : defaultColor, activeColor, hoveredColor, buttonSize)) + { + val = option; + result = true; + } + } + + return result; + } +} diff --git a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs index 597b472c6..43b54fc93 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs @@ -1,3 +1,5 @@ +using Dalamud.Interface.Utility.Raii; + using ImGuiNET; namespace Dalamud.Interface.Components; @@ -24,7 +26,13 @@ public static partial class ImGuiComponents else { ImGui.Text(value + "*"); - if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + ImGui.TextUnformatted(hint); + } + } } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs index 64f3d01eb..6d6e0f6c3 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs @@ -36,9 +36,14 @@ public static partial class ImGuiComponents } if (ImGui.IsItemHovered()) + { drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); + } else + { drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); + } + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); return changed; @@ -62,7 +67,7 @@ public static partial class ImGuiComponents // TODO: animate ImGui.InvisibleButton(id, new Vector2(width, height)); - var dimFactor = 0.5f; + const float dimFactor = 0.5f; drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs new file mode 100644 index 000000000..4b7a531c0 --- /dev/null +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs @@ -0,0 +1,124 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Memory; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ImGuiNET; + +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; + +namespace Dalamud.Interface.Internal.UiDebug2.Browsing; + +public unsafe partial class AddonTree +{ + /// + /// Prints a table of AtkValues associated with a given addon. + /// + /// The addon to look up. + internal static void PrintAtkValues(AtkUnitBase* addon) + { + var atkValue = addon->AtkValues; + if (addon->AtkValuesCount > 0 && atkValue != null) + { + using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}"); + if (tree) + { + using (ImRaii.Table( + "atkUnitBase_atkValueTable", + 3, + ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg)) + { + ImGui.TableSetupColumn("Index"); + ImGui.TableSetupColumn("Type"); + ImGui.TableSetupColumn("Value"); + ImGui.TableHeadersRow(); + + try + { + for (var i = 0; i < addon->AtkValuesCount; i++) + { + ImGui.TableNextColumn(); + if (atkValue->Type == 0) + { + ImGui.TextDisabled($"#{i}"); + } + else + { + ImGui.Text($"#{i}"); + } + + ImGui.TableNextColumn(); + if (atkValue->Type == 0) + { + ImGui.TextDisabled("Not Set"); + } + else + { + ImGui.Text($"{atkValue->Type}"); + } + + ImGui.TableNextColumn(); + + switch (atkValue->Type) + { + case 0: + break; + case ValueType.Int: + case ValueType.UInt: + { + ImGui.TextUnformatted($"{atkValue->Int}"); + break; + } + + case ValueType.ManagedString: + case ValueType.String8: + case ValueType.String: + { + if (atkValue->String == null) + { + ImGui.TextDisabled("null"); + } + else + { + var str = MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue->String)); + Util.ShowStruct(str, (ulong)atkValue); + } + + break; + } + + case ValueType.Bool: + { + ImGui.TextUnformatted($"{atkValue->Byte != 0}"); + break; + } + + case ValueType.Pointer: + ImGui.TextUnformatted($"{(nint)atkValue->Pointer}"); + break; + + default: + { + ImGui.TextDisabled("Unhandled Type"); + ImGui.SameLine(); + Util.ShowStruct(atkValue); + break; + } + } + + atkValue++; + } + } + catch (Exception ex) + { + ImGui.TextColored(new(1, 0, 0, 1), $"{ex}"); + } + } + } + + Gui.PaddedSeparator(); + } + } +} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs index 8affa1eac..0b1dcb66c 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs @@ -23,6 +23,11 @@ public unsafe partial class AddonTree /// internal Dictionary> FieldNames { get; set; } = []; + /// + /// Gets or sets the size of the addon according to its Attributes in FFXIVClientStructs. + /// + internal int AddonSize { get; set; } + private object? GetAddonObj(AtkUnitBase* addon) { if (addon == null) @@ -42,6 +47,13 @@ public unsafe partial class AddonTree select t) { AddonTypeDict[this.AddonName] = t; + + var size = t.StructLayoutAttribute?.Size; + if (size != null) + { + this.AddonSize = size.Value; + } + break; } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs index 2823a2058..9d6575a55 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Numerics; using Dalamud.Interface.Components; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -19,14 +20,12 @@ namespace Dalamud.Interface.Internal.UiDebug2.Browsing; /// public unsafe partial class AddonTree : IDisposable { - private readonly nint initialPtr; - private AddonPopoutWindow? window; private AddonTree(string name, nint ptr) { this.AddonName = name; - this.initialPtr = ptr; + this.InitialPtr = ptr; this.PopulateFieldNames(ptr); } @@ -35,6 +34,11 @@ public unsafe partial class AddonTree : IDisposable /// internal string AddonName { get; init; } + /// + /// Gets the addon's pointer at the time this was created. + /// + internal nint InitialPtr { get; init; } + /// /// Gets or sets a collection of trees representing nodes within this addon. /// @@ -81,7 +85,7 @@ public unsafe partial class AddonTree : IDisposable { if (AddonTrees.TryGetValue(name, out var tree)) { - if (tree.initialPtr == ptr) + if (tree.InitialPtr == ptr) { return tree; } @@ -143,34 +147,47 @@ public unsafe partial class AddonTree : IDisposable ImGui.SetTooltip("Toggle Popout Window"); } - ImGui.Separator(); - - PrintFieldValuePair("Address", $"{(nint)addon:X}"); + PaddedSeparator(1); var uldManager = addon->UldManager; + + PrintFieldValuePair("Address", $"{(nint)addon:X}"); + PrintFieldValuePair("Agent", $"{GameGui.FindAgentInterface(addon):X}"); + PrintFieldValuePairs( ("X", $"{addon->X}"), - ("Y", $"{addon->X}"), + ("Y", $"{addon->Y}"), ("Scale", $"{addon->Scale}"), ("Widget Count", $"{uldManager.ObjectCount}")); - ImGui.Separator(); - var addonObj = this.GetAddonObj(addon); if (addonObj != null) { + PaddedSeparator(); ShowStruct(addonObj, (ulong)addon); } - ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); - ImGui.Separator(); + PaddedSeparator(); - ResNodeTree.PrintNodeList(uldManager.NodeList, uldManager.NodeListCount, this); + PrintAtkValues(addon); - ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); - ImGui.Separator(); + if (addon->RootNode != null) + { + ResNodeTree.GetOrCreate(addon->RootNode, this).Print(0); + PaddedSeparator(); + } - ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); + if (uldManager.NodeList != null) + { + var count = uldManager.NodeListCount; + ResNodeTree.PrintNodeListAsTree(uldManager.NodeList, count, $"Node List [{count}]:", this, new(0, 0.85F, 1, 1)); + PaddedSeparator(); + } + + if (addon->CollisionNodeList != null) + { + ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); + } if (SearchResults.Length > 0 && Countdown <= 0) { @@ -218,7 +235,7 @@ public unsafe partial class AddonTree : IDisposable private bool ValidateAddon(out AtkUnitBase* addon) { addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName); - if (addon == null || (nint)addon != this.initialPtr) + if (addon == null || (nint)addon != this.InitialPtr) { this.Dispose(); return false; @@ -231,7 +248,7 @@ public unsafe partial class AddonTree : IDisposable { if (this.window == null) { - this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.initialPtr}"); + this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.InitialPtr}"); PopoutWindows.AddWindow(this.window); } else diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index 0c3a947dd..45a2d90eb 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -1,4 +1,6 @@ -using Dalamud.Interface.Internal.UiDebug2.Utility; +using System.Numerics; + +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -56,9 +58,9 @@ public static class Events ImGui.TableNextColumn(); ImGui.TextUnformatted($"{evt->State.UnkFlags1}"); ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Target:X}"); + ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1)); ImGui.TableNextColumn(); - Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); + ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1)); evt = evt->NextEvent; } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs index d9fcf52cc..4a1989441 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Component.cs @@ -34,11 +34,13 @@ internal unsafe class ComponentNodeTree : ResNodeTree private AtkUldManager* UldManager => &this.Component->UldManager; + private int? ComponentFieldOffset { get; set; } + /// private protected override string GetHeaderText() { var childCount = (int)this.UldManager->NodeListCount; - return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.Component:X})"; + return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)}"; } /// @@ -62,10 +64,10 @@ internal unsafe class ComponentNodeTree : ResNodeTree } /// - private protected override void PrintFieldNames() + private protected override void PrintFieldLabels() { - this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); - this.PrintFieldName((nint)this.Component, new(0f, 0.5f, 0.8f, 1f)); + this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset); + this.PrintFieldLabel((nint)this.Component, new(0f, 0.5f, 0.8f, 1f), this.ComponentFieldOffset); } /// @@ -108,6 +110,34 @@ internal unsafe class ComponentNodeTree : ResNodeTree } } + /// + private protected override void GetFieldOffset() + { + var nodeFound = false; + var componentFound = false; + for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8) + { + var readPtr = Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i); + + if (readPtr == (nint)this.Node) + { + this.NodeFieldOffset = i; + nodeFound = true; + } + + if (readPtr == (nint)this.Component) + { + this.ComponentFieldOffset = i; + componentFound = true; + } + + if (nodeFound && componentFound) + { + break; + } + } + } + private void PrintComponentObject() { PrintFieldValuePair("Component", $"{(nint)this.Component:X}"); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index 6cb178bd7..6b6522bb4 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Numerics; +using Dalamud.Interface.Components; using Dalamud.Interface.Internal.UiDebug2.Utility; using Dalamud.Interface.Utility.Raii; @@ -370,8 +371,8 @@ internal unsafe partial class TextNodeTree var hAlign = (int)alignment % 3; var vAlign = ((int)alignment - hAlign) / 3; - var hAlignInput = IconButtonSelect($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]); - var vAlignInput = IconButtonSelect($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]); + var hAlignInput = ImGuiComponents.IconButtonSelect($"{label}H", ref hAlign, [AlignLeft, AlignCenter, AlignRight], [0, 1, 2], 3u, new(25, 0)); + var vAlignInput = ImGuiComponents.IconButtonSelect($"{label}V", ref vAlign, [ArrowsUpToLine, GripLines, ArrowsDownToLine], [0, 1, 2], 3u, new(25, 0)); if (hAlignInput || vAlignInput) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index a333940c1..2edf4e570 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Numerics; +using System.Runtime.InteropServices; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.UiDebug2.Utility; @@ -60,6 +61,11 @@ internal unsafe partial class ResNodeTree : IDisposable /// private protected NodeType NodeType { get; init; } + /// + /// Gets or sets the offset of this node within its parent Addon. + /// + private protected int? NodeFieldOffset { get; set; } + /// /// Clears this NodeTree's popout window, if it has one. /// @@ -164,19 +170,26 @@ internal unsafe partial class ResNodeTree : IDisposable internal void WriteTreeHeading() { ImGui.TextUnformatted(this.GetHeaderText()); - this.PrintFieldNames(); + this.PrintFieldLabels(); } /// - /// If the given pointer has been identified as a field within the addon struct, this method prints that field's name. + /// If the given pointer is referenced with the addon struct, the offset within the addon will be printed. If the given pointer has been identified as a field within the addon struct, this method also prints that field's name. /// /// The pointer to check. /// The text color to use. - private protected void PrintFieldName(nint ptr, Vector4 color) + /// The field offset of the pointer, if it was found in the addon. + private protected void PrintFieldLabel(nint ptr, Vector4 color, int? fieldOffset) { + if (fieldOffset != null) + { + ImGui.SameLine(0, -1); + ImGui.TextColored(color * 0.85f, $"[0x{fieldOffset:X}]"); + } + if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result)) { - ImGui.SameLine(); + ImGui.SameLine(0, -1); ImGui.TextColored(color, string.Join(".", result)); } } @@ -188,7 +201,15 @@ internal unsafe partial class ResNodeTree : IDisposable private protected virtual string GetHeaderText() { var count = this.GetDirectChildCount(); - return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)} ({(nint)this.Node:X})"; + return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)}"; + } + + /// + /// Prints any field names for the node. + /// + private protected virtual void PrintFieldLabels() + { + this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset); } /// @@ -201,11 +222,6 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.NewLine(); } - /// - /// Prints any field names for the node. - /// - private protected virtual void PrintFieldNames() => this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); - /// /// Prints all direct children of this node. /// @@ -227,6 +243,21 @@ internal unsafe partial class ResNodeTree : IDisposable { } + /// + /// Attempts to retrieve the field offset of the given pointer within the parent addon. + /// + private protected virtual void GetFieldOffset() + { + for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8) + { + if (Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i) == (nint)this.Node) + { + this.NodeFieldOffset = i; + break; + } + } + } + private int GetDirectChildCount() { var count = 0; @@ -273,6 +304,8 @@ internal unsafe partial class ResNodeTree : IDisposable ImGui.SetNextItemOpen(true, ImGuiCond.Always); } + this.GetFieldOffset(); + using var col = ImRaii.PushColor(Text, displayColor); using var tree = ImRaii.TreeNode(label, SpanFullWidth); @@ -281,7 +314,7 @@ internal unsafe partial class ResNodeTree : IDisposable new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f)); } - ImGui.SameLine(); + ImGui.SameLine(0, -1); this.WriteTreeHeading(); col.Pop(); diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index 7f603fdac..6693c3fb0 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -79,7 +79,7 @@ internal unsafe class ElementSelector : IDisposable /// internal void DrawInterface() { - using (ImRaii.Child("###sidebar_elementSelector", new(250, 0), true)) + using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true)) { using (ImRaii.PushFont(IconFont)) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index 954e5cb72..cc73b79c6 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using System.Numerics; -using Dalamud.Interface.Components; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.Graphics; @@ -17,40 +16,6 @@ namespace Dalamud.Interface.Internal.UiDebug2.Utility; /// internal static class Gui { - /// - /// A radio-button-esque input that uses Fontawesome icon buttons. - /// - /// The type of value being set. - /// The label for the inputs. - /// The value being set. - /// A list of all options. - /// A list of icons corresponding to the options. - /// true if a button is clicked. - internal static unsafe bool IconButtonSelect(string label, ref T val, List options, List icons) - { - var ret = false; - - for (var i = 0; i < options.Count; i++) - { - if (i > 0) - { - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - } - - var option = options[i]; - var icon = icons.Count > i ? icons[i] : FontAwesomeIcon.Question; - var color = *ImGui.GetStyleColorVec4(val is not null && val.Equals(option) ? ButtonActive : Button); - - if (ImGuiComponents.IconButton($"{label}{option}{i}", icon, color)) - { - val = option; - ret = true; - } - } - - return ret; - } - /// /// Prints field name and its value. /// @@ -61,13 +26,14 @@ internal static class Gui { ImGui.TextUnformatted($"{fieldName}:"); ImGui.SameLine(); + var grey60 = new Vector4(0.6f, 0.6f, 0.6f, 1); if (copy) { - ClickToCopyText(value); + ImGuiHelpers.ClickToCopyText(value, null, grey60); } else { - ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), value); + ImGui.TextColored(grey60, value); } } @@ -102,7 +68,10 @@ internal static class Gui /// Colors the text itself either white or black, depending on the luminosity of the background color. internal static void PrintColor(Vector4 color, string fmt) { - using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)).Push(Button, color).Push(ButtonActive, color).Push(ButtonHovered, color)) + using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) + .Push(Button, color) + .Push(ButtonActive, color) + .Push(ButtonHovered, color)) { ImGui.SmallButton(fmt); } @@ -117,39 +86,6 @@ internal static class Gui 0.5f) * vector4.W; } - /// - /// Print out text that can be copied when clicked. - /// - /// The text to show. - /// The text to copy when clicked. - internal static void ClickToCopyText(string text, string? textCopy = null) - { - using (ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1))) - { - textCopy ??= text; - ImGui.TextUnformatted($"{text}"); - } - - if (ImGui.IsItemHovered()) - { - using (ImRaii.Tooltip()) - { - using (ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); - } - - ImGui.SameLine(); - ImGui.TextUnformatted($"{textCopy}"); - } - } - - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText($"{textCopy}"); - } - } - /// /// Draws a tooltip that changes based on the cursor's x-position within the hovered item. /// @@ -176,4 +112,23 @@ internal static class Gui return true; } + + /// + /// Draws a separator with some padding above and below. + /// + /// Governs whether to pad above, below, or both. + /// The amount of padding. + internal static void PaddedSeparator(uint mask = 0b11, float padding = 5f) + { + if ((mask & 0b10) > 0) + { + ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale)); + } + + ImGui.Separator(); + if ((mask & 0b01) > 0) + { + ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale)); + } + } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs index cffd676f7..3d28cb836 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/NodeBounds.cs @@ -6,7 +6,7 @@ using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using static System.Math; +using static System.MathF; using static Dalamud.Interface.ColorHelpers; namespace Dalamud.Interface.Internal.UiDebug2.Utility; @@ -133,7 +133,7 @@ public unsafe struct NodeBounds if (p.Y > Min(p1.Y, p2.Y) && p.Y <= Max(p1.Y, p2.Y) && p.X <= Max(p1.X, p2.X) && - (p1.X.Equals(p2.X) || p.X <= ((p.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y)) + p1.X)) + (p1.X.Equals(p2.X) || p.X <= (((p.Y - p1.Y) * (p2.X - p1.X)) / (p2.Y - p1.Y)) + p1.X)) { inside = !inside; } @@ -144,12 +144,12 @@ public unsafe struct NodeBounds private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s) { - var cosR = (float)Cos(r); - var sinR = (float)Sin(r); + var cosR = Cos(r); + var sinR = Sin(r); var d = (p - o) * s; return new( - o.X + (d.X * cosR) - (d.Y * sinR), + (o.X + (d.X * cosR)) - (d.Y * sinR), o.Y + (d.X * sinR) + (d.Y * cosR)); } diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 8ce7a48d7..8dedae5cd 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -167,17 +167,41 @@ public static class ImGuiHelpers /// /// The text to show. /// The text to copy when clicked. - public static void ClickToCopyText(string text, string? textCopy = null) + /// The color of the text. + public static void ClickToCopyText(string text, string? textCopy = null, Vector4? color = null) { textCopy ??= text; - ImGui.Text($"{text}"); + + using (var col = new ImRaii.Color()) + { + if (color.HasValue) + { + col.Push(ImGuiCol.Text, color.Value); + } + + ImGui.TextUnformatted($"{text}"); + } + if (ImGui.IsItemHovered()) { ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); + + using (ImRaii.Tooltip()) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString()); + } + + ImGui.SameLine(); + ImGui.TextUnformatted(textCopy); + } } - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(textCopy); + } } /// Draws a SeString. From 3100f9134d0be73bd56d16acdb251f8c437a8e5c Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 16:50:08 -0800 Subject: [PATCH 129/375] cs bumppppp --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 81ea903b7..b5240ab1f 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 81ea903b7dc90bd07a38703738fa7f84b1ed0775 +Subproject commit b5240ab1f8b0170a4637f0a3f99baa9f6bd3966b From ec8389a34e49bf6a37d6372c9972d180628b8659 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 20:27:30 -0800 Subject: [PATCH 130/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index b5240ab1f..5f4e7bf27 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit b5240ab1f8b0170a4637f0a3f99baa9f6bd3966b +Subproject commit 5f4e7bf271f8144ad72f289890b29351afa248b2 From bdc24ad23edec9cb49288313d31f229d1b53b77d Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 23:10:59 -0800 Subject: [PATCH 131/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 5f4e7bf27..679b5353c 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 5f4e7bf271f8144ad72f289890b29351afa248b2 +Subproject commit 679b5353cd61baa03b26d95efcac059f8e29b141 From d7abf9fe0d530f591d31b2ba8617f42065733148 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 23:24:32 -0800 Subject: [PATCH 132/375] Fix Fate aging step; report Tuliyollal taxes - FATE table test needs a zone with FATEs in it. - Use a `byte` rather than an `int` so we don't overflow like crazy. --- .../Windows/SelfTest/AgingSteps/FateTableAgingStep.cs | 8 +++++++- .../Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs index a8fe60aa9..eef986302 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs @@ -9,7 +9,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; /// internal class FateTableAgingStep : IAgingStep { - private int index = 0; + private byte index = 0; /// public string Name => "Test FateTable"; @@ -21,6 +21,12 @@ internal class FateTableAgingStep : IAgingStep ImGui.Text("Checking fate table..."); + if (fateTable.Length == 0) + { + ImGui.Text("Go to a zone that has FATEs currently up."); + return SelfTestStepResult.Waiting; + } + if (this.index == fateTable.Length - 1) { return SelfTestStepResult.Pass; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs index 78d43662c..513141fa9 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs @@ -169,6 +169,7 @@ internal class MarketBoardAgingStep : IAgingStep ImGui.Text($"Kugane: {this.marketTaxRate.KuganeTax.ToString()}"); ImGui.Text($"Crystarium: {this.marketTaxRate.CrystariumTax.ToString()}"); ImGui.Text($"Sharlayan: {this.marketTaxRate.SharlayanTax.ToString()}"); + ImGui.Text($"Tuliyollal: {this.marketTaxRate.TuliyollalTax.ToString()}"); ImGui.Separator(); if (ImGui.Button("Looks Correct / Skip")) { From bd5f1a32773534c56cc8d036ee3acb6797e8708d Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 14 Nov 2024 23:51:08 -0800 Subject: [PATCH 133/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 679b5353c..73ac85afd 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 679b5353cd61baa03b26d95efcac059f8e29b141 +Subproject commit 73ac85afd50968fb9c099507ce05da48bdf72e3c From d6dad8b44a01ffa7a24a50fd5deda29c03860c1e Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 15 Nov 2024 00:20:51 -0800 Subject: [PATCH 134/375] fix gamepad inputs for imgui --- Dalamud/Game/ClientState/GamePad/GamepadInput.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs index 32439cd08..d9dcea60f 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs @@ -16,25 +16,25 @@ public struct GamepadInput /// /// Left analogue stick's horizontal value, -99 for left, 99 for right. /// - [FieldOffset(0x88)] + [FieldOffset(0x78)] public int LeftStickX; /// /// Left analogue stick's vertical value, -99 for down, 99 for up. /// - [FieldOffset(0x8C)] + [FieldOffset(0x7C)] public int LeftStickY; /// /// Right analogue stick's horizontal value, -99 for left, 99 for right. /// - [FieldOffset(0x90)] + [FieldOffset(0x80)] public int RightStickX; /// /// Right analogue stick's vertical value, -99 for down, 99 for up. /// - [FieldOffset(0x94)] + [FieldOffset(0x84)] public int RightStickY; /// @@ -43,7 +43,7 @@ public struct GamepadInput /// /// This is a bitfield. /// - [FieldOffset(0x98)] + [FieldOffset(0x88)] public ushort ButtonsRaw; /// @@ -52,7 +52,7 @@ public struct GamepadInput /// /// This is a bitfield. /// - [FieldOffset(0x9C)] + [FieldOffset(0x8C)] public ushort ButtonsPressed; /// @@ -61,7 +61,7 @@ public struct GamepadInput /// /// This is a bitfield. /// - [FieldOffset(0xA0)] + [FieldOffset(0x90)] public ushort ButtonsReleased; /// @@ -70,6 +70,6 @@ public struct GamepadInput /// /// This is a bitfield. /// - [FieldOffset(0xA4)] + [FieldOffset(0x94)] public ushort ButtonsRepeat; } From b4d9ebe1ef8928124ee278f8a11defc1ef8fe03d Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 15 Nov 2024 08:11:24 -0800 Subject: [PATCH 135/375] bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 73ac85afd..fca10adca 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 73ac85afd50968fb9c099507ce05da48bdf72e3c +Subproject commit fca10adcab3911c1be79cb0753987a2a02b7e058 From 9ac1f5cdb4a7b2aa1a73cb1546e29281f61fdca6 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 15 Nov 2024 08:18:39 -0800 Subject: [PATCH 136/375] Revert OnLogin event to only fire when LocalPlayer is present --- Dalamud/Game/ClientState/ClientState.cs | 35 ++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 364466cce..5f22eb54b 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -38,11 +38,16 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private readonly ClientStateAddressResolver address; private readonly Hook setupTerritoryTypeHook; private readonly Hook uiModuleHandlePacketHook; - private readonly Hook processPacketPlayerSetupHook; private readonly Hook onLogoutHook; + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + [ServiceManager.ServiceDependency] private readonly NetworkHandlers networkHandlers = Service.Get(); + + private bool lastConditionNone = true; + [ServiceManager.ServiceConstructor] private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) @@ -60,14 +65,13 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.setupTerritoryTypeHook = Hook.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour); this.uiModuleHandlePacketHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); - this.processPacketPlayerSetupHook = Hook.FromAddress(this.address.ProcessPacketPlayerSetup, this.ProcessPacketPlayerSetupDetour); this.onLogoutHook = Hook.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour); + this.framework.Update += this.FrameworkOnOnUpdateEvent; this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop; this.setupTerritoryTypeHook.Enable(); this.uiModuleHandlePacketHook.Enable(); - this.processPacketPlayerSetupHook.Enable(); this.onLogoutHook.Enable(); } @@ -171,8 +175,9 @@ internal sealed class ClientState : IInternalDisposableService, IClientState { this.setupTerritoryTypeHook.Dispose(); this.uiModuleHandlePacketHook.Dispose(); - this.processPacketPlayerSetupHook.Dispose(); this.onLogoutHook.Dispose(); + + this.framework.Update -= this.FrameworkOnOnUpdateEvent; this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop; } @@ -255,24 +260,24 @@ internal sealed class ClientState : IInternalDisposableService, IClientState } } - private unsafe void ProcessPacketPlayerSetupDetour(nint a1, nint packet) + private void FrameworkOnOnUpdateEvent(IFramework framework1) { - // Call original first, so everything is set up. - this.processPacketPlayerSetupHook.Original(a1, packet); - + var condition = Service.GetNullable(); var gameGui = Service.GetNullable(); + var data = Service.GetNullable(); - try + if (condition == null || gameGui == null || data == null) + return; + + if (condition.Any() && this.lastConditionNone && this.LocalPlayer != null) { - Log.Debug("Login"); + Log.Debug("Is login"); + this.lastConditionNone = false; this.Login?.InvokeSafely(); - gameGui?.ResetUiHideState(); + gameGui.ResetUiHideState(); + this.lifecycle.ResetLogout(); } - catch (Exception ex) - { - Log.Error(ex, "Exception during ProcessPacketPlayerSetupDetour"); - } } private unsafe void OnLogoutDetour(LogoutCallbackInterface* thisPtr, LogoutCallbackInterface.LogoutParams* logoutParams) From b43a6b6bce2e5b9b25c89fa5ba74a87241b9dd46 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 15 Nov 2024 08:24:04 -0800 Subject: [PATCH 137/375] fix: OnLogin needs to be callable again --- Dalamud/Game/ClientState/ClientState.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 5f22eb54b..1fade54b6 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -22,6 +22,8 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent; using Lumina.Excel.Sheets; +using TerraFX.Interop.Windows; + using Action = System.Action; namespace Dalamud.Game.ClientState; @@ -309,6 +311,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState } gameGui?.ResetUiHideState(); + this.lastConditionNone = true; // unblock login flag this.lifecycle.SetLogout(); } From 4436c58749b5d4e09c186c566254ae88a2423a04 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Fri, 15 Nov 2024 08:25:19 -0800 Subject: [PATCH 138/375] covfefe --- Dalamud/Game/ClientState/ClientState.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 1fade54b6..4ab69b391 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -22,8 +22,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent; using Lumina.Excel.Sheets; -using TerraFX.Interop.Windows; - using Action = System.Action; namespace Dalamud.Game.ClientState; From bbaf60085623faa28db4d2adeb5f0db8cc2121eb Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:31:41 +0100 Subject: [PATCH 139/375] use correct gauge pointer --- Dalamud/Game/ClientState/JobGauge/JobGauges.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index 5a734ed87..e47bead36 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -28,7 +28,7 @@ internal class JobGauges : IServiceType, IJobGauges } /// - public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->CurrentGauge); + public unsafe IntPtr Address => (nint)(CSJobGaugeManager.Instance()->CurrentGauge); /// public T Get() where T : JobGaugeBase From a5b0a3817f63f29d89469af31d75b04b187ff296 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:33:53 +0100 Subject: [PATCH 140/375] ci: rollup into net9 --- .github/workflows/rollup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rollup.yml b/.github/workflows/rollup.yml index 5452d998e..49b3d8c1d 100644 --- a/.github/workflows/rollup.yml +++ b/.github/workflows/rollup.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: branches: - - api11 + - net9 defaults: run: From c1fbba2a27226253b7c17aa40ebd6002ccde03a7 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:51:23 +0100 Subject: [PATCH 141/375] Update ClientStructs (#2062) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index fca10adca..ca4649fa1 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit fca10adcab3911c1be79cb0753987a2a02b7e058 +Subproject commit ca4649fa1407b7f5c766654aebb013f62cf8d19f From abcb99d4ec1187cd8a2060229d404c97b40fab1e Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Sat, 16 Nov 2024 09:34:49 +0900 Subject: [PATCH 142/375] Add support for boxed outlined numbers in SeIconChar (#2088) --- Dalamud/Game/Text/SeIconChar.cs | 50 +++++++++++++++++++ .../Windows/Data/Widgets/SeFontTestWidget.cs | 9 +++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Dalamud/Game/Text/SeIconChar.cs b/Dalamud/Game/Text/SeIconChar.cs index 17924c671..00ffb7342 100644 --- a/Dalamud/Game/Text/SeIconChar.cs +++ b/Dalamud/Game/Text/SeIconChar.cs @@ -766,4 +766,54 @@ public enum SeIconChar /// The Japanese Eorzea time icon unicode character. /// EorzeaTimeJa = 0xE0DB, + + /// + /// The boxed, outlined number 0 icon unicode character. + /// + BoxedOutlinedNumber0 = 0xE0E0, + + /// + /// The boxed, outlined number 1 icon unicode character. + /// + BoxedOutlinedNumber1 = 0xE0E1, + + /// + /// The boxed, outlined number 2 icon unicode character. + /// + BoxedOutlinedNumber2 = 0xE0E2, + + /// + /// The boxed, outlined number 3 icon unicode character. + /// + BoxedOutlinedNumber3 = 0xE0E3, + + /// + /// The boxed, outlined number 4 icon unicode character. + /// + BoxedOutlinedNumber4 = 0xE0E4, + + /// + /// The boxed, outlined number 5 icon unicode character. + /// + BoxedOutlinedNumber5 = 0xE0E5, + + /// + /// The boxed, outlined number 6 icon unicode character. + /// + BoxedOutlinedNumber6 = 0xE0E6, + + /// + /// The boxed, outlined number 7 icon unicode character. + /// + BoxedOutlinedNumber7 = 0xE0E7, + + /// + /// The boxed, outlined number 8 icon unicode character. + /// + BoxedOutlinedNumber8 = 0xE0E8, + + /// + /// The boxed, outlined number 9 icon unicode character. + /// + BoxedOutlinedNumber9 = 0xE0E9, } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs index b59abbff1..69282a8e8 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs @@ -1,6 +1,8 @@ using Dalamud.Game.Text; using ImGuiNET; +using System.Linq; + namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// @@ -28,8 +30,11 @@ internal class SeFontTestWidget : IDataWindowWidget { var specialChars = string.Empty; - for (var i = 0xE020; i <= 0xE0DB; i++) - specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n"; + var min = (char)Enum.GetValues().Min(); + var max = (char)Enum.GetValues().Max(); + + for (var i = min; i <= max; i++) + specialChars += $"0x{(int)i:X} - {(SeIconChar)i} - {i}\n"; ImGui.TextUnformatted(specialChars); } From 16ea9ea2137ac611c688f98607918a77d13a2eab Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 16 Nov 2024 02:18:05 +0100 Subject: [PATCH 143/375] Update ClientStructs (#2089) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index ca4649fa1..60ce87a00 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit ca4649fa1407b7f5c766654aebb013f62cf8d19f +Subproject commit 60ce87a001610d114702b51f08ea726a294e75dd From 3c8f68b308ab88737e7f390bf991a107413acfac Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 16 Nov 2024 17:38:47 +0100 Subject: [PATCH 144/375] Update config options (#2090) --- Dalamud/Game/Config/SystemConfigOption.cs | 42 +++++++++++++++++++++++ Dalamud/Game/Config/UiConfigOption.cs | 22 ++++++++++++ 2 files changed, 64 insertions(+) diff --git a/Dalamud/Game/Config/SystemConfigOption.cs b/Dalamud/Game/Config/SystemConfigOption.cs index 5305e06d9..f7e3bd3f8 100644 --- a/Dalamud/Game/Config/SystemConfigOption.cs +++ b/Dalamud/Game/Config/SystemConfigOption.cs @@ -1436,4 +1436,46 @@ public enum SystemConfigOption /// [GameConfigOption("PadButton_R3", ConfigType.String)] PadButton_R3, + + /// + /// System option with the internal name ActiveInstanceGuid. + /// This option is a String. + /// + [GameConfigOption("ActiveInstanceGuid", ConfigType.String)] + ActiveInstanceGuid, + + /// + /// System option with the internal name ActiveProductGuid. + /// This option is a String. + /// + [GameConfigOption("ActiveProductGuid", ConfigType.String)] + ActiveProductGuid, + + /// + /// System option with the internal name MsqProgress. + /// This option is a UInt. + /// + [GameConfigOption("MsqProgress", ConfigType.UInt)] + MsqProgress, + + /// + /// System option with the internal name PromptConfigUpdate. + /// This option is a UInt. + /// + [GameConfigOption("PromptConfigUpdate", ConfigType.UInt)] + PromptConfigUpdate, + + /// + /// System option with the internal name TitleScreenType. + /// This option is a UInt. + /// + [GameConfigOption("TitleScreenType", ConfigType.UInt)] + TitleScreenType, + + /// + /// System option with the internal name FirstConfigBackup. + /// This option is a UInt. + /// + [GameConfigOption("FirstConfigBackup", ConfigType.UInt)] + FirstConfigBackup, } diff --git a/Dalamud/Game/Config/UiConfigOption.cs b/Dalamud/Game/Config/UiConfigOption.cs index bf329bd6c..1a44c3afa 100644 --- a/Dalamud/Game/Config/UiConfigOption.cs +++ b/Dalamud/Game/Config/UiConfigOption.cs @@ -118,6 +118,7 @@ public enum UiConfigOption /// System option with the internal name LockonDefaultZoom_179. /// This option is a Float. /// + [Obsolete("This option won't work. Use LockonDefaultZoom.")] [GameConfigOption("LockonDefaultZoom_179", ConfigType.Float)] LockonDefaultZoom_179, @@ -3578,4 +3579,25 @@ public enum UiConfigOption /// [GameConfigOption("PadMode", ConfigType.UInt)] PadMode, + + /// + /// System option with the internal name EnableMoveTiltCharacter. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)] + EnableMoveTiltCharacter, + + /// + /// System option with the internal name EnableMoveTiltMountGround. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)] + EnableMoveTiltMountGround, + + /// + /// System option with the internal name EnableMoveTiltMountFly. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)] + EnableMoveTiltMountFly, } From fe90c9a8208ec707cb118720563b119b56754ca4 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 16 Nov 2024 17:46:13 +0100 Subject: [PATCH 145/375] [master] Update ClientStructs (#2092) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 60ce87a00..b0ff14027 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 60ce87a001610d114702b51f08ea726a294e75dd +Subproject commit b0ff14027dfc5be3b8483ad54cf678e734e0ab6d From 8ccac255cbfda1ffd55ded3b6afc91579613f1f0 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sat, 16 Nov 2024 14:47:06 -0800 Subject: [PATCH 146/375] Update to Lumina 5.3.0 and Lumina.Excel 7.1.1 (#2096) --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 ++-- Dalamud/Dalamud.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 449d3db68..a01463df4 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 8e912fe8f..c5662fbfc 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all From c2588cec6b8b53ac569fcd7419195781541f47bd Mon Sep 17 00:00:00 2001 From: Infi Date: Sat, 16 Nov 2024 23:51:38 +0100 Subject: [PATCH 147/375] Switch from CurrentGauge pointer to using the gauge fields address directly (#2094) --- Dalamud/Game/ClientState/JobGauge/JobGauges.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index e47bead36..67429956b 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -28,7 +28,7 @@ internal class JobGauges : IServiceType, IJobGauges } /// - public unsafe IntPtr Address => (nint)(CSJobGaugeManager.Instance()->CurrentGauge); + public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->EmptyGauge); /// public T Get() where T : JobGaugeBase From bf9a9a05cafbbec84552ecf9ceb01de607cdcc55 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:58:21 +0100 Subject: [PATCH 148/375] Update ClientStructs (#2095) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index b0ff14027..4c8ca577d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit b0ff14027dfc5be3b8483ad54cf678e734e0ab6d +Subproject commit 4c8ca577d472fd806ea1fc5c957b3c52f628fa4c From aa450a71995cee42eabca65232494b450224fc1d Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 17 Nov 2024 00:08:23 +0100 Subject: [PATCH 149/375] Revert ItemHover/ItemOut hook removal (#2093) * Revert ItemHover/ItemOut hook removal * Update delegates for HandleItemHover hooks * Fix HandleItemHoverDetour spam * Update GameGui signature comments * Use int in HandleItemOutDetour like the game does * Hook AgentItemDetail vfuncs * Add missing hook Dispose calls --------- Co-authored-by: KazWolfe --- Dalamud/Game/Gui/GameGui.cs | 74 ++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 54f4253cd..aecbb7201 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -32,12 +32,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui { private static readonly ModuleLog Log = new("GameGui"); - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); - private readonly GameGuiAddressResolver address; private readonly Hook setGlobalBgmHook; + private readonly Hook handleItemHoverHook; + private readonly Hook handleItemOutHook; private readonly Hook handleActionHoverHook; private readonly Hook handleActionOutHook; private readonly Hook handleImmHook; @@ -57,6 +56,9 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); + this.handleItemHoverHook = Hook.FromAddress((nint)AgentItemDetail.StaticVirtualTablePointer->Update, this.HandleItemHoverDetour); + this.handleItemOutHook = Hook.FromAddress((nint)AgentItemDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleItemOutDetour); + this.handleActionHoverHook = Hook.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour); this.handleActionOutHook = Hook.FromAddress((nint)AgentActionDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleActionOutDetour); @@ -67,13 +69,13 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.utf8StringFromSequenceHook = Hook.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour); this.setGlobalBgmHook.Enable(); + this.handleItemHoverHook.Enable(); + this.handleItemOutHook.Enable(); this.handleImmHook.Enable(); this.setUiVisibilityHook.Enable(); this.handleActionHoverHook.Enable(); this.handleActionOutHook.Enable(); this.utf8StringFromSequenceHook.Enable(); - - this.framework.Update += this.FrameworkUpdate; } // Hooked delegates @@ -252,9 +254,9 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// void IInternalDisposableService.DisposeService() { - this.framework.Update -= this.FrameworkUpdate; - this.setGlobalBgmHook.Dispose(); + this.handleItemHoverHook.Dispose(); + this.handleItemOutHook.Dispose(); this.handleImmHook.Dispose(); this.setUiVisibilityHook.Dispose(); this.handleActionHoverHook.Dispose(); @@ -300,6 +302,46 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return retVal; } + private void HandleItemHoverDetour(AgentItemDetail* thisPtr, uint frameCount) + { + this.handleItemHoverHook.Original(thisPtr, frameCount); + + if (!thisPtr->IsAgentActive()) + return; + + var itemId = (ulong)thisPtr->ItemId; + if (this.HoveredItem == itemId) + return; + + this.HoveredItem = itemId; + this.HoveredItemChanged?.InvokeSafely(this, itemId); + + Log.Verbose($"HoveredItem changed: {itemId}"); + } + + private AtkValue* HandleItemOutDetour(AgentItemDetail* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind) + { + var ret = this.handleItemOutHook.Original(thisPtr, returnValue, values, valueCount, eventKind); + + if (values != null && valueCount == 1 && values->Int == -1) + { + this.HoveredItem = 0; + + try + { + this.HoveredItemChanged?.Invoke(this, 0); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredItemChanged event."); + } + + Log.Verbose("HoveredItem changed: 0"); + } + + return ret; + } + private void HandleActionHoverDetour(AgentActionDetail* hoverState, ActionKind actionKind, uint actionId, int a4, byte a5) { this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); @@ -371,24 +413,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe? } - - private unsafe void FrameworkUpdate(IFramework framework) - { - var agentItemDetail = AgentItemDetail.Instance(); - if (agentItemDetail != null) - { - var itemId = agentItemDetail->ItemId; - - if (this.HoveredItem != itemId) - { - Log.Verbose($"HoveredItem changed: {itemId}"); - - this.HoveredItem = itemId; - - this.HoveredItemChanged?.InvokeSafely(this, itemId); - } - } - } } /// From 0d9eed298c10a5a4cca49c41f235a0e9210ba246 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 17 Nov 2024 02:50:15 +0100 Subject: [PATCH 150/375] Use correct type for GameInventoryItem.Stains (#2099) --- Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index fb27346e0..a9b178411 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -122,7 +122,7 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets the color used for this item. /// - public ReadOnlySpan Stains => new(Unsafe.AsPointer(ref this.InternalItem.Stains[0]), 2); + public ReadOnlySpan Stains => new(Unsafe.AsPointer(ref this.InternalItem.Stains[0]), 2); /// /// Gets the glamour id for this item. From 52e8fca7f14d84dd8ca9cf2287d024b911d60774 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 17 Nov 2024 02:50:58 +0100 Subject: [PATCH 151/375] Add InventoryWidget (#2097) * Add InventoryWidget * Update InventoryWidget - Use GameInventoryItem.GetReadOnlySpanOfInventory - Display Stains - Display Materia * Display CrafterContentId in InventoryWidget * Fix display of HQ items in InventoryWidget Also removes the Base ItemId, because Dalamud doesn't apply flags. * Make columns Itemid and Quantity copyable --- .../Internal/Windows/Data/DataWindow.cs | 1 + .../Windows/Data/Widgets/InventoryWidget.cs | 405 ++++++++++++++++++ 2 files changed, 406 insertions(+) create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 2449cab1c..7678b395e 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -43,6 +43,7 @@ internal class DataWindow : Window, IDisposable new HookWidget(), new IconBrowserWidget(), new ImGuiWidget(), + new InventoryWidget(), new KeyStateWidget(), new MarketBoardWidget(), new NetworkMonitorWidget(), diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs new file mode 100644 index 000000000..ac576da77 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -0,0 +1,405 @@ +using System.Buffers.Binary; +using System.Numerics; +using System.Text; + +using Dalamud.Data; +using Dalamud.Game.Inventory; +using Dalamud.Game.Text; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.Internal; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using FFXIVClientStructs.FFXIV.Client.Game; + +using ImGuiNET; + +using Lumina.Excel.Sheets; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +#pragma warning disable SeStringRenderer + +/// +/// Widget for displaying inventory data. +/// +internal class InventoryWidget : IDataWindowWidget +{ + private DataManager dataManager; + private TextureManager textureManager; + private InventoryType? selectedInventoryType = InventoryType.Inventory1; + + /// + public string[]? CommandShortcuts { get; init; } = ["inv", "inventory"]; + + /// + public string DisplayName { get; init; } = "Inventory"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.Ready = true; + } + + /// + public void Draw() + { + this.dataManager ??= Service.Get(); + this.textureManager ??= Service.Get(); + + this.DrawInventoryTypeList(); + + if (this.selectedInventoryType == null) + return; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + this.DrawInventoryType((InventoryType)this.selectedInventoryType); + } + + private static string StripSoftHypen(string input) + { + return input.Replace("\u00AD", string.Empty); + } + + private unsafe void DrawInventoryTypeList() + { + using var table = ImRaii.Table("InventoryTypeTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings, new Vector2(300, -1)); + if (!table) return; + + ImGui.TableSetupColumn("Type"); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupScrollFreeze(2, 1); + ImGui.TableHeadersRow(); + + foreach (var inventoryType in Enum.GetValues()) + { + var items = GameInventoryItem.GetReadOnlySpanOfInventory((GameInventoryType)inventoryType); + + using var itemDisabled = ImRaii.Disabled(items.IsEmpty); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Type + if (ImGui.Selectable(inventoryType.ToString(), this.selectedInventoryType == inventoryType, ImGuiSelectableFlags.SpanAllColumns)) + { + this.selectedInventoryType = inventoryType; + } + + using (var contextMenu = ImRaii.ContextPopupItem($"##InventoryContext{inventoryType}")) + { + if (contextMenu) + { + if (ImGui.MenuItem("Copy Name")) + { + ImGui.SetClipboardText(inventoryType.ToString()); + } + + if (ImGui.MenuItem("Copy Address")) + { + var container = InventoryManager.Instance()->GetInventoryContainer(inventoryType); + ImGui.SetClipboardText($"0x{(nint)container:X}"); + } + } + } + + ImGui.TableNextColumn(); // Size + ImGui.TextUnformatted(items.Length.ToString()); + } + } + + private unsafe void DrawInventoryType(InventoryType inventoryType) + { + var items = GameInventoryItem.GetReadOnlySpanOfInventory((GameInventoryType)inventoryType); + if (items.IsEmpty) + { + ImGui.TextUnformatted($"{inventoryType} is empty."); + return; + } + + using var itemTable = ImRaii.Table("InventoryItemTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); + if (!itemTable) return; + ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 70); + ImGui.TableSetupColumn("Quantity", ImGuiTableColumnFlags.WidthFixed, 70); + ImGui.TableSetupColumn("Item"); + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableHeadersRow(); + + for (var slotIndex = 0; slotIndex < items.Length; slotIndex++) + { + var item = items[slotIndex]; + + using var disableditem = ImRaii.Disabled(item.ItemId == 0); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Slot + ImGui.TextUnformatted(slotIndex.ToString()); + + ImGui.TableNextColumn(); // ItemId + ImGuiHelpers.ClickToCopyText(item.ItemId.ToString()); + + ImGui.TableNextColumn(); // Quantity + ImGuiHelpers.ClickToCopyText(item.Quantity.ToString()); + + ImGui.TableNextColumn(); // Item + if (item.ItemId != 0 && item.Quantity != 0) + { + var itemName = this.GetItemName(item.ItemId); + var iconId = this.GetItemIconId(item.ItemId); + + if (item.IsHq) + itemName += " " + SeIconChar.HighQuality.ToIconString(); + + if (this.textureManager.Shared.TryGetFromGameIcon(new GameIconLookup(iconId, item.IsHq), out var tex) && tex.TryGetWrap(out var texture, out _)) + { + ImGui.Image(texture.ImGuiHandle, new Vector2(ImGui.GetTextLineHeight())); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted("Click to copy IconId"); + ImGui.TextUnformatted($"ID: {iconId} – Size: {texture.Width}x{texture.Height}"); + ImGui.Image(texture.ImGuiHandle, new(texture.Width, texture.Height)); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(iconId.ToString()); + } + + ImGui.SameLine(); + + using var itemNameColor = ImRaii.PushColor(ImGuiCol.Text, this.GetItemRarityColor(item.ItemId)); + using var node = ImRaii.TreeNode($"{itemName}###{inventoryType}_{slotIndex}", ImGuiTreeNodeFlags.SpanAvailWidth); + itemNameColor.Dispose(); + + using (var contextMenu = ImRaii.ContextPopupItem($"{inventoryType}_{slotIndex}_ContextMenu")) + { + if (contextMenu) + { + if (ImGui.MenuItem("Copy Name")) + { + ImGui.SetClipboardText(itemName); + } + } + } + + if (!node) continue; + + using var itemInfoTable = ImRaii.Table($"{inventoryType}_{slotIndex}_Table", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); + if (!itemInfoTable) continue; + + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 150); + ImGui.TableSetupColumn("Value"); + // ImGui.TableHeadersRow(); + + static void AddKeyValueRow(string fieldName, string value) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(fieldName); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText(value); + } + + static void AddValueValueRow(string value1, string value2) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText(value1); + ImGui.TableNextColumn(); + ImGuiHelpers.ClickToCopyText(value2); + } + + AddKeyValueRow("ItemId", item.ItemId.ToString()); + AddKeyValueRow("Quantity", item.Quantity.ToString()); + AddKeyValueRow("GlamourId", item.GlamourId.ToString()); + + if (!this.IsEventItem(item.ItemId)) + { + AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.Spiritbond.ToString()); + + if (item.CrafterContentId != 0) + AddKeyValueRow("CrafterContentId", item.CrafterContentId.ToString()); + } + + var flagsBuilder = new StringBuilder(); + + if (item.IsHq) + { + flagsBuilder.Append("IsHq"); + } + + if (item.IsCompanyCrestApplied) + { + if (flagsBuilder.Length != 0) + flagsBuilder.Append(", "); + + flagsBuilder.Append("IsCompanyCrestApplied"); + } + + if (item.IsRelic) + { + if (flagsBuilder.Length != 0) + flagsBuilder.Append(", "); + + flagsBuilder.Append("IsRelic"); + } + + if (item.IsCollectable) + { + if (flagsBuilder.Length != 0) + flagsBuilder.Append(", "); + + flagsBuilder.Append("IsCollectable"); + } + + if (flagsBuilder.Length == 0) + flagsBuilder.Append("None"); + + AddKeyValueRow("Flags", flagsBuilder.ToString()); + + if (this.IsNormalItem(item.ItemId) && this.dataManager.Excel.GetSheet().TryGetRow(item.ItemId, out var itemRow)) + { + if (itemRow.DyeCount > 0) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Stains"); + ImGui.TableNextColumn(); + + using var stainTable = ImRaii.Table($"{inventoryType}_{slotIndex}_StainTable", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); + if (!stainTable) continue; + + ImGui.TableSetupColumn("Stain Id", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("Name"); + ImGui.TableHeadersRow(); + + for (var i = 0; i < itemRow.DyeCount; i++) + { + AddValueValueRow(item.Stains[i].ToString(), this.GetStainName(item.Stains[i])); + } + } + + if (itemRow.MateriaSlotCount > 0) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Materia"); + ImGui.TableNextColumn(); + + using var materiaTable = ImRaii.Table($"{inventoryType}_{slotIndex}_MateriaTable", 2, ImGuiTableFlags.BordersInner | ImGuiTableFlags.NoSavedSettings); + if (!materiaTable) continue; + + ImGui.TableSetupColumn("Materia Id", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupColumn("MateriaGrade Id"); + ImGui.TableHeadersRow(); + + for (var i = 0; i < Math.Min(itemRow.MateriaSlotCount, item.Materia.Length); i++) + { + AddValueValueRow(item.Materia[i].ToString(), item.MateriaGrade[i].ToString()); + } + } + } + } + } + } + + private bool IsEventItem(uint itemId) => itemId is > 2_000_000; + + private bool IsHighQuality(uint itemId) => itemId is > 1_000_000 and < 2_000_000; + + private bool IsCollectible(uint itemId) => itemId is > 500_000 and < 1_000_000; + + private bool IsNormalItem(uint itemId) => itemId is < 500_000; + + private uint GetBaseItemId(uint itemId) + { + if (this.IsEventItem(itemId)) return itemId; // uses EventItem sheet + if (this.IsHighQuality(itemId)) return itemId - 1_000_000; + if (this.IsCollectible(itemId)) return itemId - 500_000; + return itemId; + } + + private string GetItemName(uint itemId) + { + // EventItem + if (this.IsEventItem(itemId)) + { + return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var eventItemRow) + ? StripSoftHypen(eventItemRow.Name.ExtractText()) + : $"EventItem#{itemId}"; + } + + // HighQuality + if (this.IsHighQuality(itemId)) + itemId -= 1_000_000; + + // Collectible + if (this.IsCollectible(itemId)) + itemId -= 500_000; + + return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var itemRow) + ? StripSoftHypen(itemRow.Name.ExtractText()) + : $"Item#{itemId}"; + } + + private string GetStainName(uint stainId) + { + return this.dataManager.Excel.GetSheet().TryGetRow(stainId, out var stainRow) + ? StripSoftHypen(stainRow.Name.ExtractText()) + : $"Stain#{stainId}"; + } + + private uint GetItemRarityColorType(Item item, bool isEdgeColor = false) + { + return (isEdgeColor ? 548u : 547u) + item.Rarity * 2u; + } + + private uint GetItemRarityColorType(uint itemId, bool isEdgeColor = false) + { + // EventItem + if (this.IsEventItem(itemId)) + return this.GetItemRarityColorType(1, isEdgeColor); + + if (!this.dataManager.Excel.GetSheet().TryGetRow(this.GetBaseItemId(itemId), out var item)) + return this.GetItemRarityColorType(1, isEdgeColor); + + return this.GetItemRarityColorType(item, isEdgeColor); + } + + private uint GetItemRarityColor(uint itemId, bool isEdgeColor = false) + { + if (this.IsEventItem(itemId)) + return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; + + if (!this.dataManager.Excel.GetSheet().TryGetRow(this.GetBaseItemId(itemId), out var item)) + return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; + + var rowId = this.GetItemRarityColorType(item, isEdgeColor); + return this.dataManager.Excel.GetSheet().TryGetRow(rowId, out var color) + ? BinaryPrimitives.ReverseEndianness(color.UIForeground) | 0xFF000000 + : 0xFFFFFFFF; + } + + private uint GetItemIconId(uint itemId) + { + // EventItem + if (this.IsEventItem(itemId)) + return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var eventItem) ? eventItem.Icon : 0u; + + // HighQuality + if (this.IsHighQuality(itemId)) + itemId -= 1_000_000; + + // Collectible + if (this.IsCollectible(itemId)) + itemId -= 500_000; + + return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var item) ? item.Icon : 0u; + } +} From 93b2b5228a4cd627b549a7475eda17b0f47de70e Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sat, 16 Nov 2024 20:24:33 -0800 Subject: [PATCH 152/375] Update to Lumina 5.4.0 and Lumina.Excel 7.1.2 (#2100) --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 ++-- Dalamud/Dalamud.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index a01463df4..3f0ed53f4 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index c5662fbfc..79f80ea8c 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all From dc10ba4ec52f47fd66ffa6feb5e5c719ae339cd8 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 17 Nov 2024 14:35:40 +0100 Subject: [PATCH 153/375] [master] Update ClientStructs (#2098) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 4c8ca577d..33a98af53 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 4c8ca577d472fd806ea1fc5c957b3c52f628fa4c +Subproject commit 33a98af530e52d5b54714ec9f7704c07bf9fdd91 From d856f657c2b241cd16e2dc7e5c8836643d08cc7f Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 17 Nov 2024 14:59:39 +0100 Subject: [PATCH 154/375] [master] Update ClientStructs (#2101) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 33a98af53..6ef1f01dc 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 33a98af530e52d5b54714ec9f7704c07bf9fdd91 +Subproject commit 6ef1f01dc12162e271b65705990b8175330806ca From d538ce6350ac464145b42cf90e1222ee133d38bf Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:46:17 +0100 Subject: [PATCH 155/375] Update ClientStructs (#2103) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6ef1f01dc..76b8b8982 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6ef1f01dc12162e271b65705990b8175330806ca +Subproject commit 76b8b8982b57cd85eed5a5f509feee0442ca1b6f From 2f502767386320d84192cc6acd2971d17931e31a Mon Sep 17 00:00:00 2001 From: Infi Date: Tue, 19 Nov 2024 02:29:53 +0100 Subject: [PATCH 156/375] Switch PlayerPayload Encode/Decode to using Lumina (#2107) * Switch PlayerPayload Encode/Decode to using Lumina - This also fixes a bug that PlayerPayload would encode worldIds as byte (they are ushort) * Remove comment --- .../Payloads/PlayerPayload.cs | 59 +++++++------------ 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index 07a13e5a3..55697782e 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -6,6 +6,9 @@ using Dalamud.Data; using Lumina.Excel; using Lumina.Excel.Sheets; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; @@ -84,50 +87,32 @@ public class PlayerPayload : Payload /// protected override byte[] EncodeImpl() { - var chunkLen = this.playerName.Length + 7; - var bytes = new List() - { - START_BYTE, - (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, - /* unk */ 0x01, - (byte)(this.serverId + 1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually - /* unk */ 0x01, - /* unk */ 0xFF, // these sometimes vary but are frequently this - (byte)(this.playerName.Length + 1), - }; - - bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); - bytes.Add(END_BYTE); - - // TODO: should these really be here? additional payloads should come in separately already... - - // encoded names are followed by the name in plain text again - // use the payload parsing for consistency, as this is technically a new chunk - bytes.AddRange(new TextPayload(this.playerName).Encode()); - - // unsure about this entire packet, but it seems to always follow a name - bytes.AddRange(new byte[] - { - START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator, - 0x01, 0x01, 0x01, 0xFF, 0x01, - END_BYTE, - }); - - return bytes.ToArray(); + var ssb = Lumina.Text.SeStringBuilder.SharedPool.Get(); + var res = ssb + .PushLinkCharacter(this.playerName, this.serverId) + .Append(this.playerName) + .PopLink() + .ToArray(); + Lumina.Text.SeStringBuilder.SharedPool.Return(ssb); + return res; } /// protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - // unk - reader.ReadByte(); + var body = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); + var rosps = new ReadOnlySePayloadSpan(ReadOnlySePayloadType.Macro, MacroCode.Link, body.AsSpan()); - this.serverId = GetInteger(reader); + if (!rosps.TryGetExpression(out _, out var worldIdExpression, out _, out var characterNameExpression)) + return; - // unk - reader.ReadBytes(2); + if (!worldIdExpression.TryGetUInt(out var worldId)) + return; - var nameLen = (int)GetInteger(reader); - this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); + if (!characterNameExpression.TryGetString(out var characterName)) + return; + + this.serverId = worldId; + this.playerName = characterName.ExtractText(); } } From 192dc9c3c3b37b12faac4d2776f42c21365c98e0 Mon Sep 17 00:00:00 2001 From: ItsBexy <103910869+ItsBexy@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:30:12 -0700 Subject: [PATCH 157/375] Update Util.ShowStruct() (#2104) - Now prints field offsets, if/when they are defined. - Fixed a bug wherein Boolean fields were being printed with incorrect values (now tries reading the value as a byte, which seems to do the trick) --- Dalamud/Utility/Util.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 15327a66c..805532025 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -1203,6 +1203,8 @@ public static class Util .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); + if (fixedBuffer != null) { ImGui.Text($"fixed"); @@ -1212,6 +1214,11 @@ public static class Util } else { + if (offset != null) + { + ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.SameLine(); + } ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); } @@ -1224,6 +1231,8 @@ public static class Util { if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct + else if (f.FieldType == typeof(bool) && offset != null) + ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); else ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); } From bf7ef00ec0ba2733628defc6d7e2b4764ebbe3e2 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 19 Nov 2024 02:30:47 +0100 Subject: [PATCH 158/375] Update ClientStructs (#2105) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 76b8b8982..897f9996d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 76b8b8982b57cd85eed5a5f509feee0442ca1b6f +Subproject commit 897f9996dfa65046c8cf2848c4f783c9ce3b858e From 3a3d6b6e6ad33d135615f8f11673e233657922e5 Mon Sep 17 00:00:00 2001 From: Infi Date: Tue, 19 Nov 2024 03:05:28 +0100 Subject: [PATCH 159/375] Implement Print methods that work with Lumina SeString/ReadOnlySeString (#2106) * Implement Print methods that work with Lumina SeString/ReadOnlySeString * null terminate before passing to Utf8String * Rename XivChatEntryReadOnly to XivChatEntryRaw * Fix error from wrong conversion method * Follow Rider suggestion * Switch from AppendMacroString to BeginMacro for optimization * More optimization suggested by kizer * More kizer suggested optimizations * Fix small mistake * Use XivChatEntry and read/write to Byte fields accordingly --- Dalamud/Game/Gui/ChatGui.cs | 117 ++++++++++++++++++---------- Dalamud/Game/Text/XivChatEntry.cs | 22 +++++- Dalamud/Plugin/Services/IChatGui.cs | 24 +++++- 3 files changed, 118 insertions(+), 45 deletions(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index fefc82790..84622c5e8 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -8,9 +8,11 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; +using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Memory; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -20,6 +22,8 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text.Payloads; + using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType; using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType; @@ -107,6 +111,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.handleLinkClickHook.Dispose(); } + #region DalamudSeString + /// public void Print(XivChatEntry chat) { @@ -137,6 +143,24 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); } + #endregion + + #region LuminaSeString + + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor); + } + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + { + this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); + } + + #endregion + /// /// Process a chat queue. /// @@ -145,30 +169,35 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui while (this.chatQueue.Count > 0) { var chat = this.chatQueue.Dequeue(); - var replacedMessage = new SeStringBuilder(); + var sb = LuminaSeStringBuilder.SharedPool.Get(); + var rosss = (ReadOnlySeStringSpan)chat.MessageBytes; - // Normalize Unicode NBSP to the built-in one, as the former won't renderl - foreach (var payload in chat.Message.Payloads) + foreach (var payload in rosss) { - if (payload is TextPayload { Text: not null } textPayload) + if (payload.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (payload.Type != ReadOnlySePayloadType.Text) { - var split = textPayload.Text.Split("\u202f"); // NARROW NO-BREAK SPACE - for (var i = 0; i < split.Length; i++) - { - replacedMessage.AddText(split[i]); - if (i + 1 < split.Length) - replacedMessage.Add(new RawPayload([0x02, (byte)Lumina.Text.Payloads.PayloadType.Indent, 0x01, 0x03])); - } + sb.Append(payload); + continue; } - else + + foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default)) { - replacedMessage.Add(payload); + if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); + else + sb.Append(c.EffectiveChar); } } - var sender = Utf8String.FromSequence(chat.Name.Encode()); - var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode()); - + var output = sb.ToArray(); + LuminaSeStringBuilder.SharedPool.Return(sb); + + var sender = Utf8String.FromSequence(chat.NameBytes.NullTerminate()); + var message = Utf8String.FromSequence(output.NullTerminate()); + var targetChannel = chat.Type ?? this.configuration.GeneralChatType; this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); @@ -228,29 +257,6 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } } - private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color) - { - var builder = new SeStringBuilder(); - - if (!tag.IsNullOrEmpty()) - { - if (color is not null) - { - builder.AddUiForeground($"[{tag}] ", color.Value); - } - else - { - builder.AddText($"[{tag}] "); - } - } - - this.Print(new XivChatEntry - { - Message = builder.AddText(message).Build(), - Type = channel, - }); - } - private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color) { var builder = new SeStringBuilder(); @@ -274,6 +280,31 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui }); } + private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) + { + var builder = new LuminaSeStringBuilder(); + + if (!tag.IsNullOrEmpty()) + { + if (color is not null) + { + builder.PushColorType(color.Value); + builder.Append($"[{tag}] "); + builder.PopColorType(); + } + else + { + builder.Append($"[{tag}] "); + } + } + + this.Print(new XivChatEntry + { + MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(), + Type = channel, + }); + } + private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) { this.inventoryItemCopyHook.Original(thisPtr, otherPtr); @@ -505,6 +536,14 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null) => this.chatGuiService.PrintError(message, messageTag, tagColor); + /// + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.Print(message, messageTag, tagColor); + + /// + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null) + => this.chatGuiService.PrintError(message, messageTag, tagColor); + private void OnMessageForward(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled) => this.ChatMessage?.Invoke(type, timestamp, ref sender, ref message, ref isHandled); diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index eb40d6636..7932ead72 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -20,12 +20,30 @@ public sealed class XivChatEntry /// /// Gets or sets the sender name. /// - public SeString Name { get; set; } = string.Empty; + public SeString Name + { + get => SeString.Parse(this.NameBytes); + set => this.NameBytes = value.Encode(); + } /// /// Gets or sets the message. /// - public SeString Message { get; set; } = string.Empty; + public SeString Message + { + get => SeString.Parse(this.MessageBytes); + set => this.MessageBytes = value.Encode(); + } + + /// + /// Gets or Sets the name payloads + /// + public byte[] NameBytes { get; set; } = []; + + /// + /// Gets or Sets the message payloads. + /// + public byte[] MessageBytes { get; set; } = []; /// /// Gets or sets a value indicating whether new message sounds should be silenced or not. diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 42bbd6b06..3f221b3bb 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -48,7 +48,7 @@ public interface IChatGui /// The sender name. /// The message sent. public delegate void OnMessageUnhandledDelegate(XivChatType type, int timestamp, SeString sender, SeString message); - + /// /// Event that will be fired when a chat message is sent to chat by the game. /// @@ -68,17 +68,17 @@ public interface IChatGui /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. /// public event OnMessageUnhandledDelegate ChatMessageUnhandled; - + /// /// Gets the ID of the last linked item. /// public uint LastLinkedItemId { get; } - + /// /// Gets the flags of the last linked item. /// public byte LastLinkedItemFlags { get; } - + /// /// Gets the dictionary of Dalamud Link Handlers. /// @@ -121,4 +121,20 @@ public interface IChatGui /// String to prepend message with "[messageTag] ". /// Color to display the message tag with. public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void Print(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); + + /// + /// Queue a chat message. Dalamud will send queued messages on the next framework event. + /// + /// A message to send. + /// String to prepend message with "[messageTag] ". + /// Color to display the message tag with. + public void PrintError(ReadOnlySpan message, string? messageTag = null, ushort? tagColor = null); } From c831af5a00b4cc69292aa598c92efb17e8542d6b Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:46:42 +0100 Subject: [PATCH 160/375] Update ClientStructs (#2109) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 897f9996d..6fcd967c6 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 897f9996dfa65046c8cf2848c4f783c9ce3b858e +Subproject commit 6fcd967c6258d02eba8a4ddd6b2fd5fb97b727ad From 6d664cc606ccfa0b7cbdb739c7950076e2bbf05d Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:36:17 +0100 Subject: [PATCH 161/375] Update ClientStructs (#2110) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6fcd967c6..c9d1f33c5 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6fcd967c6258d02eba8a4ddd6b2fd5fb97b727ad +Subproject commit c9d1f33c58f426b6480b2c00e5cdcb0668654973 From d19b7d70d5f1586154946c1db346e08352e71de2 Mon Sep 17 00:00:00 2001 From: srkizer Date: Thu, 21 Nov 2024 02:32:49 +0900 Subject: [PATCH 162/375] Move UtfEnumerator from Dalamud to Lumina (#2111) * Move UtfEnumerator from Dalamud to Lumina Comes with some trivial cleanups. * Update Lumina to 5.5.0 --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 2 +- Dalamud/Dalamud.csproj | 2 +- Dalamud/Game/Gui/ChatGui.cs | 96 +-- .../Game/Text/SeStringHandling/SeString.cs | 4 +- .../Internal/SeStringRenderer.cs | 4 +- .../TextProcessing/LineBreakEnumerator.cs | 2 + .../Internal/TextProcessing/UtfEnumerator.cs | 325 --------- .../TextProcessing/UtfEnumeratorFlags.cs | 58 -- .../Internal/TextProcessing/UtfValue.cs | 665 ------------------ .../Widgets/SeStringRendererTestWidget.cs | 8 +- 10 files changed, 63 insertions(+), 1103 deletions(-) delete mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs delete mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs delete mode 100644 Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 3f0ed53f4..754124ae1 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,7 +27,7 @@ - + diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 79f80ea8c..9399154c8 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,7 +71,7 @@ - + diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 84622c5e8..14346132a 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -8,7 +8,6 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; -using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -22,12 +21,13 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text; using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; -using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType; -using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; -using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType; -using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; +using SeString = Dalamud.Game.Text.SeStringHandling.SeString; +using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; namespace Dalamud.Game.Gui; @@ -166,45 +166,51 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui /// public void UpdateQueue() { - while (this.chatQueue.Count > 0) + if (this.chatQueue.Count == 0) + return; + + var sb = LSeStringBuilder.SharedPool.Get(); + Span namebuf = stackalloc byte[256]; + using var sender = new Utf8String(); + using var message = new Utf8String(); + while (this.chatQueue.TryDequeue(out var chat)) { - var chat = this.chatQueue.Dequeue(); - var sb = LuminaSeStringBuilder.SharedPool.Get(); - var rosss = (ReadOnlySeStringSpan)chat.MessageBytes; - - foreach (var payload in rosss) + sb.Clear(); + foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString)) { - if (payload.Type == ReadOnlySePayloadType.Invalid) - continue; - - if (payload.Type != ReadOnlySePayloadType.Text) - { - sb.Append(payload); - continue; - } - - foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default)) - { - if (c.Value.IntValue == 0x202F) - sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); - else - sb.Append(c.EffectiveChar); - } + if (c.IsSeStringPayload) + sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength)); + else if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); + else + sb.Append(c); } - var output = sb.ToArray(); - LuminaSeStringBuilder.SharedPool.Return(sb); + if (chat.NameBytes.Length + 1 < namebuf.Length) + { + chat.NameBytes.AsSpan().CopyTo(namebuf); + namebuf[chat.NameBytes.Length] = 0; + sender.SetString(namebuf); + } + else + { + sender.SetString(chat.NameBytes.NullTerminate()); + } - var sender = Utf8String.FromSequence(chat.NameBytes.NullTerminate()); - var message = Utf8String.FromSequence(output.NullTerminate()); + message.SetString(sb.GetViewAsSpan()); var targetChannel = chat.Type ?? this.configuration.GeneralChatType; - this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); - - sender->Dtor(true); - message->Dtor(true); + this.HandlePrintMessageDetour( + RaptureLogModule.Instance(), + targetChannel, + &sender, + &message, + chat.Timestamp, + (byte)(chat.Silent ? 1 : 0)); } + + LSeStringBuilder.SharedPool.Return(sb); } /// @@ -282,27 +288,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) { - var builder = new LuminaSeStringBuilder(); + var sb = LSeStringBuilder.SharedPool.Get(); if (!tag.IsNullOrEmpty()) { if (color is not null) { - builder.PushColorType(color.Value); - builder.Append($"[{tag}] "); - builder.PopColorType(); + sb.PushColorType(color.Value); + sb.Append($"[{tag}] "); + sb.PopColorType(); } else { - builder.Append($"[{tag}] "); + sb.Append($"[{tag}] "); } } this.Print(new XivChatEntry { - MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(), + MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(), Type = channel, }); + + LSeStringBuilder.SharedPool.Return(sb); } private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) @@ -412,7 +420,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); - var sb = LuminaSeStringBuilder.SharedPool.Get(); + var sb = LSeStringBuilder.SharedPool.Get(); try { var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload); @@ -423,7 +431,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui sb.Append(payload); if (payload.Type == ReadOnlySePayloadType.Macro && - payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link && + payload.MacroCode == MacroCode.Link && payload.TryGetExpression(out var expr1) && expr1.TryGetInt(out var expr1Val) && expr1Val == (int)LinkMacroPayloadType.Terminator) @@ -452,7 +460,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } finally { - LuminaSeStringBuilder.SharedPool.Return(sb); + LSeStringBuilder.SharedPool.Return(sb); } } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index baae181e3..7f1955da5 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -133,9 +133,7 @@ public class SeString { while (stream.Position < len) { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); + payloads.Add(Payload.Decode(reader)); } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 9d62de193..4937e4af0 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -19,6 +19,7 @@ using FFXIVClientStructs.FFXIV.Client.UI; using ImGuiNET; using Lumina.Excel.Sheets; +using Lumina.Text; using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -66,8 +67,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService [ServiceManager.ServiceConstructor] private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) { - this.colorStackSet = new( - dm.Excel.GetSheet() ?? throw new InvalidOperationException("Failed to access UIColor sheet.")); + this.colorStackSet = new(dm.Excel.GetSheet()); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs index fa994bcd2..8b5115369 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Lumina.Text; + using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass; using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory; using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass; diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs deleted file mode 100644 index b73bc85e4..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System.Collections; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -using Lumina.Text; -using Lumina.Text.Payloads; -using Lumina.Text.ReadOnly; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Enumerates a UTF-N byte sequence by codepoint. -[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")] -internal ref struct UtfEnumerator -{ - private readonly ReadOnlySpan data; - private readonly UtfEnumeratorFlags flags; - private readonly byte numBytesPerUnit; - private bool isBigEndian; - - /// Initializes a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - public UtfEnumerator(ReadOnlySpan data, UtfEnumeratorFlags flags) - { - this.data = data; - this.flags = flags; - this.numBytesPerUnit = (this.flags & UtfEnumeratorFlags.UtfMask) switch - { - UtfEnumeratorFlags.Utf8 or UtfEnumeratorFlags.Utf8SeString => 1, - UtfEnumeratorFlags.Utf16 => 2, - UtfEnumeratorFlags.Utf32 => 4, - _ => throw new ArgumentOutOfRangeException(nameof(this.flags), this.flags, "Multiple UTF flag specified."), - }; - this.isBigEndian = (flags & UtfEnumeratorFlags.EndiannessMask) switch - { - UtfEnumeratorFlags.NativeEndian => !BitConverter.IsLittleEndian, - UtfEnumeratorFlags.LittleEndian => false, - UtfEnumeratorFlags.BigEndian => true, - _ => throw new ArgumentOutOfRangeException(nameof(flags), flags, "Multiple endianness flag specified."), - }; - } - - /// - public Subsequence Current { get; private set; } = default; - - /// Creates a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - /// A new enumerator. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => new(data, flags); - - /// Creates a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - /// A new enumerator. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => - new(MemoryMarshal.Cast(data), flags); - - /// Gets the representative char for a given SeString macro code. - /// The macro code. - /// Representative char, or if none. - public static char RepresentativeCharFor(MacroCode macroCode) => macroCode switch - { - MacroCode.NewLine => '\u0085', - MacroCode.SoftHyphen => '\u00AD', - MacroCode.NonBreakingSpace => '\u00A0', - MacroCode.Hyphen => '-', - MacroCode.Icon or MacroCode.Icon2 => '\uFFFC', - _ => char.MaxValue, - }; - - /// Attempts to peek the next item. - /// Retrieved next item. - /// Whether it still should be parsed in big endian. - /// true if anything is retrieved. - /// The sequence is not a fully valid Unicode sequence, and - /// is set. - public readonly bool TryPeekNext(out Subsequence nextSubsequence, out bool isStillBigEndian) - { - var offset = this.Current.ByteOffset + this.Current.ByteLength; - isStillBigEndian = this.isBigEndian; - while (true) - { - var subspan = this.data[offset..]; - - if (subspan.IsEmpty) - { - nextSubsequence = default; - return false; - } - - UtfValue value; - int length; - var isBroken = - this.numBytesPerUnit switch - { - 1 => !UtfValue.TryDecode8(subspan, out value, out length), - 2 => !UtfValue.TryDecode16(subspan, isStillBigEndian, out value, out length), - 4 => !UtfValue.TryDecode32(subspan, isStillBigEndian, out value, out length), - _ => throw new InvalidOperationException(), - }; - if (!isBroken && value.IntValue == 0xFFFE) - { - if ((this.flags & UtfEnumeratorFlags.DisrespectByteOrderMask) == 0) - { - isStillBigEndian = !isStillBigEndian; - value = 0xFEFF; - } - - if ((this.flags & UtfEnumeratorFlags.YieldByteOrderMask) == 0) - { - offset += length; - continue; - } - } - - if (isBroken || !Rune.IsValid(value)) - { - switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) - { - case UtfEnumeratorFlags.ReplaceErrors: - break; - - case UtfEnumeratorFlags.IgnoreErrors: - offset = Math.Min(offset + this.numBytesPerUnit, this.data.Length); - continue; - - case UtfEnumeratorFlags.ThrowOnFirstError: - if (isBroken) - throw new EncoderFallbackException($"0x{subspan[0]:X02} is not a valid sequence."); - throw new EncoderFallbackException( - $"U+{value.UIntValue:X08} is not a valid unicode codepoint."); - - case UtfEnumeratorFlags.TerminateOnFirstError: - default: - nextSubsequence = default; - return false; - } - } - - if (isBroken) - value = subspan[0]; - - if (value == SeString.StartByte && (this.flags & UtfEnumeratorFlags.Utf8SeString) != 0) - { - var e = new ReadOnlySeStringSpan(subspan).GetEnumerator(); - e.MoveNext(); - switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) - { - case var _ when e.Current.Type is ReadOnlySePayloadType.Macro: - nextSubsequence = Subsequence.FromPayload( - e.Current.MacroCode, - offset, - e.Current.EnvelopeByteLength); - return true; - - case UtfEnumeratorFlags.ReplaceErrors: - value = '\uFFFE'; - length = e.Current.EnvelopeByteLength; - isBroken = true; - break; - - case UtfEnumeratorFlags.IgnoreErrors: - offset = Math.Min(offset + e.Current.EnvelopeByteLength, this.data.Length); - continue; - - case UtfEnumeratorFlags.ThrowOnFirstError: - throw new EncoderFallbackException("Invalid SeString payload."); - - case UtfEnumeratorFlags.TerminateOnFirstError: - default: - nextSubsequence = default; - return false; - } - } - - nextSubsequence = Subsequence.FromUnicode(value, offset, length, isBroken); - return true; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - if (!this.TryPeekNext(out var next, out var isStillBigEndian)) - return false; - - this.Current = next; - this.isBigEndian = isStillBigEndian; - return true; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfEnumerator GetEnumerator() => new(this.data, this.flags); - - /// A part of a UTF-N sequence containing one codepoint. - [StructLayout(LayoutKind.Explicit, Size = 16)] - [DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")] - public readonly struct Subsequence : IEquatable - { - /// The codepoint. Valid if is false. - [FieldOffset(0)] - public readonly UtfValue Value; - - /// The macro code. Valid if is true. - [FieldOffset(0)] - public readonly MacroCode MacroCode; - - /// The offset of this part of a UTF-8 sequence. - [FieldOffset(4)] - public readonly int ByteOffset; - - /// The length of this part of a UTF-8 sequence. - /// This may not match , if is true. - /// - [FieldOffset(8)] - public readonly int ByteLength; - - /// Whether this part of the UTF-8 sequence is broken. - [FieldOffset(12)] - public readonly bool BrokenSequence; - - /// Whether this part of the SeString sequence is a payload. - [FieldOffset(13)] - public readonly bool IsSeStringPayload; - - /// Storage at byte offset 0, for fast implementation. - [FieldOffset(0)] - private readonly ulong storage0; - - /// Storage at byte offset 8, for fast implementation. - [FieldOffset(8)] - private readonly ulong storage1; - - /// Initializes a new instance of the struct. - /// The value. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// Whether this part of the UTF-N sequence is broken. - /// Whether this part of the SeString sequence is a payload. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Subsequence(uint value, int byteOffset, int byteLength, bool brokenSequence, bool isSeStringPayload) - { - this.Value = new(value); - this.ByteOffset = byteOffset; - this.ByteLength = byteLength; - this.BrokenSequence = brokenSequence; - this.IsSeStringPayload = isSeStringPayload; - } - - /// Gets the effective char value, with invalid or non-representable codepoints replaced. - /// - /// if the character should not be displayed at all. - public char EffectiveChar - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue; - } - - /// Gets the effective int value, with invalid codepoints replaced. - /// if the character should not be displayed at all. - public int EffectiveInt => - this.IsSeStringPayload - ? RepresentativeCharFor(this.MacroCode) - : this.BrokenSequence || !this.Value.TryGetRune(out var rune) - ? 0xFFFD - : rune.Value; - - /// Gets the effective value, with invalid codepoints replaced. - /// if the character should not be displayed at all. - public Rune EffectiveRune - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.EffectiveInt); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Subsequence left, Subsequence right) => !left.Equals(right); - - /// Creates a new instance of the struct from a Unicode value. - /// The codepoint. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// Whether this part of the UTF-N sequence is broken. - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) => - new(codepoint, byteOffset, byteLength, brokenSequence, false); - - /// Creates a new instance of the struct from a SeString payload. - /// The macro code. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) => - new((uint)macroCode, byteOffset, byteLength, false, true); - - /// Tests whether this subsequence contains a valid Unicode codepoint. - /// true if this subsequence contains a valid Unicode codepoint. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1); - } -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs deleted file mode 100644 index 01380e40c..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Flags on enumerating a unicode sequence. -[Flags] -internal enum UtfEnumeratorFlags -{ - /// Use the default configuration of and . - Default = default, - - /// Enumerate as UTF-8 (the default.) - Utf8 = Default, - - /// Enumerate as UTF-8 in a SeString. - Utf8SeString = 1 << 1, - - /// Enumerate as UTF-16. - Utf16 = 1 << 2, - - /// Enumerate as UTF-32. - Utf32 = 1 << 3, - - /// Bitmask for specifying the encoding. - UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32, - - /// On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.) - ReplaceErrors = Default, - - /// On error, drop the invalid byte. - IgnoreErrors = 1 << 4, - - /// On error, stop the handling. - TerminateOnFirstError = 1 << 5, - - /// On error, throw an exception. - ThrowOnFirstError = 1 << 6, - - /// Bitmask for specifying the error handling mode. - ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError, - - /// Use the current system native endianness from - /// (the default.) - NativeEndian = Default, - - /// Use little endianness. - LittleEndian = 1 << 7, - - /// Use big endianness. - BigEndian = 1 << 8, - - /// Bitmask for specifying endianness. - EndiannessMask = NativeEndian | LittleEndian | BigEndian, - - /// Disrespect byte order mask. - DisrespectByteOrderMask = 1 << 9, - - /// Yield byte order masks, if it shows up. - YieldByteOrderMask = 1 << 10, -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs deleted file mode 100644 index 6930e6ba4..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs +++ /dev/null @@ -1,665 +0,0 @@ -using System.Buffers.Binary; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Represents a single value to be used in a UTF-N byte sequence. -[StructLayout(LayoutKind.Explicit, Size = 4)] -[DebuggerDisplay("0x{IntValue,h} ({CharValue})")] -internal readonly struct UtfValue : IEquatable, IComparable -{ - /// The unicode codepoint in int, that may not be in a valid range. - [FieldOffset(0)] - public readonly int IntValue; - - /// The unicode codepoint in uint, that may not be in a valid range. - [FieldOffset(0)] - public readonly uint UIntValue; - - /// The high UInt16 value in char, that may have been cut off if outside BMP. - [FieldOffset(0)] - public readonly char CharValue; - - /// Initializes a new instance of the struct. - /// The raw codepoint value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfValue(uint value) => this.UIntValue = value; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfValue(int value) => this.IntValue = value; - - /// Gets the length of this codepoint, encoded in UTF-8. - public int Length8 - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetEncodedLength8(this); - } - - /// Gets the length of this codepoint, encoded in UTF-16. - public int Length16 - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetEncodedLength16(this); - } - - /// Gets the short name, if supported. - /// The buffer containing the short name, or empty if unsupported. - public ReadOnlySpan ShortName - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetShortName(this); - } - - public static implicit operator uint(UtfValue c) => c.UIntValue; - - public static implicit operator int(UtfValue c) => c.IntValue; - - public static implicit operator UtfValue(byte c) => new(c); - - public static implicit operator UtfValue(sbyte c) => new(c); - - public static implicit operator UtfValue(ushort c) => new(c); - - public static implicit operator UtfValue(short c) => new(c); - - public static implicit operator UtfValue(uint c) => new(c); - - public static implicit operator UtfValue(int c) => new(c); - - public static implicit operator UtfValue(char c) => new(c); - - public static implicit operator UtfValue(Rune c) => new(c.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(UtfValue left, UtfValue right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(UtfValue left, UtfValue right) => !left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <(UtfValue left, UtfValue right) => left.CompareTo(right) < 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >(UtfValue left, UtfValue right) => left.CompareTo(right) > 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <=(UtfValue left, UtfValue right) => left.CompareTo(right) <= 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >=(UtfValue left, UtfValue right) => left.CompareTo(right) >= 0; - - /// Gets the short name of the codepoint, for some select codepoints. - /// The codepoint. - /// The value. - public static ReadOnlySpan GetShortName(int codepoint) => - codepoint switch - { - 0x00 => "NUL", - 0x01 => "SOH", - 0x02 => "STX", - 0x03 => "ETX", - 0x04 => "EOT", - 0x05 => "ENQ", - 0x06 => "ACK", - 0x07 => "BEL", - 0x08 => "BS", - 0x09 => "HT", - 0x0a => "LF", - 0x0b => "VT", - 0x0c => "FF", - 0x0d => "CR", - 0x0e => "SO", - 0x0f => "SI", - - 0x10 => "DLE", - 0x11 => "DC1", - 0x12 => "DC2", - 0x13 => "DC3", - 0x14 => "DC4", - 0x15 => "NAK", - 0x16 => "SYN", - 0x17 => "SOH", - 0x18 => "CAN", - 0x19 => "EOM", - 0x1a => "SUB", - 0x1b => "ESC", - 0x1c => "FS", - 0x1d => "GS", - 0x1e => "RS", - 0x1f => "US", - - 0x80 => "PAD", - 0x81 => "HOP", - 0x82 => "BPH", - 0x83 => "NBH", - 0x84 => "IND", - 0x85 => "NEL", - 0x86 => "SSA", - 0x87 => "ESA", - 0x88 => "HTS", - 0x89 => "HTJ", - 0x8a => "VTS", - 0x8b => "PLD", - 0x8c => "PLU", - 0x8d => "RI", - 0x8e => "SS2", - 0x8f => "SS3", - - 0x90 => "DCS", - 0x91 => "PU1", - 0x92 => "PU2", - 0x93 => "STS", - 0x94 => "CCH", - 0x95 => "MW", - 0x96 => "SPA", - 0x97 => "EPA", - 0x98 => "SOS", - 0x99 => "SGC", - 0x9a => "SCI", - 0x9b => "CSI", - 0x9c => "ST", - 0x9d => "OSC", - 0x9e => "PM", - 0x9f => "APC", - - 0xa0 => "NBSP", - 0xad => "SHY", - - _ => default, - }; - - /// Gets the length of the codepoint, when encoded in UTF-8. - /// The codepoint to encode. - /// The length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetEncodedLength8(int codepoint) => (uint)codepoint switch - { - < 1u << 7 => 1, - < 1u << 11 => 2, - < 1u << 16 => 3, - < 1u << 21 => 4, - // Not a valid Unicode codepoint anymore below. - < 1u << 26 => 5, - < 1u << 31 => 6, - _ => 7, - }; - - /// Gets the length of the codepoint, when encoded in UTF-16. - /// The codepoint to encode. - /// The length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetEncodedLength16(int codepoint) => (uint)codepoint switch - { - < 0x10000 => 2, - < 0x10000 + (1 << 20) => 4, - // Not a valid Unicode codepoint anymore below. - < 0x10000 + (1 << 30) => 6, - _ => 8, - }; - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode8(ref ReadOnlySpan source, out UtfValue value, out int length) - { - var v = TryDecode8(source, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-8 byte sequence. - /// The span to decode from. - /// The decoded value. - /// The length of the consumed bytes. 1 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static unsafe bool TryDecode8(ReadOnlySpan source, out UtfValue value, out int length) - { - if (source.IsEmpty) - { - value = default; - length = 0; - return false; - } - - fixed (byte* ptr = source) - { - if ((ptr[0] & 0x80) == 0) - { - length = 1; - value = ptr[0]; - } - else if ((ptr[0] & 0b11100000) == 0b11000000 && source.Length >= 2 - && ((uint)ptr[1] & 0b11000000) == 0b10000000) - { - length = 2; - value = (((uint)ptr[0] & 0x1F) << 6) | - (((uint)ptr[1] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11110000) == 0b11100000 && source.Length >= 3 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000) - { - length = 3; - value = (((uint)ptr[0] & 0x0F) << 12) | - (((uint)ptr[1] & 0x3F) << 6) | - (((uint)ptr[2] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111000) == 0b11110000 && source.Length >= 4 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000) - { - length = 4; - value = (((uint)ptr[0] & 0x07) << 18) | - (((uint)ptr[1] & 0x3F) << 12) | - (((uint)ptr[2] & 0x3F) << 6) | - (((uint)ptr[3] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111100) == 0b11111000 && source.Length >= 5 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000) - { - length = 5; - value = (((uint)ptr[0] & 0x03) << 24) | - (((uint)ptr[1] & 0x3F) << 18) | - (((uint)ptr[2] & 0x3F) << 12) | - (((uint)ptr[3] & 0x3F) << 6) | - (((uint)ptr[4] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111110) == 0b11111100 && source.Length >= 6 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000 - && ((uint)ptr[5] & 0b11000000) == 0b10000000) - { - length = 6; - value = (((uint)ptr[0] & 0x01) << 30) | - (((uint)ptr[1] & 0x3F) << 24) | - (((uint)ptr[2] & 0x3F) << 18) | - (((uint)ptr[3] & 0x3F) << 12) | - (((uint)ptr[4] & 0x3F) << 6) | - (((uint)ptr[5] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111111) == 0b11111110 && source.Length >= 7 - && ((uint)ptr[1] & 0b11111100) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000 - && ((uint)ptr[5] & 0b11000000) == 0b10000000 - && ((uint)ptr[6] & 0b11000000) == 0b10000000) - { - length = 7; - value = (((uint)ptr[1] & 0x03) << 30) | - (((uint)ptr[2] & 0x3F) << 24) | - (((uint)ptr[3] & 0x3F) << 18) | - (((uint)ptr[4] & 0x3F) << 12) | - (((uint)ptr[5] & 0x3F) << 6) | - (((uint)ptr[6] & 0x3F) << 0); - } - else - { - length = 1; - value = default; - return false; - } - - return true; - } - } - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode16(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - var v = TryDecode16(source, be, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-16 byte sequence. - /// The span to decode from. - /// Whether to use big endian. - /// The decoded value. - /// The length of the consumed bytes. 1 if cut short. - /// 2 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static unsafe bool TryDecode16(ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - if (source.Length < 2) - { - value = default; - length = source.Length; - return false; - } - - fixed (byte* ptr = source) - { - var p16 = (ushort*)ptr; - var val = be == BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(*p16) : *p16; - if (char.IsHighSurrogate((char)val)) - { - var lookahead1 = source.Length >= 4 ? p16[1] : 0; - var lookahead2 = source.Length >= 6 ? p16[2] : 0; - var lookahead3 = source.Length >= 8 ? p16[3] : 0; - if (char.IsLowSurrogate((char)lookahead1)) - { - // Not a valid Unicode codepoint anymore inside the block below. - if (char.IsLowSurrogate((char)lookahead2)) - { - if (char.IsLowSurrogate((char)lookahead3)) - { - value = 0x10000 - + (((val & 0x3) << 30) | - ((lookahead1 & 0x3FF) << 20) | - ((lookahead2 & 0x3FF) << 10) | - ((lookahead3 & 0x3FF) << 0)); - length = 8; - return true; - } - - value = 0x10000 - + (((val & 0x3FF) << 20) | - ((lookahead1 & 0x3FF) << 10) | - ((lookahead2 & 0x3FF) << 0)); - length = 6; - return true; - } - - value = 0x10000 + - (((val & 0x3FF) << 10) | - ((lookahead1 & 0x3FF) << 0)); - length = 4; - return true; - } - } - - // Calls are supposed to handle unpaired surrogates. - value = val; - length = 2; - return true; - } - } - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode32(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - var v = TryDecode32(source, be, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-32 byte sequence. - /// The span to decode from. - /// Whether to use big endian. - /// The decoded value. - /// The length of the consumed bytes. 1 to 3 if cut short. - /// 4 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static bool TryDecode32(ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - if (source.Length < 4) - { - value = default; - length = source.Length; - return false; - } - - length = 4; - if ((be && BinaryPrimitives.TryReadInt32BigEndian(source, out var i32)) - || (!be && BinaryPrimitives.TryReadInt32LittleEndian(source, out i32))) - { - value = i32; - return true; - } - - value = default; - return false; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The target stream. - /// The codepoint to encode. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode8(Stream target, int codepoint) - { - Span buf = stackalloc byte[7]; - Encode8(buf, codepoint, out var length); - target.Write(buf[..length]); - return length; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The target byte span. - /// The codepoint to encode. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode8(ref Span target, int codepoint) - { - target = Encode8(target, codepoint, out var length); - return length; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The optional target byte span. - /// The codepoint to encode. - /// The length of the encoded data. - /// The remaning region of . - public static Span Encode8(Span target, int codepoint, out int length) - { - var value = (uint)codepoint; - length = GetEncodedLength8(codepoint); - if (target.IsEmpty) - return target; - - switch (length) - { - case 1: - target[0] = (byte)value; - return target[1..]; - case 2: - target[0] = (byte)(0xC0 | ((value >> 6) & 0x1F)); - target[1] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[2..]; - case 3: - target[0] = (byte)(0xE0 | ((value >> 12) & 0x0F)); - target[1] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[3..]; - case 4: - target[0] = (byte)(0xF0 | ((value >> 18) & 0x07)); - target[1] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[4..]; - case 5: - target[0] = (byte)(0xF8 | ((value >> 24) & 0x03)); - target[1] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[5..]; - case 6: - target[0] = (byte)(0xFC | ((value >> 30) & 0x01)); - target[1] = (byte)(0x80 | ((value >> 24) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[5] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[6..]; - case 7: - target[0] = 0xFE; - target[1] = (byte)(0x80 | ((value >> 30) & 0x03)); - target[2] = (byte)(0x80 | ((value >> 24) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[5] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[6] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[7..]; - default: - Debug.Assert(false, $"{nameof(Length8)} property should have produced all possible cases."); - return target; - } - } - - /// Encodes the codepoint to the target in UTF-16. - /// The target stream. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode16(Stream target, int codepoint, bool be) - { - Span buf = stackalloc byte[8]; - Encode16(buf, codepoint, be, out var length); - target.Write(buf[..length]); - return length; - } - - /// Encodes the codepoint to the target in UTF-16. - /// The target byte span. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode16(ref Span target, int codepoint, bool be) - { - target = Encode16(target, codepoint, be, out var length); - return length; - } - - /// Encodes the codepoint to the target in UTF-16. - /// The optional target byte span. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// The remaning region of . - public static Span Encode16(Span target, int codepoint, bool be, out int length) - { - var value = (uint)codepoint; - length = GetEncodedLength16(codepoint); - if (target.IsEmpty) - return target; - - if (be) - { - switch (length) - { - case 2: - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)value); - return target[2..]; - case 4: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[4..]; - case 6: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[6..]; - case 8: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[8..]; - default: - Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); - return target; - } - } - - switch (length) - { - case 2: - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)value); - return target[2..]; - case 4: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[4..]; - case 6: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[6..]; - case 8: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[8..]; - default: - Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); - return target; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(UtfValue other) => this.IntValue == other.IntValue; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.IntValue; - - /// Attempts to get the corresponding rune. - /// The retrieved rune. - /// true if retrieved. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetRune(out Rune rune) - { - if (Rune.IsValid(this.IntValue)) - { - rune = new(this.IntValue); - return true; - } - - rune = default; - return false; - } - - /// Encodes the codepoint to the target. - /// The target byte span. - /// The remaning region of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Encode8(Span target) => Encode8(target, this, out _); -} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index f4b76942f..e026a6d2f 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Numerics; using System.Text; @@ -34,7 +33,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; private ImVectorWrapper testStringBuffer; private string testString = string.Empty; - private ExcelSheet addons; + private ExcelSheet addons = null!; private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; @@ -241,8 +240,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Button("Print to Chat Log")) { - fixed (byte* p = Service.Get().CompileAndCache(this.testString).Data.Span) - Service.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p)); + Service.Get().Print( + Game.Text.SeStringHandling.SeString.Parse( + Service.Get().CompileAndCache(this.testString).Data.Span)); } ImGuiHelpers.ScaledDummy(3); From dbbc2306dd6f3366afe618e048e64b6e9b8e1ac6 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 21 Nov 2024 00:54:05 +0100 Subject: [PATCH 163/375] NRE check LinkData in HandleLinkClickDetour (#2112) --- Dalamud/Game/Gui/ChatGui.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 14346132a..791cbb97a 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -412,7 +412,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData) { - if ((Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink) + if (linkData == null || linkData->Payload == null || (Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink) { this.handleLinkClickHook.Original(thisPtr, linkData); return; From 2fd8e49b34a62985505f8d2c55d0d55405ab5ddf Mon Sep 17 00:00:00 2001 From: Asriel Date: Sun, 24 Nov 2024 07:06:25 -0800 Subject: [PATCH 164/375] Update to Lumina 5.6.0 and Lumina.Excel 7.1.3 (#2118) --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 4 ++-- Dalamud/Dalamud.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 754124ae1..b85607f0f 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,8 +27,8 @@ - - + + all diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 9399154c8..338c231fe 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,8 +71,8 @@ - - + + all From de999b78959c568d5ca053ab092c214969064022 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Sun, 24 Nov 2024 21:35:00 +0100 Subject: [PATCH 165/375] Fix IsPvPExcludingDen (#2120) --- Dalamud/Game/ClientState/ClientState.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 4ab69b391..c898e107a 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -137,7 +137,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState public bool IsPvP { get; private set; } /// - public bool IsPvPExcludingDen { get; private set; } + public bool IsPvPExcludingDen => this.IsPvP && this.TerritoryType != 250; /// public bool IsGPosing => GameMain.IsInGPose(); @@ -195,7 +195,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState if (isPvP != this.IsPvP) { this.IsPvP = isPvP; - this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250; if (this.IsPvP) { From c950b15a22d074a49e013fef94561b61509fa672 Mon Sep 17 00:00:00 2001 From: ItsBexy <103910869+ItsBexy@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:37:39 -0700 Subject: [PATCH 166/375] Minor interface adjustments (#2121) - Further ImRaii safety in UiDebug2 - Set some mistakenly internal methods in ImGuiComponents to public - Added SpanFullWidth flag to trees in Util.ShowStruct --- .../Components/ImGuiComponents.IconButton.cs | 2 +- .../ImGuiComponents.IconButtonSelect.cs | 4 +- .../UiDebug2/Browsing/AddonTree.AtkValues.cs | 10 +- .../Internal/UiDebug2/Browsing/Events.cs | 6 +- .../UiDebug2/Browsing/NodeTree.Editor.cs | 3 +- .../UiDebug2/Browsing/NodeTree.Image.cs | 5 +- .../Browsing/NodeTree.NineGrid.Offsets.cs | 69 ---- .../UiDebug2/Browsing/NodeTree.NineGrid.cs | 60 ++++ .../UiDebug2/Browsing/NodeTree.Res.cs | 8 +- .../UiDebug2/Browsing/NodeTree.Text.cs | 2 +- .../UiDebug2/Browsing/TimelineTree.cs | 11 +- .../Internal/UiDebug2/ElementSelector.cs | 10 +- .../Internal/UiDebug2/Popout.Addon.cs | 3 +- .../Internal/UiDebug2/Popout.Node.cs | 3 +- .../Internal/UiDebug2/UiDebug2.Sidebar.cs | 16 +- .../Interface/Internal/UiDebug2/UiDebug2.cs | 4 +- .../Internal/UiDebug2/Utility/Gui.cs | 12 +- Dalamud/Utility/Util.cs | 318 +++++++++--------- 18 files changed, 282 insertions(+), 264 deletions(-) delete mode 100644 Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index 5e64fe463..d2b1b4a36 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -270,7 +270,7 @@ public static partial class ImGuiComponents /// Icon to use. /// Text to use. /// Width. - internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) + public static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) { using (ImRaii.PushFont(UiBuilder.IconFont)) { diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs index 3f9c469bb..ad83c7201 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs @@ -24,7 +24,7 @@ public static partial class ImGuiComponents /// The color of the actively-selected button. /// The color of the buttons when hovered. /// True if any button is clicked. - internal static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static bool IconButtonSelect(string label, ref T val, IEnumerable optionIcons, IEnumerable optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) { var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair(icon, value)); return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor); @@ -43,7 +43,7 @@ public static partial class ImGuiComponents /// The color of the actively-selected button. /// The color of the buttons when hovered. /// True if any button is clicked. - internal static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static unsafe bool IconButtonSelect(string label, ref T val, IEnumerable> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) { defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button); activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs index 4b7a531c0..c3930821b 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs @@ -11,6 +11,7 @@ using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; namespace Dalamud.Interface.Internal.UiDebug2.Browsing; +/// public unsafe partial class AddonTree { /// @@ -23,12 +24,11 @@ public unsafe partial class AddonTree if (addon->AtkValuesCount > 0 && atkValue != null) { using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}"); - if (tree) + if (tree.Success) { - using (ImRaii.Table( - "atkUnitBase_atkValueTable", - 3, - ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg)) + using var tbl = ImRaii.Table("atkUnitBase_atkValueTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + + if (tbl.Success) { ImGui.TableSetupColumn("Index"); ImGui.TableSetupColumn("Type"); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs index 45a2d90eb..c98cc933f 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/Events.cs @@ -30,9 +30,11 @@ public static class Events using var tree = ImRaii.TreeNode($"Events##{(nint)node:X}eventTree"); - if (tree) + if (tree.Success) { - using (ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg)) + using var tbl = ImRaii.Table($"##{(nint)node:X}eventTable", 7, Resizable | SizingFixedFit | Borders | RowBg); + + if (tbl.Success) { ImGui.TableSetupColumn("#", WidthFixed); ImGui.TableSetupColumn("Type", WidthFixed); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs index 6b6522bb4..1f5abd0bf 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Editor.cs @@ -27,7 +27,8 @@ internal unsafe partial class ResNodeTree /// private protected void DrawNodeEditorTable() { - using (ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX)) + using var tbl = ImRaii.Table($"###Editor{(nint)this.Node}", 2, SizingStretchProp | NoHostExtendX); + if (tbl.Success) { this.DrawEditorRows(); } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs index 4929fa1d9..8d93b3e76 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs @@ -65,7 +65,7 @@ internal unsafe partial class ImageNodeTree : ResNodeTree using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth); - if (tree) + if (tree.Success) { PrintFieldValuePairs( ("Texture Type", $"{this.TexData.TexType}"), @@ -189,7 +189,8 @@ internal unsafe partial class ImageNodeTree : ResNodeTree private void PrintPartsTable() { - using (ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable)) + using var tbl = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable); + if (tbl.Success) { ImGui.TableSetupColumn("Part ID", WidthFixed); ImGui.TableSetupColumn("Part Texture", WidthFixed); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs deleted file mode 100644 index 237303155..000000000 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.Offsets.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Numerics; - -using FFXIVClientStructs.FFXIV.Component.GUI; - -using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui; - -namespace Dalamud.Interface.Internal.UiDebug2.Browsing; - -/// -internal unsafe partial class NineGridNodeTree -{ - /// - /// A struct representing the four offsets of an . - /// - internal struct NineGridOffsets - { - /// Top offset. - internal int Top; - - /// Left offset. - internal int Left; - - /// Right offset. - internal int Right; - - /// Bottom offset. - internal int Bottom; - - /// - /// Initializes a new instance of the struct. - /// - /// The top offset. - /// The right offset. - /// The bottom offset. - /// The left offset. - internal NineGridOffsets(int top, int right, int bottom, int left) - { - this.Top = top; - this.Right = right; - this.Left = left; - this.Bottom = bottom; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The node using these offsets. - internal NineGridOffsets(AtkNineGridNode* ngNode) - : this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset) - { - } - - private NineGridOffsets(Vector4 v) - : this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W) - { - } - - public static implicit operator NineGridOffsets(Vector4 v) => new(v); - - public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left); - - public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a; - - public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a; - - /// Prints the offsets in ImGui. - internal readonly void Print() => PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}")); - } -} diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs index 8f4e8c196..48825becb 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.NineGrid.cs @@ -1,3 +1,5 @@ +using Dalamud.Interface.Internal.UiDebug2.Utility; + using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -85,4 +87,62 @@ internal unsafe partial class NineGridNodeTree : ImageNodeTree this.DrawTextureAndParts(); } + + /// + /// A struct representing the four offsets of an . + /// + internal struct NineGridOffsets + { + /// Top offset. + internal int Top; + + /// Left offset. + internal int Left; + + /// Right offset. + internal int Right; + + /// Bottom offset. + internal int Bottom; + + /// + /// Initializes a new instance of the struct. + /// + /// The top offset. + /// The right offset. + /// The bottom offset. + /// The left offset. + internal NineGridOffsets(int top, int right, int bottom, int left) + { + this.Top = top; + this.Right = right; + this.Left = left; + this.Bottom = bottom; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The node using these offsets. + internal NineGridOffsets(AtkNineGridNode* ngNode) + : this(ngNode->TopOffset, ngNode->RightOffset, ngNode->BottomOffset, ngNode->LeftOffset) + { + } + + private NineGridOffsets(Vector4 v) + : this((int)v.X, (int)v.Y, (int)v.Z, (int)v.W) + { + } + + public static implicit operator NineGridOffsets(Vector4 v) => new(v); + + public static implicit operator Vector4(NineGridOffsets v) => new(v.Top, v.Right, v.Bottom, v.Left); + + public static NineGridOffsets operator *(float n, NineGridOffsets a) => n * (Vector4)a; + + public static NineGridOffsets operator *(NineGridOffsets a, float n) => n * a; + + /// Prints the offsets in ImGui. + internal readonly void Print() => Gui.PrintFieldValuePairs(("Top", $"{this.Top}"), ("Bottom", $"{this.Bottom}"), ("Left", $"{this.Left}"), ("Right", $"{this.Right}")); + } } diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs index 2edf4e570..6c12d3b4c 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Res.cs @@ -128,11 +128,11 @@ internal unsafe partial class ResNodeTree : IDisposable return; } - using var c = ImRaii.PushColor(Text, color); + using var col = ImRaii.PushColor(Text, color); using var tree = ImRaii.TreeNode($"{label}##{(nint)nodeList:X}", SpanFullWidth); - c.Pop(); + col.Pop(); - if (tree) + if (tree.Success) { var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); @@ -319,7 +319,7 @@ internal unsafe partial class ResNodeTree : IDisposable col.Pop(); - if (tree) + if (tree.Success) { var lineStart = ImGui.GetCursorScreenPos() + new Vector2(-10, 2); try diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index d9b4e7986..7c924f503 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -85,7 +85,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree { using var tree = ImRaii.TreeNode($"Text Payloads##{(nint)this.Node:X}"); - if (tree) + if (tree.Success) { var utf8String = this.NodeText; var seStringBytes = new byte[utf8String.BufUsed]; diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs index 8e21f4030..57e5eff99 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/TimelineTree.cs @@ -58,7 +58,7 @@ public readonly unsafe partial struct TimelineTree { using var tree = ImRaii.TreeNode($"Timeline##{(nint)this.node:X}timeline", SpanFullWidth); - if (tree) + if (tree.Success) { PrintFieldValuePair("Timeline", $"{(nint)this.NodeTimeline:X}"); @@ -312,7 +312,7 @@ public readonly unsafe partial struct TimelineTree { using var tree = ImRaii.TreeNode($"[#{a}] [Frames {animation.StartFrameIdx}-{animation.EndFrameIdx}] {(isActive ? " (Active)" : string.Empty)}###{(nint)this.node}animTree{a}"); - if (tree) + if (tree.Success) { PrintFieldValuePair("Animation", $"{address:X}"); @@ -320,10 +320,9 @@ public readonly unsafe partial struct TimelineTree if (columns.Count > 0) { - using (ImRaii.Table( - $"##{(nint)this.node}animTable{a}", - columns.Count, - Borders | SizingFixedFit | RowBg | NoHostExtendX)) + using var tbl = ImRaii.Table($"##{(nint)this.node}animTable{a}", columns.Count, Borders | SizingFixedFit | RowBg | NoHostExtendX); + + if (tbl.Success) { foreach (var c in columns) { diff --git a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs index 6693c3fb0..65537e210 100644 --- a/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs +++ b/Dalamud/Interface/Internal/UiDebug2/ElementSelector.cs @@ -79,7 +79,9 @@ internal unsafe class ElementSelector : IDisposable /// internal void DrawInterface() { - using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true)) + using var ch = ImRaii.Child("###sidebar_elementSelector", new(250, -1), true); + + if (ch.Success) { using (ImRaii.PushFont(IconFont)) { @@ -153,9 +155,11 @@ internal unsafe class ElementSelector : IDisposable using (ImRaii.PushColor(WindowBg, new Vector4(0.5f))) { - using (ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse)) + using var ch = ImRaii.Child("noClick", new(800, 2000), false, NoInputs | NoBackground | NoScrollWithMouse); + if (ch.Success) { - using (ImRaii.Group()) + using var gr = ImRaii.Group(); + if (gr.Success) { Gui.PrintFieldValuePair("Mouse Position", $"{mousePos.X}, {mousePos.Y}"); ImGui.Spacing(); diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs index 91dfc8067..76112945e 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Addon.cs @@ -39,7 +39,8 @@ internal class AddonPopoutWindow : Window, IDisposable /// public override void Draw() { - using (ImRaii.Child($"{this.WindowName}child", new(-1, -1), true)) + using var ch = ImRaii.Child($"{this.WindowName}child", new(-1, -1), true); + if (ch.Success) { this.addonTree.Draw(); } diff --git a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs index c07806823..fe8bc87ea 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Popout.Node.cs @@ -50,7 +50,8 @@ internal unsafe class NodePopoutWindow : Window, IDisposable { if (this.Node != null && this.AddonTree.ContainsNode(this.Node)) { - using (ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true)) + using var ch = ImRaii.Child($"{(nint)this.Node:X}popoutChild", new(-1, -1), true); + if (ch.Success) { ResNodeTree.GetOrCreate(this.Node, this.AddonTree).Print(null, this.firstDraw); this.firstDraw = false; diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs index 4253f13bc..50967453d 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.Sidebar.cs @@ -53,7 +53,8 @@ internal unsafe partial class UiDebug2 private void DrawSidebar() { - using (ImRaii.Group()) + using var gr = ImRaii.Group(); + if (gr.Success) { this.DrawNameSearch(); this.DrawAddonSelectionList(); @@ -63,7 +64,9 @@ internal unsafe partial class UiDebug2 private void DrawNameSearch() { - using (ImRaii.Child("###sidebar_nameSearch", new(250, 40), true)) + using var ch = ImRaii.Child("###sidebar_nameSearch", new(250, 40), true); + + if (ch.Success) { var atkUnitBaseSearch = this.addonNameSearch; @@ -90,7 +93,8 @@ internal unsafe partial class UiDebug2 private void DrawAddonSelectionList() { - using (ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar)) + using var ch = ImRaii.Child("###sideBar_addonList", new(250, -44), true, ImGuiWindowFlags.AlwaysVerticalScrollbar); + if (ch.Success) { var unitListBaseAddr = GetUnitListBaseAddr(); @@ -146,11 +150,11 @@ internal unsafe partial class UiDebug2 var countStr = $"{(usingFilter ? $"{matchCount}/" : string.Empty)}{totalCount}"; - using var col1 = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); + using var col = ImRaii.PushColor(ImGuiCol.Text, anyVisible ? new Vector4(1) : new Vector4(0.6f, 0.6f, 0.6f, 1)); using var tree = ImRaii.TreeNode($"{unit.Name} [{countStr}]###unitListTree{unit.Index}"); - col1.Pop(); + col.Pop(); - if (tree) + if (tree.Success) { foreach (var option in options) { diff --git a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs index 17c64ddca..74727f2a5 100644 --- a/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs +++ b/Dalamud/Interface/Internal/UiDebug2/UiDebug2.cs @@ -83,7 +83,9 @@ internal partial class UiDebug2 : IDisposable { ImGui.SameLine(); - using (ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar)) + using var ch = ImRaii.Child("###uiDebugMainPanel", new(-1, -1), true, HorizontalScrollbar); + + if (ch.Success) { if (this.elementSelector.Active) { diff --git a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs index cc73b79c6..cc4f1b698 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Utility/Gui.cs @@ -68,10 +68,10 @@ internal static class Gui /// Colors the text itself either white or black, depending on the luminosity of the background color. internal static void PrintColor(Vector4 color, string fmt) { - using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) - .Push(Button, color) - .Push(ButtonActive, color) - .Push(ButtonHovered, color)) + using (ImRaii.PushColor(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)) + .Push(Button, color) + .Push(ButtonActive, color) + .Push(ButtonHovered, color)) { ImGui.SmallButton(fmt); } @@ -105,7 +105,9 @@ internal static class Gui var index = (int)Math.Floor(prog * tooltips.Length); - using (ImRaii.Tooltip()) + using var tt = ImRaii.Tooltip(); + + if (tt.Success) { ImGui.TextUnformatted(tooltips[index]); } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 805532025..c707081f4 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -28,6 +28,8 @@ using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.Memory; using Windows.Win32.System.Ole; +using Dalamud.Interface.Utility.Raii; + using static TerraFX.Interop.Windows.Windows; using Win32_PInvoke = Windows.Win32.PInvoke; @@ -1028,74 +1030,71 @@ public static class Util dm.Invoke(null, new[] { obj, path, addr }); } +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + private static unsafe void ShowSpanPrivate(ulong addr, IList path, int offset, bool isTop, in Span spanobj) { -#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type if (isTop) { fixed (void* p = spanobj) { - if (!ImGui.TreeNode( - $"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + - $"##print-obj-{addr:X}-{string.Join("-", path)}-head")) + using var tree = ImRaii.TreeNode($"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}-head", ImGuiTreeNodeFlags.SpanFullWidth); + if (tree.Success) { - return; + ShowSpanEntryPrivate(addr, path, offset, spanobj); } } } - - try + else { - const int batchSize = 20; - if (spanobj.Length > batchSize) - { - var skip = batchSize; - while ((spanobj.Length + skip - 1) / skip > batchSize) - skip *= batchSize; - for (var i = 0; i < spanobj.Length; i += skip) - { - var next = Math.Min(i + skip, spanobj.Length); - path.Add($"{offset + i:X}_{skip}"); - if (ImGui.TreeNode( - $"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + - $"##print-obj-{addr:X}-{string.Join("-", path)}")) - { - try - { - ShowSpanPrivate(addr, path, offset + i, false, spanobj[i..next]); - } - finally - { - ImGui.TreePop(); - } - } - - path.RemoveAt(path.Count - 1); - } - } - else - { - fixed (T* p = spanobj) - { - var pointerType = typeof(T*); - for (var i = 0; i < spanobj.Length; i++) - { - ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); - ImGui.SameLine(); - path.Add($"{offset + i}"); - ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); - } - } - } + ShowSpanEntryPrivate(addr, path, offset, spanobj); } - finally - { - if (isTop) - ImGui.TreePop(); - } -#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type } + private static unsafe void ShowSpanEntryPrivate(ulong addr, IList path, int offset, Span spanobj) { + const int batchSize = 20; + if (spanobj.Length > batchSize) + { + var skip = batchSize; + while ((spanobj.Length + skip - 1) / skip > batchSize) + { + skip *= batchSize; + } + + for (var i = 0; i < spanobj.Length; i += skip) + { + var next = Math.Min(i + skip, spanobj.Length); + path.Add($"{offset + i:X}_{skip}"); + + using (var tree = ImRaii.TreeNode($"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + $"##print-obj-{addr:X}-{string.Join("-", path)}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + if (tree.Success) + { + ShowSpanEntryPrivate(addr, path, offset + i, spanobj[i..next]); + } + } + + path.RemoveAt(path.Count - 1); + } + } + else + { + fixed (T* p = spanobj) + { + var pointerType = typeof(T*); + for (var i = 0; i < spanobj.Length; i++) + { + ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); + ImGui.SameLine(); + path.Add($"{offset + i}"); + ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); + } + } + } + } + +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + private static unsafe void ShowValue(ulong addr, IList path, Type type, object value, bool hideAddress) { if (type.IsPointer) @@ -1111,9 +1110,10 @@ public static class Util if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) { ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); - ImGui.PopStyleColor(); + using (ImRaii.PushColor(ImGuiCol.Text, 0xffcbc0ff)) + { + ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); + } } ImGui.SameLine(); @@ -1165,125 +1165,135 @@ public static class Util /// Do not print addresses. Use when displaying a copied value. private static void ShowStructInternal(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null, bool hideAddress = false) { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); - path ??= new List(); - var pathList = path is List ? (List)path : path.ToList(); - - if (moduleEndAddr == 0 && moduleStartAddr == 0) + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2))) { - try + path ??= new List(); + var pathList = path as List ?? path.ToList(); + + if (moduleEndAddr == 0 && moduleStartAddr == 0) { - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) + try { - moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); - moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + moduleStartAddr = (ulong)processModule.BaseAddress.ToInt64(); + moduleEndAddr = moduleStartAddr + (ulong)processModule.ModuleMemorySize; + } + else + { + moduleEndAddr = 1; + } } - else + catch { moduleEndAddr = 1; } } - catch + + if (autoExpand) { - moduleEndAddr = 1; + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); } - } - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - } + using var col = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF); + using var tree = ImRaii.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}", ImGuiTreeNodeFlags.SpanFullWidth); + col.Pop(); - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}")) - { - ImGui.PopStyleColor(); - foreach (var f in obj.GetType() - .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + if (tree.Success) { - var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); - var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); + ImGui.PopStyleColor(); + foreach (var f in obj.GetType() + .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + { + var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + var offset = (FieldOffsetAttribute)f.GetCustomAttribute(typeof(FieldOffsetAttribute)); - if (fixedBuffer != null) - { - ImGui.Text($"fixed"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), - $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); - } - else - { - if (offset != null) + if (fixedBuffer != null) { - ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.Text("fixed"); ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); } - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); - } - - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); - ImGui.SameLine(); - - pathList.Add(f.Name); - try - { - if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) - ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct - else if (f.FieldType == typeof(bool) && offset != null) - ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); else - ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); + { + if (offset != null) + { + ImGui.TextDisabled($"[0x{offset.Value:X}]"); + ImGui.SameLine(); + } + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + } + + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); + ImGui.SameLine(); + + pathList.Add(f.Name); + try + { + if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) + { + ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct + } + else if (f.FieldType == typeof(bool) && offset != null) + { + ShowValue(addr, pathList, f.FieldType, Marshal.ReadByte((nint)addr + offset.Value) > 0, hideAddress); + } + else + { + ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); + } + } + catch (Exception ex) + { + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f))) + { + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + } + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } - catch (Exception ex) + + foreach (var p in obj.GetType().GetProperties().Where(static p => p.GetGetMethod()?.GetParameters().Length == 0)) { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); - ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); - ImGui.PopStyleColor(); - } - finally - { - pathList.RemoveAt(pathList.Count - 1); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); + ImGui.SameLine(); + + pathList.Add(p.Name); + try + { + if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) + { + ShowSpanProperty(addr, pathList, p, obj); + } + else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) + { + ImGui.Text("Cannot preview ref typed properties."); + } + else + { + ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); + } + } + catch (Exception ex) + { + using (ImRaii.PushColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f))) + { + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + } + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } } - - foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) - { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); - ImGui.SameLine(); - - pathList.Add(p.Name); - try - { - if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) - ShowSpanProperty(addr, pathList, p, obj); - else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) - ImGui.Text("Cannot preview ref typed properties."); - else - ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); - } - catch (Exception ex) - { - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); - ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); - ImGui.PopStyleColor(); - } - finally - { - pathList.RemoveAt(pathList.Count - 1); - } - } - - ImGui.TreePop(); } - else - { - ImGui.PopStyleColor(); - } - - ImGui.PopStyleVar(); } } From 0fdcffd3ad70c19030ee7098e93a8acb1a403ed7 Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 24 Nov 2024 23:00:50 +0100 Subject: [PATCH 167/375] Fix small typo (#2115) --- Dalamud/Configuration/Internal/DalamudConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 5b49f5c72..5c2378f68 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -57,7 +57,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved; /// - /// Gets or sets a list of muted works. + /// Gets or sets a list of muted words. /// public List? BadWords { get; set; } From 903a5ad5da412e529526dd2807495b9a32ef891c Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Sun, 24 Nov 2024 14:01:01 -0800 Subject: [PATCH 168/375] fix: Properly handle UTF-8 characters in ImGUI paths (#2122) - Fixes a bug where users with special characters in their filenames would not be able to save `dalamudUI.ini`. - Throw a special warning if `dalamudUI.ini` doesn't exist, as it's not an error case. --- Dalamud/Interface/Internal/InterfaceManager.cs | 9 ++++++++- lib/ImGuiScene | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index cb25bd9b5..f532b0412 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -604,10 +604,17 @@ internal partial class InterfaceManager : IInternalDisposableService if (iniFileInfo.Length > 1200000) { Log.Warning("dalamudUI.ini was over 1mb, deleting"); - iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName!, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); + iniFileInfo.CopyTo( + Path.Combine( + iniFileInfo.DirectoryName!, + $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); iniFileInfo.Delete(); } } + catch (FileNotFoundException) + { + Log.Warning("dalamudUI.ini did not exist, ImGUI will create a new one."); + } catch (Exception ex) { Log.Error(ex, "Could not delete dalamudUI.ini"); diff --git a/lib/ImGuiScene b/lib/ImGuiScene index 2f37349ff..d5dfde4b3 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit 2f37349ffd778561a1103a650683116c43edc86c +Subproject commit d5dfde4b39b032430deb46bc61084c18fb54b986 From 4c9b2a1577f8cd8c8b99e828d174b7122730e808 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Mon, 25 Nov 2024 11:21:07 -0800 Subject: [PATCH 169/375] Add universalis response logging for diagnostics (#2124) --- .../Universalis/UniversalisMarketBoardUploader.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index 9528a9b50..f326c5ce0 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -102,11 +102,18 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader var uploadPath = "/upload"; var uploadData = JsonConvert.SerializeObject(uploadObject); Log.Verbose("{ListingPath}: {ListingUpload}", uploadPath, uploadData); - await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json")); + var response = await this.httpClient.PostAsync($"{ApiBase}{uploadPath}/{ApiKey}", new StringContent(uploadData, Encoding.UTF8, "application/json")); - // ==================================================================================== - - Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId); + if (response.IsSuccessStatusCode) + { + Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId); + } + else + { + var body = await response.Content.ReadAsStringAsync(); + Log.Warning("Universalis data upload for item#{CatalogId} returned status code {StatusCode}.\n" + + " Response Body: {Body}", request.CatalogId, response.StatusCode, body); + } } /// From db4b6cea2cf89fda1ce5b3da23621cb0ac2a287f Mon Sep 17 00:00:00 2001 From: srkizer Date: Tue, 26 Nov 2024 10:09:43 +0900 Subject: [PATCH 170/375] Avoid re-creating title version string every frame (#2117) * Avoid re-creating title version string every frame * Marked title version string that it has been changed by Dalamud by appending a newline payload with a string expression embedded within, which the game will ignore (effectively ``.) * Added `PluginManager.LoadedPluginCount` which will count the number of loaded plugin without making a copy of the plugin list. * Made TitleScreenMenuWindow.OnVersionStringDraw` update the title version text addon only if number of loaded plugin changes or the text is missing the custom suffix from the first point. * Use OriginalTextPointer to determine if SetText(ROS) has been called --- .../Internal/Windows/TitleScreenMenuWindow.cs | 43 ++++++++++++------- Dalamud/Plugin/Internal/PluginManager.cs | 21 +++++++++ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index faf99aa2a..88f291954 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -10,7 +10,6 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.ClientState; using Dalamud.Game.Gui; using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; @@ -19,16 +18,18 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Internal; -using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using Dalamud.Storage.Assets; -using Dalamud.Support; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + namespace Dalamud.Interface.Internal.Windows; /// @@ -59,6 +60,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable private InOutCubic? fadeOutEasing; private State state = State.Hide; + + private int lastLoadedPluginCount = -1; /// /// Initializes a new instance of the class. @@ -441,29 +444,37 @@ internal class TitleScreenMenuWindow : Window, IDisposable textNode->TextFlags |= (byte)TextFlags.MultiLine; textNode->AlignmentType = AlignmentType.TopLeft; + var containsDalamudVersionString = textNode->OriginalTextPointer == textNode->NodeText.StringPtr; if (!this.configuration.ShowTsm || !this.showTsm.Value) { - textNode->NodeText.SetString(addon->AtkValues[1].String); + if (containsDalamudVersionString) + textNode->SetText(addon->AtkValues[1].String); + this.lastLoadedPluginCount = -1; return; } var pm = Service.GetNullable(); + var count = pm?.LoadedPluginCount ?? 0; - var pluginCount = pm?.InstalledPlugins.Count(c => c.State == PluginState.Loaded) ?? 0; + // Avoid rebuilding the string every frame. + if (containsDalamudVersionString && count == this.lastLoadedPluginCount) + return; + this.lastLoadedPluginCount = count; - var titleVersionText = new SeStringBuilder() - .AddText(addon->AtkValues[1].GetValueAsString()) - .AddText("\n\n") - .AddUiGlow(701) - .AddUiForeground(SeIconChar.BoxedLetterD.ToIconString(), 539) - .AddUiGlowOff() - .AddText($" Dalamud: {Util.GetScmVersion()}") - .AddText($" - {pluginCount} {(pluginCount != 1 ? "plugins" : "plugin")} loaded"); + var lssb = LSeStringBuilder.SharedPool.Get(); + lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String)).Append("\n\n"); + lssb.PushEdgeColorType(701).PushColorType(539) + .Append(SeIconChar.BoxedLetterD.ToIconChar()) + .PopColorType().PopEdgeColorType(); + lssb.Append($" Dalamud: {Util.GetScmVersion()}"); - if (pm?.SafeMode ?? false) - titleVersionText.AddUiForeground(" [SAFE MODE]", 17); + lssb.Append($" - {count} {(count != 1 ? "plugins" : "plugin")} loaded"); - textNode->NodeText.SetString(titleVersionText.Build().EncodeWithNullTerminator()); + if (pm?.SafeMode is true) + lssb.PushColorType(17).Append(" [SAFE MODE]").PopColorType(); + + textNode->SetText(lssb.GetViewAsSpan()); + LSeStringBuilder.SharedPool.Return(lssb); } private void TitleScreenMenuEntryListChange() => this.privateAtlas.BuildFontsAsync(); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index ddb59c027..9d71c60df 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -162,6 +162,27 @@ internal class PluginManager : IInternalDisposableService /// public static int DalamudApiLevel { get; private set; } + /// + /// Gets the number of loaded plugins. + /// + public int LoadedPluginCount + { + get + { + var res = 0; + lock (this.pluginListLock) + { + foreach (var p in this.installedPluginsList) + { + if (p.State == PluginState.Loaded) + res++; + } + } + + return res; + } + } + /// /// Gets a copy of the list of all loaded plugins. /// From d08966edf8f993a9a396015ba71d48f0284ac27b Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:41:45 +0100 Subject: [PATCH 171/375] Update ClientStructs (#2114) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index c9d1f33c5..696f71495 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit c9d1f33c58f426b6480b2c00e5cdcb0668654973 +Subproject commit 696f71495229e348109d1521a5ed9483c7ba3275 From c0464b80a581608a7afd1ebca7318f9d92a79c3a Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Tue, 26 Nov 2024 08:34:28 -0800 Subject: [PATCH 172/375] chore: ImGui PDB passthrough, null terminator fix (#2123) --- lib/ImGuiScene | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ImGuiScene b/lib/ImGuiScene index d5dfde4b3..b0d41471b 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit d5dfde4b39b032430deb46bc61084c18fb54b986 +Subproject commit b0d41471b7ef3d69daaf6d862eb74e7e00a25651 From 245d7be23705dc2a587fa38f0943954c35f8733a Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:53:25 +0100 Subject: [PATCH 173/375] Update ClientStructs (#2125) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 696f71495..d4778799d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 696f71495229e348109d1521a5ed9483c7ba3275 +Subproject commit d4778799d378fe66099fd337f55849acf1f16dcd From f3db18962f714cd39bd0b94dc9727c3ba19a32e1 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 26 Nov 2024 21:09:19 +0100 Subject: [PATCH 174/375] build: 11.0.1.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 338c231fe..61fb1efe4 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 11.0.0.0 + 11.0.1.0 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 7a52cb85db6db9084eae3b064c1c28facf405006 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Thu, 28 Nov 2024 05:30:24 +0900 Subject: [PATCH 175/375] Fix ShowStructInternal ImGui crash (#2127) --- Dalamud/Utility/Util.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index c707081f4..d5e14e212 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -1202,7 +1202,6 @@ public static class Util if (tree.Success) { - ImGui.PopStyleColor(); foreach (var f in obj.GetType() .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { From 1830af59fd98a884faf0474d7997cf748ff8f5a6 Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Fri, 29 Nov 2024 02:19:11 +0900 Subject: [PATCH 176/375] Use object table index lookup for NamePlateGui GameObject resolution (#2129) --- Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 170fea687..94f185966 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -328,9 +328,9 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId; /// - public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable.CreateObjectReference( - (nint)this.context.Ui3DModule->NamePlateObjectInfoPointers[ - this.ArrayIndex].Value->GameObject); + public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable[ + this.context.Ui3DModule->NamePlateObjectInfoPointers[this.ArrayIndex] + .Value->GameObject->ObjectIndex]; /// public IBattleChara? BattleChara => this.GameObject as IBattleChara; From da718b64c19dc83872e583d8c3ba716e54178ffc Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 30 Nov 2024 19:59:42 +0100 Subject: [PATCH 177/375] Use MiniGame BGM scene for About tab --- Dalamud/Game/Gui/GameGui.cs | 48 +++++++------------ Dalamud/Game/Gui/GameGuiAddressResolver.cs | 11 ----- Dalamud/Interface/Internal/DalamudCommands.cs | 2 +- .../Windows/Settings/Tabs/SettingsTabAbout.cs | 24 +++++++--- 4 files changed, 34 insertions(+), 51 deletions(-) diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index aecbb7201..09ff17737 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -9,6 +9,7 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.String; @@ -16,6 +17,7 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Common.Component.BGCollision; using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; using Vector2 = System.Numerics.Vector2; @@ -34,7 +36,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui private readonly GameGuiAddressResolver address; - private readonly Hook setGlobalBgmHook; private readonly Hook handleItemHoverHook; private readonly Hook handleItemOutHook; private readonly Hook handleActionHoverHook; @@ -50,12 +51,8 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.address.Setup(sigScanner); Log.Verbose("===== G A M E G U I ====="); - Log.Verbose($"GameGuiManager address {Util.DescribeAddress(this.address.BaseAddress)}"); - Log.Verbose($"SetGlobalBgm address {Util.DescribeAddress(this.address.SetGlobalBgm)}"); Log.Verbose($"HandleImm address {Util.DescribeAddress(this.address.HandleImm)}"); - this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - this.handleItemHoverHook = Hook.FromAddress((nint)AgentItemDetail.StaticVirtualTablePointer->Update, this.HandleItemHoverDetour); this.handleItemOutHook = Hook.FromAddress((nint)AgentItemDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleItemOutDetour); @@ -68,7 +65,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.utf8StringFromSequenceHook = Hook.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour); - this.setGlobalBgmHook.Enable(); this.handleItemHoverHook.Enable(); this.handleItemOutHook.Enable(); this.handleImmHook.Enable(); @@ -80,9 +76,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui // Hooked delegates - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); @@ -254,7 +247,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// void IInternalDisposableService.DisposeService() { - this.setGlobalBgmHook.Dispose(); this.handleItemHoverHook.Dispose(); this.handleItemOutHook.Dispose(); this.handleImmHook.Dispose(); @@ -265,25 +257,26 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui } /// - /// Indicates if the game is on the title screen. + /// Indicates if the game is in the lobby scene (title screen, chara select, chara make, aesthetician etc.). /// - /// A value indicating whether or not the game is on the title screen. - internal bool IsOnTitleScreen() + /// A value indicating whether or not the game is in the lobby scene. + internal bool IsInLobby() { - var charaSelect = this.GetAddonByName("CharaSelect"); - var charaMake = this.GetAddonByName("CharaMake"); - var titleDcWorldMap = this.GetAddonByName("TitleDCWorldMap"); - if (charaMake != nint.Zero || charaSelect != nint.Zero || titleDcWorldMap != nint.Zero) - return false; - - return !Service.Get().IsLoggedIn; + return RaptureAtkModule.Instance()->CurrentUISceneString == "LobbyMain"; } /// - /// Set the current background music. + /// Sets the current background music. /// - /// The background music key. - internal void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); + /// The BGM row id. + /// The BGM scene index. Defaults to MiniGame scene to avoid conflicts. + internal void SetBgm(ushort bgmId, uint sceneId = 2) => BGMSystem.SetBGM(bgmId, sceneId); + + /// + /// Resets the current background music. + /// + /// The BGM scene index. + internal void ResetBgm(uint sceneId = 2) => BGMSystem.Instance()->ResetBGM(sceneId); /// /// Reset the stored "UI hide" state. @@ -293,15 +286,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.GameUiHidden = false; } - private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6) - { - var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6); - - Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal); - - return retVal; - } - private void HandleItemHoverDetour(AgentItemDetail* thisPtr, uint frameCount) { this.handleItemHoverHook.Original(thisPtr, frameCount); diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index bdd579c7e..92b89c5a9 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -5,16 +5,6 @@ namespace Dalamud.Game.Gui; /// internal sealed class GameGuiAddressResolver : BaseAddressResolver { - /// - /// Gets the base address of the native GuiManager class. - /// - public IntPtr BaseAddress { get; private set; } - - /// - /// Gets the address of the native SetGlobalBgm method. - /// - public IntPtr SetGlobalBgm { get; private set; } - /// /// Gets the address of the native HandleImm method. /// @@ -23,7 +13,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// protected override void Setup64Bit(ISigScanner sig) { - this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); // unnamed in CS this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); // unnamed in CS } } diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index fb64ad979..00997c1d5 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -266,7 +266,7 @@ internal class DalamudCommands : IServiceType else { // Revert to the original BGM by specifying an invalid one - gameGui.SetBgm(9999); + gameGui.ResetBgm(); } } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs index 6a231eeaf..428be05c2 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; @@ -194,6 +194,7 @@ Contribute at: https://github.com/goatcorp/Dalamud private readonly IFontAtlas privateAtlas; private string creditsText; + private bool isBgmSet; private bool resetNow = false; private IDalamudTextureWrap? logoTexture; @@ -222,10 +223,13 @@ Contribute at: https://github.com/goatcorp/Dalamud this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits, Util.GetGitHashClientStructs()); - var gg = Service.Get(); - if (!gg.IsOnTitleScreen() && UIState.Instance() != null) + var gameGui = Service.Get(); + var playerState = PlayerState.Instance(); + + if (!gameGui.IsInLobby() && playerState != null) { - gg.SetBgm((ushort)(UIState.Instance()->PlayerState.MaxExpansion > 3 ? 833 : 132)); + gameGui.SetBgm((ushort)(playerState->MaxExpansion > 3 ? 833 : 132)); + this.isBgmSet = true; } this.creditsThrottler.Restart(); @@ -242,9 +246,15 @@ Contribute at: https://github.com/goatcorp/Dalamud { this.creditsThrottler.Reset(); - var gg = Service.Get(); - if (!gg.IsOnTitleScreen()) - gg.SetBgm(9999); + if (this.isBgmSet) + { + var gameGui = Service.Get(); + + if (!gameGui.IsInLobby()) + gameGui.ResetBgm(); + + this.isBgmSet = false; + } Service.Get().SetCreditsDarkeningAnimation(false); } From f7f1b0afc50849284e155764ac47ccef8b0c9fa1 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 30 Nov 2024 20:42:46 +0100 Subject: [PATCH 178/375] Use span comparison in GameGui.IsInLobby --- Dalamud/Game/Gui/GameGui.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 09ff17737..7cd6d7360 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -260,10 +260,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// Indicates if the game is in the lobby scene (title screen, chara select, chara make, aesthetician etc.). /// /// A value indicating whether or not the game is in the lobby scene. - internal bool IsInLobby() - { - return RaptureAtkModule.Instance()->CurrentUISceneString == "LobbyMain"; - } + internal bool IsInLobby() => RaptureAtkModule.Instance()->CurrentUIScene.StartsWith("LobbyMain"u8); /// /// Sets the current background music. From ec72bd81e054d964f5b4ef678c4c9ed17361f0e2 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:19:04 +0100 Subject: [PATCH 179/375] [master] Update ClientStructs (#2126) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index d4778799d..8db140ca7 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit d4778799d378fe66099fd337f55849acf1f16dcd +Subproject commit 8db140ca73ea07ad908476a2f15825970653e1ef From dd26d82117d7496777377b2fade57ac87af4e2e4 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:47:31 +0100 Subject: [PATCH 180/375] [master] Update ClientStructs (#2134) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 8db140ca7..ed987b652 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 8db140ca73ea07ad908476a2f15825970653e1ef +Subproject commit ed987b65221fbd62b3306115084b6ba85681582d From bf90817457298222f11734987e3ddcf2f2052534 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Wed, 4 Dec 2024 22:21:20 +0100 Subject: [PATCH 181/375] [master] Update ClientStructs (#2135) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index ed987b652..b3a25a18b 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit ed987b65221fbd62b3306115084b6ba85681582d +Subproject commit b3a25a18bab61beda6050ef15aca721cfa14e1a0 From 4c62158aeae72e2499e6fd67c018b987f6e65195 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 4 Dec 2024 22:22:51 +0100 Subject: [PATCH 182/375] Fix config option summaries (#2138) * Fix config option summaries * Obsolete LockonDefaultZoom_179 with error --- Dalamud/Game/Config/UiConfigOption.cs | 1028 ++++++++++++------------ Dalamud/Game/Config/UiControlOption.cs | 362 ++++----- 2 files changed, 695 insertions(+), 695 deletions(-) diff --git a/Dalamud/Game/Config/UiConfigOption.cs b/Dalamud/Game/Config/UiConfigOption.cs index 1a44c3afa..53e64c89f 100644 --- a/Dalamud/Game/Config/UiConfigOption.cs +++ b/Dalamud/Game/Config/UiConfigOption.cs @@ -10,3592 +10,3592 @@ public enum UiConfigOption { /// - /// System option with the internal name BattleEffectSelf. + /// UiConfig option with the internal name BattleEffectSelf. /// This option is a UInt. /// [GameConfigOption("BattleEffectSelf", ConfigType.UInt)] BattleEffectSelf, /// - /// System option with the internal name BattleEffectParty. + /// UiConfig option with the internal name BattleEffectParty. /// This option is a UInt. /// [GameConfigOption("BattleEffectParty", ConfigType.UInt)] BattleEffectParty, /// - /// System option with the internal name BattleEffectOther. + /// UiConfig option with the internal name BattleEffectOther. /// This option is a UInt. /// [GameConfigOption("BattleEffectOther", ConfigType.UInt)] BattleEffectOther, /// - /// System option with the internal name BattleEffectPvPEnemyPc. + /// UiConfig option with the internal name BattleEffectPvPEnemyPc. /// This option is a UInt. /// [GameConfigOption("BattleEffectPvPEnemyPc", ConfigType.UInt)] BattleEffectPvPEnemyPc, /// - /// System option with the internal name WeaponAutoPutAway. + /// UiConfig option with the internal name WeaponAutoPutAway. /// This option is a UInt. /// [GameConfigOption("WeaponAutoPutAway", ConfigType.UInt)] WeaponAutoPutAway, /// - /// System option with the internal name WeaponAutoPutAwayTime. + /// UiConfig option with the internal name WeaponAutoPutAwayTime. /// This option is a UInt. /// [GameConfigOption("WeaponAutoPutAwayTime", ConfigType.UInt)] WeaponAutoPutAwayTime, /// - /// System option with the internal name LipMotionType. + /// UiConfig option with the internal name LipMotionType. /// This option is a UInt. /// [GameConfigOption("LipMotionType", ConfigType.UInt)] LipMotionType, /// - /// System option with the internal name FirstPersonDefaultYAngle. + /// UiConfig option with the internal name FirstPersonDefaultYAngle. /// This option is a Float. /// [GameConfigOption("FirstPersonDefaultYAngle", ConfigType.Float)] FirstPersonDefaultYAngle, /// - /// System option with the internal name FirstPersonDefaultZoom. + /// UiConfig option with the internal name FirstPersonDefaultZoom. /// This option is a Float. /// [GameConfigOption("FirstPersonDefaultZoom", ConfigType.Float)] FirstPersonDefaultZoom, /// - /// System option with the internal name FirstPersonDefaultDistance. + /// UiConfig option with the internal name FirstPersonDefaultDistance. /// This option is a Float. /// [GameConfigOption("FirstPersonDefaultDistance", ConfigType.Float)] FirstPersonDefaultDistance, /// - /// System option with the internal name ThirdPersonDefaultYAngle. + /// UiConfig option with the internal name ThirdPersonDefaultYAngle. /// This option is a Float. /// [GameConfigOption("ThirdPersonDefaultYAngle", ConfigType.Float)] ThirdPersonDefaultYAngle, /// - /// System option with the internal name ThirdPersonDefaultZoom. + /// UiConfig option with the internal name ThirdPersonDefaultZoom. /// This option is a Float. /// [GameConfigOption("ThirdPersonDefaultZoom", ConfigType.Float)] ThirdPersonDefaultZoom, /// - /// System option with the internal name ThirdPersonDefaultDistance. + /// UiConfig option with the internal name ThirdPersonDefaultDistance. /// This option is a Float. /// [GameConfigOption("ThirdPersonDefaultDistance", ConfigType.Float)] ThirdPersonDefaultDistance, /// - /// System option with the internal name LockonDefaultYAngle. + /// UiConfig option with the internal name LockonDefaultYAngle. /// This option is a Float. /// [GameConfigOption("LockonDefaultYAngle", ConfigType.Float)] LockonDefaultYAngle, /// - /// System option with the internal name LockonDefaultZoom. + /// UiConfig option with the internal name LockonDefaultZoom. /// This option is a Float. /// [GameConfigOption("LockonDefaultZoom", ConfigType.Float)] LockonDefaultZoom, /// - /// System option with the internal name LockonDefaultZoom_179. + /// UiConfig option with the internal name LockonDefaultZoom_179. /// This option is a Float. /// - [Obsolete("This option won't work. Use LockonDefaultZoom.")] + [Obsolete("This option won't work. Use LockonDefaultZoom.", true)] [GameConfigOption("LockonDefaultZoom_179", ConfigType.Float)] LockonDefaultZoom_179, /// - /// System option with the internal name CameraProductionOfAction. + /// UiConfig option with the internal name CameraProductionOfAction. /// This option is a UInt. /// [GameConfigOption("CameraProductionOfAction", ConfigType.UInt)] CameraProductionOfAction, /// - /// System option with the internal name FPSCameraInterpolationType. + /// UiConfig option with the internal name FPSCameraInterpolationType. /// This option is a UInt. /// [GameConfigOption("FPSCameraInterpolationType", ConfigType.UInt)] FPSCameraInterpolationType, /// - /// System option with the internal name FPSCameraVerticalInterpolation. + /// UiConfig option with the internal name FPSCameraVerticalInterpolation. /// This option is a UInt. /// [GameConfigOption("FPSCameraVerticalInterpolation", ConfigType.UInt)] FPSCameraVerticalInterpolation, /// - /// System option with the internal name LegacyCameraCorrectionFix. + /// UiConfig option with the internal name LegacyCameraCorrectionFix. /// This option is a UInt. /// [GameConfigOption("LegacyCameraCorrectionFix", ConfigType.UInt)] LegacyCameraCorrectionFix, /// - /// System option with the internal name LegacyCameraType. + /// UiConfig option with the internal name LegacyCameraType. /// This option is a UInt. /// [GameConfigOption("LegacyCameraType", ConfigType.UInt)] LegacyCameraType, /// - /// System option with the internal name EventCameraAutoControl. + /// UiConfig option with the internal name EventCameraAutoControl. /// This option is a UInt. /// [GameConfigOption("EventCameraAutoControl", ConfigType.UInt)] EventCameraAutoControl, /// - /// System option with the internal name CameraLookBlinkType. + /// UiConfig option with the internal name CameraLookBlinkType. /// This option is a UInt. /// [GameConfigOption("CameraLookBlinkType", ConfigType.UInt)] CameraLookBlinkType, /// - /// System option with the internal name IdleEmoteTime. + /// UiConfig option with the internal name IdleEmoteTime. /// This option is a UInt. /// [GameConfigOption("IdleEmoteTime", ConfigType.UInt)] IdleEmoteTime, /// - /// System option with the internal name IdleEmoteRandomType. + /// UiConfig option with the internal name IdleEmoteRandomType. /// This option is a UInt. /// [GameConfigOption("IdleEmoteRandomType", ConfigType.UInt)] IdleEmoteRandomType, /// - /// System option with the internal name CutsceneSkipIsShip. + /// UiConfig option with the internal name CutsceneSkipIsShip. /// This option is a UInt. /// [GameConfigOption("CutsceneSkipIsShip", ConfigType.UInt)] CutsceneSkipIsShip, /// - /// System option with the internal name CutsceneSkipIsContents. + /// UiConfig option with the internal name CutsceneSkipIsContents. /// This option is a UInt. /// [GameConfigOption("CutsceneSkipIsContents", ConfigType.UInt)] CutsceneSkipIsContents, /// - /// System option with the internal name CutsceneSkipIsHousing. + /// UiConfig option with the internal name CutsceneSkipIsHousing. /// This option is a UInt. /// [GameConfigOption("CutsceneSkipIsHousing", ConfigType.UInt)] CutsceneSkipIsHousing, /// - /// System option with the internal name PetTargetOffInCombat. + /// UiConfig option with the internal name PetTargetOffInCombat. /// This option is a UInt. /// [GameConfigOption("PetTargetOffInCombat", ConfigType.UInt)] PetTargetOffInCombat, /// - /// System option with the internal name GroundTargetFPSPosX. + /// UiConfig option with the internal name GroundTargetFPSPosX. /// This option is a UInt. /// [GameConfigOption("GroundTargetFPSPosX", ConfigType.UInt)] GroundTargetFPSPosX, /// - /// System option with the internal name GroundTargetFPSPosY. + /// UiConfig option with the internal name GroundTargetFPSPosY. /// This option is a UInt. /// [GameConfigOption("GroundTargetFPSPosY", ConfigType.UInt)] GroundTargetFPSPosY, /// - /// System option with the internal name GroundTargetTPSPosX. + /// UiConfig option with the internal name GroundTargetTPSPosX. /// This option is a UInt. /// [GameConfigOption("GroundTargetTPSPosX", ConfigType.UInt)] GroundTargetTPSPosX, /// - /// System option with the internal name GroundTargetTPSPosY. + /// UiConfig option with the internal name GroundTargetTPSPosY. /// This option is a UInt. /// [GameConfigOption("GroundTargetTPSPosY", ConfigType.UInt)] GroundTargetTPSPosY, /// - /// System option with the internal name TargetDisableAnchor. + /// UiConfig option with the internal name TargetDisableAnchor. /// This option is a UInt. /// [GameConfigOption("TargetDisableAnchor", ConfigType.UInt)] TargetDisableAnchor, /// - /// System option with the internal name TargetCircleClickFilterEnableNearestCursor. + /// UiConfig option with the internal name TargetCircleClickFilterEnableNearestCursor. /// This option is a UInt. /// [GameConfigOption("TargetCircleClickFilterEnableNearestCursor", ConfigType.UInt)] TargetCircleClickFilterEnableNearestCursor, /// - /// System option with the internal name TargetEnableMouseOverSelect. + /// UiConfig option with the internal name TargetEnableMouseOverSelect. /// This option is a UInt. /// [GameConfigOption("TargetEnableMouseOverSelect", ConfigType.UInt)] TargetEnableMouseOverSelect, /// - /// System option with the internal name GroundTargetCursorCorrectType. + /// UiConfig option with the internal name GroundTargetCursorCorrectType. /// This option is a UInt. /// [GameConfigOption("GroundTargetCursorCorrectType", ConfigType.UInt)] GroundTargetCursorCorrectType, /// - /// System option with the internal name GroundTargetActionExcuteType. + /// UiConfig option with the internal name GroundTargetActionExcuteType. /// This option is a UInt. /// [GameConfigOption("GroundTargetActionExcuteType", ConfigType.UInt)] GroundTargetActionExcuteType, /// - /// System option with the internal name AutoNearestTarget. + /// UiConfig option with the internal name AutoNearestTarget. /// This option is a UInt. /// [GameConfigOption("AutoNearestTarget", ConfigType.UInt)] AutoNearestTarget, /// - /// System option with the internal name AutoNearestTargetType. + /// UiConfig option with the internal name AutoNearestTargetType. /// This option is a UInt. /// [GameConfigOption("AutoNearestTargetType", ConfigType.UInt)] AutoNearestTargetType, /// - /// System option with the internal name RightClickExclusionPC. + /// UiConfig option with the internal name RightClickExclusionPC. /// This option is a UInt. /// [GameConfigOption("RightClickExclusionPC", ConfigType.UInt)] RightClickExclusionPC, /// - /// System option with the internal name RightClickExclusionBNPC. + /// UiConfig option with the internal name RightClickExclusionBNPC. /// This option is a UInt. /// [GameConfigOption("RightClickExclusionBNPC", ConfigType.UInt)] RightClickExclusionBNPC, /// - /// System option with the internal name RightClickExclusionMinion. + /// UiConfig option with the internal name RightClickExclusionMinion. /// This option is a UInt. /// [GameConfigOption("RightClickExclusionMinion", ConfigType.UInt)] RightClickExclusionMinion, /// - /// System option with the internal name TurnSpeed. + /// UiConfig option with the internal name TurnSpeed. /// This option is a UInt. /// [GameConfigOption("TurnSpeed", ConfigType.UInt)] TurnSpeed, /// - /// System option with the internal name FootEffect. + /// UiConfig option with the internal name FootEffect. /// This option is a UInt. /// [GameConfigOption("FootEffect", ConfigType.UInt)] FootEffect, /// - /// System option with the internal name LegacySeal. + /// UiConfig option with the internal name LegacySeal. /// This option is a UInt. /// [GameConfigOption("LegacySeal", ConfigType.UInt)] LegacySeal, /// - /// System option with the internal name GBarrelDisp. + /// UiConfig option with the internal name GBarrelDisp. /// This option is a UInt. /// [GameConfigOption("GBarrelDisp", ConfigType.UInt)] GBarrelDisp, /// - /// System option with the internal name EgiMirageTypeGaruda. + /// UiConfig option with the internal name EgiMirageTypeGaruda. /// This option is a UInt. /// [GameConfigOption("EgiMirageTypeGaruda", ConfigType.UInt)] EgiMirageTypeGaruda, /// - /// System option with the internal name EgiMirageTypeTitan. + /// UiConfig option with the internal name EgiMirageTypeTitan. /// This option is a UInt. /// [GameConfigOption("EgiMirageTypeTitan", ConfigType.UInt)] EgiMirageTypeTitan, /// - /// System option with the internal name EgiMirageTypeIfrit. + /// UiConfig option with the internal name EgiMirageTypeIfrit. /// This option is a UInt. /// [GameConfigOption("EgiMirageTypeIfrit", ConfigType.UInt)] EgiMirageTypeIfrit, /// - /// System option with the internal name BahamutSize. + /// UiConfig option with the internal name BahamutSize. /// This option is a UInt. /// [GameConfigOption("BahamutSize", ConfigType.UInt)] BahamutSize, /// - /// System option with the internal name PetMirageTypeCarbuncleSupport. + /// UiConfig option with the internal name PetMirageTypeCarbuncleSupport. /// This option is a UInt. /// [GameConfigOption("PetMirageTypeCarbuncleSupport", ConfigType.UInt)] PetMirageTypeCarbuncleSupport, /// - /// System option with the internal name PhoenixSize. + /// UiConfig option with the internal name PhoenixSize. /// This option is a UInt. /// [GameConfigOption("PhoenixSize", ConfigType.UInt)] PhoenixSize, /// - /// System option with the internal name GarudaSize. + /// UiConfig option with the internal name GarudaSize. /// This option is a UInt. /// [GameConfigOption("GarudaSize", ConfigType.UInt)] GarudaSize, /// - /// System option with the internal name TitanSize. + /// UiConfig option with the internal name TitanSize. /// This option is a UInt. /// [GameConfigOption("TitanSize", ConfigType.UInt)] TitanSize, /// - /// System option with the internal name IfritSize. + /// UiConfig option with the internal name IfritSize. /// This option is a UInt. /// [GameConfigOption("IfritSize", ConfigType.UInt)] IfritSize, /// - /// System option with the internal name SolBahamutSize. + /// UiConfig option with the internal name SolBahamutSize. /// This option is a UInt. /// [GameConfigOption("SolBahamutSize", ConfigType.UInt)] SolBahamutSize, /// - /// System option with the internal name PetMirageTypeFairy. + /// UiConfig option with the internal name PetMirageTypeFairy. /// This option is a UInt. /// [GameConfigOption("PetMirageTypeFairy", ConfigType.UInt)] PetMirageTypeFairy, /// - /// System option with the internal name TimeMode. + /// UiConfig option with the internal name TimeMode. /// This option is a UInt. /// [GameConfigOption("TimeMode", ConfigType.UInt)] TimeMode, /// - /// System option with the internal name Time12. + /// UiConfig option with the internal name Time12. /// This option is a UInt. /// [GameConfigOption("Time12", ConfigType.UInt)] Time12, /// - /// System option with the internal name TimeEorzea. + /// UiConfig option with the internal name TimeEorzea. /// This option is a UInt. /// [GameConfigOption("TimeEorzea", ConfigType.UInt)] TimeEorzea, /// - /// System option with the internal name TimeLocal. + /// UiConfig option with the internal name TimeLocal. /// This option is a UInt. /// [GameConfigOption("TimeLocal", ConfigType.UInt)] TimeLocal, /// - /// System option with the internal name TimeServer. + /// UiConfig option with the internal name TimeServer. /// This option is a UInt. /// [GameConfigOption("TimeServer", ConfigType.UInt)] TimeServer, /// - /// System option with the internal name ActiveLS_H. + /// UiConfig option with the internal name ActiveLS_H. /// This option is a UInt. /// [GameConfigOption("ActiveLS_H", ConfigType.UInt)] ActiveLS_H, /// - /// System option with the internal name ActiveLS_L. + /// UiConfig option with the internal name ActiveLS_L. /// This option is a UInt. /// [GameConfigOption("ActiveLS_L", ConfigType.UInt)] ActiveLS_L, /// - /// System option with the internal name HotbarLock. + /// UiConfig option with the internal name HotbarLock. /// This option is a UInt. /// [GameConfigOption("HotbarLock", ConfigType.UInt)] HotbarLock, /// - /// System option with the internal name HotbarDispRecastTime. + /// UiConfig option with the internal name HotbarDispRecastTime. /// This option is a UInt. /// [GameConfigOption("HotbarDispRecastTime", ConfigType.UInt)] HotbarDispRecastTime, /// - /// System option with the internal name HotbarCrossContentsActionEnableInput. + /// UiConfig option with the internal name HotbarCrossContentsActionEnableInput. /// This option is a UInt. /// [GameConfigOption("HotbarCrossContentsActionEnableInput", ConfigType.UInt)] HotbarCrossContentsActionEnableInput, /// - /// System option with the internal name HotbarDispRecastTimeDispType. + /// UiConfig option with the internal name HotbarDispRecastTimeDispType. /// This option is a UInt. /// [GameConfigOption("HotbarDispRecastTimeDispType", ConfigType.UInt)] HotbarDispRecastTimeDispType, /// - /// System option with the internal name ExHotbarChangeHotbar1. + /// UiConfig option with the internal name ExHotbarChangeHotbar1. /// This option is a UInt. /// [GameConfigOption("ExHotbarChangeHotbar1", ConfigType.UInt)] ExHotbarChangeHotbar1, /// - /// System option with the internal name HotbarCommon01. + /// UiConfig option with the internal name HotbarCommon01. /// This option is a UInt. /// [GameConfigOption("HotbarCommon01", ConfigType.UInt)] HotbarCommon01, /// - /// System option with the internal name HotbarCommon02. + /// UiConfig option with the internal name HotbarCommon02. /// This option is a UInt. /// [GameConfigOption("HotbarCommon02", ConfigType.UInt)] HotbarCommon02, /// - /// System option with the internal name HotbarCommon03. + /// UiConfig option with the internal name HotbarCommon03. /// This option is a UInt. /// [GameConfigOption("HotbarCommon03", ConfigType.UInt)] HotbarCommon03, /// - /// System option with the internal name HotbarCommon04. + /// UiConfig option with the internal name HotbarCommon04. /// This option is a UInt. /// [GameConfigOption("HotbarCommon04", ConfigType.UInt)] HotbarCommon04, /// - /// System option with the internal name HotbarCommon05. + /// UiConfig option with the internal name HotbarCommon05. /// This option is a UInt. /// [GameConfigOption("HotbarCommon05", ConfigType.UInt)] HotbarCommon05, /// - /// System option with the internal name HotbarCommon06. + /// UiConfig option with the internal name HotbarCommon06. /// This option is a UInt. /// [GameConfigOption("HotbarCommon06", ConfigType.UInt)] HotbarCommon06, /// - /// System option with the internal name HotbarCommon07. + /// UiConfig option with the internal name HotbarCommon07. /// This option is a UInt. /// [GameConfigOption("HotbarCommon07", ConfigType.UInt)] HotbarCommon07, /// - /// System option with the internal name HotbarCommon08. + /// UiConfig option with the internal name HotbarCommon08. /// This option is a UInt. /// [GameConfigOption("HotbarCommon08", ConfigType.UInt)] HotbarCommon08, /// - /// System option with the internal name HotbarCommon09. + /// UiConfig option with the internal name HotbarCommon09. /// This option is a UInt. /// [GameConfigOption("HotbarCommon09", ConfigType.UInt)] HotbarCommon09, /// - /// System option with the internal name HotbarCommon10. + /// UiConfig option with the internal name HotbarCommon10. /// This option is a UInt. /// [GameConfigOption("HotbarCommon10", ConfigType.UInt)] HotbarCommon10, /// - /// System option with the internal name HotbarCrossCommon01. + /// UiConfig option with the internal name HotbarCrossCommon01. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon01", ConfigType.UInt)] HotbarCrossCommon01, /// - /// System option with the internal name HotbarCrossCommon02. + /// UiConfig option with the internal name HotbarCrossCommon02. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon02", ConfigType.UInt)] HotbarCrossCommon02, /// - /// System option with the internal name HotbarCrossCommon03. + /// UiConfig option with the internal name HotbarCrossCommon03. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon03", ConfigType.UInt)] HotbarCrossCommon03, /// - /// System option with the internal name HotbarCrossCommon04. + /// UiConfig option with the internal name HotbarCrossCommon04. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon04", ConfigType.UInt)] HotbarCrossCommon04, /// - /// System option with the internal name HotbarCrossCommon05. + /// UiConfig option with the internal name HotbarCrossCommon05. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon05", ConfigType.UInt)] HotbarCrossCommon05, /// - /// System option with the internal name HotbarCrossCommon06. + /// UiConfig option with the internal name HotbarCrossCommon06. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon06", ConfigType.UInt)] HotbarCrossCommon06, /// - /// System option with the internal name HotbarCrossCommon07. + /// UiConfig option with the internal name HotbarCrossCommon07. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon07", ConfigType.UInt)] HotbarCrossCommon07, /// - /// System option with the internal name HotbarCrossCommon08. + /// UiConfig option with the internal name HotbarCrossCommon08. /// This option is a UInt. /// [GameConfigOption("HotbarCrossCommon08", ConfigType.UInt)] HotbarCrossCommon08, /// - /// System option with the internal name HotbarCrossHelpDisp. + /// UiConfig option with the internal name HotbarCrossHelpDisp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossHelpDisp", ConfigType.UInt)] HotbarCrossHelpDisp, /// - /// System option with the internal name HotbarCrossOperation. + /// UiConfig option with the internal name HotbarCrossOperation. /// This option is a UInt. /// [GameConfigOption("HotbarCrossOperation", ConfigType.UInt)] HotbarCrossOperation, /// - /// System option with the internal name HotbarCrossDisp. + /// UiConfig option with the internal name HotbarCrossDisp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossDisp", ConfigType.UInt)] HotbarCrossDisp, /// - /// System option with the internal name HotbarCrossLock. + /// UiConfig option with the internal name HotbarCrossLock. /// This option is a UInt. /// [GameConfigOption("HotbarCrossLock", ConfigType.UInt)] HotbarCrossLock, /// - /// System option with the internal name HotbarCrossUsePadGuide. + /// UiConfig option with the internal name HotbarCrossUsePadGuide. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUsePadGuide", ConfigType.UInt)] HotbarCrossUsePadGuide, /// - /// System option with the internal name HotbarCrossActiveSet. + /// UiConfig option with the internal name HotbarCrossActiveSet. /// This option is a UInt. /// [GameConfigOption("HotbarCrossActiveSet", ConfigType.UInt)] HotbarCrossActiveSet, /// - /// System option with the internal name HotbarCrossActiveSetPvP. + /// UiConfig option with the internal name HotbarCrossActiveSetPvP. /// This option is a UInt. /// [GameConfigOption("HotbarCrossActiveSetPvP", ConfigType.UInt)] HotbarCrossActiveSetPvP, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsAuto. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsAuto. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsAuto", ConfigType.UInt)] HotbarCrossSetChangeCustomIsAuto, /// - /// System option with the internal name HotbarCrossSetChangeCustom. + /// UiConfig option with the internal name HotbarCrossSetChangeCustom. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustom", ConfigType.UInt)] HotbarCrossSetChangeCustom, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet1. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet1. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet1", ConfigType.UInt)] HotbarCrossSetChangeCustomSet1, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet2. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet2. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet2", ConfigType.UInt)] HotbarCrossSetChangeCustomSet2, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet3. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet3. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet3", ConfigType.UInt)] HotbarCrossSetChangeCustomSet3, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet4. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet4. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet4", ConfigType.UInt)] HotbarCrossSetChangeCustomSet4, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet5. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet5. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet5", ConfigType.UInt)] HotbarCrossSetChangeCustomSet5, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet6. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet6. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet6", ConfigType.UInt)] HotbarCrossSetChangeCustomSet6, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet7. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet7. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet7", ConfigType.UInt)] HotbarCrossSetChangeCustomSet7, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet8. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet8. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet8", ConfigType.UInt)] HotbarCrossSetChangeCustomSet8, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSword. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSword. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSword", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSword, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet1. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet1. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet1", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet1, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet2. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet2. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet2", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet2, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet3. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet3. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet3", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet3, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet4. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet4. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet4", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet4, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet5. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet5. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet5", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet5, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet6. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet6. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet6", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet6, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet7. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet7. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet7", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet7, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet8. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet8. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet8", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet8, /// - /// System option with the internal name HotbarCrossAdvancedSetting. + /// UiConfig option with the internal name HotbarCrossAdvancedSetting. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSetting", ConfigType.UInt)] HotbarCrossAdvancedSetting, /// - /// System option with the internal name HotbarCrossAdvancedSettingLeft. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingLeft. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingLeft", ConfigType.UInt)] HotbarCrossAdvancedSettingLeft, /// - /// System option with the internal name HotbarCrossAdvancedSettingRight. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingRight. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingRight", ConfigType.UInt)] HotbarCrossAdvancedSettingRight, /// - /// System option with the internal name HotbarCrossSetPvpModeActive. + /// UiConfig option with the internal name HotbarCrossSetPvpModeActive. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetPvpModeActive", ConfigType.UInt)] HotbarCrossSetPvpModeActive, /// - /// System option with the internal name HotbarCrossSetChangeCustomPvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomPvp", ConfigType.UInt)] HotbarCrossSetChangeCustomPvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsAutoPvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsAutoPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsAutoPvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsAutoPvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet1Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet1Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet1Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet1Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet2Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet2Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet2Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet2Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet3Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet3Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet3Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet3Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet4Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet4Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet4Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet4Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet5Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet5Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet5Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet5Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet6Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet6Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet6Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet6Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet7Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet7Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet7Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet7Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomSet8Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomSet8Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomSet8Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomSet8Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordPvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordPvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordPvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet1Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet1Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet1Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet1Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet2Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet2Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet2Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet2Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet3Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet3Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet3Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet3Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet4Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet4Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet4Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet4Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet5Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet5Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet5Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet5Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet6Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet6Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet6Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet6Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet7Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet7Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet7Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet7Pvp, /// - /// System option with the internal name HotbarCrossSetChangeCustomIsSwordSet8Pvp. + /// UiConfig option with the internal name HotbarCrossSetChangeCustomIsSwordSet8Pvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossSetChangeCustomIsSwordSet8Pvp", ConfigType.UInt)] HotbarCrossSetChangeCustomIsSwordSet8Pvp, /// - /// System option with the internal name HotbarCrossAdvancedSettingPvp. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingPvp", ConfigType.UInt)] HotbarCrossAdvancedSettingPvp, /// - /// System option with the internal name HotbarCrossAdvancedSettingLeftPvp. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingLeftPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingLeftPvp", ConfigType.UInt)] HotbarCrossAdvancedSettingLeftPvp, /// - /// System option with the internal name HotbarCrossAdvancedSettingRightPvp. + /// UiConfig option with the internal name HotbarCrossAdvancedSettingRightPvp. /// This option is a UInt. /// [GameConfigOption("HotbarCrossAdvancedSettingRightPvp", ConfigType.UInt)] HotbarCrossAdvancedSettingRightPvp, /// - /// System option with the internal name HotbarWXHBEnable. + /// UiConfig option with the internal name HotbarWXHBEnable. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBEnable", ConfigType.UInt)] HotbarWXHBEnable, /// - /// System option with the internal name HotbarWXHBSetLeft. + /// UiConfig option with the internal name HotbarWXHBSetLeft. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetLeft", ConfigType.UInt)] HotbarWXHBSetLeft, /// - /// System option with the internal name HotbarWXHBSetRight. + /// UiConfig option with the internal name HotbarWXHBSetRight. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetRight", ConfigType.UInt)] HotbarWXHBSetRight, /// - /// System option with the internal name HotbarWXHBEnablePvP. + /// UiConfig option with the internal name HotbarWXHBEnablePvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBEnablePvP", ConfigType.UInt)] HotbarWXHBEnablePvP, /// - /// System option with the internal name HotbarWXHBSetLeftPvP. + /// UiConfig option with the internal name HotbarWXHBSetLeftPvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetLeftPvP", ConfigType.UInt)] HotbarWXHBSetLeftPvP, /// - /// System option with the internal name HotbarWXHBSetRightPvP. + /// UiConfig option with the internal name HotbarWXHBSetRightPvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetRightPvP", ConfigType.UInt)] HotbarWXHBSetRightPvP, /// - /// System option with the internal name HotbarWXHB8Button. + /// UiConfig option with the internal name HotbarWXHB8Button. /// This option is a UInt. /// [GameConfigOption("HotbarWXHB8Button", ConfigType.UInt)] HotbarWXHB8Button, /// - /// System option with the internal name HotbarWXHB8ButtonPvP. + /// UiConfig option with the internal name HotbarWXHB8ButtonPvP. /// This option is a UInt. /// [GameConfigOption("HotbarWXHB8ButtonPvP", ConfigType.UInt)] HotbarWXHB8ButtonPvP, /// - /// System option with the internal name HotbarWXHBSetInputTime. + /// UiConfig option with the internal name HotbarWXHBSetInputTime. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBSetInputTime", ConfigType.UInt)] HotbarWXHBSetInputTime, /// - /// System option with the internal name HotbarWXHBDisplay. + /// UiConfig option with the internal name HotbarWXHBDisplay. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBDisplay", ConfigType.UInt)] HotbarWXHBDisplay, /// - /// System option with the internal name HotbarWXHBFreeLayout. + /// UiConfig option with the internal name HotbarWXHBFreeLayout. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBFreeLayout", ConfigType.UInt)] HotbarWXHBFreeLayout, /// - /// System option with the internal name HotbarXHBActiveTransmissionAlpha. + /// UiConfig option with the internal name HotbarXHBActiveTransmissionAlpha. /// This option is a UInt. /// [GameConfigOption("HotbarXHBActiveTransmissionAlpha", ConfigType.UInt)] HotbarXHBActiveTransmissionAlpha, /// - /// System option with the internal name HotbarXHBAlphaDefault. + /// UiConfig option with the internal name HotbarXHBAlphaDefault. /// This option is a UInt. /// [GameConfigOption("HotbarXHBAlphaDefault", ConfigType.UInt)] HotbarXHBAlphaDefault, /// - /// System option with the internal name HotbarXHBAlphaActiveSet. + /// UiConfig option with the internal name HotbarXHBAlphaActiveSet. /// This option is a UInt. /// [GameConfigOption("HotbarXHBAlphaActiveSet", ConfigType.UInt)] HotbarXHBAlphaActiveSet, /// - /// System option with the internal name HotbarXHBAlphaInactiveSet. + /// UiConfig option with the internal name HotbarXHBAlphaInactiveSet. /// This option is a UInt. /// [GameConfigOption("HotbarXHBAlphaInactiveSet", ConfigType.UInt)] HotbarXHBAlphaInactiveSet, /// - /// System option with the internal name HotbarWXHBInputOnce. + /// UiConfig option with the internal name HotbarWXHBInputOnce. /// This option is a UInt. /// [GameConfigOption("HotbarWXHBInputOnce", ConfigType.UInt)] HotbarWXHBInputOnce, /// - /// System option with the internal name ExHotbarChangeHotbar1IsFashion. + /// UiConfig option with the internal name ExHotbarChangeHotbar1IsFashion. /// This option is a UInt. /// [GameConfigOption("ExHotbarChangeHotbar1IsFashion", ConfigType.UInt)] ExHotbarChangeHotbar1IsFashion, /// - /// System option with the internal name HotbarCrossUseExDirectionAutoSwitch. + /// UiConfig option with the internal name HotbarCrossUseExDirectionAutoSwitch. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUseExDirectionAutoSwitch", ConfigType.UInt)] HotbarCrossUseExDirectionAutoSwitch, /// - /// System option with the internal name IdlingCameraSwitchType. + /// UiConfig option with the internal name IdlingCameraSwitchType. /// This option is a UInt. /// [GameConfigOption("IdlingCameraSwitchType", ConfigType.UInt)] IdlingCameraSwitchType, /// - /// System option with the internal name HotbarXHBEditEnable. + /// UiConfig option with the internal name HotbarXHBEditEnable. /// This option is a UInt. /// [GameConfigOption("HotbarXHBEditEnable", ConfigType.UInt)] HotbarXHBEditEnable, /// - /// System option with the internal name PlateType. + /// UiConfig option with the internal name PlateType. /// This option is a UInt. /// [GameConfigOption("PlateType", ConfigType.UInt)] PlateType, /// - /// System option with the internal name PlateDispHPBar. + /// UiConfig option with the internal name PlateDispHPBar. /// This option is a UInt. /// [GameConfigOption("PlateDispHPBar", ConfigType.UInt)] PlateDispHPBar, /// - /// System option with the internal name PlateDisableMaxHPBar. + /// UiConfig option with the internal name PlateDisableMaxHPBar. /// This option is a UInt. /// [GameConfigOption("PlateDisableMaxHPBar", ConfigType.UInt)] PlateDisableMaxHPBar, /// - /// System option with the internal name NamePlateHpSizeType. + /// UiConfig option with the internal name NamePlateHpSizeType. /// This option is a UInt. /// [GameConfigOption("NamePlateHpSizeType", ConfigType.UInt)] NamePlateHpSizeType, /// - /// System option with the internal name NamePlateColorSelf. + /// UiConfig option with the internal name NamePlateColorSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateColorSelf", ConfigType.UInt)] NamePlateColorSelf, /// - /// System option with the internal name NamePlateEdgeSelf. + /// UiConfig option with the internal name NamePlateEdgeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeSelf", ConfigType.UInt)] NamePlateEdgeSelf, /// - /// System option with the internal name NamePlateDispTypeSelf. + /// UiConfig option with the internal name NamePlateDispTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeSelf", ConfigType.UInt)] NamePlateDispTypeSelf, /// - /// System option with the internal name NamePlateNameTypeSelf. + /// UiConfig option with the internal name NamePlateNameTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeSelf", ConfigType.UInt)] NamePlateNameTypeSelf, /// - /// System option with the internal name NamePlateHpTypeSelf. + /// UiConfig option with the internal name NamePlateHpTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeSelf", ConfigType.UInt)] NamePlateHpTypeSelf, /// - /// System option with the internal name NamePlateColorSelfBuddy. + /// UiConfig option with the internal name NamePlateColorSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorSelfBuddy", ConfigType.UInt)] NamePlateColorSelfBuddy, /// - /// System option with the internal name NamePlateEdgeSelfBuddy. + /// UiConfig option with the internal name NamePlateEdgeSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeSelfBuddy", ConfigType.UInt)] NamePlateEdgeSelfBuddy, /// - /// System option with the internal name NamePlateDispTypeSelfBuddy. + /// UiConfig option with the internal name NamePlateDispTypeSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeSelfBuddy", ConfigType.UInt)] NamePlateDispTypeSelfBuddy, /// - /// System option with the internal name NamePlateHpTypeSelfBuddy. + /// UiConfig option with the internal name NamePlateHpTypeSelfBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeSelfBuddy", ConfigType.UInt)] NamePlateHpTypeSelfBuddy, /// - /// System option with the internal name NamePlateColorSelfPet. + /// UiConfig option with the internal name NamePlateColorSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateColorSelfPet", ConfigType.UInt)] NamePlateColorSelfPet, /// - /// System option with the internal name NamePlateEdgeSelfPet. + /// UiConfig option with the internal name NamePlateEdgeSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeSelfPet", ConfigType.UInt)] NamePlateEdgeSelfPet, /// - /// System option with the internal name NamePlateDispTypeSelfPet. + /// UiConfig option with the internal name NamePlateDispTypeSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeSelfPet", ConfigType.UInt)] NamePlateDispTypeSelfPet, /// - /// System option with the internal name NamePlateHpTypeSelfPet. + /// UiConfig option with the internal name NamePlateHpTypeSelfPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeSelfPet", ConfigType.UInt)] NamePlateHpTypeSelfPet, /// - /// System option with the internal name NamePlateColorParty. + /// UiConfig option with the internal name NamePlateColorParty. /// This option is a UInt. /// [GameConfigOption("NamePlateColorParty", ConfigType.UInt)] NamePlateColorParty, /// - /// System option with the internal name NamePlateEdgeParty. + /// UiConfig option with the internal name NamePlateEdgeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeParty", ConfigType.UInt)] NamePlateEdgeParty, /// - /// System option with the internal name NamePlateDispTypeParty. + /// UiConfig option with the internal name NamePlateDispTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeParty", ConfigType.UInt)] NamePlateDispTypeParty, /// - /// System option with the internal name NamePlateNameTypeParty. + /// UiConfig option with the internal name NamePlateNameTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeParty", ConfigType.UInt)] NamePlateNameTypeParty, /// - /// System option with the internal name NamePlateHpTypeParty. + /// UiConfig option with the internal name NamePlateHpTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeParty", ConfigType.UInt)] NamePlateHpTypeParty, /// - /// System option with the internal name NamePlateDispTypePartyPet. + /// UiConfig option with the internal name NamePlateDispTypePartyPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypePartyPet", ConfigType.UInt)] NamePlateDispTypePartyPet, /// - /// System option with the internal name NamePlateHpTypePartyPet. + /// UiConfig option with the internal name NamePlateHpTypePartyPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypePartyPet", ConfigType.UInt)] NamePlateHpTypePartyPet, /// - /// System option with the internal name NamePlateDispTypePartyBuddy. + /// UiConfig option with the internal name NamePlateDispTypePartyBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypePartyBuddy", ConfigType.UInt)] NamePlateDispTypePartyBuddy, /// - /// System option with the internal name NamePlateHpTypePartyBuddy. + /// UiConfig option with the internal name NamePlateHpTypePartyBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypePartyBuddy", ConfigType.UInt)] NamePlateHpTypePartyBuddy, /// - /// System option with the internal name NamePlateColorAlliance. + /// UiConfig option with the internal name NamePlateColorAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateColorAlliance", ConfigType.UInt)] NamePlateColorAlliance, /// - /// System option with the internal name NamePlateEdgeAlliance. + /// UiConfig option with the internal name NamePlateEdgeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeAlliance", ConfigType.UInt)] NamePlateEdgeAlliance, /// - /// System option with the internal name NamePlateDispTypeAlliance. + /// UiConfig option with the internal name NamePlateDispTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeAlliance", ConfigType.UInt)] NamePlateDispTypeAlliance, /// - /// System option with the internal name NamePlateNameTypeAlliance. + /// UiConfig option with the internal name NamePlateNameTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeAlliance", ConfigType.UInt)] NamePlateNameTypeAlliance, /// - /// System option with the internal name NamePlateHpTypeAlliance. + /// UiConfig option with the internal name NamePlateHpTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeAlliance", ConfigType.UInt)] NamePlateHpTypeAlliance, /// - /// System option with the internal name NamePlateDispTypeAlliancePet. + /// UiConfig option with the internal name NamePlateDispTypeAlliancePet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeAlliancePet", ConfigType.UInt)] NamePlateDispTypeAlliancePet, /// - /// System option with the internal name NamePlateHpTypeAlliancePet. + /// UiConfig option with the internal name NamePlateHpTypeAlliancePet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeAlliancePet", ConfigType.UInt)] NamePlateHpTypeAlliancePet, /// - /// System option with the internal name NamePlateColorOther. + /// UiConfig option with the internal name NamePlateColorOther. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOther", ConfigType.UInt)] NamePlateColorOther, /// - /// System option with the internal name NamePlateEdgeOther. + /// UiConfig option with the internal name NamePlateEdgeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOther", ConfigType.UInt)] NamePlateEdgeOther, /// - /// System option with the internal name NamePlateDispTypeOther. + /// UiConfig option with the internal name NamePlateDispTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeOther", ConfigType.UInt)] NamePlateDispTypeOther, /// - /// System option with the internal name NamePlateNameTypeOther. + /// UiConfig option with the internal name NamePlateNameTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeOther", ConfigType.UInt)] NamePlateNameTypeOther, /// - /// System option with the internal name NamePlateHpTypeOther. + /// UiConfig option with the internal name NamePlateHpTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeOther", ConfigType.UInt)] NamePlateHpTypeOther, /// - /// System option with the internal name NamePlateDispTypeOtherPet. + /// UiConfig option with the internal name NamePlateDispTypeOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeOtherPet", ConfigType.UInt)] NamePlateDispTypeOtherPet, /// - /// System option with the internal name NamePlateHpTypeOtherPet. + /// UiConfig option with the internal name NamePlateHpTypeOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeOtherPet", ConfigType.UInt)] NamePlateHpTypeOtherPet, /// - /// System option with the internal name NamePlateDispTypeOtherBuddy. + /// UiConfig option with the internal name NamePlateDispTypeOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeOtherBuddy", ConfigType.UInt)] NamePlateDispTypeOtherBuddy, /// - /// System option with the internal name NamePlateHpTypeOtherBuddy. + /// UiConfig option with the internal name NamePlateHpTypeOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeOtherBuddy", ConfigType.UInt)] NamePlateHpTypeOtherBuddy, /// - /// System option with the internal name NamePlateColorUnengagedEnemy. + /// UiConfig option with the internal name NamePlateColorUnengagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUnengagedEnemy", ConfigType.UInt)] NamePlateColorUnengagedEnemy, /// - /// System option with the internal name NamePlateEdgeUnengagedEnemy. + /// UiConfig option with the internal name NamePlateEdgeUnengagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeUnengagedEnemy", ConfigType.UInt)] NamePlateEdgeUnengagedEnemy, /// - /// System option with the internal name NamePlateDispTypeUnengagedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeUnengagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeUnengagedEnemy", ConfigType.UInt)] NamePlateDispTypeUnengagedEnemy, /// - /// System option with the internal name NamePlateHpTypeUnengagedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeUnengagedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeUnengagedEmemy", ConfigType.UInt)] NamePlateHpTypeUnengagedEmemy, /// - /// System option with the internal name NamePlateColorEngagedEnemy. + /// UiConfig option with the internal name NamePlateColorEngagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorEngagedEnemy", ConfigType.UInt)] NamePlateColorEngagedEnemy, /// - /// System option with the internal name NamePlateEdgeEngagedEnemy. + /// UiConfig option with the internal name NamePlateEdgeEngagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeEngagedEnemy", ConfigType.UInt)] NamePlateEdgeEngagedEnemy, /// - /// System option with the internal name NamePlateDispTypeEngagedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeEngagedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeEngagedEnemy", ConfigType.UInt)] NamePlateDispTypeEngagedEnemy, /// - /// System option with the internal name NamePlateHpTypeEngagedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeEngagedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeEngagedEmemy", ConfigType.UInt)] NamePlateHpTypeEngagedEmemy, /// - /// System option with the internal name NamePlateColorClaimedEnemy. + /// UiConfig option with the internal name NamePlateColorClaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorClaimedEnemy", ConfigType.UInt)] NamePlateColorClaimedEnemy, /// - /// System option with the internal name NamePlateEdgeClaimedEnemy. + /// UiConfig option with the internal name NamePlateEdgeClaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeClaimedEnemy", ConfigType.UInt)] NamePlateEdgeClaimedEnemy, /// - /// System option with the internal name NamePlateDispTypeClaimedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeClaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeClaimedEnemy", ConfigType.UInt)] NamePlateDispTypeClaimedEnemy, /// - /// System option with the internal name NamePlateHpTypeClaimedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeClaimedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeClaimedEmemy", ConfigType.UInt)] NamePlateHpTypeClaimedEmemy, /// - /// System option with the internal name NamePlateColorUnclaimedEnemy. + /// UiConfig option with the internal name NamePlateColorUnclaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUnclaimedEnemy", ConfigType.UInt)] NamePlateColorUnclaimedEnemy, /// - /// System option with the internal name NamePlateEdgeUnclaimedEnemy. + /// UiConfig option with the internal name NamePlateEdgeUnclaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeUnclaimedEnemy", ConfigType.UInt)] NamePlateEdgeUnclaimedEnemy, /// - /// System option with the internal name NamePlateDispTypeUnclaimedEnemy. + /// UiConfig option with the internal name NamePlateDispTypeUnclaimedEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeUnclaimedEnemy", ConfigType.UInt)] NamePlateDispTypeUnclaimedEnemy, /// - /// System option with the internal name NamePlateHpTypeUnclaimedEmemy. + /// UiConfig option with the internal name NamePlateHpTypeUnclaimedEmemy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeUnclaimedEmemy", ConfigType.UInt)] NamePlateHpTypeUnclaimedEmemy, /// - /// System option with the internal name NamePlateColorNpc. + /// UiConfig option with the internal name NamePlateColorNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateColorNpc", ConfigType.UInt)] NamePlateColorNpc, /// - /// System option with the internal name NamePlateEdgeNpc. + /// UiConfig option with the internal name NamePlateEdgeNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeNpc", ConfigType.UInt)] NamePlateEdgeNpc, /// - /// System option with the internal name NamePlateDispTypeNpc. + /// UiConfig option with the internal name NamePlateDispTypeNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeNpc", ConfigType.UInt)] NamePlateDispTypeNpc, /// - /// System option with the internal name NamePlateHpTypeNpc. + /// UiConfig option with the internal name NamePlateHpTypeNpc. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeNpc", ConfigType.UInt)] NamePlateHpTypeNpc, /// - /// System option with the internal name NamePlateColorObject. + /// UiConfig option with the internal name NamePlateColorObject. /// This option is a UInt. /// [GameConfigOption("NamePlateColorObject", ConfigType.UInt)] NamePlateColorObject, /// - /// System option with the internal name NamePlateEdgeObject. + /// UiConfig option with the internal name NamePlateEdgeObject. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeObject", ConfigType.UInt)] NamePlateEdgeObject, /// - /// System option with the internal name NamePlateDispTypeObject. + /// UiConfig option with the internal name NamePlateDispTypeObject. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeObject", ConfigType.UInt)] NamePlateDispTypeObject, /// - /// System option with the internal name NamePlateHpTypeObject. + /// UiConfig option with the internal name NamePlateHpTypeObject. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeObject", ConfigType.UInt)] NamePlateHpTypeObject, /// - /// System option with the internal name NamePlateColorMinion. + /// UiConfig option with the internal name NamePlateColorMinion. /// This option is a UInt. /// [GameConfigOption("NamePlateColorMinion", ConfigType.UInt)] NamePlateColorMinion, /// - /// System option with the internal name NamePlateEdgeMinion. + /// UiConfig option with the internal name NamePlateEdgeMinion. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeMinion", ConfigType.UInt)] NamePlateEdgeMinion, /// - /// System option with the internal name NamePlateDispTypeMinion. + /// UiConfig option with the internal name NamePlateDispTypeMinion. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeMinion", ConfigType.UInt)] NamePlateDispTypeMinion, /// - /// System option with the internal name NamePlateColorOtherBuddy. + /// UiConfig option with the internal name NamePlateColorOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOtherBuddy", ConfigType.UInt)] NamePlateColorOtherBuddy, /// - /// System option with the internal name NamePlateEdgeOtherBuddy. + /// UiConfig option with the internal name NamePlateEdgeOtherBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOtherBuddy", ConfigType.UInt)] NamePlateEdgeOtherBuddy, /// - /// System option with the internal name NamePlateColorOtherPet. + /// UiConfig option with the internal name NamePlateColorOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOtherPet", ConfigType.UInt)] NamePlateColorOtherPet, /// - /// System option with the internal name NamePlateEdgeOtherPet. + /// UiConfig option with the internal name NamePlateEdgeOtherPet. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOtherPet", ConfigType.UInt)] NamePlateEdgeOtherPet, /// - /// System option with the internal name NamePlateNameTitleTypeSelf. + /// UiConfig option with the internal name NamePlateNameTitleTypeSelf. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeSelf", ConfigType.UInt)] NamePlateNameTitleTypeSelf, /// - /// System option with the internal name NamePlateNameTitleTypeParty. + /// UiConfig option with the internal name NamePlateNameTitleTypeParty. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeParty", ConfigType.UInt)] NamePlateNameTitleTypeParty, /// - /// System option with the internal name NamePlateNameTitleTypeAlliance. + /// UiConfig option with the internal name NamePlateNameTitleTypeAlliance. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeAlliance", ConfigType.UInt)] NamePlateNameTitleTypeAlliance, /// - /// System option with the internal name NamePlateNameTitleTypeOther. + /// UiConfig option with the internal name NamePlateNameTitleTypeOther. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeOther", ConfigType.UInt)] NamePlateNameTitleTypeOther, /// - /// System option with the internal name NamePlateNameTitleTypeFriend. + /// UiConfig option with the internal name NamePlateNameTitleTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeFriend", ConfigType.UInt)] NamePlateNameTitleTypeFriend, /// - /// System option with the internal name NamePlateColorFriend. + /// UiConfig option with the internal name NamePlateColorFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateColorFriend", ConfigType.UInt)] NamePlateColorFriend, /// - /// System option with the internal name NamePlateColorFriendEdge. + /// UiConfig option with the internal name NamePlateColorFriendEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorFriendEdge", ConfigType.UInt)] NamePlateColorFriendEdge, /// - /// System option with the internal name NamePlateDispTypeFriend. + /// UiConfig option with the internal name NamePlateDispTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFriend", ConfigType.UInt)] NamePlateDispTypeFriend, /// - /// System option with the internal name NamePlateNameTypeFriend. + /// UiConfig option with the internal name NamePlateNameTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeFriend", ConfigType.UInt)] NamePlateNameTypeFriend, /// - /// System option with the internal name NamePlateHpTypeFriend. + /// UiConfig option with the internal name NamePlateHpTypeFriend. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFriend", ConfigType.UInt)] NamePlateHpTypeFriend, /// - /// System option with the internal name NamePlateDispTypeFriendPet. + /// UiConfig option with the internal name NamePlateDispTypeFriendPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFriendPet", ConfigType.UInt)] NamePlateDispTypeFriendPet, /// - /// System option with the internal name NamePlateHpTypeFriendPet. + /// UiConfig option with the internal name NamePlateHpTypeFriendPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFriendPet", ConfigType.UInt)] NamePlateHpTypeFriendPet, /// - /// System option with the internal name NamePlateDispTypeFriendBuddy. + /// UiConfig option with the internal name NamePlateDispTypeFriendBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFriendBuddy", ConfigType.UInt)] NamePlateDispTypeFriendBuddy, /// - /// System option with the internal name NamePlateHpTypeFriendBuddy. + /// UiConfig option with the internal name NamePlateHpTypeFriendBuddy. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFriendBuddy", ConfigType.UInt)] NamePlateHpTypeFriendBuddy, /// - /// System option with the internal name NamePlateColorLim. + /// UiConfig option with the internal name NamePlateColorLim. /// This option is a UInt. /// [GameConfigOption("NamePlateColorLim", ConfigType.UInt)] NamePlateColorLim, /// - /// System option with the internal name NamePlateColorLimEdge. + /// UiConfig option with the internal name NamePlateColorLimEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorLimEdge", ConfigType.UInt)] NamePlateColorLimEdge, /// - /// System option with the internal name NamePlateColorGri. + /// UiConfig option with the internal name NamePlateColorGri. /// This option is a UInt. /// [GameConfigOption("NamePlateColorGri", ConfigType.UInt)] NamePlateColorGri, /// - /// System option with the internal name NamePlateColorGriEdge. + /// UiConfig option with the internal name NamePlateColorGriEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorGriEdge", ConfigType.UInt)] NamePlateColorGriEdge, /// - /// System option with the internal name NamePlateColorUld. + /// UiConfig option with the internal name NamePlateColorUld. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUld", ConfigType.UInt)] NamePlateColorUld, /// - /// System option with the internal name NamePlateColorUldEdge. + /// UiConfig option with the internal name NamePlateColorUldEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorUldEdge", ConfigType.UInt)] NamePlateColorUldEdge, /// - /// System option with the internal name NamePlateColorHousingFurniture. + /// UiConfig option with the internal name NamePlateColorHousingFurniture. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingFurniture", ConfigType.UInt)] NamePlateColorHousingFurniture, /// - /// System option with the internal name NamePlateColorHousingFurnitureEdge. + /// UiConfig option with the internal name NamePlateColorHousingFurnitureEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingFurnitureEdge", ConfigType.UInt)] NamePlateColorHousingFurnitureEdge, /// - /// System option with the internal name NamePlateDispTypeHousingFurniture. + /// UiConfig option with the internal name NamePlateDispTypeHousingFurniture. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeHousingFurniture", ConfigType.UInt)] NamePlateDispTypeHousingFurniture, /// - /// System option with the internal name NamePlateColorHousingField. + /// UiConfig option with the internal name NamePlateColorHousingField. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingField", ConfigType.UInt)] NamePlateColorHousingField, /// - /// System option with the internal name NamePlateColorHousingFieldEdge. + /// UiConfig option with the internal name NamePlateColorHousingFieldEdge. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHousingFieldEdge", ConfigType.UInt)] NamePlateColorHousingFieldEdge, /// - /// System option with the internal name NamePlateDispTypeHousingField. + /// UiConfig option with the internal name NamePlateDispTypeHousingField. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeHousingField", ConfigType.UInt)] NamePlateDispTypeHousingField, /// - /// System option with the internal name NamePlateNameTypePvPEnemy. + /// UiConfig option with the internal name NamePlateNameTypePvPEnemy. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypePvPEnemy", ConfigType.UInt)] NamePlateNameTypePvPEnemy, /// - /// System option with the internal name NamePlateDispTypeFeast. + /// UiConfig option with the internal name NamePlateDispTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFeast", ConfigType.UInt)] NamePlateDispTypeFeast, /// - /// System option with the internal name NamePlateNameTypeFeast. + /// UiConfig option with the internal name NamePlateNameTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTypeFeast", ConfigType.UInt)] NamePlateNameTypeFeast, /// - /// System option with the internal name NamePlateHpTypeFeast. + /// UiConfig option with the internal name NamePlateHpTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFeast", ConfigType.UInt)] NamePlateHpTypeFeast, /// - /// System option with the internal name NamePlateDispTypeFeastPet. + /// UiConfig option with the internal name NamePlateDispTypeFeastPet. /// This option is a UInt. /// [GameConfigOption("NamePlateDispTypeFeastPet", ConfigType.UInt)] NamePlateDispTypeFeastPet, /// - /// System option with the internal name NamePlateHpTypeFeastPet. + /// UiConfig option with the internal name NamePlateHpTypeFeastPet. /// This option is a UInt. /// [GameConfigOption("NamePlateHpTypeFeastPet", ConfigType.UInt)] NamePlateHpTypeFeastPet, /// - /// System option with the internal name NamePlateNameTitleTypeFeast. + /// UiConfig option with the internal name NamePlateNameTitleTypeFeast. /// This option is a UInt. /// [GameConfigOption("NamePlateNameTitleTypeFeast", ConfigType.UInt)] NamePlateNameTitleTypeFeast, /// - /// System option with the internal name NamePlateDispSize. + /// UiConfig option with the internal name NamePlateDispSize. /// This option is a UInt. /// [GameConfigOption("NamePlateDispSize", ConfigType.UInt)] NamePlateDispSize, /// - /// System option with the internal name NamePlateDispJobIcon. + /// UiConfig option with the internal name NamePlateDispJobIcon. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIcon", ConfigType.UInt)] NamePlateDispJobIcon, /// - /// System option with the internal name NamePlateDispJobIconType. + /// UiConfig option with the internal name NamePlateDispJobIconType. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconType", ConfigType.UInt)] NamePlateDispJobIconType, /// - /// System option with the internal name NamePlateSetRoleColor. + /// UiConfig option with the internal name NamePlateSetRoleColor. /// This option is a UInt. /// [GameConfigOption("NamePlateSetRoleColor", ConfigType.UInt)] NamePlateSetRoleColor, /// - /// System option with the internal name NamePlateColorTank. + /// UiConfig option with the internal name NamePlateColorTank. /// This option is a UInt. /// [GameConfigOption("NamePlateColorTank", ConfigType.UInt)] NamePlateColorTank, /// - /// System option with the internal name NamePlateEdgeTank. + /// UiConfig option with the internal name NamePlateEdgeTank. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeTank", ConfigType.UInt)] NamePlateEdgeTank, /// - /// System option with the internal name NamePlateColorHealer. + /// UiConfig option with the internal name NamePlateColorHealer. /// This option is a UInt. /// [GameConfigOption("NamePlateColorHealer", ConfigType.UInt)] NamePlateColorHealer, /// - /// System option with the internal name NamePlateEdgeHealer. + /// UiConfig option with the internal name NamePlateEdgeHealer. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeHealer", ConfigType.UInt)] NamePlateEdgeHealer, /// - /// System option with the internal name NamePlateColorDps. + /// UiConfig option with the internal name NamePlateColorDps. /// This option is a UInt. /// [GameConfigOption("NamePlateColorDps", ConfigType.UInt)] NamePlateColorDps, /// - /// System option with the internal name NamePlateEdgeDps. + /// UiConfig option with the internal name NamePlateEdgeDps. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeDps", ConfigType.UInt)] NamePlateEdgeDps, /// - /// System option with the internal name NamePlateColorOtherClass. + /// UiConfig option with the internal name NamePlateColorOtherClass. /// This option is a UInt. /// [GameConfigOption("NamePlateColorOtherClass", ConfigType.UInt)] NamePlateColorOtherClass, /// - /// System option with the internal name NamePlateEdgeOtherClass. + /// UiConfig option with the internal name NamePlateEdgeOtherClass. /// This option is a UInt. /// [GameConfigOption("NamePlateEdgeOtherClass", ConfigType.UInt)] NamePlateEdgeOtherClass, /// - /// System option with the internal name NamePlateDispWorldTravel. + /// UiConfig option with the internal name NamePlateDispWorldTravel. /// This option is a UInt. /// [GameConfigOption("NamePlateDispWorldTravel", ConfigType.UInt)] NamePlateDispWorldTravel, /// - /// System option with the internal name NamePlateDispJobIconInPublicParty. + /// UiConfig option with the internal name NamePlateDispJobIconInPublicParty. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInPublicParty", ConfigType.UInt)] NamePlateDispJobIconInPublicParty, /// - /// System option with the internal name NamePlateDispJobIconInPublicOther. + /// UiConfig option with the internal name NamePlateDispJobIconInPublicOther. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInPublicOther", ConfigType.UInt)] NamePlateDispJobIconInPublicOther, /// - /// System option with the internal name NamePlateDispJobIconInInstanceParty. + /// UiConfig option with the internal name NamePlateDispJobIconInInstanceParty. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInInstanceParty", ConfigType.UInt)] NamePlateDispJobIconInInstanceParty, /// - /// System option with the internal name NamePlateDispJobIconInInstanceOther. + /// UiConfig option with the internal name NamePlateDispJobIconInInstanceOther. /// This option is a UInt. /// [GameConfigOption("NamePlateDispJobIconInInstanceOther", ConfigType.UInt)] NamePlateDispJobIconInInstanceOther, /// - /// System option with the internal name ActiveInfo. + /// UiConfig option with the internal name ActiveInfo. /// This option is a UInt. /// [GameConfigOption("ActiveInfo", ConfigType.UInt)] ActiveInfo, /// - /// System option with the internal name PartyList. + /// UiConfig option with the internal name PartyList. /// This option is a UInt. /// [GameConfigOption("PartyList", ConfigType.UInt)] PartyList, /// - /// System option with the internal name PartyListStatus. + /// UiConfig option with the internal name PartyListStatus. /// This option is a UInt. /// [GameConfigOption("PartyListStatus", ConfigType.UInt)] PartyListStatus, /// - /// System option with the internal name PartyListStatusTimer. + /// UiConfig option with the internal name PartyListStatusTimer. /// This option is a UInt. /// [GameConfigOption("PartyListStatusTimer", ConfigType.UInt)] PartyListStatusTimer, /// - /// System option with the internal name EnemyList. + /// UiConfig option with the internal name EnemyList. /// This option is a UInt. /// [GameConfigOption("EnemyList", ConfigType.UInt)] EnemyList, /// - /// System option with the internal name TargetInfo. + /// UiConfig option with the internal name TargetInfo. /// This option is a UInt. /// [GameConfigOption("TargetInfo", ConfigType.UInt)] TargetInfo, /// - /// System option with the internal name Gil. + /// UiConfig option with the internal name Gil. /// This option is a UInt. /// [GameConfigOption("Gil", ConfigType.UInt)] Gil, /// - /// System option with the internal name DTR. + /// UiConfig option with the internal name DTR. /// This option is a UInt. /// [GameConfigOption("DTR", ConfigType.UInt)] DTR, /// - /// System option with the internal name PlayerInfo. + /// UiConfig option with the internal name PlayerInfo. /// This option is a UInt. /// [GameConfigOption("PlayerInfo", ConfigType.UInt)] PlayerInfo, /// - /// System option with the internal name NaviMap. + /// UiConfig option with the internal name NaviMap. /// This option is a UInt. /// [GameConfigOption("NaviMap", ConfigType.UInt)] NaviMap, /// - /// System option with the internal name Help. + /// UiConfig option with the internal name Help. /// This option is a UInt. /// [GameConfigOption("Help", ConfigType.UInt)] Help, /// - /// System option with the internal name CrossMainHelp. + /// UiConfig option with the internal name CrossMainHelp. /// This option is a UInt. /// [GameConfigOption("CrossMainHelp", ConfigType.UInt)] CrossMainHelp, /// - /// System option with the internal name HousingLocatePreview. + /// UiConfig option with the internal name HousingLocatePreview. /// This option is a UInt. /// [GameConfigOption("HousingLocatePreview", ConfigType.UInt)] HousingLocatePreview, /// - /// System option with the internal name Log. + /// UiConfig option with the internal name Log. /// This option is a UInt. /// [GameConfigOption("Log", ConfigType.UInt)] Log, /// - /// System option with the internal name LogTell. + /// UiConfig option with the internal name LogTell. /// This option is a UInt. /// [GameConfigOption("LogTell", ConfigType.UInt)] LogTell, /// - /// System option with the internal name LogFontSize. + /// UiConfig option with the internal name LogFontSize. /// This option is a UInt. /// [GameConfigOption("LogFontSize", ConfigType.UInt)] LogFontSize, /// - /// System option with the internal name LogTabName2. + /// UiConfig option with the internal name LogTabName2. /// This option is a String. /// [GameConfigOption("LogTabName2", ConfigType.String)] LogTabName2, /// - /// System option with the internal name LogTabName3. + /// UiConfig option with the internal name LogTabName3. /// This option is a String. /// [GameConfigOption("LogTabName3", ConfigType.String)] LogTabName3, /// - /// System option with the internal name LogTabFilter0. + /// UiConfig option with the internal name LogTabFilter0. /// This option is a UInt. /// [GameConfigOption("LogTabFilter0", ConfigType.UInt)] LogTabFilter0, /// - /// System option with the internal name LogTabFilter1. + /// UiConfig option with the internal name LogTabFilter1. /// This option is a UInt. /// [GameConfigOption("LogTabFilter1", ConfigType.UInt)] LogTabFilter1, /// - /// System option with the internal name LogTabFilter2. + /// UiConfig option with the internal name LogTabFilter2. /// This option is a UInt. /// [GameConfigOption("LogTabFilter2", ConfigType.UInt)] LogTabFilter2, /// - /// System option with the internal name LogTabFilter3. + /// UiConfig option with the internal name LogTabFilter3. /// This option is a UInt. /// [GameConfigOption("LogTabFilter3", ConfigType.UInt)] LogTabFilter3, /// - /// System option with the internal name LogChatFilter. + /// UiConfig option with the internal name LogChatFilter. /// This option is a UInt. /// [GameConfigOption("LogChatFilter", ConfigType.UInt)] LogChatFilter, /// - /// System option with the internal name LogEnableErrMsgLv1. + /// UiConfig option with the internal name LogEnableErrMsgLv1. /// This option is a UInt. /// [GameConfigOption("LogEnableErrMsgLv1", ConfigType.UInt)] LogEnableErrMsgLv1, /// - /// System option with the internal name LogNameType. + /// UiConfig option with the internal name LogNameType. /// This option is a UInt. /// [GameConfigOption("LogNameType", ConfigType.UInt)] LogNameType, /// - /// System option with the internal name LogTimeDisp. + /// UiConfig option with the internal name LogTimeDisp. /// This option is a UInt. /// [GameConfigOption("LogTimeDisp", ConfigType.UInt)] LogTimeDisp, /// - /// System option with the internal name LogTimeSettingType. + /// UiConfig option with the internal name LogTimeSettingType. /// This option is a UInt. /// [GameConfigOption("LogTimeSettingType", ConfigType.UInt)] LogTimeSettingType, /// - /// System option with the internal name LogTimeDispType. + /// UiConfig option with the internal name LogTimeDispType. /// This option is a UInt. /// [GameConfigOption("LogTimeDispType", ConfigType.UInt)] LogTimeDispType, /// - /// System option with the internal name IsLogTell. + /// UiConfig option with the internal name IsLogTell. /// This option is a UInt. /// [GameConfigOption("IsLogTell", ConfigType.UInt)] IsLogTell, /// - /// System option with the internal name IsLogParty. + /// UiConfig option with the internal name IsLogParty. /// This option is a UInt. /// [GameConfigOption("IsLogParty", ConfigType.UInt)] IsLogParty, /// - /// System option with the internal name LogParty. + /// UiConfig option with the internal name LogParty. /// This option is a UInt. /// [GameConfigOption("LogParty", ConfigType.UInt)] LogParty, /// - /// System option with the internal name IsLogAlliance. + /// UiConfig option with the internal name IsLogAlliance. /// This option is a UInt. /// [GameConfigOption("IsLogAlliance", ConfigType.UInt)] IsLogAlliance, /// - /// System option with the internal name LogAlliance. + /// UiConfig option with the internal name LogAlliance. /// This option is a UInt. /// [GameConfigOption("LogAlliance", ConfigType.UInt)] LogAlliance, /// - /// System option with the internal name IsLogFc. + /// UiConfig option with the internal name IsLogFc. /// This option is a UInt. /// [GameConfigOption("IsLogFc", ConfigType.UInt)] IsLogFc, /// - /// System option with the internal name LogFc. + /// UiConfig option with the internal name LogFc. /// This option is a UInt. /// [GameConfigOption("LogFc", ConfigType.UInt)] LogFc, /// - /// System option with the internal name IsLogPvpTeam. + /// UiConfig option with the internal name IsLogPvpTeam. /// This option is a UInt. /// [GameConfigOption("IsLogPvpTeam", ConfigType.UInt)] IsLogPvpTeam, /// - /// System option with the internal name LogPvpTeam. + /// UiConfig option with the internal name LogPvpTeam. /// This option is a UInt. /// [GameConfigOption("LogPvpTeam", ConfigType.UInt)] LogPvpTeam, /// - /// System option with the internal name IsLogLs1. + /// UiConfig option with the internal name IsLogLs1. /// This option is a UInt. /// [GameConfigOption("IsLogLs1", ConfigType.UInt)] IsLogLs1, /// - /// System option with the internal name LogLs1. + /// UiConfig option with the internal name LogLs1. /// This option is a UInt. /// [GameConfigOption("LogLs1", ConfigType.UInt)] LogLs1, /// - /// System option with the internal name IsLogLs2. + /// UiConfig option with the internal name IsLogLs2. /// This option is a UInt. /// [GameConfigOption("IsLogLs2", ConfigType.UInt)] IsLogLs2, /// - /// System option with the internal name LogLs2. + /// UiConfig option with the internal name LogLs2. /// This option is a UInt. /// [GameConfigOption("LogLs2", ConfigType.UInt)] LogLs2, /// - /// System option with the internal name IsLogLs3. + /// UiConfig option with the internal name IsLogLs3. /// This option is a UInt. /// [GameConfigOption("IsLogLs3", ConfigType.UInt)] IsLogLs3, /// - /// System option with the internal name LogLs3. + /// UiConfig option with the internal name LogLs3. /// This option is a UInt. /// [GameConfigOption("LogLs3", ConfigType.UInt)] LogLs3, /// - /// System option with the internal name IsLogLs4. + /// UiConfig option with the internal name IsLogLs4. /// This option is a UInt. /// [GameConfigOption("IsLogLs4", ConfigType.UInt)] IsLogLs4, /// - /// System option with the internal name LogLs4. + /// UiConfig option with the internal name LogLs4. /// This option is a UInt. /// [GameConfigOption("LogLs4", ConfigType.UInt)] LogLs4, /// - /// System option with the internal name IsLogLs5. + /// UiConfig option with the internal name IsLogLs5. /// This option is a UInt. /// [GameConfigOption("IsLogLs5", ConfigType.UInt)] IsLogLs5, /// - /// System option with the internal name LogLs5. + /// UiConfig option with the internal name LogLs5. /// This option is a UInt. /// [GameConfigOption("LogLs5", ConfigType.UInt)] LogLs5, /// - /// System option with the internal name IsLogLs6. + /// UiConfig option with the internal name IsLogLs6. /// This option is a UInt. /// [GameConfigOption("IsLogLs6", ConfigType.UInt)] IsLogLs6, /// - /// System option with the internal name LogLs6. + /// UiConfig option with the internal name LogLs6. /// This option is a UInt. /// [GameConfigOption("LogLs6", ConfigType.UInt)] LogLs6, /// - /// System option with the internal name IsLogLs7. + /// UiConfig option with the internal name IsLogLs7. /// This option is a UInt. /// [GameConfigOption("IsLogLs7", ConfigType.UInt)] IsLogLs7, /// - /// System option with the internal name LogLs7. + /// UiConfig option with the internal name LogLs7. /// This option is a UInt. /// [GameConfigOption("LogLs7", ConfigType.UInt)] LogLs7, /// - /// System option with the internal name IsLogLs8. + /// UiConfig option with the internal name IsLogLs8. /// This option is a UInt. /// [GameConfigOption("IsLogLs8", ConfigType.UInt)] IsLogLs8, /// - /// System option with the internal name LogLs8. + /// UiConfig option with the internal name LogLs8. /// This option is a UInt. /// [GameConfigOption("LogLs8", ConfigType.UInt)] LogLs8, /// - /// System option with the internal name IsLogBeginner. + /// UiConfig option with the internal name IsLogBeginner. /// This option is a UInt. /// [GameConfigOption("IsLogBeginner", ConfigType.UInt)] IsLogBeginner, /// - /// System option with the internal name LogBeginner. + /// UiConfig option with the internal name LogBeginner. /// This option is a UInt. /// [GameConfigOption("LogBeginner", ConfigType.UInt)] LogBeginner, /// - /// System option with the internal name IsLogCwls. + /// UiConfig option with the internal name IsLogCwls. /// This option is a UInt. /// [GameConfigOption("IsLogCwls", ConfigType.UInt)] IsLogCwls, /// - /// System option with the internal name IsLogCwls2. + /// UiConfig option with the internal name IsLogCwls2. /// This option is a UInt. /// [GameConfigOption("IsLogCwls2", ConfigType.UInt)] IsLogCwls2, /// - /// System option with the internal name IsLogCwls3. + /// UiConfig option with the internal name IsLogCwls3. /// This option is a UInt. /// [GameConfigOption("IsLogCwls3", ConfigType.UInt)] IsLogCwls3, /// - /// System option with the internal name IsLogCwls4. + /// UiConfig option with the internal name IsLogCwls4. /// This option is a UInt. /// [GameConfigOption("IsLogCwls4", ConfigType.UInt)] IsLogCwls4, /// - /// System option with the internal name IsLogCwls5. + /// UiConfig option with the internal name IsLogCwls5. /// This option is a UInt. /// [GameConfigOption("IsLogCwls5", ConfigType.UInt)] IsLogCwls5, /// - /// System option with the internal name IsLogCwls6. + /// UiConfig option with the internal name IsLogCwls6. /// This option is a UInt. /// [GameConfigOption("IsLogCwls6", ConfigType.UInt)] IsLogCwls6, /// - /// System option with the internal name IsLogCwls7. + /// UiConfig option with the internal name IsLogCwls7. /// This option is a UInt. /// [GameConfigOption("IsLogCwls7", ConfigType.UInt)] IsLogCwls7, /// - /// System option with the internal name IsLogCwls8. + /// UiConfig option with the internal name IsLogCwls8. /// This option is a UInt. /// [GameConfigOption("IsLogCwls8", ConfigType.UInt)] IsLogCwls8, /// - /// System option with the internal name LogCwls. + /// UiConfig option with the internal name LogCwls. /// This option is a UInt. /// [GameConfigOption("LogCwls", ConfigType.UInt)] LogCwls, /// - /// System option with the internal name LogCwls2. + /// UiConfig option with the internal name LogCwls2. /// This option is a UInt. /// [GameConfigOption("LogCwls2", ConfigType.UInt)] LogCwls2, /// - /// System option with the internal name LogCwls3. + /// UiConfig option with the internal name LogCwls3. /// This option is a UInt. /// [GameConfigOption("LogCwls3", ConfigType.UInt)] LogCwls3, /// - /// System option with the internal name LogCwls4. + /// UiConfig option with the internal name LogCwls4. /// This option is a UInt. /// [GameConfigOption("LogCwls4", ConfigType.UInt)] LogCwls4, /// - /// System option with the internal name LogCwls5. + /// UiConfig option with the internal name LogCwls5. /// This option is a UInt. /// [GameConfigOption("LogCwls5", ConfigType.UInt)] LogCwls5, /// - /// System option with the internal name LogCwls6. + /// UiConfig option with the internal name LogCwls6. /// This option is a UInt. /// [GameConfigOption("LogCwls6", ConfigType.UInt)] LogCwls6, /// - /// System option with the internal name LogCwls7. + /// UiConfig option with the internal name LogCwls7. /// This option is a UInt. /// [GameConfigOption("LogCwls7", ConfigType.UInt)] LogCwls7, /// - /// System option with the internal name LogCwls8. + /// UiConfig option with the internal name LogCwls8. /// This option is a UInt. /// [GameConfigOption("LogCwls8", ConfigType.UInt)] LogCwls8, /// - /// System option with the internal name LogRecastActionErrDisp. + /// UiConfig option with the internal name LogRecastActionErrDisp. /// This option is a UInt. /// [GameConfigOption("LogRecastActionErrDisp", ConfigType.UInt)] LogRecastActionErrDisp, /// - /// System option with the internal name LogPermeationRate. + /// UiConfig option with the internal name LogPermeationRate. /// This option is a UInt. /// [GameConfigOption("LogPermeationRate", ConfigType.UInt)] LogPermeationRate, /// - /// System option with the internal name LogFontSizeForm. + /// UiConfig option with the internal name LogFontSizeForm. /// This option is a UInt. /// [GameConfigOption("LogFontSizeForm", ConfigType.UInt)] LogFontSizeForm, /// - /// System option with the internal name LogItemLinkEnableType. + /// UiConfig option with the internal name LogItemLinkEnableType. /// This option is a UInt. /// [GameConfigOption("LogItemLinkEnableType", ConfigType.UInt)] LogItemLinkEnableType, /// - /// System option with the internal name LogFontSizeLog2. + /// UiConfig option with the internal name LogFontSizeLog2. /// This option is a UInt. /// [GameConfigOption("LogFontSizeLog2", ConfigType.UInt)] LogFontSizeLog2, /// - /// System option with the internal name LogTimeDispLog2. + /// UiConfig option with the internal name LogTimeDispLog2. /// This option is a UInt. /// [GameConfigOption("LogTimeDispLog2", ConfigType.UInt)] LogTimeDispLog2, /// - /// System option with the internal name LogPermeationRateLog2. + /// UiConfig option with the internal name LogPermeationRateLog2. /// This option is a UInt. /// [GameConfigOption("LogPermeationRateLog2", ConfigType.UInt)] LogPermeationRateLog2, /// - /// System option with the internal name LogFontSizeLog3. + /// UiConfig option with the internal name LogFontSizeLog3. /// This option is a UInt. /// [GameConfigOption("LogFontSizeLog3", ConfigType.UInt)] LogFontSizeLog3, /// - /// System option with the internal name LogTimeDispLog3. + /// UiConfig option with the internal name LogTimeDispLog3. /// This option is a UInt. /// [GameConfigOption("LogTimeDispLog3", ConfigType.UInt)] LogTimeDispLog3, /// - /// System option with the internal name LogPermeationRateLog3. + /// UiConfig option with the internal name LogPermeationRateLog3. /// This option is a UInt. /// [GameConfigOption("LogPermeationRateLog3", ConfigType.UInt)] LogPermeationRateLog3, /// - /// System option with the internal name LogFontSizeLog4. + /// UiConfig option with the internal name LogFontSizeLog4. /// This option is a UInt. /// [GameConfigOption("LogFontSizeLog4", ConfigType.UInt)] LogFontSizeLog4, /// - /// System option with the internal name LogTimeDispLog4. + /// UiConfig option with the internal name LogTimeDispLog4. /// This option is a UInt. /// [GameConfigOption("LogTimeDispLog4", ConfigType.UInt)] LogTimeDispLog4, /// - /// System option with the internal name LogPermeationRateLog4. + /// UiConfig option with the internal name LogPermeationRateLog4. /// This option is a UInt. /// [GameConfigOption("LogPermeationRateLog4", ConfigType.UInt)] LogPermeationRateLog4, /// - /// System option with the internal name LogFlyingHeightMaxErrDisp. + /// UiConfig option with the internal name LogFlyingHeightMaxErrDisp. /// This option is a UInt. /// [GameConfigOption("LogFlyingHeightMaxErrDisp", ConfigType.UInt)] LogFlyingHeightMaxErrDisp, /// - /// System option with the internal name LogCrossWorldName. + /// UiConfig option with the internal name LogCrossWorldName. /// This option is a UInt. /// [GameConfigOption("LogCrossWorldName", ConfigType.UInt)] LogCrossWorldName, /// - /// System option with the internal name LogDragResize. + /// UiConfig option with the internal name LogDragResize. /// This option is a UInt. /// [GameConfigOption("LogDragResize", ConfigType.UInt)] LogDragResize, /// - /// System option with the internal name LogNameIconType. + /// UiConfig option with the internal name LogNameIconType. /// This option is a UInt. /// [GameConfigOption("LogNameIconType", ConfigType.UInt)] LogNameIconType, /// - /// System option with the internal name LogDispClassJobName. + /// UiConfig option with the internal name LogDispClassJobName. /// This option is a UInt. /// [GameConfigOption("LogDispClassJobName", ConfigType.UInt)] LogDispClassJobName, /// - /// System option with the internal name LogSetRoleColor. + /// UiConfig option with the internal name LogSetRoleColor. /// This option is a UInt. /// [GameConfigOption("LogSetRoleColor", ConfigType.UInt)] LogSetRoleColor, /// - /// System option with the internal name LogColorRoleTank. + /// UiConfig option with the internal name LogColorRoleTank. /// This option is a UInt. /// [GameConfigOption("LogColorRoleTank", ConfigType.UInt)] LogColorRoleTank, /// - /// System option with the internal name LogColorRoleHealer. + /// UiConfig option with the internal name LogColorRoleHealer. /// This option is a UInt. /// [GameConfigOption("LogColorRoleHealer", ConfigType.UInt)] LogColorRoleHealer, /// - /// System option with the internal name LogColorRoleDPS. + /// UiConfig option with the internal name LogColorRoleDPS. /// This option is a UInt. /// [GameConfigOption("LogColorRoleDPS", ConfigType.UInt)] LogColorRoleDPS, /// - /// System option with the internal name LogColorOtherClass. + /// UiConfig option with the internal name LogColorOtherClass. /// This option is a UInt. /// [GameConfigOption("LogColorOtherClass", ConfigType.UInt)] LogColorOtherClass, /// - /// System option with the internal name ChatType. + /// UiConfig option with the internal name ChatType. /// This option is a UInt. /// [GameConfigOption("ChatType", ConfigType.UInt)] ChatType, /// - /// System option with the internal name ShopSell. + /// UiConfig option with the internal name ShopSell. /// This option is a UInt. /// [GameConfigOption("ShopSell", ConfigType.UInt)] ShopSell, /// - /// System option with the internal name ColorSay. + /// UiConfig option with the internal name ColorSay. /// This option is a UInt. /// [GameConfigOption("ColorSay", ConfigType.UInt)] ColorSay, /// - /// System option with the internal name ColorShout. + /// UiConfig option with the internal name ColorShout. /// This option is a UInt. /// [GameConfigOption("ColorShout", ConfigType.UInt)] ColorShout, /// - /// System option with the internal name ColorTell. + /// UiConfig option with the internal name ColorTell. /// This option is a UInt. /// [GameConfigOption("ColorTell", ConfigType.UInt)] ColorTell, /// - /// System option with the internal name ColorParty. + /// UiConfig option with the internal name ColorParty. /// This option is a UInt. /// [GameConfigOption("ColorParty", ConfigType.UInt)] ColorParty, /// - /// System option with the internal name ColorAlliance. + /// UiConfig option with the internal name ColorAlliance. /// This option is a UInt. /// [GameConfigOption("ColorAlliance", ConfigType.UInt)] ColorAlliance, /// - /// System option with the internal name ColorLS1. + /// UiConfig option with the internal name ColorLS1. /// This option is a UInt. /// [GameConfigOption("ColorLS1", ConfigType.UInt)] ColorLS1, /// - /// System option with the internal name ColorLS2. + /// UiConfig option with the internal name ColorLS2. /// This option is a UInt. /// [GameConfigOption("ColorLS2", ConfigType.UInt)] ColorLS2, /// - /// System option with the internal name ColorLS3. + /// UiConfig option with the internal name ColorLS3. /// This option is a UInt. /// [GameConfigOption("ColorLS3", ConfigType.UInt)] ColorLS3, /// - /// System option with the internal name ColorLS4. + /// UiConfig option with the internal name ColorLS4. /// This option is a UInt. /// [GameConfigOption("ColorLS4", ConfigType.UInt)] ColorLS4, /// - /// System option with the internal name ColorLS5. + /// UiConfig option with the internal name ColorLS5. /// This option is a UInt. /// [GameConfigOption("ColorLS5", ConfigType.UInt)] ColorLS5, /// - /// System option with the internal name ColorLS6. + /// UiConfig option with the internal name ColorLS6. /// This option is a UInt. /// [GameConfigOption("ColorLS6", ConfigType.UInt)] ColorLS6, /// - /// System option with the internal name ColorLS7. + /// UiConfig option with the internal name ColorLS7. /// This option is a UInt. /// [GameConfigOption("ColorLS7", ConfigType.UInt)] ColorLS7, /// - /// System option with the internal name ColorLS8. + /// UiConfig option with the internal name ColorLS8. /// This option is a UInt. /// [GameConfigOption("ColorLS8", ConfigType.UInt)] ColorLS8, /// - /// System option with the internal name ColorFCompany. + /// UiConfig option with the internal name ColorFCompany. /// This option is a UInt. /// [GameConfigOption("ColorFCompany", ConfigType.UInt)] ColorFCompany, /// - /// System option with the internal name ColorPvPGroup. + /// UiConfig option with the internal name ColorPvPGroup. /// This option is a UInt. /// [GameConfigOption("ColorPvPGroup", ConfigType.UInt)] ColorPvPGroup, /// - /// System option with the internal name ColorPvPGroupAnnounce. + /// UiConfig option with the internal name ColorPvPGroupAnnounce. /// This option is a UInt. /// [GameConfigOption("ColorPvPGroupAnnounce", ConfigType.UInt)] ColorPvPGroupAnnounce, /// - /// System option with the internal name ColorBeginner. + /// UiConfig option with the internal name ColorBeginner. /// This option is a UInt. /// [GameConfigOption("ColorBeginner", ConfigType.UInt)] ColorBeginner, /// - /// System option with the internal name ColorEmoteUser. + /// UiConfig option with the internal name ColorEmoteUser. /// This option is a UInt. /// [GameConfigOption("ColorEmoteUser", ConfigType.UInt)] ColorEmoteUser, /// - /// System option with the internal name ColorEmote. + /// UiConfig option with the internal name ColorEmote. /// This option is a UInt. /// [GameConfigOption("ColorEmote", ConfigType.UInt)] ColorEmote, /// - /// System option with the internal name ColorYell. + /// UiConfig option with the internal name ColorYell. /// This option is a UInt. /// [GameConfigOption("ColorYell", ConfigType.UInt)] ColorYell, /// - /// System option with the internal name ColorBeginnerAnnounce. + /// UiConfig option with the internal name ColorBeginnerAnnounce. /// This option is a UInt. /// [GameConfigOption("ColorBeginnerAnnounce", ConfigType.UInt)] ColorBeginnerAnnounce, /// - /// System option with the internal name ColorCWLS. + /// UiConfig option with the internal name ColorCWLS. /// This option is a UInt. /// [GameConfigOption("ColorCWLS", ConfigType.UInt)] ColorCWLS, /// - /// System option with the internal name ColorCWLS2. + /// UiConfig option with the internal name ColorCWLS2. /// This option is a UInt. /// [GameConfigOption("ColorCWLS2", ConfigType.UInt)] ColorCWLS2, /// - /// System option with the internal name ColorCWLS3. + /// UiConfig option with the internal name ColorCWLS3. /// This option is a UInt. /// [GameConfigOption("ColorCWLS3", ConfigType.UInt)] ColorCWLS3, /// - /// System option with the internal name ColorCWLS4. + /// UiConfig option with the internal name ColorCWLS4. /// This option is a UInt. /// [GameConfigOption("ColorCWLS4", ConfigType.UInt)] ColorCWLS4, /// - /// System option with the internal name ColorCWLS5. + /// UiConfig option with the internal name ColorCWLS5. /// This option is a UInt. /// [GameConfigOption("ColorCWLS5", ConfigType.UInt)] ColorCWLS5, /// - /// System option with the internal name ColorCWLS6. + /// UiConfig option with the internal name ColorCWLS6. /// This option is a UInt. /// [GameConfigOption("ColorCWLS6", ConfigType.UInt)] ColorCWLS6, /// - /// System option with the internal name ColorCWLS7. + /// UiConfig option with the internal name ColorCWLS7. /// This option is a UInt. /// [GameConfigOption("ColorCWLS7", ConfigType.UInt)] ColorCWLS7, /// - /// System option with the internal name ColorCWLS8. + /// UiConfig option with the internal name ColorCWLS8. /// This option is a UInt. /// [GameConfigOption("ColorCWLS8", ConfigType.UInt)] ColorCWLS8, /// - /// System option with the internal name ColorAttackSuccess. + /// UiConfig option with the internal name ColorAttackSuccess. /// This option is a UInt. /// [GameConfigOption("ColorAttackSuccess", ConfigType.UInt)] ColorAttackSuccess, /// - /// System option with the internal name ColorAttackFailure. + /// UiConfig option with the internal name ColorAttackFailure. /// This option is a UInt. /// [GameConfigOption("ColorAttackFailure", ConfigType.UInt)] ColorAttackFailure, /// - /// System option with the internal name ColorAction. + /// UiConfig option with the internal name ColorAction. /// This option is a UInt. /// [GameConfigOption("ColorAction", ConfigType.UInt)] ColorAction, /// - /// System option with the internal name ColorItem. + /// UiConfig option with the internal name ColorItem. /// This option is a UInt. /// [GameConfigOption("ColorItem", ConfigType.UInt)] ColorItem, /// - /// System option with the internal name ColorCureGive. + /// UiConfig option with the internal name ColorCureGive. /// This option is a UInt. /// [GameConfigOption("ColorCureGive", ConfigType.UInt)] ColorCureGive, /// - /// System option with the internal name ColorBuffGive. + /// UiConfig option with the internal name ColorBuffGive. /// This option is a UInt. /// [GameConfigOption("ColorBuffGive", ConfigType.UInt)] ColorBuffGive, /// - /// System option with the internal name ColorDebuffGive. + /// UiConfig option with the internal name ColorDebuffGive. /// This option is a UInt. /// [GameConfigOption("ColorDebuffGive", ConfigType.UInt)] ColorDebuffGive, /// - /// System option with the internal name ColorEcho. + /// UiConfig option with the internal name ColorEcho. /// This option is a UInt. /// [GameConfigOption("ColorEcho", ConfigType.UInt)] ColorEcho, /// - /// System option with the internal name ColorSysMsg. + /// UiConfig option with the internal name ColorSysMsg. /// This option is a UInt. /// [GameConfigOption("ColorSysMsg", ConfigType.UInt)] ColorSysMsg, /// - /// System option with the internal name ColorFCAnnounce. + /// UiConfig option with the internal name ColorFCAnnounce. /// This option is a UInt. /// [GameConfigOption("ColorFCAnnounce", ConfigType.UInt)] ColorFCAnnounce, /// - /// System option with the internal name ColorSysBattle. + /// UiConfig option with the internal name ColorSysBattle. /// This option is a UInt. /// [GameConfigOption("ColorSysBattle", ConfigType.UInt)] ColorSysBattle, /// - /// System option with the internal name ColorSysGathering. + /// UiConfig option with the internal name ColorSysGathering. /// This option is a UInt. /// [GameConfigOption("ColorSysGathering", ConfigType.UInt)] ColorSysGathering, /// - /// System option with the internal name ColorSysErr. + /// UiConfig option with the internal name ColorSysErr. /// This option is a UInt. /// [GameConfigOption("ColorSysErr", ConfigType.UInt)] ColorSysErr, /// - /// System option with the internal name ColorNpcSay. + /// UiConfig option with the internal name ColorNpcSay. /// This option is a UInt. /// [GameConfigOption("ColorNpcSay", ConfigType.UInt)] ColorNpcSay, /// - /// System option with the internal name ColorItemNotice. + /// UiConfig option with the internal name ColorItemNotice. /// This option is a UInt. /// [GameConfigOption("ColorItemNotice", ConfigType.UInt)] ColorItemNotice, /// - /// System option with the internal name ColorGrowup. + /// UiConfig option with the internal name ColorGrowup. /// This option is a UInt. /// [GameConfigOption("ColorGrowup", ConfigType.UInt)] ColorGrowup, /// - /// System option with the internal name ColorLoot. + /// UiConfig option with the internal name ColorLoot. /// This option is a UInt. /// [GameConfigOption("ColorLoot", ConfigType.UInt)] ColorLoot, /// - /// System option with the internal name ColorCraft. + /// UiConfig option with the internal name ColorCraft. /// This option is a UInt. /// [GameConfigOption("ColorCraft", ConfigType.UInt)] ColorCraft, /// - /// System option with the internal name ColorGathering. + /// UiConfig option with the internal name ColorGathering. /// This option is a UInt. /// [GameConfigOption("ColorGathering", ConfigType.UInt)] ColorGathering, /// - /// System option with the internal name ShopConfirm. + /// UiConfig option with the internal name ShopConfirm. /// This option is a UInt. /// [GameConfigOption("ShopConfirm", ConfigType.UInt)] ShopConfirm, /// - /// System option with the internal name ShopConfirmMateria. + /// UiConfig option with the internal name ShopConfirmMateria. /// This option is a UInt. /// [GameConfigOption("ShopConfirmMateria", ConfigType.UInt)] ShopConfirmMateria, /// - /// System option with the internal name ShopConfirmExRare. + /// UiConfig option with the internal name ShopConfirmExRare. /// This option is a UInt. /// [GameConfigOption("ShopConfirmExRare", ConfigType.UInt)] ShopConfirmExRare, /// - /// System option with the internal name ShopConfirmSpiritBondMax. + /// UiConfig option with the internal name ShopConfirmSpiritBondMax. /// This option is a UInt. /// [GameConfigOption("ShopConfirmSpiritBondMax", ConfigType.UInt)] ShopConfirmSpiritBondMax, /// - /// System option with the internal name ItemSortItemCategory. + /// UiConfig option with the internal name ItemSortItemCategory. /// This option is a UInt. /// [GameConfigOption("ItemSortItemCategory", ConfigType.UInt)] ItemSortItemCategory, /// - /// System option with the internal name ItemSortEquipLevel. + /// UiConfig option with the internal name ItemSortEquipLevel. /// This option is a UInt. /// [GameConfigOption("ItemSortEquipLevel", ConfigType.UInt)] ItemSortEquipLevel, /// - /// System option with the internal name ItemSortItemLevel. + /// UiConfig option with the internal name ItemSortItemLevel. /// This option is a UInt. /// [GameConfigOption("ItemSortItemLevel", ConfigType.UInt)] ItemSortItemLevel, /// - /// System option with the internal name ItemSortItemStack. + /// UiConfig option with the internal name ItemSortItemStack. /// This option is a UInt. /// [GameConfigOption("ItemSortItemStack", ConfigType.UInt)] ItemSortItemStack, /// - /// System option with the internal name ItemSortTidyingType. + /// UiConfig option with the internal name ItemSortTidyingType. /// This option is a UInt. /// [GameConfigOption("ItemSortTidyingType", ConfigType.UInt)] ItemSortTidyingType, /// - /// System option with the internal name ItemNoArmoryMaskOff. + /// UiConfig option with the internal name ItemNoArmoryMaskOff. /// This option is a UInt. /// [GameConfigOption("ItemNoArmoryMaskOff", ConfigType.UInt)] ItemNoArmoryMaskOff, /// - /// System option with the internal name ItemInventryStoreEnd. + /// UiConfig option with the internal name ItemInventryStoreEnd. /// This option is a UInt. /// [GameConfigOption("ItemInventryStoreEnd", ConfigType.UInt)] ItemInventryStoreEnd, /// - /// System option with the internal name InfoSettingDispWorldNameType. + /// UiConfig option with the internal name InfoSettingDispWorldNameType. /// This option is a UInt. /// [GameConfigOption("InfoSettingDispWorldNameType", ConfigType.UInt)] InfoSettingDispWorldNameType, /// - /// System option with the internal name TargetNamePlateNameType. + /// UiConfig option with the internal name TargetNamePlateNameType. /// This option is a UInt. /// [GameConfigOption("TargetNamePlateNameType", ConfigType.UInt)] TargetNamePlateNameType, /// - /// System option with the internal name FocusTargetNamePlateNameType. + /// UiConfig option with the internal name FocusTargetNamePlateNameType. /// This option is a UInt. /// [GameConfigOption("FocusTargetNamePlateNameType", ConfigType.UInt)] FocusTargetNamePlateNameType, /// - /// System option with the internal name ItemDetailTemporarilySwitch. + /// UiConfig option with the internal name ItemDetailTemporarilySwitch. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilySwitch", ConfigType.UInt)] ItemDetailTemporarilySwitch, /// - /// System option with the internal name ItemDetailTemporarilySwitchKey. + /// UiConfig option with the internal name ItemDetailTemporarilySwitchKey. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilySwitchKey", ConfigType.UInt)] ItemDetailTemporarilySwitchKey, /// - /// System option with the internal name ItemDetailTemporarilyHide. + /// UiConfig option with the internal name ItemDetailTemporarilyHide. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilyHide", ConfigType.UInt)] ItemDetailTemporarilyHide, /// - /// System option with the internal name ItemDetailTemporarilyHideKey. + /// UiConfig option with the internal name ItemDetailTemporarilyHideKey. /// This option is a UInt. /// [GameConfigOption("ItemDetailTemporarilyHideKey", ConfigType.UInt)] ItemDetailTemporarilyHideKey, /// - /// System option with the internal name ToolTipDispSize. + /// UiConfig option with the internal name ToolTipDispSize. /// This option is a UInt. /// [GameConfigOption("ToolTipDispSize", ConfigType.UInt)] ToolTipDispSize, /// - /// System option with the internal name RecommendLoginDisp. + /// UiConfig option with the internal name RecommendLoginDisp. /// This option is a UInt. /// [GameConfigOption("RecommendLoginDisp", ConfigType.UInt)] RecommendLoginDisp, /// - /// System option with the internal name RecommendAreaChangeDisp. + /// UiConfig option with the internal name RecommendAreaChangeDisp. /// This option is a UInt. /// [GameConfigOption("RecommendAreaChangeDisp", ConfigType.UInt)] RecommendAreaChangeDisp, /// - /// System option with the internal name PlayGuideLoginDisp. + /// UiConfig option with the internal name PlayGuideLoginDisp. /// This option is a UInt. /// [GameConfigOption("PlayGuideLoginDisp", ConfigType.UInt)] PlayGuideLoginDisp, /// - /// System option with the internal name PlayGuideAreaChangeDisp. + /// UiConfig option with the internal name PlayGuideAreaChangeDisp. /// This option is a UInt. /// [GameConfigOption("PlayGuideAreaChangeDisp", ConfigType.UInt)] PlayGuideAreaChangeDisp, /// - /// System option with the internal name MapPadOperationYReverse. + /// UiConfig option with the internal name MapPadOperationYReverse. /// This option is a UInt. /// [GameConfigOption("MapPadOperationYReverse", ConfigType.UInt)] MapPadOperationYReverse, /// - /// System option with the internal name MapPadOperationXReverse. + /// UiConfig option with the internal name MapPadOperationXReverse. /// This option is a UInt. /// [GameConfigOption("MapPadOperationXReverse", ConfigType.UInt)] MapPadOperationXReverse, /// - /// System option with the internal name MapDispSize. + /// UiConfig option with the internal name MapDispSize. /// This option is a UInt. /// [GameConfigOption("MapDispSize", ConfigType.UInt)] MapDispSize, /// - /// System option with the internal name FlyTextDispSize. + /// UiConfig option with the internal name FlyTextDispSize. /// This option is a UInt. /// [GameConfigOption("FlyTextDispSize", ConfigType.UInt)] FlyTextDispSize, /// - /// System option with the internal name PopUpTextDispSize. + /// UiConfig option with the internal name PopUpTextDispSize. /// This option is a UInt. /// [GameConfigOption("PopUpTextDispSize", ConfigType.UInt)] PopUpTextDispSize, /// - /// System option with the internal name DetailDispDelayType. + /// UiConfig option with the internal name DetailDispDelayType. /// This option is a UInt. /// [GameConfigOption("DetailDispDelayType", ConfigType.UInt)] DetailDispDelayType, /// - /// System option with the internal name PartyListSortTypeTank. + /// UiConfig option with the internal name PartyListSortTypeTank. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeTank", ConfigType.UInt)] PartyListSortTypeTank, /// - /// System option with the internal name PartyListSortTypeHealer. + /// UiConfig option with the internal name PartyListSortTypeHealer. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeHealer", ConfigType.UInt)] PartyListSortTypeHealer, /// - /// System option with the internal name PartyListSortTypeDps. + /// UiConfig option with the internal name PartyListSortTypeDps. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeDps", ConfigType.UInt)] PartyListSortTypeDps, /// - /// System option with the internal name PartyListSortTypeOther. + /// UiConfig option with the internal name PartyListSortTypeOther. /// This option is a UInt. /// [GameConfigOption("PartyListSortTypeOther", ConfigType.UInt)] PartyListSortTypeOther, /// - /// System option with the internal name RatioHpDisp. + /// UiConfig option with the internal name RatioHpDisp. /// This option is a UInt. /// [GameConfigOption("RatioHpDisp", ConfigType.UInt)] RatioHpDisp, /// - /// System option with the internal name BuffDispType. + /// UiConfig option with the internal name BuffDispType. /// This option is a UInt. /// [GameConfigOption("BuffDispType", ConfigType.UInt)] BuffDispType, /// - /// System option with the internal name ContentsFinderListSortType. + /// UiConfig option with the internal name ContentsFinderListSortType. /// This option is a UInt. /// [GameConfigOption("ContentsFinderListSortType", ConfigType.UInt)] ContentsFinderListSortType, /// - /// System option with the internal name ContentsFinderSupplyEnable. + /// UiConfig option with the internal name ContentsFinderSupplyEnable. /// This option is a UInt. /// [GameConfigOption("ContentsFinderSupplyEnable", ConfigType.UInt)] ContentsFinderSupplyEnable, /// - /// System option with the internal name EnemyListCastbarEnable. + /// UiConfig option with the internal name EnemyListCastbarEnable. /// This option is a UInt. /// [GameConfigOption("EnemyListCastbarEnable", ConfigType.UInt)] EnemyListCastbarEnable, /// - /// System option with the internal name AchievementAppealLoginDisp. + /// UiConfig option with the internal name AchievementAppealLoginDisp. /// This option is a UInt. /// [GameConfigOption("AchievementAppealLoginDisp", ConfigType.UInt)] AchievementAppealLoginDisp, /// - /// System option with the internal name ContentsFinderUseLangTypeJA. + /// UiConfig option with the internal name ContentsFinderUseLangTypeJA. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeJA", ConfigType.UInt)] ContentsFinderUseLangTypeJA, /// - /// System option with the internal name ContentsFinderUseLangTypeEN. + /// UiConfig option with the internal name ContentsFinderUseLangTypeEN. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeEN", ConfigType.UInt)] ContentsFinderUseLangTypeEN, /// - /// System option with the internal name ContentsFinderUseLangTypeDE. + /// UiConfig option with the internal name ContentsFinderUseLangTypeDE. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeDE", ConfigType.UInt)] ContentsFinderUseLangTypeDE, /// - /// System option with the internal name ContentsFinderUseLangTypeFR. + /// UiConfig option with the internal name ContentsFinderUseLangTypeFR. /// This option is a UInt. /// [GameConfigOption("ContentsFinderUseLangTypeFR", ConfigType.UInt)] ContentsFinderUseLangTypeFR, /// - /// System option with the internal name ItemInventryWindowSizeType. + /// UiConfig option with the internal name ItemInventryWindowSizeType. /// This option is a UInt. /// [GameConfigOption("ItemInventryWindowSizeType", ConfigType.UInt)] ItemInventryWindowSizeType, /// - /// System option with the internal name ItemInventryRetainerWindowSizeType. + /// UiConfig option with the internal name ItemInventryRetainerWindowSizeType. /// This option is a UInt. /// [GameConfigOption("ItemInventryRetainerWindowSizeType", ConfigType.UInt)] ItemInventryRetainerWindowSizeType, /// - /// System option with the internal name BattleTalkShowFace. + /// UiConfig option with the internal name BattleTalkShowFace. /// This option is a UInt. /// [GameConfigOption("BattleTalkShowFace", ConfigType.UInt)] BattleTalkShowFace, /// - /// System option with the internal name BannerContentsDispType. + /// UiConfig option with the internal name BannerContentsDispType. /// This option is a UInt. /// [GameConfigOption("BannerContentsDispType", ConfigType.UInt)] BannerContentsDispType, /// - /// System option with the internal name BannerContentsNotice. + /// UiConfig option with the internal name BannerContentsNotice. /// This option is a UInt. /// [GameConfigOption("BannerContentsNotice", ConfigType.UInt)] BannerContentsNotice, /// - /// System option with the internal name MipDispType. + /// UiConfig option with the internal name MipDispType. /// This option is a UInt. /// [GameConfigOption("MipDispType", ConfigType.UInt)] MipDispType, /// - /// System option with the internal name BannerContentsOrderType. + /// UiConfig option with the internal name BannerContentsOrderType. /// This option is a UInt. /// [GameConfigOption("BannerContentsOrderType", ConfigType.UInt)] BannerContentsOrderType, /// - /// System option with the internal name CCProgressAllyFixLeftSide. + /// UiConfig option with the internal name CCProgressAllyFixLeftSide. /// This option is a UInt. /// [GameConfigOption("CCProgressAllyFixLeftSide", ConfigType.UInt)] CCProgressAllyFixLeftSide, /// - /// System option with the internal name CCMapAllyFixLeftSide. + /// UiConfig option with the internal name CCMapAllyFixLeftSide. /// This option is a UInt. /// [GameConfigOption("CCMapAllyFixLeftSide", ConfigType.UInt)] CCMapAllyFixLeftSide, /// - /// System option with the internal name DispCCCountDown. + /// UiConfig option with the internal name DispCCCountDown. /// This option is a UInt. /// [GameConfigOption("DispCCCountDown", ConfigType.UInt)] DispCCCountDown, /// - /// System option with the internal name EmoteTextType. + /// UiConfig option with the internal name EmoteTextType. /// This option is a UInt. /// [GameConfigOption("EmoteTextType", ConfigType.UInt)] EmoteTextType, /// - /// System option with the internal name IsEmoteSe. + /// UiConfig option with the internal name IsEmoteSe. /// This option is a UInt. /// [GameConfigOption("IsEmoteSe", ConfigType.UInt)] IsEmoteSe, /// - /// System option with the internal name EmoteSeType. + /// UiConfig option with the internal name EmoteSeType. /// This option is a UInt. /// [GameConfigOption("EmoteSeType", ConfigType.UInt)] EmoteSeType, /// - /// System option with the internal name PartyFinderNewArrivalDisp. + /// UiConfig option with the internal name PartyFinderNewArrivalDisp. /// This option is a UInt. /// [GameConfigOption("PartyFinderNewArrivalDisp", ConfigType.UInt)] PartyFinderNewArrivalDisp, /// - /// System option with the internal name GPoseTargetFilterNPCLookAt. + /// UiConfig option with the internal name GPoseTargetFilterNPCLookAt. /// This option is a UInt. /// [GameConfigOption("GPoseTargetFilterNPCLookAt", ConfigType.UInt)] GPoseTargetFilterNPCLookAt, /// - /// System option with the internal name GPoseMotionFilterAction. + /// UiConfig option with the internal name GPoseMotionFilterAction. /// This option is a UInt. /// [GameConfigOption("GPoseMotionFilterAction", ConfigType.UInt)] GPoseMotionFilterAction, /// - /// System option with the internal name LsListSortPriority. + /// UiConfig option with the internal name LsListSortPriority. /// This option is a UInt. /// [GameConfigOption("LsListSortPriority", ConfigType.UInt)] LsListSortPriority, /// - /// System option with the internal name FriendListSortPriority. + /// UiConfig option with the internal name FriendListSortPriority. /// This option is a UInt. /// [GameConfigOption("FriendListSortPriority", ConfigType.UInt)] FriendListSortPriority, /// - /// System option with the internal name FriendListFilterType. + /// UiConfig option with the internal name FriendListFilterType. /// This option is a UInt. /// [GameConfigOption("FriendListFilterType", ConfigType.UInt)] FriendListFilterType, /// - /// System option with the internal name FriendListSortType. + /// UiConfig option with the internal name FriendListSortType. /// This option is a UInt. /// [GameConfigOption("FriendListSortType", ConfigType.UInt)] FriendListSortType, /// - /// System option with the internal name LetterListFilterType. + /// UiConfig option with the internal name LetterListFilterType. /// This option is a UInt. /// [GameConfigOption("LetterListFilterType", ConfigType.UInt)] LetterListFilterType, /// - /// System option with the internal name LetterListSortType. + /// UiConfig option with the internal name LetterListSortType. /// This option is a UInt. /// [GameConfigOption("LetterListSortType", ConfigType.UInt)] LetterListSortType, /// - /// System option with the internal name ContentsReplayEnable. + /// UiConfig option with the internal name ContentsReplayEnable. /// This option is a UInt. /// [GameConfigOption("ContentsReplayEnable", ConfigType.UInt)] ContentsReplayEnable, /// - /// System option with the internal name MouseWheelOperationUp. + /// UiConfig option with the internal name MouseWheelOperationUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationUp", ConfigType.UInt)] MouseWheelOperationUp, /// - /// System option with the internal name MouseWheelOperationDown. + /// UiConfig option with the internal name MouseWheelOperationDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationDown", ConfigType.UInt)] MouseWheelOperationDown, /// - /// System option with the internal name MouseWheelOperationCtrlUp. + /// UiConfig option with the internal name MouseWheelOperationCtrlUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationCtrlUp", ConfigType.UInt)] MouseWheelOperationCtrlUp, /// - /// System option with the internal name MouseWheelOperationCtrlDown. + /// UiConfig option with the internal name MouseWheelOperationCtrlDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationCtrlDown", ConfigType.UInt)] MouseWheelOperationCtrlDown, /// - /// System option with the internal name MouseWheelOperationAltUp. + /// UiConfig option with the internal name MouseWheelOperationAltUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationAltUp", ConfigType.UInt)] MouseWheelOperationAltUp, /// - /// System option with the internal name MouseWheelOperationAltDown. + /// UiConfig option with the internal name MouseWheelOperationAltDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationAltDown", ConfigType.UInt)] MouseWheelOperationAltDown, /// - /// System option with the internal name MouseWheelOperationShiftUp. + /// UiConfig option with the internal name MouseWheelOperationShiftUp. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationShiftUp", ConfigType.UInt)] MouseWheelOperationShiftUp, /// - /// System option with the internal name MouseWheelOperationShiftDown. + /// UiConfig option with the internal name MouseWheelOperationShiftDown. /// This option is a UInt. /// [GameConfigOption("MouseWheelOperationShiftDown", ConfigType.UInt)] MouseWheelOperationShiftDown, /// - /// System option with the internal name TelepoTicketUseType. + /// UiConfig option with the internal name TelepoTicketUseType. /// This option is a UInt. /// [GameConfigOption("TelepoTicketUseType", ConfigType.UInt)] TelepoTicketUseType, /// - /// System option with the internal name TelepoTicketGilSetting. + /// UiConfig option with the internal name TelepoTicketGilSetting. /// This option is a UInt. /// [GameConfigOption("TelepoTicketGilSetting", ConfigType.UInt)] TelepoTicketGilSetting, /// - /// System option with the internal name TelepoCategoryType. + /// UiConfig option with the internal name TelepoCategoryType. /// This option is a UInt. /// [GameConfigOption("TelepoCategoryType", ConfigType.UInt)] TelepoCategoryType, /// - /// System option with the internal name HidePcAroundQuestProgressNpc. + /// UiConfig option with the internal name HidePcAroundQuestProgressNpc. /// This option is a UInt. /// [GameConfigOption("HidePcAroundQuestProgressNpc", ConfigType.UInt)] HidePcAroundQuestProgressNpc, /// - /// System option with the internal name HidePcAroundQuestProgressNpcIncludeParty. + /// UiConfig option with the internal name HidePcAroundQuestProgressNpcIncludeParty. /// This option is a UInt. /// [GameConfigOption("HidePcAroundQuestProgressNpcIncludeParty", ConfigType.UInt)] HidePcAroundQuestProgressNpcIncludeParty, /// - /// System option with the internal name HidePcAroundNpcAccessingQuest. + /// UiConfig option with the internal name HidePcAroundNpcAccessingQuest. /// This option is a UInt. /// [GameConfigOption("HidePcAroundNpcAccessingQuest", ConfigType.UInt)] HidePcAroundNpcAccessingQuest, /// - /// System option with the internal name HidePcAroundNpcAccessingQuestIncludeParty. + /// UiConfig option with the internal name HidePcAroundNpcAccessingQuestIncludeParty. /// This option is a UInt. /// [GameConfigOption("HidePcAroundNpcAccessingQuestIncludeParty", ConfigType.UInt)] HidePcAroundNpcAccessingQuestIncludeParty, /// - /// System option with the internal name PvPFrontlinesGCFree. + /// UiConfig option with the internal name PvPFrontlinesGCFree. /// This option is a UInt. /// [GameConfigOption("PvPFrontlinesGCFree", ConfigType.UInt)] PvPFrontlinesGCFree, /// - /// System option with the internal name PadMode. + /// UiConfig option with the internal name PadMode. /// This option is a UInt. /// [GameConfigOption("PadMode", ConfigType.UInt)] PadMode, /// - /// System option with the internal name EnableMoveTiltCharacter. + /// UiConfig option with the internal name EnableMoveTiltCharacter. /// This option is a UInt. /// [GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)] EnableMoveTiltCharacter, /// - /// System option with the internal name EnableMoveTiltMountGround. + /// UiConfig option with the internal name EnableMoveTiltMountGround. /// This option is a UInt. /// [GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)] EnableMoveTiltMountGround, /// - /// System option with the internal name EnableMoveTiltMountFly. + /// UiConfig option with the internal name EnableMoveTiltMountFly. /// This option is a UInt. /// [GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)] diff --git a/Dalamud/Game/Config/UiControlOption.cs b/Dalamud/Game/Config/UiControlOption.cs index 5d36ee84d..99fb44c3b 100644 --- a/Dalamud/Game/Config/UiControlOption.cs +++ b/Dalamud/Game/Config/UiControlOption.cs @@ -10,1267 +10,1267 @@ public enum UiControlOption { /// - /// System option with the internal name AutoChangePointOfView. + /// UiControl option with the internal name AutoChangePointOfView. /// This option is a UInt. /// [GameConfigOption("AutoChangePointOfView", ConfigType.UInt)] AutoChangePointOfView, /// - /// System option with the internal name KeyboardCameraInterpolationType. + /// UiControl option with the internal name KeyboardCameraInterpolationType. /// This option is a UInt. /// [GameConfigOption("KeyboardCameraInterpolationType", ConfigType.UInt)] KeyboardCameraInterpolationType, /// - /// System option with the internal name KeyboardCameraVerticalInterpolation. + /// UiControl option with the internal name KeyboardCameraVerticalInterpolation. /// This option is a UInt. /// [GameConfigOption("KeyboardCameraVerticalInterpolation", ConfigType.UInt)] KeyboardCameraVerticalInterpolation, /// - /// System option with the internal name TiltOffset. + /// UiControl option with the internal name TiltOffset. /// This option is a Float. /// [GameConfigOption("TiltOffset", ConfigType.Float)] TiltOffset, /// - /// System option with the internal name KeyboardSpeed. + /// UiControl option with the internal name KeyboardSpeed. /// This option is a Float. /// [GameConfigOption("KeyboardSpeed", ConfigType.Float)] KeyboardSpeed, /// - /// System option with the internal name PadSpeed. + /// UiControl option with the internal name PadSpeed. /// This option is a Float. /// [GameConfigOption("PadSpeed", ConfigType.Float)] PadSpeed, /// - /// System option with the internal name PadFpsXReverse. + /// UiControl option with the internal name PadFpsXReverse. /// This option is a UInt. /// [GameConfigOption("PadFpsXReverse", ConfigType.UInt)] PadFpsXReverse, /// - /// System option with the internal name PadFpsYReverse. + /// UiControl option with the internal name PadFpsYReverse. /// This option is a UInt. /// [GameConfigOption("PadFpsYReverse", ConfigType.UInt)] PadFpsYReverse, /// - /// System option with the internal name PadTpsXReverse. + /// UiControl option with the internal name PadTpsXReverse. /// This option is a UInt. /// [GameConfigOption("PadTpsXReverse", ConfigType.UInt)] PadTpsXReverse, /// - /// System option with the internal name PadTpsYReverse. + /// UiControl option with the internal name PadTpsYReverse. /// This option is a UInt. /// [GameConfigOption("PadTpsYReverse", ConfigType.UInt)] PadTpsYReverse, /// - /// System option with the internal name MouseFpsXReverse. + /// UiControl option with the internal name MouseFpsXReverse. /// This option is a UInt. /// [GameConfigOption("MouseFpsXReverse", ConfigType.UInt)] MouseFpsXReverse, /// - /// System option with the internal name MouseFpsYReverse. + /// UiControl option with the internal name MouseFpsYReverse. /// This option is a UInt. /// [GameConfigOption("MouseFpsYReverse", ConfigType.UInt)] MouseFpsYReverse, /// - /// System option with the internal name MouseTpsXReverse. + /// UiControl option with the internal name MouseTpsXReverse. /// This option is a UInt. /// [GameConfigOption("MouseTpsXReverse", ConfigType.UInt)] MouseTpsXReverse, /// - /// System option with the internal name MouseTpsYReverse. + /// UiControl option with the internal name MouseTpsYReverse. /// This option is a UInt. /// [GameConfigOption("MouseTpsYReverse", ConfigType.UInt)] MouseTpsYReverse, /// - /// System option with the internal name MouseCharaViewRotYReverse. + /// UiControl option with the internal name MouseCharaViewRotYReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewRotYReverse", ConfigType.UInt)] MouseCharaViewRotYReverse, /// - /// System option with the internal name MouseCharaViewRotXReverse. + /// UiControl option with the internal name MouseCharaViewRotXReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewRotXReverse", ConfigType.UInt)] MouseCharaViewRotXReverse, /// - /// System option with the internal name MouseCharaViewMoveYReverse. + /// UiControl option with the internal name MouseCharaViewMoveYReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewMoveYReverse", ConfigType.UInt)] MouseCharaViewMoveYReverse, /// - /// System option with the internal name MouseCharaViewMoveXReverse. + /// UiControl option with the internal name MouseCharaViewMoveXReverse. /// This option is a UInt. /// [GameConfigOption("MouseCharaViewMoveXReverse", ConfigType.UInt)] MouseCharaViewMoveXReverse, /// - /// System option with the internal name PADCharaViewRotYReverse. + /// UiControl option with the internal name PADCharaViewRotYReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewRotYReverse", ConfigType.UInt)] PADCharaViewRotYReverse, /// - /// System option with the internal name PADCharaViewRotXReverse. + /// UiControl option with the internal name PADCharaViewRotXReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewRotXReverse", ConfigType.UInt)] PADCharaViewRotXReverse, /// - /// System option with the internal name PADCharaViewMoveYReverse. + /// UiControl option with the internal name PADCharaViewMoveYReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewMoveYReverse", ConfigType.UInt)] PADCharaViewMoveYReverse, /// - /// System option with the internal name PADCharaViewMoveXReverse. + /// UiControl option with the internal name PADCharaViewMoveXReverse. /// This option is a UInt. /// [GameConfigOption("PADCharaViewMoveXReverse", ConfigType.UInt)] PADCharaViewMoveXReverse, /// - /// System option with the internal name FlyingControlType. + /// UiControl option with the internal name FlyingControlType. /// This option is a UInt. /// [GameConfigOption("FlyingControlType", ConfigType.UInt)] FlyingControlType, /// - /// System option with the internal name FlyingLegacyAutorun. + /// UiControl option with the internal name FlyingLegacyAutorun. /// This option is a UInt. /// [GameConfigOption("FlyingLegacyAutorun", ConfigType.UInt)] FlyingLegacyAutorun, /// - /// System option with the internal name AutoFaceTargetOnAction. + /// UiControl option with the internal name AutoFaceTargetOnAction. /// This option is a UInt. /// [GameConfigOption("AutoFaceTargetOnAction", ConfigType.UInt)] AutoFaceTargetOnAction, /// - /// System option with the internal name SelfClick. + /// UiControl option with the internal name SelfClick. /// This option is a UInt. /// [GameConfigOption("SelfClick", ConfigType.UInt)] SelfClick, /// - /// System option with the internal name NoTargetClickCancel. + /// UiControl option with the internal name NoTargetClickCancel. /// This option is a UInt. /// [GameConfigOption("NoTargetClickCancel", ConfigType.UInt)] NoTargetClickCancel, /// - /// System option with the internal name AutoTarget. + /// UiControl option with the internal name AutoTarget. /// This option is a UInt. /// [GameConfigOption("AutoTarget", ConfigType.UInt)] AutoTarget, /// - /// System option with the internal name TargetTypeSelect. + /// UiControl option with the internal name TargetTypeSelect. /// This option is a UInt. /// [GameConfigOption("TargetTypeSelect", ConfigType.UInt)] TargetTypeSelect, /// - /// System option with the internal name AutoLockOn. + /// UiControl option with the internal name AutoLockOn. /// This option is a UInt. /// [GameConfigOption("AutoLockOn", ConfigType.UInt)] AutoLockOn, /// - /// System option with the internal name CircleBattleModeAutoChange. + /// UiControl option with the internal name CircleBattleModeAutoChange. /// This option is a UInt. /// [GameConfigOption("CircleBattleModeAutoChange", ConfigType.UInt)] CircleBattleModeAutoChange, /// - /// System option with the internal name CircleIsCustom. + /// UiControl option with the internal name CircleIsCustom. /// This option is a UInt. /// [GameConfigOption("CircleIsCustom", ConfigType.UInt)] CircleIsCustom, /// - /// System option with the internal name CircleSwordDrawnIsActive. + /// UiControl option with the internal name CircleSwordDrawnIsActive. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnIsActive", ConfigType.UInt)] CircleSwordDrawnIsActive, /// - /// System option with the internal name CircleSwordDrawnNonPartyPc. + /// UiControl option with the internal name CircleSwordDrawnNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnNonPartyPc", ConfigType.UInt)] CircleSwordDrawnNonPartyPc, /// - /// System option with the internal name CircleSwordDrawnParty. + /// UiControl option with the internal name CircleSwordDrawnParty. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnParty", ConfigType.UInt)] CircleSwordDrawnParty, /// - /// System option with the internal name CircleSwordDrawnEnemy. + /// UiControl option with the internal name CircleSwordDrawnEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnEnemy", ConfigType.UInt)] CircleSwordDrawnEnemy, /// - /// System option with the internal name CircleSwordDrawnAggro. + /// UiControl option with the internal name CircleSwordDrawnAggro. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnAggro", ConfigType.UInt)] CircleSwordDrawnAggro, /// - /// System option with the internal name CircleSwordDrawnNpcOrObject. + /// UiControl option with the internal name CircleSwordDrawnNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnNpcOrObject", ConfigType.UInt)] CircleSwordDrawnNpcOrObject, /// - /// System option with the internal name CircleSwordDrawnMinion. + /// UiControl option with the internal name CircleSwordDrawnMinion. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnMinion", ConfigType.UInt)] CircleSwordDrawnMinion, /// - /// System option with the internal name CircleSwordDrawnDutyEnemy. + /// UiControl option with the internal name CircleSwordDrawnDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnDutyEnemy", ConfigType.UInt)] CircleSwordDrawnDutyEnemy, /// - /// System option with the internal name CircleSwordDrawnPet. + /// UiControl option with the internal name CircleSwordDrawnPet. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnPet", ConfigType.UInt)] CircleSwordDrawnPet, /// - /// System option with the internal name CircleSwordDrawnAlliance. + /// UiControl option with the internal name CircleSwordDrawnAlliance. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnAlliance", ConfigType.UInt)] CircleSwordDrawnAlliance, /// - /// System option with the internal name CircleSwordDrawnMark. + /// UiControl option with the internal name CircleSwordDrawnMark. /// This option is a UInt. /// [GameConfigOption("CircleSwordDrawnMark", ConfigType.UInt)] CircleSwordDrawnMark, /// - /// System option with the internal name CircleSheathedIsActive. + /// UiControl option with the internal name CircleSheathedIsActive. /// This option is a UInt. /// [GameConfigOption("CircleSheathedIsActive", ConfigType.UInt)] CircleSheathedIsActive, /// - /// System option with the internal name CircleSheathedNonPartyPc. + /// UiControl option with the internal name CircleSheathedNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleSheathedNonPartyPc", ConfigType.UInt)] CircleSheathedNonPartyPc, /// - /// System option with the internal name CircleSheathedParty. + /// UiControl option with the internal name CircleSheathedParty. /// This option is a UInt. /// [GameConfigOption("CircleSheathedParty", ConfigType.UInt)] CircleSheathedParty, /// - /// System option with the internal name CircleSheathedEnemy. + /// UiControl option with the internal name CircleSheathedEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSheathedEnemy", ConfigType.UInt)] CircleSheathedEnemy, /// - /// System option with the internal name CircleSheathedAggro. + /// UiControl option with the internal name CircleSheathedAggro. /// This option is a UInt. /// [GameConfigOption("CircleSheathedAggro", ConfigType.UInt)] CircleSheathedAggro, /// - /// System option with the internal name CircleSheathedNpcOrObject. + /// UiControl option with the internal name CircleSheathedNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleSheathedNpcOrObject", ConfigType.UInt)] CircleSheathedNpcOrObject, /// - /// System option with the internal name CircleSheathedMinion. + /// UiControl option with the internal name CircleSheathedMinion. /// This option is a UInt. /// [GameConfigOption("CircleSheathedMinion", ConfigType.UInt)] CircleSheathedMinion, /// - /// System option with the internal name CircleSheathedDutyEnemy. + /// UiControl option with the internal name CircleSheathedDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleSheathedDutyEnemy", ConfigType.UInt)] CircleSheathedDutyEnemy, /// - /// System option with the internal name CircleSheathedPet. + /// UiControl option with the internal name CircleSheathedPet. /// This option is a UInt. /// [GameConfigOption("CircleSheathedPet", ConfigType.UInt)] CircleSheathedPet, /// - /// System option with the internal name CircleSheathedAlliance. + /// UiControl option with the internal name CircleSheathedAlliance. /// This option is a UInt. /// [GameConfigOption("CircleSheathedAlliance", ConfigType.UInt)] CircleSheathedAlliance, /// - /// System option with the internal name CircleSheathedMark. + /// UiControl option with the internal name CircleSheathedMark. /// This option is a UInt. /// [GameConfigOption("CircleSheathedMark", ConfigType.UInt)] CircleSheathedMark, /// - /// System option with the internal name CircleClickIsActive. + /// UiControl option with the internal name CircleClickIsActive. /// This option is a UInt. /// [GameConfigOption("CircleClickIsActive", ConfigType.UInt)] CircleClickIsActive, /// - /// System option with the internal name CircleClickNonPartyPc. + /// UiControl option with the internal name CircleClickNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleClickNonPartyPc", ConfigType.UInt)] CircleClickNonPartyPc, /// - /// System option with the internal name CircleClickParty. + /// UiControl option with the internal name CircleClickParty. /// This option is a UInt. /// [GameConfigOption("CircleClickParty", ConfigType.UInt)] CircleClickParty, /// - /// System option with the internal name CircleClickEnemy. + /// UiControl option with the internal name CircleClickEnemy. /// This option is a UInt. /// [GameConfigOption("CircleClickEnemy", ConfigType.UInt)] CircleClickEnemy, /// - /// System option with the internal name CircleClickAggro. + /// UiControl option with the internal name CircleClickAggro. /// This option is a UInt. /// [GameConfigOption("CircleClickAggro", ConfigType.UInt)] CircleClickAggro, /// - /// System option with the internal name CircleClickNpcOrObject. + /// UiControl option with the internal name CircleClickNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleClickNpcOrObject", ConfigType.UInt)] CircleClickNpcOrObject, /// - /// System option with the internal name CircleClickMinion. + /// UiControl option with the internal name CircleClickMinion. /// This option is a UInt. /// [GameConfigOption("CircleClickMinion", ConfigType.UInt)] CircleClickMinion, /// - /// System option with the internal name CircleClickDutyEnemy. + /// UiControl option with the internal name CircleClickDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleClickDutyEnemy", ConfigType.UInt)] CircleClickDutyEnemy, /// - /// System option with the internal name CircleClickPet. + /// UiControl option with the internal name CircleClickPet. /// This option is a UInt. /// [GameConfigOption("CircleClickPet", ConfigType.UInt)] CircleClickPet, /// - /// System option with the internal name CircleClickAlliance. + /// UiControl option with the internal name CircleClickAlliance. /// This option is a UInt. /// [GameConfigOption("CircleClickAlliance", ConfigType.UInt)] CircleClickAlliance, /// - /// System option with the internal name CircleClickMark. + /// UiControl option with the internal name CircleClickMark. /// This option is a UInt. /// [GameConfigOption("CircleClickMark", ConfigType.UInt)] CircleClickMark, /// - /// System option with the internal name CircleXButtonIsActive. + /// UiControl option with the internal name CircleXButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleXButtonIsActive", ConfigType.UInt)] CircleXButtonIsActive, /// - /// System option with the internal name CircleXButtonNonPartyPc. + /// UiControl option with the internal name CircleXButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleXButtonNonPartyPc", ConfigType.UInt)] CircleXButtonNonPartyPc, /// - /// System option with the internal name CircleXButtonParty. + /// UiControl option with the internal name CircleXButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleXButtonParty", ConfigType.UInt)] CircleXButtonParty, /// - /// System option with the internal name CircleXButtonEnemy. + /// UiControl option with the internal name CircleXButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleXButtonEnemy", ConfigType.UInt)] CircleXButtonEnemy, /// - /// System option with the internal name CircleXButtonAggro. + /// UiControl option with the internal name CircleXButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleXButtonAggro", ConfigType.UInt)] CircleXButtonAggro, /// - /// System option with the internal name CircleXButtonNpcOrObject. + /// UiControl option with the internal name CircleXButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleXButtonNpcOrObject", ConfigType.UInt)] CircleXButtonNpcOrObject, /// - /// System option with the internal name CircleXButtonMinion. + /// UiControl option with the internal name CircleXButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleXButtonMinion", ConfigType.UInt)] CircleXButtonMinion, /// - /// System option with the internal name CircleXButtonDutyEnemy. + /// UiControl option with the internal name CircleXButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleXButtonDutyEnemy", ConfigType.UInt)] CircleXButtonDutyEnemy, /// - /// System option with the internal name CircleXButtonPet. + /// UiControl option with the internal name CircleXButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleXButtonPet", ConfigType.UInt)] CircleXButtonPet, /// - /// System option with the internal name CircleXButtonAlliance. + /// UiControl option with the internal name CircleXButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleXButtonAlliance", ConfigType.UInt)] CircleXButtonAlliance, /// - /// System option with the internal name CircleXButtonMark. + /// UiControl option with the internal name CircleXButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleXButtonMark", ConfigType.UInt)] CircleXButtonMark, /// - /// System option with the internal name CircleYButtonIsActive. + /// UiControl option with the internal name CircleYButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleYButtonIsActive", ConfigType.UInt)] CircleYButtonIsActive, /// - /// System option with the internal name CircleYButtonNonPartyPc. + /// UiControl option with the internal name CircleYButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleYButtonNonPartyPc", ConfigType.UInt)] CircleYButtonNonPartyPc, /// - /// System option with the internal name CircleYButtonParty. + /// UiControl option with the internal name CircleYButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleYButtonParty", ConfigType.UInt)] CircleYButtonParty, /// - /// System option with the internal name CircleYButtonEnemy. + /// UiControl option with the internal name CircleYButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleYButtonEnemy", ConfigType.UInt)] CircleYButtonEnemy, /// - /// System option with the internal name CircleYButtonAggro. + /// UiControl option with the internal name CircleYButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleYButtonAggro", ConfigType.UInt)] CircleYButtonAggro, /// - /// System option with the internal name CircleYButtonNpcOrObject. + /// UiControl option with the internal name CircleYButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleYButtonNpcOrObject", ConfigType.UInt)] CircleYButtonNpcOrObject, /// - /// System option with the internal name CircleYButtonMinion. + /// UiControl option with the internal name CircleYButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleYButtonMinion", ConfigType.UInt)] CircleYButtonMinion, /// - /// System option with the internal name CircleYButtonDutyEnemy. + /// UiControl option with the internal name CircleYButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleYButtonDutyEnemy", ConfigType.UInt)] CircleYButtonDutyEnemy, /// - /// System option with the internal name CircleYButtonPet. + /// UiControl option with the internal name CircleYButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleYButtonPet", ConfigType.UInt)] CircleYButtonPet, /// - /// System option with the internal name CircleYButtonAlliance. + /// UiControl option with the internal name CircleYButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleYButtonAlliance", ConfigType.UInt)] CircleYButtonAlliance, /// - /// System option with the internal name CircleYButtonMark. + /// UiControl option with the internal name CircleYButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleYButtonMark", ConfigType.UInt)] CircleYButtonMark, /// - /// System option with the internal name CircleBButtonIsActive. + /// UiControl option with the internal name CircleBButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleBButtonIsActive", ConfigType.UInt)] CircleBButtonIsActive, /// - /// System option with the internal name CircleBButtonNonPartyPc. + /// UiControl option with the internal name CircleBButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleBButtonNonPartyPc", ConfigType.UInt)] CircleBButtonNonPartyPc, /// - /// System option with the internal name CircleBButtonParty. + /// UiControl option with the internal name CircleBButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleBButtonParty", ConfigType.UInt)] CircleBButtonParty, /// - /// System option with the internal name CircleBButtonEnemy. + /// UiControl option with the internal name CircleBButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleBButtonEnemy", ConfigType.UInt)] CircleBButtonEnemy, /// - /// System option with the internal name CircleBButtonAggro. + /// UiControl option with the internal name CircleBButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleBButtonAggro", ConfigType.UInt)] CircleBButtonAggro, /// - /// System option with the internal name CircleBButtonNpcOrObject. + /// UiControl option with the internal name CircleBButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleBButtonNpcOrObject", ConfigType.UInt)] CircleBButtonNpcOrObject, /// - /// System option with the internal name CircleBButtonMinion. + /// UiControl option with the internal name CircleBButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleBButtonMinion", ConfigType.UInt)] CircleBButtonMinion, /// - /// System option with the internal name CircleBButtonDutyEnemy. + /// UiControl option with the internal name CircleBButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleBButtonDutyEnemy", ConfigType.UInt)] CircleBButtonDutyEnemy, /// - /// System option with the internal name CircleBButtonPet. + /// UiControl option with the internal name CircleBButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleBButtonPet", ConfigType.UInt)] CircleBButtonPet, /// - /// System option with the internal name CircleBButtonAlliance. + /// UiControl option with the internal name CircleBButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleBButtonAlliance", ConfigType.UInt)] CircleBButtonAlliance, /// - /// System option with the internal name CircleBButtonMark. + /// UiControl option with the internal name CircleBButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleBButtonMark", ConfigType.UInt)] CircleBButtonMark, /// - /// System option with the internal name CircleAButtonIsActive. + /// UiControl option with the internal name CircleAButtonIsActive. /// This option is a UInt. /// [GameConfigOption("CircleAButtonIsActive", ConfigType.UInt)] CircleAButtonIsActive, /// - /// System option with the internal name CircleAButtonNonPartyPc. + /// UiControl option with the internal name CircleAButtonNonPartyPc. /// This option is a UInt. /// [GameConfigOption("CircleAButtonNonPartyPc", ConfigType.UInt)] CircleAButtonNonPartyPc, /// - /// System option with the internal name CircleAButtonParty. + /// UiControl option with the internal name CircleAButtonParty. /// This option is a UInt. /// [GameConfigOption("CircleAButtonParty", ConfigType.UInt)] CircleAButtonParty, /// - /// System option with the internal name CircleAButtonEnemy. + /// UiControl option with the internal name CircleAButtonEnemy. /// This option is a UInt. /// [GameConfigOption("CircleAButtonEnemy", ConfigType.UInt)] CircleAButtonEnemy, /// - /// System option with the internal name CircleAButtonAggro. + /// UiControl option with the internal name CircleAButtonAggro. /// This option is a UInt. /// [GameConfigOption("CircleAButtonAggro", ConfigType.UInt)] CircleAButtonAggro, /// - /// System option with the internal name CircleAButtonNpcOrObject. + /// UiControl option with the internal name CircleAButtonNpcOrObject. /// This option is a UInt. /// [GameConfigOption("CircleAButtonNpcOrObject", ConfigType.UInt)] CircleAButtonNpcOrObject, /// - /// System option with the internal name CircleAButtonMinion. + /// UiControl option with the internal name CircleAButtonMinion. /// This option is a UInt. /// [GameConfigOption("CircleAButtonMinion", ConfigType.UInt)] CircleAButtonMinion, /// - /// System option with the internal name CircleAButtonDutyEnemy. + /// UiControl option with the internal name CircleAButtonDutyEnemy. /// This option is a UInt. /// [GameConfigOption("CircleAButtonDutyEnemy", ConfigType.UInt)] CircleAButtonDutyEnemy, /// - /// System option with the internal name CircleAButtonPet. + /// UiControl option with the internal name CircleAButtonPet. /// This option is a UInt. /// [GameConfigOption("CircleAButtonPet", ConfigType.UInt)] CircleAButtonPet, /// - /// System option with the internal name CircleAButtonAlliance. + /// UiControl option with the internal name CircleAButtonAlliance. /// This option is a UInt. /// [GameConfigOption("CircleAButtonAlliance", ConfigType.UInt)] CircleAButtonAlliance, /// - /// System option with the internal name CircleAButtonMark. + /// UiControl option with the internal name CircleAButtonMark. /// This option is a UInt. /// [GameConfigOption("CircleAButtonMark", ConfigType.UInt)] CircleAButtonMark, /// - /// System option with the internal name GroundTargetType. + /// UiControl option with the internal name GroundTargetType. /// This option is a UInt. /// [GameConfigOption("GroundTargetType", ConfigType.UInt)] GroundTargetType, /// - /// System option with the internal name GroundTargetCursorSpeed. + /// UiControl option with the internal name GroundTargetCursorSpeed. /// This option is a UInt. /// [GameConfigOption("GroundTargetCursorSpeed", ConfigType.UInt)] GroundTargetCursorSpeed, /// - /// System option with the internal name TargetCircleType. + /// UiControl option with the internal name TargetCircleType. /// This option is a UInt. /// [GameConfigOption("TargetCircleType", ConfigType.UInt)] TargetCircleType, /// - /// System option with the internal name TargetLineType. + /// UiControl option with the internal name TargetLineType. /// This option is a UInt. /// [GameConfigOption("TargetLineType", ConfigType.UInt)] TargetLineType, /// - /// System option with the internal name LinkLineType. + /// UiControl option with the internal name LinkLineType. /// This option is a UInt. /// [GameConfigOption("LinkLineType", ConfigType.UInt)] LinkLineType, /// - /// System option with the internal name ObjectBorderingType. + /// UiControl option with the internal name ObjectBorderingType. /// This option is a UInt. /// [GameConfigOption("ObjectBorderingType", ConfigType.UInt)] ObjectBorderingType, /// - /// System option with the internal name MoveMode. + /// UiControl option with the internal name MoveMode. /// This option is a UInt. /// [GameConfigOption("MoveMode", ConfigType.UInt)] MoveMode, /// - /// System option with the internal name HotbarDisp. + /// UiControl option with the internal name HotbarDisp. /// This option is a UInt. /// [GameConfigOption("HotbarDisp", ConfigType.UInt)] HotbarDisp, /// - /// System option with the internal name HotbarEmptyVisible. + /// UiControl option with the internal name HotbarEmptyVisible. /// This option is a UInt. /// [GameConfigOption("HotbarEmptyVisible", ConfigType.UInt)] HotbarEmptyVisible, /// - /// System option with the internal name HotbarNoneSlotDisp01. + /// UiControl option with the internal name HotbarNoneSlotDisp01. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp01", ConfigType.UInt)] HotbarNoneSlotDisp01, /// - /// System option with the internal name HotbarNoneSlotDisp02. + /// UiControl option with the internal name HotbarNoneSlotDisp02. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp02", ConfigType.UInt)] HotbarNoneSlotDisp02, /// - /// System option with the internal name HotbarNoneSlotDisp03. + /// UiControl option with the internal name HotbarNoneSlotDisp03. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp03", ConfigType.UInt)] HotbarNoneSlotDisp03, /// - /// System option with the internal name HotbarNoneSlotDisp04. + /// UiControl option with the internal name HotbarNoneSlotDisp04. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp04", ConfigType.UInt)] HotbarNoneSlotDisp04, /// - /// System option with the internal name HotbarNoneSlotDisp05. + /// UiControl option with the internal name HotbarNoneSlotDisp05. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp05", ConfigType.UInt)] HotbarNoneSlotDisp05, /// - /// System option with the internal name HotbarNoneSlotDisp06. + /// UiControl option with the internal name HotbarNoneSlotDisp06. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp06", ConfigType.UInt)] HotbarNoneSlotDisp06, /// - /// System option with the internal name HotbarNoneSlotDisp07. + /// UiControl option with the internal name HotbarNoneSlotDisp07. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp07", ConfigType.UInt)] HotbarNoneSlotDisp07, /// - /// System option with the internal name HotbarNoneSlotDisp08. + /// UiControl option with the internal name HotbarNoneSlotDisp08. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp08", ConfigType.UInt)] HotbarNoneSlotDisp08, /// - /// System option with the internal name HotbarNoneSlotDisp09. + /// UiControl option with the internal name HotbarNoneSlotDisp09. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp09", ConfigType.UInt)] HotbarNoneSlotDisp09, /// - /// System option with the internal name HotbarNoneSlotDisp10. + /// UiControl option with the internal name HotbarNoneSlotDisp10. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDisp10", ConfigType.UInt)] HotbarNoneSlotDisp10, /// - /// System option with the internal name HotbarNoneSlotDispEX. + /// UiControl option with the internal name HotbarNoneSlotDispEX. /// This option is a UInt. /// [GameConfigOption("HotbarNoneSlotDispEX", ConfigType.UInt)] HotbarNoneSlotDispEX, /// - /// System option with the internal name ExHotbarSetting. + /// UiControl option with the internal name ExHotbarSetting. /// This option is a UInt. /// [GameConfigOption("ExHotbarSetting", ConfigType.UInt)] ExHotbarSetting, /// - /// System option with the internal name HotbarExHotbarUseSetting. + /// UiControl option with the internal name HotbarExHotbarUseSetting. /// This option is a UInt. /// [GameConfigOption("HotbarExHotbarUseSetting", ConfigType.UInt)] HotbarExHotbarUseSetting, /// - /// System option with the internal name HotbarCrossUseEx. + /// UiControl option with the internal name HotbarCrossUseEx. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUseEx", ConfigType.UInt)] HotbarCrossUseEx, /// - /// System option with the internal name HotbarCrossUseExDirection. + /// UiControl option with the internal name HotbarCrossUseExDirection. /// This option is a UInt. /// [GameConfigOption("HotbarCrossUseExDirection", ConfigType.UInt)] HotbarCrossUseExDirection, /// - /// System option with the internal name HotbarCrossDispType. + /// UiControl option with the internal name HotbarCrossDispType. /// This option is a UInt. /// [GameConfigOption("HotbarCrossDispType", ConfigType.UInt)] HotbarCrossDispType, /// - /// System option with the internal name PartyListSoloOff. + /// UiControl option with the internal name PartyListSoloOff. /// This option is a UInt. /// [GameConfigOption("PartyListSoloOff", ConfigType.UInt)] PartyListSoloOff, /// - /// System option with the internal name HowTo. + /// UiControl option with the internal name HowTo. /// This option is a UInt. /// [GameConfigOption("HowTo", ConfigType.UInt)] HowTo, /// - /// System option with the internal name HousingFurnitureBindConfirm. + /// UiControl option with the internal name HousingFurnitureBindConfirm. /// This option is a UInt. /// [GameConfigOption("HousingFurnitureBindConfirm", ConfigType.UInt)] HousingFurnitureBindConfirm, /// - /// System option with the internal name DirectChat. + /// UiControl option with the internal name DirectChat. /// This option is a UInt. /// [GameConfigOption("DirectChat", ConfigType.UInt)] DirectChat, /// - /// System option with the internal name CharaParamDisp. + /// UiControl option with the internal name CharaParamDisp. /// This option is a UInt. /// [GameConfigOption("CharaParamDisp", ConfigType.UInt)] CharaParamDisp, /// - /// System option with the internal name LimitBreakGaugeDisp. + /// UiControl option with the internal name LimitBreakGaugeDisp. /// This option is a UInt. /// [GameConfigOption("LimitBreakGaugeDisp", ConfigType.UInt)] LimitBreakGaugeDisp, /// - /// System option with the internal name ScenarioTreeDisp. + /// UiControl option with the internal name ScenarioTreeDisp. /// This option is a UInt. /// [GameConfigOption("ScenarioTreeDisp", ConfigType.UInt)] ScenarioTreeDisp, /// - /// System option with the internal name ScenarioTreeCompleteDisp. + /// UiControl option with the internal name ScenarioTreeCompleteDisp. /// This option is a UInt. /// [GameConfigOption("ScenarioTreeCompleteDisp", ConfigType.UInt)] ScenarioTreeCompleteDisp, /// - /// System option with the internal name HotbarCrossDispAlways. + /// UiControl option with the internal name HotbarCrossDispAlways. /// This option is a UInt. /// [GameConfigOption("HotbarCrossDispAlways", ConfigType.UInt)] HotbarCrossDispAlways, /// - /// System option with the internal name ExpDisp. + /// UiControl option with the internal name ExpDisp. /// This option is a UInt. /// [GameConfigOption("ExpDisp", ConfigType.UInt)] ExpDisp, /// - /// System option with the internal name InventryStatusDisp. + /// UiControl option with the internal name InventryStatusDisp. /// This option is a UInt. /// [GameConfigOption("InventryStatusDisp", ConfigType.UInt)] InventryStatusDisp, /// - /// System option with the internal name DutyListDisp. + /// UiControl option with the internal name DutyListDisp. /// This option is a UInt. /// [GameConfigOption("DutyListDisp", ConfigType.UInt)] DutyListDisp, /// - /// System option with the internal name NaviMapDisp. + /// UiControl option with the internal name NaviMapDisp. /// This option is a UInt. /// [GameConfigOption("NaviMapDisp", ConfigType.UInt)] NaviMapDisp, /// - /// System option with the internal name GilStatusDisp. + /// UiControl option with the internal name GilStatusDisp. /// This option is a UInt. /// [GameConfigOption("GilStatusDisp", ConfigType.UInt)] GilStatusDisp, /// - /// System option with the internal name InfoSettingDisp. + /// UiControl option with the internal name InfoSettingDisp. /// This option is a UInt. /// [GameConfigOption("InfoSettingDisp", ConfigType.UInt)] InfoSettingDisp, /// - /// System option with the internal name InfoSettingDispType. + /// UiControl option with the internal name InfoSettingDispType. /// This option is a UInt. /// [GameConfigOption("InfoSettingDispType", ConfigType.UInt)] InfoSettingDispType, /// - /// System option with the internal name TargetInfoDisp. + /// UiControl option with the internal name TargetInfoDisp. /// This option is a UInt. /// [GameConfigOption("TargetInfoDisp", ConfigType.UInt)] TargetInfoDisp, /// - /// System option with the internal name EnemyListDisp. + /// UiControl option with the internal name EnemyListDisp. /// This option is a UInt. /// [GameConfigOption("EnemyListDisp", ConfigType.UInt)] EnemyListDisp, /// - /// System option with the internal name FocusTargetDisp. + /// UiControl option with the internal name FocusTargetDisp. /// This option is a UInt. /// [GameConfigOption("FocusTargetDisp", ConfigType.UInt)] FocusTargetDisp, /// - /// System option with the internal name ItemDetailDisp. + /// UiControl option with the internal name ItemDetailDisp. /// This option is a UInt. /// [GameConfigOption("ItemDetailDisp", ConfigType.UInt)] ItemDetailDisp, /// - /// System option with the internal name ActionDetailDisp. + /// UiControl option with the internal name ActionDetailDisp. /// This option is a UInt. /// [GameConfigOption("ActionDetailDisp", ConfigType.UInt)] ActionDetailDisp, /// - /// System option with the internal name DetailTrackingType. + /// UiControl option with the internal name DetailTrackingType. /// This option is a UInt. /// [GameConfigOption("DetailTrackingType", ConfigType.UInt)] DetailTrackingType, /// - /// System option with the internal name ToolTipDisp. + /// UiControl option with the internal name ToolTipDisp. /// This option is a UInt. /// [GameConfigOption("ToolTipDisp", ConfigType.UInt)] ToolTipDisp, /// - /// System option with the internal name MapPermeationRate. + /// UiControl option with the internal name MapPermeationRate. /// This option is a UInt. /// [GameConfigOption("MapPermeationRate", ConfigType.UInt)] MapPermeationRate, /// - /// System option with the internal name MapOperationType. + /// UiControl option with the internal name MapOperationType. /// This option is a UInt. /// [GameConfigOption("MapOperationType", ConfigType.UInt)] MapOperationType, /// - /// System option with the internal name PartyListDisp. + /// UiControl option with the internal name PartyListDisp. /// This option is a UInt. /// [GameConfigOption("PartyListDisp", ConfigType.UInt)] PartyListDisp, /// - /// System option with the internal name PartyListNameType. + /// UiControl option with the internal name PartyListNameType. /// This option is a UInt. /// [GameConfigOption("PartyListNameType", ConfigType.UInt)] PartyListNameType, /// - /// System option with the internal name FlyTextDisp. + /// UiControl option with the internal name FlyTextDisp. /// This option is a UInt. /// [GameConfigOption("FlyTextDisp", ConfigType.UInt)] FlyTextDisp, /// - /// System option with the internal name MapPermeationMode. + /// UiControl option with the internal name MapPermeationMode. /// This option is a UInt. /// [GameConfigOption("MapPermeationMode", ConfigType.UInt)] MapPermeationMode, /// - /// System option with the internal name AllianceList1Disp. + /// UiControl option with the internal name AllianceList1Disp. /// This option is a UInt. /// [GameConfigOption("AllianceList1Disp", ConfigType.UInt)] AllianceList1Disp, /// - /// System option with the internal name AllianceList2Disp. + /// UiControl option with the internal name AllianceList2Disp. /// This option is a UInt. /// [GameConfigOption("AllianceList2Disp", ConfigType.UInt)] AllianceList2Disp, /// - /// System option with the internal name TargetInfoSelfBuff. + /// UiControl option with the internal name TargetInfoSelfBuff. /// This option is a UInt. /// [GameConfigOption("TargetInfoSelfBuff", ConfigType.UInt)] TargetInfoSelfBuff, /// - /// System option with the internal name PopUpTextDisp. + /// UiControl option with the internal name PopUpTextDisp. /// This option is a UInt. /// [GameConfigOption("PopUpTextDisp", ConfigType.UInt)] PopUpTextDisp, /// - /// System option with the internal name ContentsInfoDisp. + /// UiControl option with the internal name ContentsInfoDisp. /// This option is a UInt. /// [GameConfigOption("ContentsInfoDisp", ConfigType.UInt)] ContentsInfoDisp, /// - /// System option with the internal name DutyListHideWhenCntInfoDisp. + /// UiControl option with the internal name DutyListHideWhenCntInfoDisp. /// This option is a UInt. /// [GameConfigOption("DutyListHideWhenCntInfoDisp", ConfigType.UInt)] DutyListHideWhenCntInfoDisp, /// - /// System option with the internal name DutyListNumDisp. + /// UiControl option with the internal name DutyListNumDisp. /// This option is a UInt. /// [GameConfigOption("DutyListNumDisp", ConfigType.UInt)] DutyListNumDisp, /// - /// System option with the internal name InInstanceContentDutyListDisp. + /// UiControl option with the internal name InInstanceContentDutyListDisp. /// This option is a UInt. /// [GameConfigOption("InInstanceContentDutyListDisp", ConfigType.UInt)] InInstanceContentDutyListDisp, /// - /// System option with the internal name InPublicContentDutyListDisp. + /// UiControl option with the internal name InPublicContentDutyListDisp. /// This option is a UInt. /// [GameConfigOption("InPublicContentDutyListDisp", ConfigType.UInt)] InPublicContentDutyListDisp, /// - /// System option with the internal name ContentsInfoJoiningRequestDisp. + /// UiControl option with the internal name ContentsInfoJoiningRequestDisp. /// This option is a UInt. /// [GameConfigOption("ContentsInfoJoiningRequestDisp", ConfigType.UInt)] ContentsInfoJoiningRequestDisp, /// - /// System option with the internal name ContentsInfoJoiningRequestSituationDisp. + /// UiControl option with the internal name ContentsInfoJoiningRequestSituationDisp. /// This option is a UInt. /// [GameConfigOption("ContentsInfoJoiningRequestSituationDisp", ConfigType.UInt)] ContentsInfoJoiningRequestSituationDisp, /// - /// System option with the internal name HotbarDispSetNum. + /// UiControl option with the internal name HotbarDispSetNum. /// This option is a UInt. /// [GameConfigOption("HotbarDispSetNum", ConfigType.UInt)] HotbarDispSetNum, /// - /// System option with the internal name HotbarDispSetChangeType. + /// UiControl option with the internal name HotbarDispSetChangeType. /// This option is a UInt. /// [GameConfigOption("HotbarDispSetChangeType", ConfigType.UInt)] HotbarDispSetChangeType, /// - /// System option with the internal name HotbarDispSetDragType. + /// UiControl option with the internal name HotbarDispSetDragType. /// This option is a UInt. /// [GameConfigOption("HotbarDispSetDragType", ConfigType.UInt)] HotbarDispSetDragType, /// - /// System option with the internal name MainCommandType. + /// UiControl option with the internal name MainCommandType. /// This option is a UInt. /// [GameConfigOption("MainCommandType", ConfigType.UInt)] MainCommandType, /// - /// System option with the internal name MainCommandDisp. + /// UiControl option with the internal name MainCommandDisp. /// This option is a UInt. /// [GameConfigOption("MainCommandDisp", ConfigType.UInt)] MainCommandDisp, /// - /// System option with the internal name MainCommandDragShortcut. + /// UiControl option with the internal name MainCommandDragShortcut. /// This option is a UInt. /// [GameConfigOption("MainCommandDragShortcut", ConfigType.UInt)] MainCommandDragShortcut, /// - /// System option with the internal name HotbarDispLookNum. + /// UiControl option with the internal name HotbarDispLookNum. /// This option is a UInt. /// [GameConfigOption("HotbarDispLookNum", ConfigType.UInt)] From 75f414578d26cc021f2d79a061079c6804bbad7f Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Thu, 5 Dec 2024 06:23:17 +0900 Subject: [PATCH 183/375] Don't allow nameplate GameObject lookup when ObjectId is invalid (#2137) --- .../Gui/NamePlate/NamePlateUpdateHandler.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 94f185966..4f16ab660 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -328,9 +328,22 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId; /// - public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable[ - this.context.Ui3DModule->NamePlateObjectInfoPointers[this.ArrayIndex] - .Value->GameObject->ObjectIndex]; + public IGameObject? GameObject + { + get + { + if (this.GameObjectId == 0xE0000000) + { + // Skipping Ui3DModule lookup for invalid nameplate (NamePlateInfo->ObjectId is 0xE0000000). This + // prevents crashes around certain Doman Reconstruction cutscenes. + return null; + } + + return this.gameObject ??= this.context.ObjectTable[ + this.context.Ui3DModule->NamePlateObjectInfoPointers[this.ArrayIndex] + .Value->GameObject->ObjectIndex]; + } + } /// public IBattleChara? BattleChara => this.GameObject as IBattleChara; From fd0b6722adcc3a3745c29d937c4a9f6702086b26 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 6 Dec 2024 18:12:57 +0100 Subject: [PATCH 184/375] build: 11.0.2.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 61fb1efe4..39bc83762 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 11.0.1.0 + 11.0.2.0 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From e2298ea09967836db2eec6a5c63044aea0526ff8 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Fri, 6 Dec 2024 21:42:35 +0100 Subject: [PATCH 185/375] Update ClientStructs (#2139) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index b3a25a18b..87b2ccfdd 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit b3a25a18bab61beda6050ef15aca721cfa14e1a0 +Subproject commit 87b2ccfdd68242d51eaa179c6c38537489a0184d From 70af62885b105ad98df341b50841b5fdb93ce95c Mon Sep 17 00:00:00 2001 From: srkizer Date: Wed, 18 Dec 2024 00:31:44 +0900 Subject: [PATCH 186/375] chore: remove [Experimental] from SeStringRenderer (#2144) It hasn't caused problem for a while (though performance probably can be better), and looks good enough to remove this attribute. --- Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs | 2 -- .../Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs | 2 -- .../Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs | 2 -- .../Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs | 2 -- Dalamud/Interface/Utility/ImGuiHelpers.cs | 2 -- 5 files changed, 10 deletions(-) diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index 7c924f503..61e0e79b8 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -60,9 +60,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree EdgeStrength = 1f, }; -#pragma warning disable SeStringRenderer ImGuiHelpers.SeStringWrapped(this.NodeText.AsSpan(), style); -#pragma warning restore SeStringRenderer } catch { diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs index c134e02c3..791dc5310 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs @@ -12,8 +12,6 @@ using Lumina.Text.ReadOnly; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; -#pragma warning disable SeStringRenderer - /// /// Widget for displaying AtkArrayData. /// diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs index ac576da77..5cefc0853 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -18,8 +18,6 @@ using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; -#pragma warning disable SeStringRenderer - /// /// Widget for displaying inventory data. /// diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index e026a6d2f..5afda62ac 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -23,8 +23,6 @@ using Lumina.Text.ReadOnly; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; -#pragma warning disable SeStringRenderer - /// /// Widget for displaying Addon Data. /// diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 8dedae5cd..2093d9bcb 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -212,7 +212,6 @@ public static class ImGuiHelpers /// Interaction result of the rendered text. /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. /// The function definition is stable; only in the next API version a function may be removed. - [Experimental("SeStringRenderer")] public static SeStringDrawResult SeStringWrapped( ReadOnlySpan sss, scoped in SeStringDrawParams style = default, @@ -229,7 +228,6 @@ public static class ImGuiHelpers /// Interaction result of the rendered text. /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. /// The function definition is stable; only in the next API version a function may be removed. - [Experimental("SeStringRenderer")] public static SeStringDrawResult CompileSeStringWrapped( string text, scoped in SeStringDrawParams style = default, From 4dedaac50e43674773b0e97e4ec2f5f1faff06af Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:42:09 +0100 Subject: [PATCH 187/375] Update ClientStructs (#2140) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 87b2ccfdd..df03181cc 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 87b2ccfdd68242d51eaa179c6c38537489a0184d +Subproject commit df03181ccbbbfead3db116b59359dae4a31cb07d From 3ccd93aaf15b5206b39bc0a2dca5c0f8759272cd Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 17 Dec 2024 16:56:55 +0100 Subject: [PATCH 188/375] Return nullable on XivChatType.GetDetails (#2143) --- Dalamud/Game/Text/XivChatTypeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Game/Text/XivChatTypeExtensions.cs b/Dalamud/Game/Text/XivChatTypeExtensions.cs index 3bdeb1525..457d3b145 100644 --- a/Dalamud/Game/Text/XivChatTypeExtensions.cs +++ b/Dalamud/Game/Text/XivChatTypeExtensions.cs @@ -12,7 +12,7 @@ public static class XivChatTypeExtensions /// /// The chat type. /// The info attribute. - public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) + public static XivChatTypeInfoAttribute? GetDetails(this XivChatType chatType) { return chatType.GetAttribute(); } From f7ef68d65e215afba1cf24cbfba9e0bb21e595c6 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 17 Dec 2024 21:50:03 +0100 Subject: [PATCH 189/375] build: 11.0.3.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 39bc83762..75afaae1b 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,7 +9,7 @@ - 11.0.2.0 + 11.0.3.0 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 2e6cb6ef006da80a1d053d7693530444463d6d34 Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 19 Dec 2024 22:19:50 +0100 Subject: [PATCH 190/375] - Add chat notification back to AutoUpdate (#2146) - ImRaii UI elements in AutoUpdate tab - Fix scoping in IconButton --- .../Internal/DalamudConfiguration.cs | 19 ++- .../Components/ImGuiComponents.IconButton.cs | 13 +- .../DalamudComponents.PluginPicker.cs | 16 +- .../Settings/Tabs/SettingsTabAutoUpdate.cs | 76 +++++---- .../Internal/AutoUpdate/AutoUpdateManager.cs | 156 ++++++++++++------ 5 files changed, 166 insertions(+), 114 deletions(-) diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 5c2378f68..4df38d6df 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -249,7 +249,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. /// public bool IsDocking { get; set; } - + /// /// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects. /// This setting is effected by the in-game "System Sounds" option and volume. @@ -484,10 +484,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService public AutoUpdateBehavior? AutoUpdateBehavior { get; set; } = null; /// - /// Gets or sets a value indicating whether or not users should be notified regularly about pending updates. + /// Gets or sets a value indicating whether users should be notified regularly about pending updates. /// public bool CheckPeriodicallyForUpdates { get; set; } = true; + /// + /// Gets or sets a value indicating whether users should be notified about updates in chat. + /// + public bool SendUpdateNotificationToChat { get; set; } = false; + /// /// Load a configuration from the provided path. /// @@ -504,7 +509,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { deserialized = JsonConvert.DeserializeObject(text, SerializerSettings); - + // If this reads as null, the file was empty, that's no good if (deserialized == null) throw new Exception("Read config was null."); @@ -530,7 +535,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { Log.Error(e, "Failed to set defaults for DalamudConfiguration"); } - + return deserialized; } @@ -549,7 +554,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { this.Save(); } - + /// void IInternalDisposableService.DisposeService() { @@ -595,14 +600,14 @@ internal sealed class DalamudConfiguration : IInternalDisposableService this.ReduceMotions = winAnimEnabled == 0; } } - + // Migrate old auto-update setting to new auto-update behavior this.AutoUpdateBehavior ??= this.AutoUpdatePlugins ? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll : Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify; #pragma warning restore CS0618 } - + private void Save() { ThreadSafety.AssertMainThread(); diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index d2b1b4a36..10f177590 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -272,15 +272,14 @@ public static partial class ImGuiComponents /// Width. public static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) { + Vector2 iconSize; using (ImRaii.PushFont(UiBuilder.IconFont)) { - var iconSize = ImGui.CalcTextSize(icon.ToIconString()); - - var textSize = ImGui.CalcTextSize(text); - - var iconPadding = 3 * ImGuiHelpers.GlobalScale; - - return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; + iconSize = ImGui.CalcTextSize(icon.ToIconString()); } + + var textSize = ImGui.CalcTextSize(text); + var iconPadding = 3 * ImGuiHelpers.GlobalScale; + return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; } } diff --git a/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs b/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs index f0ce6bc82..3d31bbda6 100644 --- a/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs +++ b/Dalamud/Interface/Internal/DesignSystem/DalamudComponents.PluginPicker.cs @@ -32,19 +32,21 @@ internal static partial class DalamudComponents var pm = Service.GetNullable(); if (pm == null) return 0; - + var addPluginToProfilePopupId = ImGui.GetID(id); using var popup = ImRaii.Popup(id); if (popup.Success) { var width = ImGuiHelpers.GlobalScale * 300; - + ImGui.SetNextItemWidth(width); ImGui.InputTextWithHint("###pluginPickerSearch", Locs.SearchHint, ref pickerSearch, 255); var currentSearchString = pickerSearch; - if (ImGui.BeginListBox("###pluginPicker", new Vector2(width, width - 80))) + + using var listBox = ImRaii.ListBox("###pluginPicker", new Vector2(width, width - 80)); + if (listBox.Success) { // TODO: Plugin searching should be abstracted... installer and this should use the same search var plugins = pm.InstalledPlugins.Where( @@ -53,19 +55,15 @@ internal static partial class DalamudComponents currentSearchString, StringComparison.InvariantCultureIgnoreCase))) .Where(pluginFiltered ?? (_ => true)); - + foreach (var plugin in plugins) { - using var disabled2 = - ImRaii.Disabled(pluginDisabled(plugin)); - + using var disabled2 = ImRaii.Disabled(pluginDisabled(plugin)); if (ImGui.Selectable($"{plugin.Manifest.Name}{(plugin is LocalDevPlugin ? "(dev plugin)" : string.Empty)}###selector{plugin.Manifest.InternalName}")) { onClicked(plugin); } } - - ImGui.EndListBox(); } } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs index 77c79c96d..9356131ad 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAutoUpdate.cs @@ -23,10 +23,11 @@ public class SettingsTabAutoUpdates : SettingsTab { private AutoUpdateBehavior behavior; private bool checkPeriodically; + private bool chatNotification; private string pickerSearch = string.Empty; private List autoUpdatePreferences = []; - - public override SettingsEntry[] Entries { get; } = Array.Empty(); + + public override SettingsEntry[] Entries { get; } = []; public override string Title => Loc.Localize("DalamudSettingsAutoUpdates", "Auto-Updates"); @@ -36,15 +37,15 @@ public class SettingsTabAutoUpdates : SettingsTab "Dalamud can update your plugins automatically, making sure that you always " + "have the newest features and bug fixes. You can choose when and how auto-updates are run here.")); ImGuiHelpers.ScaledDummy(2); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdateDisclaimer1", "You can always update your plugins manually by clicking the update button in the plugin list. " + "You can also opt into updates for specific plugins by right-clicking them and selecting \"Always auto-update\".")); ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdateDisclaimer2", "Dalamud will only notify you about updates while you are idle.")); - + ImGuiHelpers.ScaledDummy(8); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudWhite, Loc.Localize("DalamudSettingsAutoUpdateBehavior", "When the game starts...")); var behaviorInt = (int)this.behavior; @@ -62,20 +63,21 @@ public class SettingsTabAutoUpdates : SettingsTab "These updates are not reviewed by the Dalamud team and may contain malicious code."); ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudOrange, warning); } - + ImGuiHelpers.ScaledDummy(8); - + + ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdateChatMessage", "Show notification about updates available in chat"), ref this.chatNotification); ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePeriodically", "Periodically check for new updates while playing"), ref this.checkPeriodically); ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePeriodicallyHint", "Plugins won't update automatically after startup, you will only receive a notification while you are not actively playing.")); - + ImGuiHelpers.ScaledDummy(5); ImGui.Separator(); ImGuiHelpers.ScaledDummy(5); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudWhite, Loc.Localize("DalamudSettingsAutoUpdateOptedIn", "Per-plugin overrides")); - + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudWhite, Loc.Localize("DalamudSettingsAutoUpdateOverrideHint", "Here, you can choose to receive or not to receive updates for specific plugins. " + "This will override the settings above for the selected plugins.")); @@ -83,25 +85,25 @@ public class SettingsTabAutoUpdates : SettingsTab if (this.autoUpdatePreferences.Count == 0) { ImGuiHelpers.ScaledDummy(20); - + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) { ImGuiHelpers.CenteredText(Loc.Localize("DalamudSettingsAutoUpdateOptedInHint2", "You don't have auto-update rules for any plugins.")); } - + ImGuiHelpers.ScaledDummy(2); } else { ImGuiHelpers.ScaledDummy(5); - + var pic = Service.Get(); var windowSize = ImGui.GetWindowSize(); var pluginLineHeight = 32 * ImGuiHelpers.GlobalScale; Guid? wantRemovePluginGuid = null; - + foreach (var preference in this.autoUpdatePreferences) { var pmPlugin = Service.Get().InstalledPlugins @@ -120,11 +122,12 @@ public class SettingsTabAutoUpdates : SettingsTab if (pmPlugin.IsDev) { ImGui.SetCursorPos(cursorBeforeIcon); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.7f); - ImGui.Image(pic.DevPluginIcon.ImGuiHandle, new Vector2(pluginLineHeight)); - ImGui.PopStyleVar(); + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.7f)) + { + ImGui.Image(pic.DevPluginIcon.ImGuiHandle, new Vector2(pluginLineHeight)); + } } - + ImGui.SameLine(); var text = $"{pmPlugin.Name}{(pmPlugin.IsDev ? " (dev plugin" : string.Empty)}"; @@ -147,7 +150,7 @@ public class SettingsTabAutoUpdates : SettingsTab ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (textHeight.Y / 2)); ImGui.TextUnformatted(text); - + ImGui.SetCursorPos(before); } @@ -166,19 +169,18 @@ public class SettingsTabAutoUpdates : SettingsTab } ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 250); - if (ImGui.BeginCombo( - $"###autoUpdateBehavior{preference.WorkingPluginId}", - OptKindToString(preference.Kind))) + using (var combo = ImRaii.Combo($"###autoUpdateBehavior{preference.WorkingPluginId}", OptKindToString(preference.Kind))) { - foreach (var kind in Enum.GetValues()) + if (combo.Success) { - if (ImGui.Selectable(OptKindToString(kind))) + foreach (var kind in Enum.GetValues()) { - preference.Kind = kind; + if (ImGui.Selectable(OptKindToString(kind))) + { + preference.Kind = kind; + } } } - - ImGui.EndCombo(); } ImGui.SameLine(); @@ -193,7 +195,7 @@ public class SettingsTabAutoUpdates : SettingsTab if (ImGui.IsItemHovered()) ImGui.SetTooltip(Loc.Localize("DalamudSettingsAutoUpdateOptInRemove", "Remove this override")); } - + if (wantRemovePluginGuid != null) { this.autoUpdatePreferences.RemoveAll(x => x.WorkingPluginId == wantRemovePluginGuid); @@ -205,19 +207,19 @@ public class SettingsTabAutoUpdates : SettingsTab var id = plugin.EffectiveWorkingPluginId; if (id == Guid.Empty) throw new InvalidOperationException("Plugin ID is empty."); - + this.autoUpdatePreferences.Add(new AutoUpdatePreference(id)); } - + bool IsPluginDisabled(LocalPlugin plugin) => this.autoUpdatePreferences.Any(x => x.WorkingPluginId == plugin.EffectiveWorkingPluginId); - + bool IsPluginFiltered(LocalPlugin plugin) => !plugin.IsDev; - + var pickerId = DalamudComponents.DrawPluginPicker( "###autoUpdatePicker", ref this.pickerSearch, OnPluginPicked, IsPluginDisabled, IsPluginFiltered); - + const FontAwesomeIcon addButtonIcon = FontAwesomeIcon.Plus; var addButtonText = Loc.Localize("DalamudSettingsAutoUpdateOptInAdd", "Add new override"); ImGuiHelpers.CenterCursorFor(ImGuiComponents.GetIconButtonWithTextWidth(addButtonIcon, addButtonText)); @@ -235,20 +237,22 @@ public class SettingsTabAutoUpdates : SettingsTab var configuration = Service.Get(); this.behavior = configuration.AutoUpdateBehavior ?? AutoUpdateBehavior.None; + this.chatNotification = configuration.SendUpdateNotificationToChat; this.checkPeriodically = configuration.CheckPeriodicallyForUpdates; this.autoUpdatePreferences = configuration.PluginAutoUpdatePreferences; - + base.Load(); } public override void Save() { var configuration = Service.Get(); - + configuration.AutoUpdateBehavior = this.behavior; + configuration.SendUpdateNotificationToChat = this.chatNotification; configuration.CheckPeriodicallyForUpdates = this.checkPeriodically; configuration.PluginAutoUpdatePreferences = this.autoUpdatePreferences; - + base.Save(); } } diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index c25ec4ee4..99850ddb4 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -9,6 +9,10 @@ using Dalamud.Console; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.Gui; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.EventArgs; @@ -31,17 +35,17 @@ namespace Dalamud.Plugin.Internal.AutoUpdate; internal class AutoUpdateManager : IServiceType { private static readonly ModuleLog Log = new("AUTOUPDATE"); - + /// /// Time we should wait after login to update. /// private static readonly TimeSpan UpdateTimeAfterLogin = TimeSpan.FromSeconds(20); - + /// /// Time we should wait between scheduled update checks. /// private static readonly TimeSpan TimeBetweenUpdateChecks = TimeSpan.FromHours(2); - + /// /// Time we should wait between scheduled update checks if the user has dismissed the notification, /// instead of updating. We don't want to spam the user with notifications. @@ -56,28 +60,30 @@ internal class AutoUpdateManager : IServiceType [ServiceManager.ServiceDependency] private readonly PluginManager pluginManager = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly DalamudConfiguration config = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly NotificationManager notificationManager = Service.Get(); - + [ServiceManager.ServiceDependency] private readonly DalamudInterface dalamudInterface = Service.Get(); - + private readonly IConsoleVariable isDryRun; - + private DateTime? loginTime; private DateTime? nextUpdateCheckTime; private DateTime? unblockedSince; - + private bool hasStartedInitialUpdateThisSession; private IActiveNotification? updateNotification; - + private Task? autoUpdateTask; - + + private readonly Task openInstallerWindowLink; + /// /// Initializes a new instance of the class. /// @@ -92,7 +98,18 @@ internal class AutoUpdateManager : IServiceType t.Result.Logout += (int type, int code) => this.OnLogout(); }); Service.GetAsync().ContinueWith(t => { t.Result.Update += this.OnUpdate; }); - + + this.openInstallerWindowLink = + Service.GetAsync().ContinueWith( + chatGuiTask => chatGuiTask.Result.AddChatLinkHandler( + "Dalamud", + 1001, + (_, _) => + { + Service.GetNullable()?.OpenPluginInstallerTo(PluginInstallerOpenKind.InstalledPlugins); + })); + + this.isDryRun = console.AddVariable("dalamud.autoupdate.dry_run", "Simulate updates instead", false); console.AddCommand("dalamud.autoupdate.trigger_login", "Trigger a login event", () => { @@ -106,36 +123,36 @@ internal class AutoUpdateManager : IServiceType return true; }); } - + private enum UpdateListingRestriction { Unrestricted, AllowNone, AllowMainRepo, } - + /// /// Gets a value indicating whether or not auto-updates have already completed this session. /// public bool IsAutoUpdateComplete { get; private set; } - + /// /// Gets the time of the next scheduled update check. /// public DateTime? NextUpdateCheckTime => this.nextUpdateCheckTime; - + /// /// Gets the time the auto-update was unblocked. /// public DateTime? UnblockedSince => this.unblockedSince; - + private static UpdateListingRestriction DecideUpdateListingRestriction(AutoUpdateBehavior behavior) { return behavior switch { // We don't generally allow any updates in this mode, but specific opt-ins. AutoUpdateBehavior.None => UpdateListingRestriction.AllowNone, - + // If we're only notifying, I guess it's fine to list all plugins. AutoUpdateBehavior.OnlyNotify => UpdateListingRestriction.Unrestricted, @@ -144,7 +161,7 @@ internal class AutoUpdateManager : IServiceType _ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, null), }; } - + private static void DrawOpenInstallerNotificationButton(bool primary, PluginInstallerOpenKind kind, IActiveNotification notification) { if (primary ? @@ -179,7 +196,7 @@ internal class AutoUpdateManager : IServiceType this.updateNotification = null; } } - + // If we're blocked, we don't do anything. if (!isUnblocked) return; @@ -199,16 +216,16 @@ internal class AutoUpdateManager : IServiceType if (!this.hasStartedInitialUpdateThisSession && DateTime.Now > this.loginTime.Value.Add(UpdateTimeAfterLogin)) { this.hasStartedInitialUpdateThisSession = true; - + var currentlyUpdatablePlugins = this.GetAvailablePluginUpdates(DecideUpdateListingRestriction(behavior)); if (currentlyUpdatablePlugins.Count == 0) { this.IsAutoUpdateComplete = true; this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks; - + return; } - + // TODO: This is not 100% what we want... Plugins that are opted-in should be updated regardless of the behavior, // and we should show a notification for the others afterwards. if (behavior == AutoUpdateBehavior.OnlyNotify) @@ -241,6 +258,7 @@ internal class AutoUpdateManager : IServiceType Log.Error(t.Exception!, "Failed to reload plugin masters for auto-update"); } + Log.Verbose($"Available Updates: {string.Join(", ", this.pluginManager.UpdatablePlugins.Select(s => s.UpdateManifest.InternalName))}"); var updatable = this.GetAvailablePluginUpdates( DecideUpdateListingRestriction(behavior)); @@ -252,7 +270,7 @@ internal class AutoUpdateManager : IServiceType { this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks; Log.Verbose( - "Auto update found nothing to do, next update at {Time}", + "Auto update found nothing to do, next update at {Time}", this.nextUpdateCheckTime); } }); @@ -263,13 +281,13 @@ internal class AutoUpdateManager : IServiceType { if (this.updateNotification != null) throw new InvalidOperationException("Already showing a notification"); - + this.updateNotification = this.notificationManager.AddNotification(notification); this.updateNotification.Dismiss += _ => { this.updateNotification = null; - + // Schedule the next update opportunistically for when this closes. this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks; }; @@ -291,7 +309,7 @@ internal class AutoUpdateManager : IServiceType { Log.Warning("Auto-update task was canceled"); } - + this.autoUpdateTask = null; this.IsAutoUpdateComplete = true; }); @@ -321,20 +339,20 @@ internal class AutoUpdateManager : IServiceType notification.Content = Locs.NotificationContentUpdating(updateProgress.CurrentPluginManifest.Name); notification.Progress = (float)updateProgress.PluginsProcessed / updateProgress.TotalPlugins; }; - + var pluginStates = (await this.pluginManager.UpdatePluginsAsync(updatablePlugins, this.isDryRun.Value, true, progress)).ToList(); this.pluginManager.PrintUpdatedPlugins(pluginStates, Loc.Localize("DalamudPluginAutoUpdate", "The following plugins were auto-updated:")); notification.Progress = 1; notification.UserDismissable = true; notification.HardExpiry = DateTime.Now.AddSeconds(30); - + notification.DrawActions += _ => { ImGuiHelpers.ScaledDummy(2); DrawOpenInstallerNotificationButton(true, PluginInstallerOpenKind.InstalledPlugins, notification); }; - + // Update the notification to show the final state if (pluginStates.All(x => x.Status == PluginUpdateStatus.StatusKind.Success)) { @@ -342,7 +360,7 @@ internal class AutoUpdateManager : IServiceType // Janky way to make sure the notification does not change before it's minimized... await Task.Delay(500); - + notification.Title = Locs.NotificationTitleUpdatesSuccessful; notification.MinimizedText = Locs.NotificationContentUpdatesSuccessfulMinimized; notification.Type = NotificationType.Success; @@ -354,11 +372,11 @@ internal class AutoUpdateManager : IServiceType notification.MinimizedText = Locs.NotificationContentUpdatesFailedMinimized; notification.Type = NotificationType.Error; notification.Content = Locs.NotificationContentUpdatesFailed; - + var failedPlugins = pluginStates .Where(x => x.Status != PluginUpdateStatus.StatusKind.Success) .Select(x => x.Name).ToList(); - + notification.Content += "\n" + Locs.NotificationContentFailedPlugins(failedPlugins); } } @@ -367,7 +385,7 @@ internal class AutoUpdateManager : IServiceType { if (updatablePlugins.Count == 0) return; - + var notification = this.GetBaseNotification(new Notification { Title = Locs.NotificationTitleUpdatesAvailable, @@ -400,16 +418,44 @@ internal class AutoUpdateManager : IServiceType notification.Dismiss += args => { if (args.Reason != NotificationDismissReason.Manual) return; - + this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecksIfDismissed; Log.Verbose("User dismissed update notification, next check at {Time}", this.nextUpdateCheckTime); }; + + // Send out a chat message only if the user requested so + if (!this.config.SendUpdateNotificationToChat) + return; + + var chatGui = Service.GetNullable(); + if (chatGui == null) + { + Log.Verbose("Unable to get chat gui, discard notification for chat."); + return; + } + + chatGui.Print(new XivChatEntry + { + Message = new SeString(new List + { + new TextPayload(Locs.NotificationContentUpdatesAvailableMinimized(updatablePlugins.Count)), + new TextPayload(" ["), + new UIForegroundPayload(500), + this.openInstallerWindowLink.Result, + new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")), + RawPayload.LinkTerminator, + new UIForegroundPayload(0), + new TextPayload("]"), + }), + + Type = XivChatType.Urgent, + }); } - + private List GetAvailablePluginUpdates(UpdateListingRestriction restriction) { var optIns = this.config.PluginAutoUpdatePreferences.ToArray(); - + // Get all of our updatable plugins and do some initial filtering that must apply to all plugins. var updateablePlugins = this.pluginManager.UpdatablePlugins .Where( @@ -423,14 +469,14 @@ internal class AutoUpdateManager : IServiceType bool FilterPlugin(AvailablePluginUpdate availablePluginUpdate) { var optIn = optIns.FirstOrDefault(x => x.WorkingPluginId == availablePluginUpdate.InstalledPlugin.EffectiveWorkingPluginId); - + // If this is an opt-out, we don't update. if (optIn is { Kind: AutoUpdatePreference.OptKind.NeverUpdate }) return false; if (restriction == UpdateListingRestriction.AllowNone && optIn is not { Kind: AutoUpdatePreference.OptKind.AlwaysUpdate }) return false; - + if (restriction == UpdateListingRestriction.AllowMainRepo && availablePluginUpdate.InstalledPlugin.IsThirdParty) return false; @@ -442,7 +488,7 @@ internal class AutoUpdateManager : IServiceType { this.loginTime = DateTime.Now; } - + private void OnLogout() { this.loginTime = null; @@ -452,7 +498,7 @@ internal class AutoUpdateManager : IServiceType { var condition = Service.Get(); return this.IsPluginManagerReady() && - !this.dalamudInterface.IsPluginInstallerOpen && + !this.dalamudInterface.IsPluginInstallerOpen && condition.OnlyAny(ConditionFlag.NormalConditions, ConditionFlag.Jumping, ConditionFlag.Mounted, @@ -469,21 +515,21 @@ internal class AutoUpdateManager : IServiceType public static string NotificationButtonOpenPluginInstaller => Loc.Localize("AutoUpdateOpenPluginInstaller", "Open installer"); public static string NotificationButtonUpdate => Loc.Localize("AutoUpdateUpdate", "Update"); - + public static string NotificationTitleUpdatesAvailable => Loc.Localize("AutoUpdateUpdatesAvailable", "Updates available!"); - + public static string NotificationTitleUpdatesSuccessful => Loc.Localize("AutoUpdateUpdatesSuccessful", "Updates successful!"); - + public static string NotificationTitleUpdatingPlugins => Loc.Localize("AutoUpdateUpdatingPlugins", "Updating plugins..."); - + public static string NotificationTitleUpdatesFailed => Loc.Localize("AutoUpdateUpdatesFailed", "Updates failed!"); - + public static string NotificationContentUpdatesSuccessful => Loc.Localize("AutoUpdateUpdatesSuccessfulContent", "All plugins have been updated successfully."); - + public static string NotificationContentUpdatesSuccessfulMinimized => Loc.Localize("AutoUpdateUpdatesSuccessfulContentMinimized", "Plugins updated successfully."); - + public static string NotificationContentUpdatesFailed => Loc.Localize("AutoUpdateUpdatesFailedContent", "Some plugins failed to update. Please check the plugin installer for more information."); - + public static string NotificationContentUpdatesFailedMinimized => Loc.Localize("AutoUpdateUpdatesFailedContentMinimized", "Plugins failed to update."); public static string NotificationContentUpdatesAvailable(ICollection updatablePlugins) @@ -497,20 +543,20 @@ internal class AutoUpdateManager : IServiceType "There are {0} plugins that can be updated:"), updatablePlugins.Count)) + "\n\n" + string.Join(", ", updatablePlugins.Select(x => x.InstalledPlugin.Manifest.Name)); - + public static string NotificationContentUpdatesAvailableMinimized(int numUpdates) => numUpdates == 1 ? - Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedSingular", "1 plugin update available") : + Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedSingular", "1 plugin update available") : string.Format(Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedPlural", "{0} plugin updates available"), numUpdates); - + public static string NotificationContentPreparingToUpdate(int numPlugins) => numPlugins == 1 ? - Loc.Localize("AutoUpdatePreparingToUpdateSingular", "Preparing to update 1 plugin...") : + Loc.Localize("AutoUpdatePreparingToUpdateSingular", "Preparing to update 1 plugin...") : string.Format(Loc.Localize("AutoUpdatePreparingToUpdatePlural", "Preparing to update {0} plugins..."), numPlugins); - + public static string NotificationContentUpdating(string name) => string.Format(Loc.Localize("AutoUpdateUpdating", "Updating {0}..."), name); - + public static string NotificationContentFailedPlugins(IEnumerable failedPlugins) => string.Format(Loc.Localize("AutoUpdateFailedPlugins", "Failed plugin(s): {0}"), string.Join(", ", failedPlugins)); } From 87d069267bf848424530900add96f37dbab7c4d4 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:33:54 +0100 Subject: [PATCH 191/375] Update ClientStructs (#2148) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index df03181cc..cc98a564d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit df03181ccbbbfead3db116b59359dae4a31cb07d +Subproject commit cc98a564d0787813d4be082bf75f5bb98e0ed12f From 2d6689b9d3d390abaaf9a9dd24d2a9eb4fdcd666 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 22 Dec 2024 19:14:18 +0100 Subject: [PATCH 192/375] move lumina version out of csproj --- Dalamud.CorePlugin/Dalamud.CorePlugin.csproj | 6 +++--- Dalamud/Dalamud.csproj | 8 ++++---- Directory.Build.props | 9 ++++++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index b85607f0f..ca78f09ad 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,9 +27,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 75afaae1b..e1bb20db6 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -9,8 +9,8 @@ - 11.0.3.0 XIV Launcher addon framework + 11.0.3.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) @@ -71,14 +71,14 @@ - - + + all - + diff --git a/Directory.Build.props b/Directory.Build.props index 0c5af2e37..6ee6f4b11 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,12 @@ - + + + 5.6.0 + 7.1.3 + 13.0.3 + + + From c647d07d94f7f6e2f192d22d3900f18e583df529 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 22 Dec 2024 19:19:56 +0100 Subject: [PATCH 193/375] build: 11.0.4.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index e1bb20db6..a6ac07227 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.3.0 + 11.0.4.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From bc21621d9ac1549a12e02854797be7a4234cd400 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 16:50:06 +0100 Subject: [PATCH 194/375] add release script --- release.ps1 | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 release.ps1 diff --git a/release.ps1 b/release.ps1 new file mode 100644 index 000000000..8863a1214 --- /dev/null +++ b/release.ps1 @@ -0,0 +1,31 @@ +param( + [string]$VersionString +) + +if (-not $VersionString) { + Write-Error "Version string is required as the first argument." + exit 1 +} + +$csprojPath = "Dalamud/Dalamud.csproj" + +if (-not (Test-Path $csprojPath)) { + Write-Error "Cannot find Dalamud.csproj at the specified path." + exit 1 +} + +# Update the version in the csproj file +(Get-Content $csprojPath) -replace '.*?', "$VersionString" | Set-Content $csprojPath + +# Commit the change +git add $csprojPath +git commit -m "build: $VersionString" + +# Get the current branch +$currentBranch = git rev-parse --abbrev-ref HEAD + +# Create a tag +git tag -a -m "v$VersionString" $VersionString + +# Push atomically +git push origin $currentBranch $VersionString \ No newline at end of file From 8572ed8b1ef34c6fb0f961e7a073b6ed0d9c001b Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 17:05:35 +0100 Subject: [PATCH 195/375] ProfileCommandHandler -> PluginManagementCommandHandler --- .../Windows/PluginInstaller/ProfileManagerWidget.cs | 6 +++--- ...eCommandHandler.cs => PluginManagementCommandHandler.cs} | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename Dalamud/Plugin/Internal/Profiles/{ProfileCommandHandler.cs => PluginManagementCommandHandler.cs} (95%) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs index 95315dbd3..ee93d2042 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs @@ -625,13 +625,13 @@ internal class ProfileManagerWidget Loc.Localize("ProfileManagerTutorialCommands", "You can use the following commands in chat or in macros to manage active collections:"); public static string TutorialCommandsEnable => - Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(ProfileCommandHandler.CommandEnable); + Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(PluginManagementCommandHandler.CommandEnable); public static string TutorialCommandsDisable => - Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(ProfileCommandHandler.CommandDisable); + Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(PluginManagementCommandHandler.CommandDisable); public static string TutorialCommandsToggle => - Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(ProfileCommandHandler.CommandToggle); + Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(PluginManagementCommandHandler.CommandToggle); public static string TutorialCommandsEnd => Loc.Localize("ProfileManagerTutorialCommandsEnd", "If you run multiple of these commands, they will be executed in order."); diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs similarity index 95% rename from Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs rename to Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs index 7b7b4cfd0..d219b659b 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs +++ b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs @@ -16,7 +16,7 @@ namespace Dalamud.Plugin.Internal.Profiles; /// Service responsible for profile-related chat commands. /// [ServiceManager.EarlyLoadedService] -internal class ProfileCommandHandler : IInternalDisposableService +internal class PluginManagementCommandHandler : IInternalDisposableService { #pragma warning disable SA1600 public const string CommandEnable = "/xlenablecollection"; @@ -36,14 +36,14 @@ internal class ProfileCommandHandler : IInternalDisposableService private List<(string, ProfileOp)> queue = new(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Command handler. /// Profile manager. /// Chat handler. /// Framework. [ServiceManager.ServiceConstructor] - public ProfileCommandHandler(CommandManager cmd, ProfileManager profileManager, ChatGui chat, Framework framework) + public PluginManagementCommandHandler(CommandManager cmd, ProfileManager profileManager, ChatGui chat, Framework framework) { this.cmd = cmd; this.profileManager = profileManager; From 8276c19c6f016d58da414916074c4a5a764ad26e Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 20:55:57 +0100 Subject: [PATCH 196/375] add plugin enable/disable/toggle commands --- .../PluginInstaller/ProfileManagerWidget.cs | 6 +- .../PluginManagementCommandHandler.cs | 318 ++++++++++++++---- 2 files changed, 254 insertions(+), 70 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs index ee93d2042..ddb89d38c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs @@ -625,13 +625,13 @@ internal class ProfileManagerWidget Loc.Localize("ProfileManagerTutorialCommands", "You can use the following commands in chat or in macros to manage active collections:"); public static string TutorialCommandsEnable => - Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(PluginManagementCommandHandler.CommandEnable); + Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(PluginManagementCommandHandler.CommandEnableProfile); public static string TutorialCommandsDisable => - Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(PluginManagementCommandHandler.CommandDisable); + Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(PluginManagementCommandHandler.CommandDisableProfile); public static string TutorialCommandsToggle => - Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(PluginManagementCommandHandler.CommandToggle); + Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(PluginManagementCommandHandler.CommandToggleProfile); public static string TutorialCommandsEnd => Loc.Localize("ProfileManagerTutorialCommandsEnd", "If you run multiple of these commands, they will be executed in order."); diff --git a/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs index d219b659b..ad5aad286 100644 --- a/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs +++ b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs @@ -6,6 +6,7 @@ using CheapLoc; using Dalamud.Game; using Dalamud.Game.Command; using Dalamud.Game.Gui; +using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using Dalamud.Utility; using Serilog; @@ -19,50 +20,62 @@ namespace Dalamud.Plugin.Internal.Profiles; internal class PluginManagementCommandHandler : IInternalDisposableService { #pragma warning disable SA1600 - public const string CommandEnable = "/xlenablecollection"; - public const string CommandDisable = "/xldisablecollection"; - public const string CommandToggle = "/xltogglecollection"; + public const string CommandEnableProfile = "/xlenablecollection"; + public const string CommandDisableProfile = "/xldisablecollection"; + public const string CommandToggleProfile = "/xltogglecollection"; + + public const string CommandEnablePlugin = "/xlenableplugin"; + public const string CommandDisablePlugin = "/xldisableplugin"; + public const string CommandTogglePlugin = "/xltoggleplugin"; #pragma warning restore SA1600 - private static readonly string LegacyCommandEnable = CommandEnable.Replace("collection", "profile"); - private static readonly string LegacyCommandDisable = CommandDisable.Replace("collection", "profile"); - private static readonly string LegacyCommandToggle = CommandToggle.Replace("collection", "profile"); + private static readonly string LegacyCommandEnable = CommandEnableProfile.Replace("collection", "profile"); + private static readonly string LegacyCommandDisable = CommandDisableProfile.Replace("collection", "profile"); + private static readonly string LegacyCommandToggle = CommandToggleProfile.Replace("collection", "profile"); private readonly CommandManager cmd; private readonly ProfileManager profileManager; + private readonly PluginManager pluginManager; private readonly ChatGui chat; private readonly Framework framework; - private List<(string, ProfileOp)> queue = new(); - + private List<(Target Target, PluginCommandOperation Operation)> commandQueue = new(); + /// /// Initializes a new instance of the class. /// /// Command handler. /// Profile manager. + /// Plugin manager. /// Chat handler. /// Framework. [ServiceManager.ServiceConstructor] - public PluginManagementCommandHandler(CommandManager cmd, ProfileManager profileManager, ChatGui chat, Framework framework) + public PluginManagementCommandHandler( + CommandManager cmd, + ProfileManager profileManager, + PluginManager pluginManager, + ChatGui chat, + Framework framework) { this.cmd = cmd; this.profileManager = profileManager; + this.pluginManager = pluginManager; this.chat = chat; this.framework = framework; - this.cmd.AddHandler(CommandEnable, new CommandInfo(this.OnEnableProfile) + this.cmd.AddHandler(CommandEnableProfile, new CommandInfo(this.OnEnableProfile) { HelpMessage = Loc.Localize("ProfileCommandsEnableHint", "Enable a collection. Usage: /xlenablecollection \"Collection Name\""), ShowInHelp = true, }); - this.cmd.AddHandler(CommandDisable, new CommandInfo(this.OnDisableProfile) + this.cmd.AddHandler(CommandDisableProfile, new CommandInfo(this.OnDisableProfile) { HelpMessage = Loc.Localize("ProfileCommandsDisableHint", "Disable a collection. Usage: /xldisablecollection \"Collection Name\""), ShowInHelp = true, }); - this.cmd.AddHandler(CommandToggle, new CommandInfo(this.OnToggleProfile) + this.cmd.AddHandler(CommandToggleProfile, new CommandInfo(this.OnToggleProfile) { HelpMessage = Loc.Localize("ProfileCommandsToggleHint", "Toggle a collection. Usage: /xltogglecollection \"Collection Name\""), ShowInHelp = true, @@ -75,18 +88,36 @@ internal class PluginManagementCommandHandler : IInternalDisposableService this.cmd.AddHandler(LegacyCommandDisable, new CommandInfo(this.OnDisableProfile) { - ShowInHelp = true, + ShowInHelp = false, }); this.cmd.AddHandler(LegacyCommandToggle, new CommandInfo(this.OnToggleProfile) { + ShowInHelp = false, + }); + + this.cmd.AddHandler(CommandEnablePlugin, new CommandInfo(this.OnEnablePlugin) + { + HelpMessage = Loc.Localize("PluginCommandsEnableHint", "Enable a plugin. Usage: /xlenableplugin \"Plugin Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(CommandDisablePlugin, new CommandInfo(this.OnDisablePlugin) + { + HelpMessage = Loc.Localize("PluginCommandsDisableHint", "Disable a plugin. Usage: /xldisableplugin \"Plugin Name\""), + ShowInHelp = true, + }); + + this.cmd.AddHandler(CommandTogglePlugin, new CommandInfo(this.OnTogglePlugin) + { + HelpMessage = Loc.Localize("PluginCommandsToggleHint", "Toggle a plugin. Usage: /xltoggleplugin \"Plugin Name\""), ShowInHelp = true, }); this.framework.Update += this.FrameworkOnUpdate; } - private enum ProfileOp + private enum PluginCommandOperation { Enable, Disable, @@ -96,109 +127,262 @@ internal class PluginManagementCommandHandler : IInternalDisposableService /// void IInternalDisposableService.DisposeService() { - this.cmd.RemoveHandler(CommandEnable); - this.cmd.RemoveHandler(CommandDisable); - this.cmd.RemoveHandler(CommandToggle); + this.cmd.RemoveHandler(CommandEnableProfile); + this.cmd.RemoveHandler(CommandDisableProfile); + this.cmd.RemoveHandler(CommandToggleProfile); this.cmd.RemoveHandler(LegacyCommandEnable); this.cmd.RemoveHandler(LegacyCommandDisable); this.cmd.RemoveHandler(LegacyCommandToggle); this.framework.Update += this.FrameworkOnUpdate; } - - private void FrameworkOnUpdate(IFramework framework1) + + private void HandleProfileOperation(string profileName, PluginCommandOperation operation) { - if (this.profileManager.IsBusy) + var profile = this.profileManager.Profiles.FirstOrDefault( + x => x.Name == profileName); + if (profile == null || profile.IsDefaultProfile) return; - if (this.queue.Count > 0) + switch (operation) { - var op = this.queue[0]; - this.queue.RemoveAt(0); + case PluginCommandOperation.Enable: + if (!profile.IsEnabled) + Task.Run(() => profile.SetStateAsync(true, false)).GetAwaiter().GetResult(); + break; + case PluginCommandOperation.Disable: + if (profile.IsEnabled) + Task.Run(() => profile.SetStateAsync(false, false)).GetAwaiter().GetResult(); + break; + case PluginCommandOperation.Toggle: + Task.Run(() => profile.SetStateAsync(!profile.IsEnabled, false)).GetAwaiter().GetResult(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(operation), operation, null); + } - var profile = this.profileManager.Profiles.FirstOrDefault(x => x.Name == op.Item1); - if (profile == null || profile.IsDefaultProfile) - return; + this.chat.Print( + profile.IsEnabled + ? Loc.Localize("ProfileCommandsEnabling", "Enabling collection \"{0}\"...").Format(profile.Name) + : Loc.Localize("ProfileCommandsDisabling", "Disabling collection \"{0}\"...").Format(profile.Name)); - switch (op.Item2) + Task.Run(this.profileManager.ApplyAllWantStatesAsync).ContinueWith(t => + { + if (!t.IsCompletedSuccessfully && t.Exception != null) { - case ProfileOp.Enable: - if (!profile.IsEnabled) - Task.Run(() => profile.SetStateAsync(true, false)).GetAwaiter().GetResult(); - break; - case ProfileOp.Disable: - if (profile.IsEnabled) - Task.Run(() => profile.SetStateAsync(false, false)).GetAwaiter().GetResult(); - break; - case ProfileOp.Toggle: - Task.Run(() => profile.SetStateAsync(!profile.IsEnabled, false)).GetAwaiter().GetResult(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - if (profile.IsEnabled) - { - this.chat.Print(Loc.Localize("ProfileCommandsEnabling", "Enabling collection \"{0}\"...").Format(profile.Name)); + Log.Error(t.Exception, "Could not apply profiles through commands"); + this.chat.PrintError(Loc.Localize("ProfileCommandsApplyFailed", "Failed to apply your collections. Please check the console for errors.")); } else { - this.chat.Print(Loc.Localize("ProfileCommandsDisabling", "Disabling collection \"{0}\"...").Format(profile.Name)); + this.chat.Print(Loc.Localize("ProfileCommandsApplySuccess", "Collections applied.")); } + }); + } + + private bool HandlePluginOperation(Guid workingPluginId, PluginCommandOperation operation) + { + var plugin = this.pluginManager.InstalledPlugins.FirstOrDefault(x => x.EffectiveWorkingPluginId == workingPluginId); + if (plugin == null) + return true; - Task.Run(this.profileManager.ApplyAllWantStatesAsync).ContinueWith(t => + switch (plugin.State) + { + // Ignore if the plugin is in a fail state + case PluginState.LoadError or PluginState.UnloadError: + this.chat.Print(Loc.Localize("PluginCommandsFailed", "Plugin \"{0}\" has previously failed to load/unload, not continuing.").Format(plugin.Name)); + return true; + + case PluginState.Loaded when operation == PluginCommandOperation.Enable: + this.chat.Print(Loc.Localize("PluginCommandsAlreadyEnabled", "Plugin \"{0}\" is already enabled.").Format(plugin.Name)); + return true; + case PluginState.Unloaded when operation == PluginCommandOperation.Disable: + this.chat.Print(Loc.Localize("PluginCommandsAlreadyDisabled", "Plugin \"{0}\" is already disabled.").Format(plugin.Name)); + return true; + + // Defer if this plugin is busy right now + case PluginState.Loading or PluginState.Unloading: + return false; + } + + void Continuation(Task t, string onSuccess, string onError) + { + if (!t.IsCompletedSuccessfully && t.Exception != null) { - if (!t.IsCompletedSuccessfully && t.Exception != null) - { - Log.Error(t.Exception, "Could not apply profiles through commands"); - this.chat.PrintError(Loc.Localize("ProfileCommandsApplyFailed", "Failed to apply your collections. Please check the console for errors.")); - } - else - { - this.chat.Print(Loc.Localize("ProfileCommandsApplySuccess", "Collections applied.")); - } - }); + Log.Error(t.Exception, "Plugin command operation failed for plugin {PluginName}", plugin.Name); + this.chat.PrintError(onError); + return; + } + + this.chat.Print(onSuccess); + } + + switch (operation) + { + case PluginCommandOperation.Enable: + this.chat.Print(Loc.Localize("PluginCommandsEnabling", "Enabling plugin \"{0}\"...").Format(plugin.Name)); + Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) + .ContinueWith(t => Continuation(t, + Loc.Localize("PluginCommandsEnableSuccess", "Plugin \"{0}\" enabled.").Format(plugin.Name), + Loc.Localize("PluginCommandsEnableFailed", "Failed to enable plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) + .ConfigureAwait(false); + break; + case PluginCommandOperation.Disable: + this.chat.Print(Loc.Localize("PluginCommandsDisabling", "Disabling plugin \"{0}\"...").Format(plugin.Name)); + Task.Run(() => plugin.UnloadAsync()) + .ContinueWith(t => Continuation(t, + Loc.Localize("PluginCommandsDisableSuccess", "Plugin \"{0}\" disabled.").Format(plugin.Name), + Loc.Localize("PluginCommandsDisableFailed", "Failed to disable plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) + .ConfigureAwait(false); + break; + case PluginCommandOperation.Toggle: + this.chat.Print(Loc.Localize("PluginCommandsToggling", "Toggling plugin \"{0}\"...").Format(plugin.Name)); + Task.Run(() => plugin.State == PluginState.Loaded ? plugin.UnloadAsync() : plugin.LoadAsync(PluginLoadReason.Installer)) + .ContinueWith(t => Continuation(t, + Loc.Localize("PluginCommandsToggleSuccess", "Plugin \"{0}\" toggled.").Format(plugin.Name), + Loc.Localize("PluginCommandsToggleFailed", "Failed to toggle plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) + .ConfigureAwait(false); + break; + default: + throw new ArgumentOutOfRangeException(nameof(operation), operation, null); + } + + return true; + } + + private void FrameworkOnUpdate(IFramework framework1) + { + if (this.profileManager.IsBusy) + { + return; + } + + if (this.commandQueue.Count > 0) + { + var op = this.commandQueue[0]; + + var remove = true; + switch (op.Target) + { + case PluginTarget pluginTarget: + remove = this.HandlePluginOperation(pluginTarget.WorkingPluginId, op.Operation); + break; + case ProfileTarget profileTarget: + this.HandleProfileOperation(profileTarget.ProfileName, op.Operation); + break; + } + + if (remove) + { + this.commandQueue.RemoveAt(0); + } } } private void OnEnableProfile(string command, string arguments) { - var name = this.ValidateName(arguments); + var name = this.ValidateProfileName(arguments); if (name == null) return; - this.queue = this.queue.Where(x => x.Item1 != name).ToList(); - this.queue.Add((name, ProfileOp.Enable)); + var target = new ProfileTarget(name); + this.commandQueue = this.commandQueue.Where(x => x.Target != target).ToList(); + this.commandQueue.Add((target, PluginCommandOperation.Enable)); } private void OnDisableProfile(string command, string arguments) { - var name = this.ValidateName(arguments); + var name = this.ValidateProfileName(arguments); if (name == null) return; - this.queue = this.queue.Where(x => x.Item1 != name).ToList(); - this.queue.Add((name, ProfileOp.Disable)); + var target = new ProfileTarget(name); + this.commandQueue = this.commandQueue.Where(x => x.Target != target).ToList(); + this.commandQueue.Add((target, PluginCommandOperation.Disable)); } private void OnToggleProfile(string command, string arguments) { - var name = this.ValidateName(arguments); + var name = this.ValidateProfileName(arguments); if (name == null) return; - this.queue.Add((name, ProfileOp.Toggle)); + var target = new ProfileTarget(name); + this.commandQueue.Add((target, PluginCommandOperation.Toggle)); + } + + private void OnEnablePlugin(string command, string arguments) + { + var plugin = this.ValidatePluginName(arguments); + if (plugin == null) + return; + + var target = new PluginTarget(plugin.EffectiveWorkingPluginId); + this.commandQueue + .RemoveAll(x => x.Target == target); + this.commandQueue.Add((target, PluginCommandOperation.Enable)); + } + + private void OnDisablePlugin(string command, string arguments) + { + var plugin = this.ValidatePluginName(arguments); + if (plugin == null) + return; + + var target = new PluginTarget(plugin.EffectiveWorkingPluginId); + this.commandQueue + .RemoveAll(x => x.Target == target); + this.commandQueue.Add((target, PluginCommandOperation.Disable)); + } + + private void OnTogglePlugin(string command, string arguments) + { + var plugin = this.ValidatePluginName(arguments); + if (plugin == null) + return; + + var target = new PluginTarget(plugin.EffectiveWorkingPluginId); + this.commandQueue + .RemoveAll(x => x.Target == target); + this.commandQueue.Add((target, PluginCommandOperation.Toggle)); } - private string? ValidateName(string arguments) + private string? ValidateProfileName(string arguments) { var name = arguments.Replace("\"", string.Empty); if (this.profileManager.Profiles.All(x => x.Name != name)) { - this.chat.PrintError($"No collection like \"{name}\"."); + this.chat.PrintError(Loc.Localize("ProfileCommandsNotFound", "Collection \"{0}\" not found.").Format(name)); return null; } return name; } + + private LocalPlugin? ValidatePluginName(string arguments) + { + var name = arguments.Replace("\"", string.Empty); + var targetPlugin = + this.pluginManager.InstalledPlugins.FirstOrDefault(x => x.InternalName == name || x.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase)); + + if (targetPlugin == null) + { + this.chat.PrintError(Loc.Localize("PluginCommandsNotFound", "Plugin \"{0}\" not found.").Format(name)); + return null; + } + + if (!this.profileManager.IsInDefaultProfile(targetPlugin.EffectiveWorkingPluginId)) + { + this.chat.PrintError(Loc.Localize("PluginCommandsNotInDefaultProfile", "Plugin \"{0}\" is in a collection and can't be managed through commands. Manage the collection instead.") + .Format(targetPlugin.Name)); + } + + return targetPlugin; + } + + private abstract record Target; + + private record PluginTarget(Guid WorkingPluginId) : Target; + + private record ProfileTarget(string ProfileName) : Target; } From a1ae33bfee3f5a8f61c7617d59bdafa4279887ef Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 21:03:31 +0100 Subject: [PATCH 197/375] don't set plugin to fail state if we threw InvalidPluginOperationException These are supposed to indicate to the user that they called a function at the wrong point in time. We don't want to actually mutate the state in that case. --- .../Exceptions/InternalPluginStateException.cs | 16 ++++++++++++++++ Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 11 ++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs diff --git a/Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs b/Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs new file mode 100644 index 000000000..03e37afcf --- /dev/null +++ b/Dalamud/Plugin/Internal/Exceptions/InternalPluginStateException.cs @@ -0,0 +1,16 @@ +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// An exception to be thrown when policy blocks a plugin from loading. +/// +internal class InternalPluginStateException : InvalidPluginOperationException +{ + /// + /// Initializes a new instance of the class. + /// + /// The message to associate with this exception. + public InternalPluginStateException(string message) + : base(message) + { + } +} diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 59f6b23c1..ed3a94994 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -281,7 +281,7 @@ internal class LocalPlugin : IAsyncDisposable case PluginState.Unloaded: if (this.instance is not null) { - throw new InvalidPluginOperationException( + throw new InternalPluginStateException( "Plugin should have been unloaded but instance is not cleared"); } @@ -413,7 +413,9 @@ internal class LocalPlugin : IAsyncDisposable } catch (Exception ex) { - this.State = PluginState.LoadError; + // These are "user errors", we don't want to mark the plugin as failed + if (ex is not InvalidPluginOperationException) + this.State = PluginState.LoadError; // If a precondition fails, don't record it as an error, as it isn't really. if (ex is PluginPreconditionFailedException) @@ -476,7 +478,10 @@ internal class LocalPlugin : IAsyncDisposable } catch (Exception ex) { - this.State = PluginState.UnloadError; + // These are "user errors", we don't want to mark the plugin as failed + if (ex is not InvalidPluginOperationException) + this.State = PluginState.UnloadError; + Log.Error(ex, "Error while unloading {PluginName}", this.InternalName); throw; From 667ae312c56b217f8245f1683de7ca76f3f5c8dd Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:15:03 +0100 Subject: [PATCH 198/375] some readme housekeeping --- README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8a925f1c6..bbf9ee1ce 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,18 @@ Dalamud

-Dalamud is a plugin development framework for FINAL FANTASY XIV that provides access to game data and native interoperability with the game itself to add functionality and quality-of-life. +Dalamud is a plugin development framework for FFXIV that provides access to game data and native interoperability with the game itself to add functionality and quality-of-life. -It is meant to be used in conjunction with [FFXIVQuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher), which manages and launches Dalamud for you. __It is generally not recommended for users to try to run Dalamud manually as there are multiple dependencies and assumed folder paths.__ +It is meant to be used in conjunction with [XIVLauncher](https://github.com/goatcorp/FFXIVQuickLauncher), which manages and launches Dalamud for you. __It is generally not recommended for end users to try to run Dalamud manually as XIVLauncher manages multiple required dependencies.__ ## Hold Up! + If you are just trying to **use** Dalamud, you don't need to do anything on this page - please [download XIVLauncher](https://goatcorp.github.io/) from its official page and follow the setup instructions. +## Building and testing locally + +Please check the [docs page on building Dalamud](https://dalamud.dev/building) for more information and required dependencies. + ## Plugin development Dalamud features a growing API for in-game plugin development with game data and chat access and overlays. Please see our [Developer FAQ](https://goatcorp.github.io/faq/development) and the [API documentation](https://dalamud.dev) for more details. @@ -34,15 +39,6 @@ Dalamud can be loaded via DLL injection, or by rewriting a process' entrypoint. | *Dalamud* (C#) | Core API, game bindings, plugin framework | | *Dalamud.CorePlugin* (C#) | Testbed plugin that can access Dalamud internals, to prototype new Dalamud features | -## Branches - -We are currently working from the following branches. - -| Name | API Level | Purpose | .NET Version | Track | -|----------|-----------|------------------------------------------------------------|----------------------------|-------------------| -| *master* | **9** | Current release branch | .NET 8.0.0 (November 2023) | Release & Staging | -| *api10* | **10** | Next major version, slated for release alongside Patch 7.0 | .NET 8.0.0 (November 2023) | api10 | -
##### Final Fantasy XIV © 2010-2021 SQUARE ENIX CO., LTD. All Rights Reserved. We are not affiliated with SQUARE ENIX CO., LTD. in any way. From 5a2473293d28a076835d02e9ec1f446924109952 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 22:00:57 +0100 Subject: [PATCH 199/375] reorganize solution a bit --- Dalamud.sln | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Dalamud.sln b/Dalamud.sln index 22cc59a8d..5d4b0737f 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -6,11 +6,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore + tools\BannedSymbols.txt = tools\BannedSymbols.txt targets\Dalamud.Plugin.Bootstrap.targets = targets\Dalamud.Plugin.Bootstrap.targets targets\Dalamud.Plugin.targets = targets\Dalamud.Plugin.targets - Directory.Build.props = Directory.Build.props - tools\BannedSymbols.txt = tools\BannedSymbols.txt tools\dalamud.ruleset = tools\dalamud.ruleset + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}" @@ -25,7 +25,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Injector.Boot", "Da EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Test\Dalamud.Test.csproj", "{C8004563-1806-4329-844F-0EF6274291FC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}" EndProject @@ -49,6 +49,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator", "lib\FFX EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropGenerator.Runtime", "lib\FFXIVClientStructs\InteropGenerator.Runtime\InteropGenerator.Runtime.csproj", "{A6AA1C3F-9470-4922-9D3F-D4549657AB22}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Injector", "Injector", "{19775C83-7117-4A5F-AA00-18889F46A490}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -97,6 +101,10 @@ Global {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU + {E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.Build.0 = Release|Any CPU {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 @@ -117,19 +125,20 @@ Global {A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.Build.0 = Release|Any CPU - {E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0D51896-604F-4B40-8CFE-51941607B3A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0D51896-604F-4B40-8CFE-51941607B3A1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490} + {8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490} {0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} {C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} {2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944} {C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {E0D51896-604F-4B40-8CFE-51941607B3A1} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0} = {8F079208-C227-4D96-9427-2BEBE0003944} {3620414C-7DFC-423E-929F-310E19F5D930} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} {A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} EndGlobalSection From 9616573f402af7ccb0b0873e788737240903a4bd Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 22:36:31 +0100 Subject: [PATCH 200/375] reference and add vcxproj for cimgui, cimplot, cimguizmo --- .gitmodules | 9 ++ Dalamud.sln | 41 ++++- external/Directory.Build.props | 3 + external/cimgui/cimgui.vcxproj | 158 ++++++++++++++++++ external/cimgui/cimgui.vcxproj.filters | 45 ++++++ external/cimguizmo/cimguizmo.vcxproj | 162 +++++++++++++++++++ external/cimguizmo/cimguizmo.vcxproj.filters | 57 +++++++ external/cimplot/cimplot.vcxproj | 160 ++++++++++++++++++ external/cimplot/cimplot.vcxproj.filters | 51 ++++++ lib/cimgui | 1 + lib/cimguizmo | 1 + lib/cimplot | 1 + 12 files changed, 682 insertions(+), 7 deletions(-) create mode 100644 external/Directory.Build.props create mode 100644 external/cimgui/cimgui.vcxproj create mode 100644 external/cimgui/cimgui.vcxproj.filters create mode 100644 external/cimguizmo/cimguizmo.vcxproj create mode 100644 external/cimguizmo/cimguizmo.vcxproj.filters create mode 100644 external/cimplot/cimplot.vcxproj create mode 100644 external/cimplot/cimplot.vcxproj.filters create mode 160000 lib/cimgui create mode 160000 lib/cimguizmo create mode 160000 lib/cimplot diff --git a/.gitmodules b/.gitmodules index dd184b54e..227653d48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,12 @@ [submodule "lib/TsudaKageyu-minhook"] path = lib/TsudaKageyu-minhook url = https://github.com/TsudaKageyu/minhook.git +[submodule "lib/cimgui"] + path = lib/cimgui + url = https://github.com/goatcorp/gc-cimgui +[submodule "lib/cimplot"] + path = lib/cimplot + url = https://github.com/goatcorp/gc-cimplot +[submodule "lib/cimguizmo"] + path = lib/cimguizmo + url = https://github.com/goatcorp/gc-cimguizmo diff --git a/Dalamud.sln b/Dalamud.sln index 5d4b0737f..49fc9e011 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -53,6 +53,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Injector", "Injector", "{19 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{8F079208-C227-4D96-9427-2BEBE0003944}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimgui", "external\cimgui\cimgui.vcxproj", "{8430077C-F736-4246-A052-8EA1CECE844E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "imgui", "imgui", "{DBE5345E-6594-4A59-B183-1C3D5592269D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CS", "CS", "{8BBACF2D-7AB8-4610-A115-0E363D35C291}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimplot", "external\cimplot\cimplot.vcxproj", "{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimguizmo", "external\cimguizmo\cimguizmo.vcxproj", "{F258347D-31BE-4605-98CE-40E43BDF6F9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +135,18 @@ Global {A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6AA1C3F-9470-4922-9D3F-D4549657AB22}.Release|Any CPU.Build.0 = Release|Any CPU + {8430077C-F736-4246-A052-8EA1CECE844E}.Debug|Any CPU.ActiveCfg = Debug|x64 + {8430077C-F736-4246-A052-8EA1CECE844E}.Debug|Any CPU.Build.0 = Debug|x64 + {8430077C-F736-4246-A052-8EA1CECE844E}.Release|Any CPU.ActiveCfg = Release|x64 + {8430077C-F736-4246-A052-8EA1CECE844E}.Release|Any CPU.Build.0 = Release|x64 + {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Debug|Any CPU.ActiveCfg = Debug|x64 + {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Debug|Any CPU.Build.0 = Debug|x64 + {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Release|Any CPU.ActiveCfg = Release|x64 + {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16}.Release|Any CPU.Build.0 = Release|x64 + {F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.Build.0 = Debug|x64 + {F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.ActiveCfg = Release|x64 + {F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,15 +154,20 @@ Global GlobalSection(NestedProjects) = preSolution {5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490} {8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490} - {0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} - {C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} - {2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {0483026E-C6CE-4B1A-AA68-46544C08140B} = {DBE5345E-6594-4A59-B183-1C3D5592269D} + {C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {DBE5345E-6594-4A59-B183-1C3D5592269D} + {2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {DBE5345E-6594-4A59-B183-1C3D5592269D} {4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944} - {C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} - {E0D51896-604F-4B40-8CFE-51941607B3A1} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291} + {E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291} {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0} = {8F079208-C227-4D96-9427-2BEBE0003944} - {3620414C-7DFC-423E-929F-310E19F5D930} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} - {A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {3620414C-7DFC-423E-929F-310E19F5D930} = {8BBACF2D-7AB8-4610-A115-0E363D35C291} + {A6AA1C3F-9470-4922-9D3F-D4549657AB22} = {8BBACF2D-7AB8-4610-A115-0E363D35C291} + {8430077C-F736-4246-A052-8EA1CECE844E} = {DBE5345E-6594-4A59-B183-1C3D5592269D} + {DBE5345E-6594-4A59-B183-1C3D5592269D} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {8BBACF2D-7AB8-4610-A115-0E363D35C291} = {E15BDA6D-E881-4482-94BA-BE5527E917FF} + {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {DBE5345E-6594-4A59-B183-1C3D5592269D} + {F258347D-31BE-4605-98CE-40E43BDF6F9D} = {DBE5345E-6594-4A59-B183-1C3D5592269D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599} diff --git a/external/Directory.Build.props b/external/Directory.Build.props new file mode 100644 index 000000000..f719442cd --- /dev/null +++ b/external/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj new file mode 100644 index 000000000..c5663fa05 --- /dev/null +++ b/external/cimgui/cimgui.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {8430077c-f736-4246-a052-8ea1cece844e} + cimgui + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)lib\cimgui\imgui;%(AdditionalIncludeDirectories) + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/external/cimgui/cimgui.vcxproj.filters b/external/cimgui/cimgui.vcxproj.filters new file mode 100644 index 000000000..d48c361f1 --- /dev/null +++ b/external/cimgui/cimgui.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj new file mode 100644 index 000000000..3fe6fb073 --- /dev/null +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {F258347D-31BE-4605-98CE-40E43BDF6F9D} + cimplot + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)lib\cimgui\imgui;$(SolutionDir)lib\cimguizmo\ImGuizmo;$(SolutionDir)lib\cimgui;%(AdditionalIncludeDirectories) + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/external/cimguizmo/cimguizmo.vcxproj.filters b/external/cimguizmo/cimguizmo.vcxproj.filters new file mode 100644 index 000000000..f954dcc2c --- /dev/null +++ b/external/cimguizmo/cimguizmo.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj new file mode 100644 index 000000000..872448ea8 --- /dev/null +++ b/external/cimplot/cimplot.vcxproj @@ -0,0 +1,160 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {76caa246-c405-4a8c-b0ae-f4a0ef3d4e16} + cimplot + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + $(SolutionDir)lib\cimgui\imgui;$(SolutionDir)lib\cimplot\implot;$(SolutionDir)lib\cimgui;%(AdditionalIncludeDirectories) + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/external/cimplot/cimplot.vcxproj.filters b/external/cimplot/cimplot.vcxproj.filters new file mode 100644 index 000000000..ad8bfd11b --- /dev/null +++ b/external/cimplot/cimplot.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/lib/cimgui b/lib/cimgui new file mode 160000 index 000000000..7b4955bbd --- /dev/null +++ b/lib/cimgui @@ -0,0 +1 @@ +Subproject commit 7b4955bbd8f7bc6c022d39503dc806fa53a6a590 diff --git a/lib/cimguizmo b/lib/cimguizmo new file mode 160000 index 000000000..dbad4fdb4 --- /dev/null +++ b/lib/cimguizmo @@ -0,0 +1 @@ +Subproject commit dbad4fdb4d465e1f48d20c4c54a20925095297b0 diff --git a/lib/cimplot b/lib/cimplot new file mode 160000 index 000000000..939f8f36d --- /dev/null +++ b/lib/cimplot @@ -0,0 +1 @@ +Subproject commit 939f8f36deebd895f6cda522ee4bb2b798920935 From 43ab6bd24fdb80bed9824ffcde0c409c5207150c Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 22:39:55 +0100 Subject: [PATCH 201/375] remove win32 targets --- external/cimgui/cimgui.vcxproj | 61 ---------------------------- external/cimguizmo/cimguizmo.vcxproj | 61 ---------------------------- external/cimplot/cimplot.vcxproj | 61 ---------------------------- 3 files changed, 183 deletions(-) diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index c5663fa05..7c75fd853 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -1,14 +1,6 @@ - - Debug - Win32 - - - Release - Win32 - Debug x64 @@ -38,19 +30,6 @@ 10.0 - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - DynamicLibrary true @@ -69,12 +48,6 @@ - - - - - - @@ -82,40 +55,6 @@ - - - Level3 - true - WIN32;_DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - false - - - - - Level3 - true - true - true - WIN32;NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - true - true - false - - Level3 diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj index 3fe6fb073..11200ad72 100644 --- a/external/cimguizmo/cimguizmo.vcxproj +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -1,14 +1,6 @@ - - Debug - Win32 - - - Release - Win32 - Debug x64 @@ -42,19 +34,6 @@ 10.0 - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - DynamicLibrary true @@ -73,12 +52,6 @@ - - - - - - @@ -86,40 +59,6 @@ - - - Level3 - true - WIN32;_DEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - false - - - - - Level3 - true - true - true - WIN32;NDEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - true - true - false - - Level3 diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj index 872448ea8..eacebe3de 100644 --- a/external/cimplot/cimplot.vcxproj +++ b/external/cimplot/cimplot.vcxproj @@ -1,14 +1,6 @@ - - Debug - Win32 - - - Release - Win32 - Debug x64 @@ -40,19 +32,6 @@ 10.0 - - DynamicLibrary - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - DynamicLibrary true @@ -71,12 +50,6 @@ - - - - - - @@ -84,40 +57,6 @@ - - - Level3 - true - WIN32;_DEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - false - - - - - Level3 - true - true - true - WIN32;NDEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - true - true - false - - Level3 From 371f1bfa1895eeb42db28d51536705649770b79f Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 23:17:33 +0100 Subject: [PATCH 202/375] comment out kizer hack for now, need to move to natives --- Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs index a682ed215..6ba5a1d94 100644 --- a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs +++ b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs @@ -39,6 +39,8 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableServi private ImGuiDrawListFixProvider(InterfaceManager.InterfaceManagerWithScene imws) { // Force cimgui.dll to be loaded. + // TODO(goat): Apply fixes in natives + /* _ = ImGui.GetCurrentContext(); var cimgui = Process.GetCurrentProcess().Modules.Cast() .First(x => x.ModuleName == "cimgui.dll") @@ -56,6 +58,7 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableServi this.hookImDrawListAddPolyline.Enable(); this.hookImDrawListAddRectFilled.Enable(); this.hookImDrawListAddImageRounded.Enable(); + */ } private delegate void ImDrawListAddPolyLine( From f696f7250f1f3232ab5eefbe955a77b2667b442c Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 23:24:13 +0100 Subject: [PATCH 203/375] shoddy include in dalamud build for now --- Dalamud/Dalamud.csproj | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index a6ac07227..610bd7f84 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -103,6 +103,44 @@ + + + + + + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + + %(RecursiveDir)%(FileName)%(Extension) + PreserveNewest + + + From 1714191711da7e07e5edfd416b505497ba363ed5 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 23:25:21 +0100 Subject: [PATCH 204/375] attest imgui DLLs --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 669d6255a..930adf8ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,6 +55,7 @@ jobs: bin/Release/Dalamud.*.dll bin/Release/Dalamud.*.exe bin/Release/FFXIVClientStructs.dll + bin/Release/cim*.dll - name: Upload artifact uses: actions/upload-artifact@v4 with: From 7629cac8af3c800bf5a26f8b0c912d0526c8bac1 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 23 Dec 2024 23:29:16 +0100 Subject: [PATCH 205/375] use ImGuiScene without shipped binaries --- lib/ImGuiScene | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ImGuiScene b/lib/ImGuiScene index b0d41471b..d336b20a8 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit b0d41471b7ef3d69daaf6d862eb74e7e00a25651 +Subproject commit d336b20a85ea48723a98681b18bdfe14a56a3403 From 87525091c903895dba88ec72655301c7e48c5151 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 24 Dec 2024 01:52:38 +0100 Subject: [PATCH 206/375] make it work with nuke --- .nuke/build.schema.json | 8 ++++ Dalamud.sln | 5 +++ Dalamud/Dalamud.csproj | 54 +++++++------------------- build/DalamudBuild.cs | 58 +++++++++++++++++++++++++++- external/cimgui/cimgui.vcxproj | 5 ++- external/cimguizmo/cimguizmo.vcxproj | 5 ++- external/cimplot/cimplot.vcxproj | 5 ++- 7 files changed, 97 insertions(+), 43 deletions(-) diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 497f2b89a..e7e1a446a 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -78,9 +78,13 @@ "enum": [ "Clean", "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", "CompileDalamud", "CompileDalamudBoot", "CompileDalamudCrashHandler", + "CompileImGuiNatives", "CompileInjector", "CompileInjectorBoot", "Restore", @@ -100,9 +104,13 @@ "enum": [ "Clean", "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", "CompileDalamud", "CompileDalamudBoot", "CompileDalamudCrashHandler", + "CompileImGuiNatives", "CompileInjector", "CompileInjectorBoot", "Restore", diff --git a/Dalamud.sln b/Dalamud.sln index 49fc9e011..5b6f56c6e 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -16,6 +16,11 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "build", "build\build.csproj", "{94E5B016-02B1-459B-97D9-E783F28764B2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud", "Dalamud\Dalamud.csproj", "{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}" + ProjectSection(ProjectDependencies) = postProject + {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} + {8430077C-F736-4246-A052-8EA1CECE844E} = {8430077C-F736-4246-A052-8EA1CECE844E} + {F258347D-31BE-4605-98CE-40E43BDF6F9D} = {F258347D-31BE-4605-98CE-40E43BDF6F9D} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dalamud.Boot", "Dalamud.Boot\Dalamud.Boot.vcxproj", "{55198DC3-A03D-408E-A8EB-2077780C8576}" EndProject diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 610bd7f84..1cd9fc336 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -3,7 +3,7 @@ net8.0-windows x64 - x64;AnyCPU + x64 12.0 True @@ -103,44 +103,6 @@ - - - - - - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - - %(RecursiveDir)%(FileName)%(Extension) - PreserveNewest - - - @@ -243,4 +205,18 @@ + + + + + + + + + + + + + + diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index d704d54e0..6340c36fa 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -47,6 +47,14 @@ public class DalamudBuild : NukeBuild AbsolutePath TestProjectDir => RootDirectory / "Dalamud.Test"; AbsolutePath TestProjectFile => TestProjectDir / "Dalamud.Test.csproj"; + AbsolutePath ExternalsDir => RootDirectory / "external"; + AbsolutePath CImGuiDir => ExternalsDir / "cimgui"; + AbsolutePath CImGuiProjectFile => CImGuiDir / "cimgui.vcxproj"; + AbsolutePath CImPlotDir => ExternalsDir / "cimplot"; + AbsolutePath CImPlotProjectFile => CImPlotDir / "cimplot.vcxproj"; + AbsolutePath CImGuizmoDir => ExternalsDir / "cimguizmo"; + AbsolutePath CImGuizmoProjectFile => CImGuizmoDir / "cimguizmo.vcxproj"; + AbsolutePath ArtifactsDirectory => RootDirectory / "bin" / Configuration; private static AbsolutePath LibraryDirectory => RootDirectory / "lib"; @@ -59,9 +67,42 @@ public class DalamudBuild : NukeBuild DotNetTasks.DotNetRestore(s => s .SetProjectFile(Solution)); }); - + + Target CompileCImGui => _ => _ + .Executes(() => + { + MSBuildTasks.MSBuild(s => s + .SetTargetPath(CImGuiProjectFile) + .SetConfiguration(Configuration) + .SetTargetPlatform(MSBuildTargetPlatform.x64)); + }); + + Target CompileCImPlot => _ => _ + .Executes(() => + { + MSBuildTasks.MSBuild(s => s + .SetTargetPath(CImPlotProjectFile) + .SetConfiguration(Configuration) + .SetTargetPlatform(MSBuildTargetPlatform.x64)); + }); + + Target CompileCImGuizmo => _ => _ + .Executes(() => + { + MSBuildTasks.MSBuild(s => s + .SetTargetPath(CImGuizmoProjectFile) + .SetConfiguration(Configuration) + .SetTargetPlatform(MSBuildTargetPlatform.x64)); + }); + + Target CompileImGuiNatives => _ => _ + .DependsOn(CompileCImGui) + .DependsOn(CompileCImPlot) + .DependsOn(CompileCImGuizmo); + Target CompileDalamud => _ => _ .DependsOn(Restore) + .DependsOn(CompileImGuiNatives) .Executes(() => { DotNetTasks.DotNetBuild(s => @@ -138,6 +179,21 @@ public class DalamudBuild : NukeBuild Target Clean => _ => _ .Executes(() => { + MSBuildTasks.MSBuild(s => s + .SetProjectFile(CImGuiProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); + + MSBuildTasks.MSBuild(s => s + .SetProjectFile(CImPlotProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); + + MSBuildTasks.MSBuild(s => s + .SetProjectFile(CImGuizmoProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); + DotNetTasks.DotNetClean(s => s .SetProject(DalamudProjectFile) .SetConfiguration(Configuration)); diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index 7c75fd853..12bb376f8 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -55,6 +55,9 @@ + + ..\$(Platform)\$(Configuration)\ + Level3 @@ -64,7 +67,7 @@ NotUsing - $(SolutionDir)lib\cimgui\imgui;%(AdditionalIncludeDirectories) + ..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories) Windows diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj index 11200ad72..849cbfba5 100644 --- a/external/cimguizmo/cimguizmo.vcxproj +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -59,6 +59,9 @@ + + ..\$(Platform)\$(Configuration)\ + Level3 @@ -68,7 +71,7 @@ NotUsing - $(SolutionDir)lib\cimgui\imgui;$(SolutionDir)lib\cimguizmo\ImGuizmo;$(SolutionDir)lib\cimgui;%(AdditionalIncludeDirectories) + ..\..\lib\cimgui\imgui;..\..\lib\cimguizmo\ImGuizmo;..\..\lib\cimgui;%(AdditionalIncludeDirectories) Windows diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj index eacebe3de..3477ab262 100644 --- a/external/cimplot/cimplot.vcxproj +++ b/external/cimplot/cimplot.vcxproj @@ -57,6 +57,9 @@ + + ..\$(Platform)\$(Configuration)\ + Level3 @@ -66,7 +69,7 @@ NotUsing - $(SolutionDir)lib\cimgui\imgui;$(SolutionDir)lib\cimplot\implot;$(SolutionDir)lib\cimgui;%(AdditionalIncludeDirectories) + ..\..\lib\cimgui\imgui;..\..\lib\cimplot\implot;..\..\lib\cimgui;%(AdditionalIncludeDirectories) Windows From 419b62b2c981e446f6949b16175b662391ec9ce4 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 24 Dec 2024 02:48:42 +0100 Subject: [PATCH 207/375] fix release build --- external/cimgui/cimgui.vcxproj | 18 ++++++++++-------- external/cimguizmo/cimguizmo.vcxproj | 16 +++++++++------- external/cimplot/cimplot.vcxproj | 16 +++++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index 12bb376f8..c6109a131 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -55,19 +55,23 @@ - + ..\$(Platform)\$(Configuration)\ + + + + ..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories) + NotUsing + + + Level3 true _DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) true - NotUsing - - - ..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories) Windows @@ -81,10 +85,8 @@ true true true - NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) true - Use - pch.h Windows diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj index 849cbfba5..56c085da3 100644 --- a/external/cimguizmo/cimguizmo.vcxproj +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -59,19 +59,23 @@ - + ..\$(Platform)\$(Configuration)\ + + + + ..\..\lib\cimgui\imgui;..\..\lib\cimguizmo\ImGuizmo;..\..\lib\cimgui;%(AdditionalIncludeDirectories) + NotUsing + + + Level3 true _DEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - NotUsing - - - ..\..\lib\cimgui\imgui;..\..\lib\cimguizmo\ImGuizmo;..\..\lib\cimgui;%(AdditionalIncludeDirectories) Windows @@ -87,8 +91,6 @@ true NDEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - Use - pch.h Windows diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj index 3477ab262..44bcf95bb 100644 --- a/external/cimplot/cimplot.vcxproj +++ b/external/cimplot/cimplot.vcxproj @@ -57,19 +57,23 @@ - + ..\$(Platform)\$(Configuration)\ + + + + ..\..\lib\cimgui\imgui;..\..\lib\cimplot\implot;..\..\lib\cimgui;%(AdditionalIncludeDirectories) + NotUsing + + + Level3 true _DEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - NotUsing - - - ..\..\lib\cimgui\imgui;..\..\lib\cimplot\implot;..\..\lib\cimgui;%(AdditionalIncludeDirectories) Windows @@ -85,8 +89,6 @@ true NDEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true - Use - pch.h Windows From c31fe0920fd1395b2e3f997471a6790646c067e4 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 24 Dec 2024 03:44:31 +0100 Subject: [PATCH 208/375] remove hackfix hooks --- .../Internal/ImGuiDrawListFixProvider.cs | 225 ------------------ lib/cimgui | 2 +- 2 files changed, 1 insertion(+), 226 deletions(-) delete mode 100644 Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs diff --git a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs deleted file mode 100644 index 6ba5a1d94..000000000 --- a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Diagnostics; -using System.Linq; -using System.Numerics; - -using Dalamud.Hooking; - -using ImGuiNET; - -namespace Dalamud.Interface.Internal; - -/// -/// Fixes ImDrawList not correctly dealing with the current texture for that draw list not in tune with the global -/// state. Currently, ImDrawList::AddPolyLine and ImDrawList::AddRectFilled are affected. -/// -/// * The implementation for AddRectFilled is entirely replaced with the hook below. -/// * The implementation for AddPolyLine is wrapped with Push/PopTextureID. -/// -/// TODO: -/// * imgui_draw.cpp:1433 ImDrawList::AddRectFilled -/// The if block needs a PushTextureID(_Data->TexIdCommon)/PopTextureID() block, -/// if _Data->TexIdCommon != _CmdHeader.TextureId. -/// * imgui_draw.cpp:729 ImDrawList::AddPolyLine -/// The if block always needs to call PushTextureID if the abovementioned condition is not met. -/// Change push_texture_id to only have one condition. -/// -[ServiceManager.EarlyLoadedService] -internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableService -{ - private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0; - private const int CImGuiImDrawListAddRectFilled = 0x59FD0; - private const int CImGuiImDrawListAddImageRounded = 0x58390; - private const int CImGuiImDrawListSharedDataTexIdCommonOffset = 0; - - private readonly Hook hookImDrawListAddPolyline; - private readonly Hook hookImDrawListAddRectFilled; - private readonly Hook hookImDrawListAddImageRounded; - - [ServiceManager.ServiceConstructor] - private ImGuiDrawListFixProvider(InterfaceManager.InterfaceManagerWithScene imws) - { - // Force cimgui.dll to be loaded. - // TODO(goat): Apply fixes in natives - /* - _ = ImGui.GetCurrentContext(); - var cimgui = Process.GetCurrentProcess().Modules.Cast() - .First(x => x.ModuleName == "cimgui.dll") - .BaseAddress; - - this.hookImDrawListAddPolyline = Hook.FromAddress( - cimgui + CImGuiImDrawListAddPolyLineOffset, - this.ImDrawListAddPolylineDetour); - this.hookImDrawListAddRectFilled = Hook.FromAddress( - cimgui + CImGuiImDrawListAddRectFilled, - this.ImDrawListAddRectFilledDetour); - this.hookImDrawListAddImageRounded = Hook.FromAddress( - cimgui + CImGuiImDrawListAddImageRounded, - this.ImDrawListAddImageRoundedDetour); - this.hookImDrawListAddPolyline.Enable(); - this.hookImDrawListAddRectFilled.Enable(); - this.hookImDrawListAddImageRounded.Enable(); - */ - } - - private delegate void ImDrawListAddPolyLine( - ImDrawListPtr drawListPtr, - ref Vector2 points, - int pointsCount, - uint color, - ImDrawFlags flags, - float thickness); - - private delegate void ImDrawListAddRectFilled( - ImDrawListPtr drawListPtr, - ref Vector2 min, - ref Vector2 max, - uint col, - float rounding, - ImDrawFlags flags); - - private delegate void ImDrawListAddImageRounded( - ImDrawListPtr drawListPtr, - nint userTextureId, ref Vector2 xy0, - ref Vector2 xy1, - ref Vector2 uv0, - ref Vector2 uv1, - uint col, - float rounding, - ImDrawFlags flags); - - /// - void IInternalDisposableService.DisposeService() - { - this.hookImDrawListAddPolyline.Dispose(); - this.hookImDrawListAddRectFilled.Dispose(); - this.hookImDrawListAddImageRounded.Dispose(); - } - - private static ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) - { -#if !IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) - // ~0 --> ImDrawFlags_RoundCornersAll or 0 - if ((int)flags == ~0) - return ImDrawFlags.RoundCornersAll; - - // Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations) - // 0x01 --> ImDrawFlags_RoundCornersTopLeft (VALUE 0x01 OVERLAPS ImDrawFlags_Closed but ImDrawFlags_Closed is never valid in this path!) - // 0x02 --> ImDrawFlags_RoundCornersTopRight - // 0x03 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight - // 0x04 --> ImDrawFlags_RoundCornersBotLeft - // 0x05 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBotLeft - // ... - // 0x0F --> ImDrawFlags_RoundCornersAll or 0 - // (See all values in ImDrawCornerFlags_) - if ((int)flags >= 0x01 && (int)flags <= 0x0F) - return (ImDrawFlags)((int)flags << 4); - - // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' -#endif - - // If this triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. - // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc... - if (((int)flags & 0x0F) != 0) - throw new ArgumentException("Misuse of legacy hardcoded ImDrawCornerFlags values!"); - - if ((flags & ImDrawFlags.RoundCornersMask) == 0) - flags |= ImDrawFlags.RoundCornersAll; - - return flags; - } - - private void ImDrawListAddRectFilledDetour( - ImDrawListPtr drawListPtr, - ref Vector2 min, - ref Vector2 max, - uint col, - float rounding, - ImDrawFlags flags) - { - // Skip drawing if we're drawing something with alpha value of 0. - if ((col & 0xFF000000) == 0) - return; - - if (rounding < 0.5f || (flags & ImDrawFlags.RoundCornersMask) == ImDrawFlags.RoundCornersMask) - { - // Take the fast path of drawing two triangles if no rounded corners are required. - - var texIdCommon = *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); - var pushTextureId = texIdCommon != drawListPtr._CmdHeader.TextureId; - if (pushTextureId) - drawListPtr.PushTextureID(texIdCommon); - - drawListPtr.PrimReserve(6, 4); - drawListPtr.PrimRect(min, max, col); - - if (pushTextureId) - drawListPtr.PopTextureID(); - } - else - { - // Defer drawing rectangle with rounded corners to path drawing operations. - // Note that this may have a slightly different extent behaviors from the above if case. - // This is how it is in imgui_draw.cpp. - drawListPtr.PathRect(min, max, rounding, flags); - drawListPtr.PathFillConvex(col); - } - } - - private void ImDrawListAddPolylineDetour( - ImDrawListPtr drawListPtr, - ref Vector2 points, - int pointsCount, - uint color, - ImDrawFlags flags, - float thickness) - { - var texIdCommon = *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); - var pushTextureId = texIdCommon != drawListPtr._CmdHeader.TextureId; - if (pushTextureId) - drawListPtr.PushTextureID(texIdCommon); - - this.hookImDrawListAddPolyline.Original(drawListPtr, ref points, pointsCount, color, flags, thickness); - - if (pushTextureId) - drawListPtr.PopTextureID(); - } - - private void ImDrawListAddImageRoundedDetour(ImDrawListPtr drawListPtr, nint userTextureId, ref Vector2 xy0, ref Vector2 xy1, ref Vector2 uv0, ref Vector2 uv1, uint col, float rounding, ImDrawFlags flags) - { - // Skip drawing if we're drawing something with alpha value of 0. - if ((col & 0xFF000000) == 0) - return; - - // Handle non-rounded cases. - flags = FixRectCornerFlags(flags); - if (rounding < 0.5f || (flags & ImDrawFlags.RoundCornersMask) == ImDrawFlags.RoundCornersNone) - { - drawListPtr.AddImage(userTextureId, xy0, xy1, uv0, uv1, col); - return; - } - - // Temporary provide the requested image as the common texture ID, so that the underlying - // ImDrawList::AddConvexPolyFilled does not create a separate draw command and then revert back. - // ImDrawList::AddImageRounded will temporarily push the texture ID provided by the user if the latest draw - // command does not point to the texture we're trying to draw. Once pushed, ImDrawList::AddConvexPolyFilled - // will leave the list of draw commands alone, so that ImGui::ShadeVertsLinearUV can safely work on the latest - // draw command. - ref var texIdCommon = ref *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); - var texIdCommonPrev = texIdCommon; - texIdCommon = userTextureId; - - this.hookImDrawListAddImageRounded.Original( - drawListPtr, - texIdCommon, - ref xy0, - ref xy1, - ref uv0, - ref uv1, - col, - rounding, - flags); - - texIdCommon = texIdCommonPrev; - } -} diff --git a/lib/cimgui b/lib/cimgui index 7b4955bbd..a302ebabc 160000 --- a/lib/cimgui +++ b/lib/cimgui @@ -1 +1 @@ -Subproject commit 7b4955bbd8f7bc6c022d39503dc806fa53a6a590 +Subproject commit a302ebabcca49c2e37711ea14a0b0915d38253b0 From 2d4e4122aa77ae9e35e6cd20b8b2f37424034f6e Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 24 Dec 2024 03:57:28 +0100 Subject: [PATCH 209/375] link with static CRT --- external/cimgui/cimgui.vcxproj | 1 + external/cimguizmo/cimguizmo.vcxproj | 1 + external/cimplot/cimplot.vcxproj | 1 + 3 files changed, 3 insertions(+) diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index c6109a131..9af4ce88d 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -63,6 +63,7 @@ ..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories) NotUsing + MultiThreaded diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj index 56c085da3..6ff40ed36 100644 --- a/external/cimguizmo/cimguizmo.vcxproj +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -67,6 +67,7 @@ ..\..\lib\cimgui\imgui;..\..\lib\cimguizmo\ImGuizmo;..\..\lib\cimgui;%(AdditionalIncludeDirectories) NotUsing + MultiThreaded diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj index 44bcf95bb..3444ebd31 100644 --- a/external/cimplot/cimplot.vcxproj +++ b/external/cimplot/cimplot.vcxproj @@ -65,6 +65,7 @@ ..\..\lib\cimgui\imgui;..\..\lib\cimplot\implot;..\..\lib\cimgui;%(AdditionalIncludeDirectories) NotUsing + MultiThreaded From 8773d3b873a5eef9458d65c5bb78c4b90882d7d5 Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 24 Dec 2024 16:00:09 +0100 Subject: [PATCH 210/375] fix debug build --- external/cimgui/cimgui.vcxproj | 3 ++- external/cimguizmo/cimguizmo.vcxproj | 3 ++- external/cimplot/cimplot.vcxproj | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index 9af4ce88d..c8a4294dc 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -63,7 +63,6 @@ ..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories) NotUsing - MultiThreaded @@ -73,6 +72,7 @@ true _DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) true + MultiThreadedDebug Windows @@ -88,6 +88,7 @@ true NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) true + MultiThreaded Windows diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj index 6ff40ed36..48b432327 100644 --- a/external/cimguizmo/cimguizmo.vcxproj +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -67,7 +67,6 @@ ..\..\lib\cimgui\imgui;..\..\lib\cimguizmo\ImGuizmo;..\..\lib\cimgui;%(AdditionalIncludeDirectories) NotUsing - MultiThreaded @@ -77,6 +76,7 @@ true _DEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true + MultiThreadedDebug Windows @@ -92,6 +92,7 @@ true NDEBUG;CIMGUIZMO_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true + MultiThreaded Windows diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj index 3444ebd31..c7ae46a6b 100644 --- a/external/cimplot/cimplot.vcxproj +++ b/external/cimplot/cimplot.vcxproj @@ -65,7 +65,6 @@ ..\..\lib\cimgui\imgui;..\..\lib\cimplot\implot;..\..\lib\cimgui;%(AdditionalIncludeDirectories) NotUsing - MultiThreaded @@ -75,6 +74,7 @@ true _DEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true + MultiThreadedDebug Windows @@ -90,6 +90,7 @@ true NDEBUG;CIMPLOT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true + MultiThreaded Windows From 12bf2f4478134fac5068679c69fb46e8829a28b1 Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 25 Dec 2024 12:46:54 +0100 Subject: [PATCH 211/375] replace nonfunctional managed asserts with proper imgui-handled assert mechanism --- .../Internal/DalamudConfiguration.cs | 8 +- Dalamud/Interface/Internal/AssertHandler.cs | 177 ++++++++++++++++++ Dalamud/Interface/Internal/DalamudIme.cs | 35 ++-- .../Interface/Internal/DalamudInterface.cs | 18 +- .../Interface/Internal/InterfaceManager.cs | 17 +- .../ManagedAsserts/ImGuiContextOffsets.cs | 23 --- .../ManagedAsserts/ImGuiManagedAsserts.cs | 140 -------------- .../Widgets/DevPluginsSettingsEntry.cs | 4 + Dalamud/Interface/UiBuilder.cs | 7 - Dalamud/Interface/Windowing/WindowSystem.cs | 6 - external/cimgui/cimgui.vcxproj | 8 +- lib/cimgui | 2 +- 12 files changed, 238 insertions(+), 207 deletions(-) create mode 100644 Dalamud/Interface/Internal/AssertHandler.cs delete mode 100644 Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs delete mode 100644 Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 4df38d6df..3b140e9cd 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -243,7 +243,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup. /// - public bool AssertsEnabledAtStartup { get; set; } + public bool? ImGuiAssertsEnabledAtStartup { get; set; } /// /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. @@ -605,6 +605,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService this.AutoUpdateBehavior ??= this.AutoUpdatePlugins ? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll : Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify; + + // Turn ImGui asserts on by default if we have any active dev plugins + if (!this.ImGuiAssertsEnabledAtStartup.HasValue && this.DevPluginLoadLocations.Any(x => x.IsEnabled)) + { + this.ImGuiAssertsEnabledAtStartup = true; + } #pragma warning restore CS0618 } diff --git a/Dalamud/Interface/Internal/AssertHandler.cs b/Dalamud/Interface/Internal/AssertHandler.cs new file mode 100644 index 000000000..22aa75b9e --- /dev/null +++ b/Dalamud/Interface/Internal/AssertHandler.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; + +using Dalamud.Utility; + +using Serilog; + +namespace Dalamud.Interface.Internal; + +/// +/// Class responsible for registering and handling ImGui asserts. +/// +internal class AssertHandler : IDisposable +{ + private readonly HashSet ignoredAsserts = []; + + // Store callback to avoid it from being GC'd + private readonly AssertCallbackDelegate callback; + + /// + /// Initializes a new instance of the class. + /// + public AssertHandler() + { + this.callback = (expr, file, line) => this.OnImGuiAssert(expr, file, line); + } + + private delegate void AssertCallbackDelegate( + [MarshalAs(UnmanagedType.LPStr)] string expr, + [MarshalAs(UnmanagedType.LPStr)] string file, + int line); + + /// + /// Gets or sets a value indicating whether ImGui asserts should be shown to the user. + /// + public bool ShowAsserts { get; set; } = false; + + /// + /// Register the cimgui assert handler with the native library. + /// + public void Setup() + { + CustomNativeFunctions.igCustom_SetAssertCallback(this.callback); + } + + /// + /// Unregister the cimgui assert handler with the native library. + /// + public void Shutdown() + { + CustomNativeFunctions.igCustom_SetAssertCallback(null); + } + + /// + public void Dispose() + { + this.Shutdown(); + } + + private void OnImGuiAssert(string expr, string file, int line) + { + Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}", expr, file, line); + + if (!this.ShowAsserts) + return; + + var key = $"{file}:{line}"; + if (this.ignoredAsserts.Contains(key)) + return; + + // TODO: It would be nice to get unmanaged stack frames here, seems hard though without pulling that + // entire code in from the crash handler + var originalStackTrace = new StackTrace(2).ToString(); + + string? GetRepoUrl() + { + // TODO: implot, imguizmo? + const string userName = "goatcorp"; + const string repoName = "gc-imgui"; + const string branch = "1.88-enhanced-abifix"; + + if (!file.Contains("imgui", StringComparison.OrdinalIgnoreCase)) + return null; + + var lastSlash = file.LastIndexOf('\\'); + var fileName = file[(lastSlash + 1)..]; + return $"https://github.com/{userName}/{repoName}/blob/{branch}/{fileName}#L{line}"; + } + + var gitHubUrl = GetRepoUrl(); + var showOnGitHubButton = new TaskDialogButton + { + Text = "Show on GitHub", + AllowCloseDialog = false, + Enabled = !gitHubUrl.IsNullOrEmpty(), + }; + showOnGitHubButton.Click += (_, _) => + { + if (!gitHubUrl.IsNullOrEmpty()) + Util.OpenLink(gitHubUrl); + }; + + var breakButton = new TaskDialogButton + { + Text = "Break", + AllowCloseDialog = true, + }; + + var ignoreButton = TaskDialogButton.Ignore; + var abortButton = TaskDialogButton.Abort; + + TaskDialogButton? result = null; + void DialogThreadStart() + { + // TODO(goat): This is probably not gonna work if we showed the loading dialog + // this session since it already loaded visual styles... + Application.EnableVisualStyles(); + + var page = new TaskDialogPage() + { + Heading = "ImGui assertion failed", + Caption = "Dalamud", + Expander = new TaskDialogExpander + { + CollapsedButtonText = "Show stack trace", + ExpandedButtonText = "Hide stack trace", + Text = originalStackTrace, + }, + Text = $"Some code in a plugin or Dalamud itself has caused an internal assertion in ImGui to fail. The game will most likely crash now.\n\n{expr}\nAt: {file}:{line}", + Icon = TaskDialogIcon.Warning, + Buttons = + [ + showOnGitHubButton, + breakButton, + ignoreButton, + abortButton, + ], + DefaultButton = showOnGitHubButton, + }; + + result = TaskDialog.ShowDialog(page); + } + + // Run in a separate thread because of STA and to not mess up other stuff + var thread = new Thread(DialogThreadStart) + { + Name = "Dalamud ImGui Assert Dialog", + }; + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + if (result == breakButton) + { + Debugger.Break(); + } + else if (result == abortButton) + { + Environment.Exit(-1); + } + else if (result == ignoreButton) + { + this.ignoredAsserts.Add(key); + } + } + + private static class CustomNativeFunctions + { + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] +#pragma warning disable SA1300 + public static extern void igCustom_SetAssertCallback(AssertCallbackDelegate callback); +#pragma warning restore SA1300 + } +} diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index c061ec12d..7beb5e58b 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -16,7 +16,6 @@ using Dalamud.Game.Text; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.Colors; using Dalamud.Interface.GameFonts; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Utility; @@ -185,7 +184,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService return true; if (!ImGui.GetIO().ConfigInputTextCursorBlink) return true; - var textState = TextState; + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); if (textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0) return true; if (textState->CursorAnim <= 0) @@ -194,9 +193,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService } } - private static ImGuiInputTextState* TextState => - (ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextOffsets.TextStateOffset); - /// Gets a value indicating whether to display partial conversion status. private bool ShowPartialConversion => this.partialConversionFrom != 0 || this.partialConversionTo != this.compositionString.Length; @@ -341,7 +337,8 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService try { - var invalidTarget = TextState->Id == 0 || (TextState->Flags & ImGuiInputTextFlags.ReadOnly) != 0; + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); + var invalidTarget = textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0; #if IMEDEBUG switch (args.Message) @@ -570,19 +567,20 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService this.ReflectCharacterEncounters(newString); + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); if (this.temporaryUndoSelection is not null) { - TextState->Undo(); - TextState->SelectionTuple = this.temporaryUndoSelection.Value; + textState->Undo(); + textState->SelectionTuple = this.temporaryUndoSelection.Value; this.temporaryUndoSelection = null; } - TextState->SanitizeSelectionRange(); - if (TextState->ReplaceSelectionAndPushUndo(newString)) - this.temporaryUndoSelection = TextState->SelectionTuple; + textState->SanitizeSelectionRange(); + if (textState->ReplaceSelectionAndPushUndo(newString)) + this.temporaryUndoSelection = textState->SelectionTuple; // Put the cursor at the beginning, so that the candidate window appears aligned with the text. - TextState->SetSelectionRange(TextState->SelectionTuple.Start, newString.Length, 0); + textState->SetSelectionRange(textState->SelectionTuple.Start, newString.Length, 0); if (finalCommit) { @@ -627,7 +625,10 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService this.partialConversionFrom = this.partialConversionTo = 0; this.compositionCursorOffset = 0; this.temporaryUndoSelection = null; - TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd; + + var textState = CustomNativeFunctions.igCustom_GetInputTextState(); + textState->Stb.SelectStart = textState->Stb.Cursor = textState->Stb.SelectEnd; + this.candidateStrings.Clear(); this.immCandNative = default; if (invokeCancel) @@ -1113,6 +1114,14 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService } } + private static class CustomNativeFunctions + { + [DllImport("cimgui")] +#pragma warning disable SA1300 + public static extern ImGuiInputTextState* igCustom_GetInputTextState(); +#pragma warning restore SA1300 + } + #if IMEDEBUG private static class ImeDebug { diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index a21cf2070..0aa10f514 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -18,7 +18,6 @@ using Dalamud.Game.Internal; using Dalamud.Hooking; using Dalamud.Interface.Animation.EasingFunctions; using Dalamud.Interface.Colors; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.Internal.Windows.Data; using Dalamud.Interface.Internal.Windows.PluginInstaller; @@ -163,7 +162,7 @@ internal class DalamudInterface : IInternalDisposableService this.WindowSystem.AddWindow(this.branchSwitcherWindow); this.WindowSystem.AddWindow(this.hitchSettingsWindow); - ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; + this.interfaceManager.ShowAsserts = configuration.ImGuiAssertsEnabledAtStartup ?? false; this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; this.interfaceManager.Draw += this.OnDraw; @@ -832,6 +831,12 @@ internal class DalamudInterface : IInternalDisposableService hook.Enable(); } } + + if (ImGui.MenuItem("Cause ImGui assert")) + { + ImGui.PopStyleVar(); + ImGui.PopStyleVar(); + } ImGui.EndMenu(); } @@ -865,15 +870,16 @@ internal class DalamudInterface : IInternalDisposableService ImGui.Separator(); - var val = ImGuiManagedAsserts.AssertsEnabled; + var val = this.interfaceManager.ShowAsserts; if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) { - ImGuiManagedAsserts.AssertsEnabled = val; + this.interfaceManager.ShowAsserts = val; } - if (ImGui.MenuItem("Enable asserts at startup", null, this.configuration.AssertsEnabledAtStartup)) + var assertsEnabled = this.configuration.ImGuiAssertsEnabledAtStartup ?? false; + if (ImGui.MenuItem("Enable asserts at startup", null, assertsEnabled)) { - this.configuration.AssertsEnabledAtStartup = !this.configuration.AssertsEnabledAtStartup; + this.configuration.ImGuiAssertsEnabledAtStartup = !assertsEnabled; this.configuration.QueueSave(); } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index f532b0412..af9f61647 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -20,7 +20,6 @@ using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal.DesignSystem; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; @@ -93,6 +92,8 @@ internal partial class InterfaceManager : IInternalDisposableService private readonly ConcurrentQueue runBeforeImGuiRender = new(); private readonly ConcurrentQueue runAfterImGuiRender = new(); + + private readonly AssertHandler assertHandler = new(); private RawDX11Scene? scene; @@ -267,11 +268,20 @@ internal partial class InterfaceManager : IInternalDisposableService /// public long CumulativePresentCalls { get; private set; } + /// + public bool ShowAsserts + { + get => this.assertHandler.ShowAsserts; + set => this.assertHandler.ShowAsserts = value; + } + /// /// Dispose of managed and unmanaged resources. /// void IInternalDisposableService.DisposeService() { + this.assertHandler.Dispose(); + // Unload hooks from the framework thread if possible. // We're currently off the framework thread, as this function can only be called from // ServiceManager.UnloadAllServices, which is called from EntryPoint.RunThread. @@ -565,6 +575,7 @@ internal partial class InterfaceManager : IInternalDisposableService { try { + this.assertHandler.Setup(); newScene = new RawDX11Scene((nint)swapChain); } catch (DllNotFoundException ex) @@ -1128,15 +1139,11 @@ internal partial class InterfaceManager : IInternalDisposableService WindowSystem.HasAnyWindowSystemFocus = false; WindowSystem.FocusedWindowSystemNamespace = string.Empty; - var snap = ImGuiManagedAsserts.GetSnapshot(); - if (this.IsDispatchingEvents) { this.Draw?.Invoke(); Service.GetNullable()?.Draw(); } - - ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); } /// diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs deleted file mode 100644 index 89e23ab78..000000000 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Dalamud.Interface.Internal.ManagedAsserts; - -/// -/// Offsets to various data in ImGui context. -/// -/// -/// Last updated for ImGui 1.83. -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] -internal static class ImGuiContextOffsets -{ - public const int CurrentWindowStackOffset = 0x73A; - - public const int ColorStackOffset = 0x79C; - - public const int StyleVarStackOffset = 0x7A0; - - public const int FontStackOffset = 0x7A4; - - public const int BeginPopupStackOffset = 0x7B8; - - public const int TextStateOffset = 0x4588; -} diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs deleted file mode 100644 index ff42e93f3..000000000 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System.Diagnostics; - -using ImGuiNET; - -using static Dalamud.NativeFunctions; - -namespace Dalamud.Interface.Internal.ManagedAsserts; - -/// -/// Report ImGui problems with a MessageBox dialog. -/// -internal static class ImGuiManagedAsserts -{ - /// - /// Gets or sets a value indicating whether asserts are enabled for ImGui. - /// - public static bool AssertsEnabled { get; set; } - - /// - /// Create a snapshot of the current ImGui context. - /// Should be called before rendering an ImGui frame. - /// - /// A snapshot of the current context. - public static unsafe ImGuiContextSnapshot GetSnapshot() - { - var contextPtr = ImGui.GetCurrentContext(); - - var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size - var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size - var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size - var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size - var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size - - return new ImGuiContextSnapshot - { - StyleVarStackSize = styleVarStack, - ColorStackSize = colorStack, - FontStackSize = fontStack, - BeginPopupStackSize = popupStack, - WindowStackSize = windowStack, - }; - } - - /// - /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. - /// - /// The source of any problems, something to blame. - /// ImGui context snapshot. - public static void ReportProblems(string source, ImGuiContextSnapshot before) - { - // TODO: Needs to be updated for ImGui 1.88 - return; - -#pragma warning disable CS0162 - if (!AssertsEnabled) - { - return; - } - - var cSnap = GetSnapshot(); - - if (before.StyleVarStackSize != cSnap.StyleVarStackSize) - { - ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); - return; - } - - if (before.ColorStackSize != cSnap.ColorStackSize) - { - ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); - return; - } - - if (before.FontStackSize != cSnap.FontStackSize) - { - ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); - return; - } - - if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) - { - ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); - return; - } - - if (cSnap.WindowStackSize != 1) - { - if (cSnap.WindowStackSize > 1) - { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); - } - else - { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); - } - } -#pragma warning restore CS0162 - } - - private static void ShowAssert(string source, string message) - { - var caption = $"You fucked up"; - message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; - var flags = MessageBoxType.Ok | MessageBoxType.IconError; - - _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); - AssertsEnabled = false; - } - - /// - /// A snapshot of various ImGui context properties. - /// - public class ImGuiContextSnapshot - { - /// - /// Gets the ImGui style var stack size. - /// - public int StyleVarStackSize { get; init; } - - /// - /// Gets the ImGui color stack size. - /// - public int ColorStackSize { get; init; } - - /// - /// Gets the ImGui font stack size. - /// - public int FontStackSize { get; init; } - - /// - /// Gets the ImGui begin popup stack size. - /// - public int BeginPopupStackSize { get; init; } - - /// - /// Gets the ImGui window stack size. - /// - public int WindowStackSize { get; init; } - } -} diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs index fe4462ce2..101b21d38 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs @@ -222,6 +222,10 @@ public class DevPluginsSettingsEntry : SettingsEntry this.devPluginLocationsChanged = true; this.devPluginTempLocation = string.Empty; } + + var config = Service.Get(); + if (!config.ImGuiAssertsEnabledAtStartup.HasValue) + config.ImGuiAssertsEnabledAtStartup = true; } public override void PostDraw() diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 1413f3347..2fc8c83b1 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -9,7 +9,6 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Gui; using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Plugin.Internal.Types; @@ -713,8 +712,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder ImGui.End(); } - var snapshot = this.Draw is null ? null : ImGuiManagedAsserts.GetSnapshot(); - try { this.Draw?.InvokeSafely(); @@ -728,10 +725,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder this.hasErrorWindow = true; } - // Only if Draw was successful - if (this.Draw is not null && snapshot is not null) - ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); - this.FrameCount++; if (DoStats) diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index dc4d6aca1..a3eab2a60 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Configuration.Internal; -using Dalamud.Interface.Internal.ManagedAsserts; using ImGuiNET; using Serilog; @@ -112,12 +111,7 @@ public class WindowSystem #if DEBUG // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif - var snapshot = ImGuiManagedAsserts.GetSnapshot(); - window.DrawInternal(config); - - var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; - ImGuiManagedAsserts.ReportProblems(source, snapshot); } var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index c8a4294dc..a7a7ddb0e 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -58,19 +58,17 @@ ..\$(Platform)\$(Configuration)\ - - ..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories) + ..\..\lib\cimgui\imgui;..\..\lib\cimgui;%(AdditionalIncludeDirectories) NotUsing - Level3 true - _DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) + _DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;IMGUI_USER_CONFIG="cimgui_user.h";%(PreprocessorDefinitions) true MultiThreadedDebug @@ -86,7 +84,7 @@ true true true - NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions) + NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;IMGUI_USER_CONFIG="cimgui_user.h";%(PreprocessorDefinitions) true MultiThreaded diff --git a/lib/cimgui b/lib/cimgui index a302ebabc..fd2377934 160000 --- a/lib/cimgui +++ b/lib/cimgui @@ -1 +1 @@ -Subproject commit a302ebabcca49c2e37711ea14a0b0915d38253b0 +Subproject commit fd2377934f2cc007982e21ab82e54b41955cb658 From 9d8bcb7a24148a33f2a8d182d73a4baf243e91a1 Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 25 Dec 2024 17:03:22 +0100 Subject: [PATCH 212/375] add "disable for this session" button to ImGui asserts --- .../Internal/{ => Asserts}/AssertHandler.cs | 23 +++++++++++-------- .../Interface/Internal/InterfaceManager.cs | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) rename Dalamud/Interface/Internal/{ => Asserts}/AssertHandler.cs (91%) diff --git a/Dalamud/Interface/Internal/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs similarity index 91% rename from Dalamud/Interface/Internal/AssertHandler.cs rename to Dalamud/Interface/Internal/Asserts/AssertHandler.cs index 22aa75b9e..b0cfb462c 100644 --- a/Dalamud/Interface/Internal/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -8,7 +8,7 @@ using Dalamud.Utility; using Serilog; -namespace Dalamud.Interface.Internal; +namespace Dalamud.Interface.Internal.Asserts; /// /// Class responsible for registering and handling ImGui asserts. @@ -36,7 +36,7 @@ internal class AssertHandler : IDisposable /// /// Gets or sets a value indicating whether ImGui asserts should be shown to the user. /// - public bool ShowAsserts { get; set; } = false; + public bool ShowAsserts { get; set; } /// /// Register the cimgui assert handler with the native library. @@ -93,7 +93,7 @@ internal class AssertHandler : IDisposable var gitHubUrl = GetRepoUrl(); var showOnGitHubButton = new TaskDialogButton { - Text = "Show on GitHub", + Text = "Open GitHub", AllowCloseDialog = false, Enabled = !gitHubUrl.IsNullOrEmpty(), }; @@ -108,9 +108,14 @@ internal class AssertHandler : IDisposable Text = "Break", AllowCloseDialog = true, }; + + var disableButton = new TaskDialogButton + { + Text = "Disable for this session", + AllowCloseDialog = true, + }; var ignoreButton = TaskDialogButton.Ignore; - var abortButton = TaskDialogButton.Abort; TaskDialogButton? result = null; void DialogThreadStart() @@ -119,7 +124,7 @@ internal class AssertHandler : IDisposable // this session since it already loaded visual styles... Application.EnableVisualStyles(); - var page = new TaskDialogPage() + var page = new TaskDialogPage { Heading = "ImGui assertion failed", Caption = "Dalamud", @@ -135,8 +140,8 @@ internal class AssertHandler : IDisposable [ showOnGitHubButton, breakButton, + disableButton, ignoreButton, - abortButton, ], DefaultButton = showOnGitHubButton, }; @@ -157,9 +162,9 @@ internal class AssertHandler : IDisposable { Debugger.Break(); } - else if (result == abortButton) + else if (result == disableButton) { - Environment.Exit(-1); + this.ShowAsserts = false; } else if (result == ignoreButton) { @@ -171,7 +176,7 @@ internal class AssertHandler : IDisposable { [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] #pragma warning disable SA1300 - public static extern void igCustom_SetAssertCallback(AssertCallbackDelegate callback); + public static extern void igCustom_SetAssertCallback(AssertCallbackDelegate? callback); #pragma warning restore SA1300 } } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index af9f61647..36f48f9b8 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -19,6 +19,7 @@ using Dalamud.Hooking.Internal; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; +using Dalamud.Interface.Internal.Asserts; using Dalamud.Interface.Internal.DesignSystem; using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.ManagedFontAtlas; From 8b06ae3f006b8f392c29de1a2cc61f57c66b3f37 Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 25 Dec 2024 17:04:35 +0100 Subject: [PATCH 213/375] add ImGui assert options to experimental tab --- .../Settings/Tabs/SettingsTabExperimental.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs index aef674ac4..4354daffe 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs @@ -65,6 +65,26 @@ public class SettingsTabExperimental : SettingsTab new GapSettingsEntry(5, true), new DevPluginsSettingsEntry(), + + new SettingsEntry( + Loc.Localize( + "DalamudSettingEnableImGuiAsserts", + "Enable ImGui asserts"), + Loc.Localize( + "DalamudSettingEnableImGuiAssertsHint", + "If this setting is enabled, a window containing further details will be shown when an internal assertion in ImGui fails.\nWe recommend enabling this when developing plugins."), + c => Service.Get().ShowAsserts, + (v, _) => Service.Get().ShowAsserts = v), + + new SettingsEntry( + Loc.Localize( + "DalamudSettingEnableImGuiAssertsAtStartup", + "Always enable ImGui asserts at startup"), + Loc.Localize( + "DalamudSettingEnableImGuiAssertsAtStartupHint", + "This will enable ImGui asserts every time the game starts."), + c => c.ImGuiAssertsEnabledAtStartup ?? false, + (v, c) => c.ImGuiAssertsEnabledAtStartup = v), new GapSettingsEntry(5, true), From 108308170638753feb4e8d06811612ed5bfbfd07 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Wed, 25 Dec 2024 14:33:23 -0800 Subject: [PATCH 214/375] fix: Safely invoke marketboard events being sent to plugins. (#2150) --- Dalamud/Game/Marketboard/MarketBoard.cs | 117 ++++++++++++++++++++---- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/Dalamud/Game/Marketboard/MarketBoard.cs b/Dalamud/Game/Marketboard/MarketBoard.cs index 3642b86b8..79de09dea 100644 --- a/Dalamud/Game/Marketboard/MarketBoard.cs +++ b/Dalamud/Game/Marketboard/MarketBoard.cs @@ -1,9 +1,15 @@ -using Dalamud.Game.Network.Internal; +using System.Linq; + +using Dalamud.Game.Network.Internal; using Dalamud.Game.Network.Structures; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Logging.Internal; +using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using static Dalamud.Plugin.Services.IMarketBoard; + namespace Dalamud.Game.MarketBoard; /// @@ -29,19 +35,19 @@ internal class MarketBoard : IInternalDisposableService, IMarketBoard } /// - public event IMarketBoard.HistoryReceivedDelegate? HistoryReceived; + public event HistoryReceivedDelegate? HistoryReceived; /// - public event IMarketBoard.ItemPurchasedDelegate? ItemPurchased; + public event ItemPurchasedDelegate? ItemPurchased; /// - public event IMarketBoard.OfferingsReceivedDelegate? OfferingsReceived; + public event OfferingsReceivedDelegate? OfferingsReceived; /// - public event IMarketBoard.PurchaseRequestedDelegate? PurchaseRequested; + public event PurchaseRequestedDelegate? PurchaseRequested; /// - public event IMarketBoard.TaxRatesReceivedDelegate? TaxRatesReceived; + public event TaxRatesReceivedDelegate? TaxRatesReceived; /// public void DisposeService() @@ -89,35 +95,42 @@ internal class MarketBoard : IInternalDisposableService, IMarketBoard #pragma warning restore SA1015 internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoard { + private static readonly ModuleLog Log = new(nameof(MarketBoardPluginScoped)); + [ServiceManager.ServiceDependency] private readonly MarketBoard marketBoardService = Service.Get(); + private readonly string owningPluginName; + /// /// Initializes a new instance of the class. /// - internal MarketBoardPluginScoped() + /// The plugin owning this service. + internal MarketBoardPluginScoped(LocalPlugin? plugin) { this.marketBoardService.HistoryReceived += this.OnHistoryReceived; this.marketBoardService.ItemPurchased += this.OnItemPurchased; this.marketBoardService.OfferingsReceived += this.OnOfferingsReceived; this.marketBoardService.PurchaseRequested += this.OnPurchaseRequested; this.marketBoardService.TaxRatesReceived += this.OnTaxRatesReceived; + + this.owningPluginName = plugin?.InternalName ?? "DalamudInternal"; } /// - public event IMarketBoard.HistoryReceivedDelegate? HistoryReceived; + public event HistoryReceivedDelegate? HistoryReceived; /// - public event IMarketBoard.ItemPurchasedDelegate? ItemPurchased; + public event ItemPurchasedDelegate? ItemPurchased; /// - public event IMarketBoard.OfferingsReceivedDelegate? OfferingsReceived; + public event OfferingsReceivedDelegate? OfferingsReceived; /// - public event IMarketBoard.PurchaseRequestedDelegate? PurchaseRequested; + public event PurchaseRequestedDelegate? PurchaseRequested; /// - public event IMarketBoard.TaxRatesReceivedDelegate? TaxRatesReceived; + public event TaxRatesReceivedDelegate? TaxRatesReceived; /// void IInternalDisposableService.DisposeService() @@ -137,26 +150,96 @@ internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoar private void OnHistoryReceived(IMarketBoardHistory history) { - this.HistoryReceived?.Invoke(history); + if (this.HistoryReceived == null) return; + + foreach (var action in this.HistoryReceived.GetInvocationList().Cast()) + { + try + { + action.Invoke(history); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.HistoryReceived)); + } + } } private void OnItemPurchased(IMarketBoardPurchase purchase) { - this.ItemPurchased?.Invoke(purchase); + if (this.ItemPurchased == null) return; + + foreach (var action in this.ItemPurchased.GetInvocationList().Cast()) + { + try + { + action.Invoke(purchase); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.ItemPurchased)); + } + } } private void OnOfferingsReceived(IMarketBoardCurrentOfferings currentOfferings) { - this.OfferingsReceived?.Invoke(currentOfferings); + if (this.OfferingsReceived == null) return; + + foreach (var action in this.OfferingsReceived.GetInvocationList() + .Cast()) + { + try + { + action.Invoke(currentOfferings); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.OfferingsReceived)); + } + } } private void OnPurchaseRequested(IMarketBoardPurchaseHandler purchaseHandler) { - this.PurchaseRequested?.Invoke(purchaseHandler); + if (this.PurchaseRequested == null) return; + + foreach (var action in this.PurchaseRequested.GetInvocationList().Cast()) + { + try + { + action.Invoke(purchaseHandler); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.PurchaseRequested)); + } + } } private void OnTaxRatesReceived(IMarketTaxRates taxRates) { - this.TaxRatesReceived?.Invoke(taxRates); + if (this.TaxRatesReceived == null) return; + + foreach (var action in this.TaxRatesReceived.GetInvocationList().Cast()) + { + try + { + action.Invoke(taxRates); + } + catch (Exception ex) + { + this.LogInvocationError(ex, nameof(this.TaxRatesReceived)); + } + } + } + + private void LogInvocationError(Exception ex, string delegateName) + { + Log.Error( + ex, + "An error occured while invoking event `{evName}` for {plugin}", + delegateName, + this.owningPluginName); } } From b79c646b9ba5dfb31677a3d8ae1085c16979bc6f Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 25 Dec 2024 23:42:50 +0100 Subject: [PATCH 215/375] only turn ImGui asserts on if we are actually loading a devplugin --- Dalamud/Configuration/Internal/DalamudConfiguration.cs | 6 ------ Dalamud/Plugin/Internal/PluginManager.cs | 3 +++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 3b140e9cd..22fb2f448 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -605,12 +605,6 @@ internal sealed class DalamudConfiguration : IInternalDisposableService this.AutoUpdateBehavior ??= this.AutoUpdatePlugins ? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll : Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify; - - // Turn ImGui asserts on by default if we have any active dev plugins - if (!this.ImGuiAssertsEnabledAtStartup.HasValue && this.DevPluginLoadLocations.Any(x => x.IsEnabled)) - { - this.ImGuiAssertsEnabledAtStartup = true; - } #pragma warning restore CS0618 } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 9d71c60df..45a923aed 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -1572,6 +1572,9 @@ internal class PluginManager : IInternalDisposableService { Log.Information($"Loading dev plugin {name}"); plugin = new LocalDevPlugin(dllFile, manifest); + + // This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet + this.configuration.ImGuiAssertsEnabledAtStartup ??= true; } else { From 776a838dbeb15a8002d33b637a136ffbeeb330b7 Mon Sep 17 00:00:00 2001 From: goat Date: Wed, 25 Dec 2024 23:47:18 +0100 Subject: [PATCH 216/375] remove legacy MonoMod patches for codebase and assembly location --- Dalamud/Plugin/Internal/PluginManager.cs | 125 ------------------- Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 4 - 2 files changed, 129 deletions(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 45a923aed..b70b74b03 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -134,9 +134,6 @@ internal class PluginManager : IInternalDisposableService this.configuration.PluginTestingOptIns ??= new(); this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient); - // NET8 CHORE - // this.ApplyPatches(); - registerStartupBlocker( Task.Run(this.LoadAndStartLoadSyncPlugins), "Waiting for plugins that asked to be loaded before the game."); @@ -433,10 +430,6 @@ internal class PluginManager : IInternalDisposableService await Task.WhenAll(disposablePlugins.Select(plugin => plugin.DisposeAsync().AsTask())) .SuppressException(); } - - // NET8 CHORE - // this.assemblyLocationMonoHook?.Dispose(); - // this.assemblyCodeBaseMonoHook?.Dispose(); } /// @@ -876,9 +869,6 @@ internal class PluginManager : IInternalDisposableService this.installedPluginsList.Remove(plugin); } - // NET8 CHORE - // PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); - this.NotifyinstalledPluginsListChanged(); this.NotifyAvailablePluginsChanged(); } @@ -1677,8 +1667,6 @@ internal class PluginManager : IInternalDisposableService } catch (InvalidPluginException) { - // NET8 CHORE - // PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); throw; } catch (BannedPluginException) @@ -1724,8 +1712,6 @@ internal class PluginManager : IInternalDisposableService } else { - // NET8 CHORE - // PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _); throw; } } @@ -1934,114 +1920,3 @@ internal class PluginManager : IInternalDisposableService public static string DalamudPluginUpdateFailed(string name, Version version, string why) => Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed ({2}).").Format(name, version, why); } } - -// NET8 CHORE -/* -/// -/// Class responsible for loading and unloading plugins. -/// This contains the assembly patching functionality to resolve assembly locations. -/// -internal partial class PluginManager -{ - /// - /// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading - /// plugins via byte[]. - /// - internal static readonly ConcurrentDictionary PluginLocations = new(); - - private MonoMod.RuntimeDetour.Hook? assemblyLocationMonoHook; - private MonoMod.RuntimeDetour.Hook? assemblyCodeBaseMonoHook; - - /// - /// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin location, or the result from the original method. - private static string AssemblyLocationPatch(Func orig, Assembly self) - { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.Location; - break; - } - } - } - - result ??= string.Empty; - - Log.Verbose($"Assembly.Location // {self.FullName} // {result}"); - return result; - } - - /// - /// Patch method for internal class RuntimeAssembly.CodeBase, also known as Assembly.CodeBase. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin code base, or the result from the original method. - private static string AssemblyCodeBasePatch(Func orig, Assembly self) - { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.CodeBase; - break; - } - } - } - - result ??= string.Empty; - - Log.Verbose($"Assembly.CodeBase // {self.FullName} // {result}"); - return result; - } - - private static IEnumerable GetStackFrameAssemblyNames() - { - var stackTrace = new StackTrace(); - var stackFrames = stackTrace.GetFrames(); - - foreach (var stackFrame in stackFrames) - { - var methodBase = stackFrame.GetMethod(); - if (methodBase == null) - continue; - - yield return methodBase.Module.Assembly.FullName!; - } - } - - private void ApplyPatches() - { - var targetType = typeof(PluginManager).Assembly.GetType(); - - var locationTarget = targetType.GetProperty(nameof(Assembly.Location))!.GetGetMethod(); - var locationPatch = typeof(PluginManager).GetMethod(nameof(AssemblyLocationPatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyLocationMonoHook = new MonoMod.RuntimeDetour.Hook(locationTarget, locationPatch); - -#pragma warning disable CS0618 -#pragma warning disable SYSLIB0012 - var codebaseTarget = targetType.GetProperty(nameof(Assembly.CodeBase))?.GetGetMethod(); -#pragma warning restore SYSLIB0012 -#pragma warning restore CS0618 - var codebasePatch = typeof(PluginManager).GetMethod(nameof(AssemblyCodeBasePatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch); - } -} -*/ diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index ed3a94994..43bba0a5b 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -382,10 +382,6 @@ internal class LocalPlugin : IAsyncDisposable } } - // Update the location for the Location and CodeBase patches - // NET8 CHORE - // PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile); - this.dalamudInterface = new(this, reason); this.serviceScope = ioc.GetScope(); From b02194ca6aa90a5c976146632ef44e5b195a610e Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 13:09:06 +0100 Subject: [PATCH 217/375] DalamudInterface doesn't need DataManager --- Dalamud/Interface/Internal/DalamudInterface.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 0aa10f514..d432d9d4c 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -57,7 +57,6 @@ internal class DalamudInterface : IInternalDisposableService private readonly Dalamud dalamud; private readonly DalamudConfiguration configuration; private readonly InterfaceManager interfaceManager; - private readonly DataManager dataManager; private readonly ChangelogWindow changelogWindow; private readonly ColorDemoWindow colorDemoWindow; @@ -99,7 +98,6 @@ internal class DalamudInterface : IInternalDisposableService DalamudConfiguration configuration, FontAtlasFactory fontAtlasFactory, InterfaceManager interfaceManager, - DataManager dataManager, PluginImageCache pluginImageCache, DalamudAssetManager dalamudAssetManager, Game.Framework framework, @@ -112,7 +110,6 @@ internal class DalamudInterface : IInternalDisposableService this.dalamud = dalamud; this.configuration = configuration; this.interfaceManager = interfaceManager; - this.dataManager = dataManager; this.WindowSystem = new WindowSystem("DalamudCore"); From 1d9116f0a0b01d29cb8af5c9d9d44d49e794769c Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 13:29:54 +0100 Subject: [PATCH 218/375] unhandled exceptions when calling Draw() must be fatal --- Dalamud/Interface/Internal/InterfaceManager.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 36f48f9b8..65f2da705 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -1142,7 +1142,18 @@ internal partial class InterfaceManager : IInternalDisposableService if (this.IsDispatchingEvents) { - this.Draw?.Invoke(); + try + { + this.Draw?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, "Error when invoking global Draw"); + + // We should always handle this in the callbacks. + Util.Fatal("An internal error occurred while drawing the Dalamud UI and the game must close.\nPlease report this error.", "Dalamud"); + } + Service.GetNullable()?.Draw(); } } From ce5ee71c91dc85ebaff62240e2553e6ca368bd80 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 13:48:28 +0100 Subject: [PATCH 219/375] patch NNBSP number separator out of cultures that use it (fixes #2157) --- Dalamud/EntryPoint.cs | 3 +++ Dalamud/Utility/CultureFixes.cs | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 Dalamud/Utility/CultureFixes.cs diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 512acd4cc..6d5c219dd 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -178,6 +178,9 @@ public sealed class EntryPoint throw new Exception("Working directory was invalid"); Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory); + + // Apply common fixes for culture issues + CultureFixes.Apply(); // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; diff --git a/Dalamud/Utility/CultureFixes.cs b/Dalamud/Utility/CultureFixes.cs new file mode 100644 index 000000000..0c048ed38 --- /dev/null +++ b/Dalamud/Utility/CultureFixes.cs @@ -0,0 +1,45 @@ +using System.Globalization; + +namespace Dalamud.Utility; + +/// +/// Class containing fixes for culture-specific issues. +/// +internal static class CultureFixes +{ + /// + /// Apply all fixes. + /// + public static void Apply() + { + PatchNumberSeparator(); + } + + private static void PatchNumberSeparator() + { + // Reset formatting specifier for the "digit grouping symbol" to an empty string + // for cultures that use a narrow no-break space (U+202F). + // This glyph is not present in any game fonts and not in the range for our Noto + // so it will be rendered as a geta (=) instead. That's a hack, but it works and + // doesn't look as weird. + void PatchCulture(CultureInfo info) + { + const string invalidGroupSeparator = "\u202F"; + const string replacedGroupSeparator = " "; + if (info.NumberFormat.NumberGroupSeparator == invalidGroupSeparator) + info.NumberFormat.NumberGroupSeparator = replacedGroupSeparator; + + if (info.NumberFormat.NumberDecimalSeparator == invalidGroupSeparator) + info.NumberFormat.NumberDecimalSeparator = replacedGroupSeparator; + + if (info.NumberFormat.CurrencyGroupSeparator == invalidGroupSeparator) + info.NumberFormat.CurrencyGroupSeparator = replacedGroupSeparator; + + if (info.NumberFormat.CurrencyDecimalSeparator == invalidGroupSeparator) + info.NumberFormat.CurrencyDecimalSeparator = replacedGroupSeparator; + } + + PatchCulture(CultureInfo.CurrentCulture); + PatchCulture(CultureInfo.CurrentUICulture); + } +} From 2e2feb144f45dd1a46c2940a6d0110d724b74f08 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 14:11:14 +0100 Subject: [PATCH 220/375] fix some warnings --- Dalamud/Game/ClientState/ClientState.cs | 1 - Dalamud/Game/Text/XivChatEntry.cs | 4 ++-- .../Components/ImGuiComponents.IconButton.cs | 10 +++++++-- .../ImGuiComponents.IconButtonSelect.cs | 3 +++ .../Windows/Data/Widgets/IconBrowserWidget.cs | 7 +++---- .../Windows/Data/Widgets/SeFontTestWidget.cs | 6 +++--- .../Widgets/DevPluginsSettingsEntry.cs | 21 +++++++++---------- .../Internal/AutoUpdate/AutoUpdateManager.cs | 9 ++++---- Dalamud/Utility/Util.cs | 13 ++++++------ 9 files changed, 40 insertions(+), 34 deletions(-) diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index c898e107a..da9873d8d 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -48,7 +48,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState private bool lastConditionNone = true; - [ServiceManager.ServiceConstructor] private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle) { diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index 7932ead72..d4ec751f3 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -36,12 +36,12 @@ public sealed class XivChatEntry } /// - /// Gets or Sets the name payloads + /// Gets or sets the name payloads. /// public byte[] NameBytes { get; set; } = []; /// - /// Gets or Sets the message payloads. + /// Gets or sets the message payloads. /// public byte[] MessageBytes { get; set; } = []; diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index 10f177590..3e61e16bb 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -182,7 +182,10 @@ public static partial class ImGuiComponents /// /// Icon to show. /// Text to show. - /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text. + /// + /// Sets the size of the button. If either dimension is set to 0, + /// that dimension will conform to the size of the icon and text. + /// /// Indicator if button is clicked. public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector2 size) => IconButtonWithText(icon, text, null, null, null, size); @@ -194,7 +197,10 @@ public static partial class ImGuiComponents /// The default color of the button. /// The color of the button when active. /// The color of the button when hovered. - /// Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text. + /// + /// Sets the size of the button. If either dimension is set to 0, + /// that dimension will conform to the size of the icon and text. + /// /// Indicator if button is clicked. public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) { diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs index ad83c7201..99050473f 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs @@ -8,6 +8,9 @@ using ImGuiNET; namespace Dalamud.Interface.Components; +/// +/// ImGui component used to create a radio-like input that uses icon buttons. +/// public static partial class ImGuiComponents { /// diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs index ff34574b5..3f510b088 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/IconBrowserWidget.cs @@ -18,7 +18,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; public class IconBrowserWidget : IDataWindowWidget { private const int MaxIconId = 250_000; - + private Vector2 iconSize = new(64.0f, 64.0f); private Vector2 editIconSize = new(64.0f, 64.0f); @@ -126,7 +126,6 @@ public class IconBrowserWidget : IDataWindowWidget this.valueRange = null; } - ImGui.NextColumn(); ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputInt("##StopRange", ref this.stopRange, 0, 0)) @@ -204,7 +203,7 @@ public class IconBrowserWidget : IDataWindowWidget ImGui.GetColorU32(ImGuiColors.DalamudRed), iconText); } - + if (ImGui.IsItemHovered()) ImGui.SetTooltip($"{iconId}\n{exc}".Replace("%", "%%")); @@ -224,7 +223,7 @@ public class IconBrowserWidget : IDataWindowWidget cursor + ((this.iconSize - textSize) / 2), color, text); - + if (ImGui.IsItemHovered()) ImGui.SetTooltip(iconId.ToString()); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs index 69282a8e8..35a2a616e 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeFontTestWidget.cs @@ -1,7 +1,7 @@ -using Dalamud.Game.Text; -using ImGuiNET; +using System.Linq; -using System.Linq; +using Dalamud.Game.Text; +using ImGuiNET; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs index 101b21d38..4a78735f6 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs @@ -196,6 +196,14 @@ public class DevPluginsSettingsEntry : SettingsEntry } } + public override void PostDraw() + { + this.fileDialogManager.Draw(); + } + + private static bool ValidDevPluginPath(string path) + => Path.IsPathRooted(path) && Path.GetExtension(path) == ".dll"; + private void AddDevPlugin() { if (this.devPluginLocations.Any( @@ -223,16 +231,7 @@ public class DevPluginsSettingsEntry : SettingsEntry this.devPluginTempLocation = string.Empty; } - var config = Service.Get(); - if (!config.ImGuiAssertsEnabledAtStartup.HasValue) - config.ImGuiAssertsEnabledAtStartup = true; + // Enable ImGui asserts if a dev plugin is added, if no choice was made prior + Service.Get().ImGuiAssertsEnabledAtStartup ??= true; } - - public override void PostDraw() - { - this.fileDialogManager.Draw(); - } - - private static bool ValidDevPluginPath(string path) - => Path.IsPathRooted(path) && Path.GetExtension(path) == ".dll"; } diff --git a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs index 99850ddb4..ce135b947 100644 --- a/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs +++ b/Dalamud/Plugin/Internal/AutoUpdate/AutoUpdateManager.cs @@ -72,6 +72,8 @@ internal class AutoUpdateManager : IServiceType private readonly IConsoleVariable isDryRun; + private readonly Task openInstallerWindowLinkTask; + private DateTime? loginTime; private DateTime? nextUpdateCheckTime; private DateTime? unblockedSince; @@ -82,8 +84,6 @@ internal class AutoUpdateManager : IServiceType private Task? autoUpdateTask; - private readonly Task openInstallerWindowLink; - /// /// Initializes a new instance of the class. /// @@ -99,7 +99,7 @@ internal class AutoUpdateManager : IServiceType }); Service.GetAsync().ContinueWith(t => { t.Result.Update += this.OnUpdate; }); - this.openInstallerWindowLink = + this.openInstallerWindowLinkTask = Service.GetAsync().ContinueWith( chatGuiTask => chatGuiTask.Result.AddChatLinkHandler( "Dalamud", @@ -109,7 +109,6 @@ internal class AutoUpdateManager : IServiceType Service.GetNullable()?.OpenPluginInstallerTo(PluginInstallerOpenKind.InstalledPlugins); })); - this.isDryRun = console.AddVariable("dalamud.autoupdate.dry_run", "Simulate updates instead", false); console.AddCommand("dalamud.autoupdate.trigger_login", "Trigger a login event", () => { @@ -441,7 +440,7 @@ internal class AutoUpdateManager : IServiceType new TextPayload(Locs.NotificationContentUpdatesAvailableMinimized(updatablePlugins.Count)), new TextPayload(" ["), new UIForegroundPayload(500), - this.openInstallerWindowLink.Result, + this.openInstallerWindowLinkTask.Result, new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")), RawPayload.LinkTerminator, new UIForegroundPayload(0), diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index d5e14e212..d53828858 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -19,6 +19,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Dalamud.Support; using ImGuiNET; using Lumina.Excel.Sheets; @@ -28,8 +29,6 @@ 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; @@ -139,11 +138,11 @@ public static class Util public static string GetScmVersion() { if (scmVersionInternal != null) return scmVersionInternal; - + var asm = typeof(Util).Assembly; var attrs = asm.GetCustomAttributes(); - return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value + return scmVersionInternal = attrs.First(a => a.Key == "SCMVersion").Value ?? asm.GetName().Version!.ToString(); } @@ -853,7 +852,7 @@ public static class Util // ignore } } - + /// /// Print formatted IGameObject Information to ImGui. /// @@ -1051,7 +1050,8 @@ public static class Util } } - private static unsafe void ShowSpanEntryPrivate(ulong addr, IList path, int offset, Span spanobj) { + private static unsafe void ShowSpanEntryPrivate(ulong addr, IList path, int offset, Span spanobj) + { const int batchSize = 20; if (spanobj.Length > batchSize) { @@ -1221,6 +1221,7 @@ public static class Util ImGui.TextDisabled($"[0x{offset.Value:X}]"); ImGui.SameLine(); } + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); } From b3c0a8c20758bda2a752868c472f5f58b27fe9af Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 14:14:16 +0100 Subject: [PATCH 221/375] editorconfig: trim trailing whitespace --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 141e8c9c9..7b1cbadda 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,7 @@ charset = utf-8 end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true # 4 space indentation indent_style = space From 0a3e88efca62441e72ea4b995d1ead51c4f7a418 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 15:26:22 +0100 Subject: [PATCH 222/375] clone culture to apply fixes The default cultures are read-only --- Dalamud/Utility/CultureFixes.cs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/Dalamud/Utility/CultureFixes.cs b/Dalamud/Utility/CultureFixes.cs index 0c048ed38..133e79c71 100644 --- a/Dalamud/Utility/CultureFixes.cs +++ b/Dalamud/Utility/CultureFixes.cs @@ -12,34 +12,41 @@ internal static class CultureFixes /// public static void Apply() { - PatchNumberSeparator(); + PatchFrenchNumberSeparator(); } - private static void PatchNumberSeparator() + private static void PatchFrenchNumberSeparator() { // Reset formatting specifier for the "digit grouping symbol" to an empty string // for cultures that use a narrow no-break space (U+202F). // This glyph is not present in any game fonts and not in the range for our Noto // so it will be rendered as a geta (=) instead. That's a hack, but it works and // doesn't look as weird. - void PatchCulture(CultureInfo info) + CultureInfo PatchCulture(CultureInfo info) { + var newCulture = (CultureInfo)info.Clone(); + const string invalidGroupSeparator = "\u202F"; const string replacedGroupSeparator = " "; + if (info.NumberFormat.NumberGroupSeparator == invalidGroupSeparator) - info.NumberFormat.NumberGroupSeparator = replacedGroupSeparator; - + newCulture.NumberFormat.NumberGroupSeparator = replacedGroupSeparator; + if (info.NumberFormat.NumberDecimalSeparator == invalidGroupSeparator) - info.NumberFormat.NumberDecimalSeparator = replacedGroupSeparator; + newCulture.NumberFormat.NumberDecimalSeparator = replacedGroupSeparator; if (info.NumberFormat.CurrencyGroupSeparator == invalidGroupSeparator) - info.NumberFormat.CurrencyGroupSeparator = replacedGroupSeparator; - + newCulture.NumberFormat.CurrencyGroupSeparator = replacedGroupSeparator; + if (info.NumberFormat.CurrencyDecimalSeparator == invalidGroupSeparator) - info.NumberFormat.CurrencyDecimalSeparator = replacedGroupSeparator; + newCulture.NumberFormat.CurrencyDecimalSeparator = replacedGroupSeparator; + + return newCulture; } - - PatchCulture(CultureInfo.CurrentCulture); - PatchCulture(CultureInfo.CurrentUICulture); + + CultureInfo.CurrentCulture = PatchCulture(CultureInfo.CurrentCulture); + CultureInfo.CurrentUICulture = PatchCulture(CultureInfo.CurrentUICulture); + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CurrentCulture; + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CurrentUICulture; } } From f98da094f416030b115e5e37356da49dda35aa42 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 26 Dec 2024 16:39:54 +0100 Subject: [PATCH 223/375] Fetch market board uploader info from main thread (#2158) --- .../IMarketBoardUploader.cs | 12 +++- .../UniversalisMarketBoardUploader.cs | 33 +++-------- .../Game/Network/Internal/NetworkHandlers.cs | 55 +++++++++++++++---- 3 files changed, 62 insertions(+), 38 deletions(-) diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs index dadfef604..adf5f0228 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs @@ -13,20 +13,26 @@ internal interface IMarketBoardUploader /// Upload data about an item. /// /// The item request data being uploaded. + /// The uploaders ContentId. + /// The uploaders WorldId. /// An async task. - Task Upload(MarketBoardItemRequest item); + Task Upload(MarketBoardItemRequest item, ulong uploaderId, uint worldId); /// /// Upload tax rate data. /// /// The tax rate data being uploaded. + /// The uploaders ContentId. + /// The uploaders WorldId. /// An async task. - Task UploadTax(MarketTaxRates taxRates); + Task UploadTax(MarketTaxRates taxRates, ulong uploaderId, uint worldId); /// /// Upload information about a purchase this client has made. /// /// The purchase handler data associated with the sale. + /// The uploaders ContentId. + /// The uploaders WorldId. /// An async task. - Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); + Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler, ulong uploaderId, uint worldId); } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index f326c5ce0..7ecb9c397 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -33,21 +32,16 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader this.httpClient = happyHttpClient.SharedHttpClient; /// - public async Task Upload(MarketBoardItemRequest request) + public async Task Upload(MarketBoardItemRequest request, ulong uploaderId, uint worldId) { - var clientState = Service.GetNullable(); - if (clientState == null) - return; - Log.Verbose("Starting Universalis upload"); - var uploader = clientState.LocalContentId; // ==================================================================================== var uploadObject = new UniversalisItemUploadRequest { - WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0, - UploaderId = uploader.ToString(), + WorldId = worldId, + UploaderId = uploaderId.ToString(), ItemId = request.CatalogId, Listings = [], Sales = [], @@ -117,18 +111,12 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader } /// - public async Task UploadTax(MarketTaxRates taxRates) + public async Task UploadTax(MarketTaxRates taxRates, ulong uploaderId, uint worldId) { - var clientState = Service.GetNullable(); - if (clientState == null) - return; - - // ==================================================================================== - var taxUploadObject = new UniversalisTaxUploadRequest { - WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0, - UploaderId = clientState.LocalContentId.ToString(), + WorldId = worldId, + UploaderId = uploaderId.ToString(), TaxData = new UniversalisTaxData { LimsaLominsa = taxRates.LimsaLominsaTax, @@ -159,14 +147,9 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader /// to track the available listings, that is done via the listings packet. All this does is remove /// a listing, or delete it, when a purchase has been made. /// - public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) + public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler, ulong uploaderId, uint worldId) { - var clientState = Service.GetNullable(); - if (clientState == null) - return; - var itemId = purchaseHandler.CatalogId; - var worldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0; // ==================================================================================== @@ -176,7 +159,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader Quantity = purchaseHandler.ItemQuantity, ListingId = purchaseHandler.ListingId.ToString(), RetainerId = purchaseHandler.RetainerId.ToString(), - UploaderId = clientState.LocalContentId.ToString(), + UploaderId = uploaderId.ToString(), }; var deletePath = $"/api/{worldId}/{itemId}/delete"; diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index a25444f10..2ba7f2587 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -16,8 +16,11 @@ using Dalamud.Hooking; using Dalamud.Networking.Http; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Network; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Info; using Lumina.Excel.Sheets; using Serilog; @@ -264,6 +267,33 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.cfPopHook.Dispose(); } + private static (ulong UploaderId, uint WorldId) GetUploaderInfo() + { + var agentLobby = AgentLobby.Instance(); + + var uploaderId = agentLobby->LobbyData.ContentId; + if (uploaderId == 0) + { + var playerState = PlayerState.Instance(); + if (playerState->IsLoaded == 1) + { + uploaderId = playerState->ContentId; + } + } + + var worldId = agentLobby->LobbyData.CurrentWorldId; + if (worldId == 0) + { + var localPlayer = Control.GetLocalPlayer(); + if (localPlayer != null) + { + worldId = localPlayer->CurrentWorld; + } + } + + return (uploaderId, worldId); + } + private unsafe nint CfPopDetour(PublicContentDirector.EnterContentInfoPacket* packetData) { var result = this.cfPopHook.OriginalDisposeSafe(packetData); @@ -424,14 +454,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService startObservable .And(this.OnMarketBoardSalesBatch(startObservable)) .And(this.OnMarketBoardListingsBatch(startObservable)) - .Then((request, sales, listings) => (request, sales, listings))) + .Then((request, sales, listings) => (request, sales, listings, GetUploaderInfo()))) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( data => { - var (request, sales, listings) = data; - this.UploadMarketBoardData(request, sales, listings); + var (request, sales, listings, uploaderInfo) = data; + this.UploadMarketBoardData(request, sales, listings, uploaderInfo.UploaderId, uploaderInfo.WorldId); }, ex => Log.Error(ex, "Failed to handle Market Board item request event")); } @@ -439,7 +469,9 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private void UploadMarketBoardData( MarketBoardItemRequest request, (uint CatalogId, ICollection Sales) sales, - ICollection listings) + ICollection listings, + ulong uploaderId, + uint worldId) { var catalogId = sales.CatalogId; if (listings.Count != request.AmountToArrive) @@ -460,7 +492,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService request.Listings.AddRange(listings); request.History.AddRange(sales.Sales); - Task.Run(() => this.uploader.Upload(request)) + Task.Run(() => this.uploader.Upload(request, uploaderId, worldId)) .ContinueWith( task => Log.Error(task.Exception, "Market Board offerings data upload failed"), TaskContinuationOptions.OnlyOnFaulted); @@ -469,11 +501,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private IDisposable HandleMarketTaxRates() { return this.MbTaxesObservable + .Select((taxes) => (taxes, GetUploaderInfo())) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( - taxes => + data => { + var (taxes, uploaderInfo) = data; + Log.Verbose( "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5} sh#{6}", taxes.LimsaLominsaTax, @@ -484,7 +519,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService taxes.CrystariumTax, taxes.SharlayanTax); - Task.Run(() => this.uploader.UploadTax(taxes)) + Task.Run(() => this.uploader.UploadTax(taxes, uploaderInfo.UploaderId, uploaderInfo.WorldId)) .ContinueWith( task => Log.Error(task.Exception, "Market Board tax data upload failed"), TaskContinuationOptions.OnlyOnFaulted); @@ -495,13 +530,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private IDisposable HandleMarketBoardPurchaseHandler() { return this.MbPurchaseSentObservable - .Zip(this.MbPurchaseObservable) + .Zip(this.MbPurchaseObservable, (handler, purchase) => (handler, purchase, GetUploaderInfo())) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( data => { - var (handler, purchase) = data; + var (handler, purchase, uploaderInfo) = data; var sameQty = purchase.ItemQuantity == handler.ItemQuantity; var itemMatch = purchase.CatalogId == handler.CatalogId; @@ -516,7 +551,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService handler.CatalogId, handler.PricePerUnit * purchase.ItemQuantity, handler.ListingId); - Task.Run(() => this.uploader.UploadPurchase(handler)) + Task.Run(() => this.uploader.UploadPurchase(handler, uploaderInfo.UploaderId, uploaderInfo.WorldId)) .ContinueWith( task => Log.Error(task.Exception, "Market Board purchase data upload failed"), TaskContinuationOptions.OnlyOnFaulted); From f3d7c6f2ea81660e4e664dd076064ad897447be8 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 17:05:10 +0100 Subject: [PATCH 224/375] don't access nullptr addon when shutting down early --- Dalamud/Game/Gui/ContextMenu/ContextMenu.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs index 604af5ac7..fb78e6b80 100644 --- a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs +++ b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs @@ -47,9 +47,9 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM } private delegate ushort AtkModuleVf22OpenAddonByAgentDelegate(AtkModule* module, byte* addonName, int valueCount, AtkValue* values, AgentInterface* agent, nint a7, bool a8); - + private delegate bool AddonContextMenuOnMenuSelectedDelegate(AddonContextMenu* addon, int selectedIdx, byte a3); - + private delegate ushort RaptureAtkModuleOpenAddonDelegate(RaptureAtkModule* a1, uint addonNameId, uint valueCount, AtkValue* values, AgentInterface* parentAgent, ulong unk, ushort parentAddonId, int unk2); /// @@ -92,16 +92,22 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM /// void IInternalDisposableService.DisposeService() { + this.atkModuleVf22OpenAddonByAgentHook.Dispose(); + this.addonContextMenuOnMenuSelectedHook.Dispose(); + var manager = RaptureAtkUnitManager.Instance(); + if (manager == null) + return; + var menu = manager->GetAddonByName("ContextMenu"); var submenu = manager->GetAddonByName("AddonContextSub"); + if (menu == null || submenu == null) + return; + if (menu->IsVisible) menu->FireCallbackInt(-1); if (submenu->IsVisible) submenu->FireCallbackInt(-1); - - this.atkModuleVf22OpenAddonByAgentHook.Dispose(); - this.addonContextMenuOnMenuSelectedHook.Dispose(); } /// From 10f4009a0c03be9e165f90e14811bda2e6972733 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 17:16:19 +0100 Subject: [PATCH 225/375] don't implicitly depend on DataManager in data widget load functions --- .../Internal/Asserts/RenderScopes.cs | 23 +++++++++++++++++++ .../Widgets/SeStringRendererTestWidget.cs | 10 ++++---- .../Windows/Data/Widgets/UIColorWidget.cs | 18 ++++++--------- 3 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 Dalamud/Interface/Internal/Asserts/RenderScopes.cs diff --git a/Dalamud/Interface/Internal/Asserts/RenderScopes.cs b/Dalamud/Interface/Internal/Asserts/RenderScopes.cs new file mode 100644 index 000000000..1e0448e87 --- /dev/null +++ b/Dalamud/Interface/Internal/Asserts/RenderScopes.cs @@ -0,0 +1,23 @@ +using System.Diagnostics; + +namespace Dalamud.Interface.Internal.Asserts; + +public static class RenderScopes +{ + private static RenderScopeFrame currentFrame = new(); + private static RenderScopeFrame lastFrame = new(); + + public static RenderScopeFrame GetLastFrame() => lastFrame; + + public static RenderScopeFrame GetCurrentFrame() => currentFrame; + + public static void NewFrame() + { + //Debug.Assert(currentFrame.IsRoot, "NewFrame() but we didn't pop all the way to ."); + } + + public class RenderScopeFrame + { + + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index 5afda62ac..323c9ce62 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -15,7 +15,6 @@ using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using Lumina.Excel; using Lumina.Excel.Sheets; using Lumina.Text; using Lumina.Text.Payloads; @@ -31,7 +30,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; private ImVectorWrapper testStringBuffer; private string testString = string.Empty; - private ExcelSheet addons = null!; private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; @@ -51,7 +49,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget public void Load() { this.style = new() { GetEntity = this.GetEntity }; - this.addons = Service.Get().GetExcelSheet(); this.logkind = null; this.testString = string.Empty; this.interactable = this.useEntity = true; @@ -193,13 +190,16 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget ImGui.CalcTextSize("AAAAAAAAAAAAAAAAA").X); ImGui.TableHeadersRow(); + var addon = Service.GetNullable()?.GetExcelSheet() ?? + throw new InvalidOperationException("Addon sheet not loaded."); + var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - clipper.Begin(this.addons.Count); + clipper.Begin(addon.Count); while (clipper.Step()) { for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - var row = this.addons.GetRowAt(i); + var row = addon.GetRowAt(i); ImGui.TableNextRow(); ImGui.PushID(i); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs index 6b57f7a6a..d43ae50a3 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs @@ -1,5 +1,4 @@ using System.Buffers.Binary; -using System.Linq; using System.Numerics; using System.Text; @@ -7,12 +6,9 @@ using Dalamud.Data; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; -using Dalamud.Interface.Utility; -using Dalamud.Storage.Assets; using ImGuiNET; -using Lumina.Excel; using Lumina.Excel.Sheets; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -22,8 +18,6 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// internal class UiColorWidget : IDataWindowWidget { - private ExcelSheet colors; - /// public string[]? CommandShortcuts { get; init; } = ["uicolor"]; @@ -37,12 +31,14 @@ internal class UiColorWidget : IDataWindowWidget public void Load() { this.Ready = true; - this.colors = Service.Get().GetExcelSheet(); } /// public unsafe void Draw() { + var colors = Service.GetNullable()?.GetExcelSheet() + ?? throw new InvalidOperationException("UIColor sheet not loaded."); + Service.Get().CompileAndDrawWrapped( "· Color notation is #" + "RR" + @@ -71,16 +67,16 @@ internal class UiColorWidget : IDataWindowWidget ImGui.TableHeadersRow(); var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - clipper.Begin(this.colors.Count, ImGui.GetFrameHeightWithSpacing()); + clipper.Begin(colors.Count, ImGui.GetFrameHeightWithSpacing()); while (clipper.Step()) { for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - var row = this.colors.GetRowAt(i); + var row = colors.GetRowAt(i); UIColor? adjacentRow = null; - if (i + 1 < this.colors.Count) + if (i + 1 < colors.Count) { - var adjRow = this.colors.GetRowAt(i + 1); + var adjRow = colors.GetRowAt(i + 1); if (adjRow.RowId == row.RowId + 1) { adjacentRow = adjRow; From 3f3f5f4b7203c9a253b8ce277f59b5f36b12edf5 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 26 Dec 2024 22:10:10 +0100 Subject: [PATCH 226/375] hide assert logs that spam every frame automatically, add "verbose" mode --- .../Internal/Asserts/AssertHandler.cs | 57 +++++++++++++++---- .../Interface/Internal/DalamudInterface.cs | 48 +++++++++------- .../Interface/Internal/InterfaceManager.cs | 19 +++++-- 3 files changed, 87 insertions(+), 37 deletions(-) diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs index b0cfb462c..c1dc12206 100644 --- a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -15,7 +15,11 @@ namespace Dalamud.Interface.Internal.Asserts; /// internal class AssertHandler : IDisposable { + private const int HideThreshold = 20; + private const int HidePrintEvery = 500; + private readonly HashSet ignoredAsserts = []; + private readonly Dictionary assertCounts = new(); // Store callback to avoid it from being GC'd private readonly AssertCallbackDelegate callback; @@ -37,7 +41,13 @@ internal class AssertHandler : IDisposable /// Gets or sets a value indicating whether ImGui asserts should be shown to the user. /// public bool ShowAsserts { get; set; } - + + /// + /// Gets or sets a value indicating whether we want to hide asserts that occur frequently (= every update) + /// and whether we want to log callstacks. + /// + public bool EnableVerboseLogging { get; set; } + /// /// Register the cimgui assert handler with the native library. /// @@ -62,18 +72,43 @@ internal class AssertHandler : IDisposable private void OnImGuiAssert(string expr, string file, int line) { - Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}", expr, file, line); - - if (!this.ShowAsserts) - return; - var key = $"{file}:{line}"; if (this.ignoredAsserts.Contains(key)) return; - // TODO: It would be nice to get unmanaged stack frames here, seems hard though without pulling that - // entire code in from the crash handler - var originalStackTrace = new StackTrace(2).ToString(); + Lazy stackTrace = new(() => new StackTrace(3).ToString()); + + if (!this.EnableVerboseLogging) + { + if (this.assertCounts.TryGetValue(key, out var count)) + { + this.assertCounts[key] = count + 1; + + if (count <= HideThreshold || count % HidePrintEvery == 0) + { + Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)", + expr, + file, + line, + count); + } + } + else + { + this.assertCounts[key] = 1; + } + } + else + { + Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}", + expr, + file, + line, + stackTrace.Value); + } + + if (!this.ShowAsserts) + return; string? GetRepoUrl() { @@ -108,7 +143,7 @@ internal class AssertHandler : IDisposable Text = "Break", AllowCloseDialog = true, }; - + var disableButton = new TaskDialogButton { Text = "Disable for this session", @@ -132,7 +167,7 @@ internal class AssertHandler : IDisposable { CollapsedButtonText = "Show stack trace", ExpandedButtonText = "Hide stack trace", - Text = originalStackTrace, + Text = stackTrace.Value, }, Text = $"Some code in a plugin or Dalamud itself has caused an internal assertion in ImGui to fail. The game will most likely crash now.\n\n{expr}\nAt: {file}:{line}", Icon = TaskDialogIcon.Warning, diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index d432d9d4c..0381164c9 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -91,7 +91,7 @@ internal class DalamudInterface : IInternalDisposableService private bool isImPlotDrawDemoWindow = false; private bool isImGuiTestWindowsInMonospace = false; private bool isImGuiDrawMetricsWindow = false; - + [ServiceManager.ServiceConstructor] private DalamudInterface( Dalamud dalamud, @@ -112,7 +112,7 @@ internal class DalamudInterface : IInternalDisposableService this.interfaceManager = interfaceManager; this.WindowSystem = new WindowSystem("DalamudCore"); - + this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; this.dataWindow = new DataWindow() { IsOpen = false }; @@ -193,7 +193,7 @@ internal class DalamudInterface : IInternalDisposableService this.creditsDarkeningAnimation.Point1 = Vector2.Zero; this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha); - + // This is temporary, until we know the repercussions of vtable hooking mode consoleManager.AddCommand( "dalamud.interface.swapchain_mode", @@ -212,14 +212,14 @@ internal class DalamudInterface : IInternalDisposableService Log.Error("Unknown swapchain mode: {Mode}", mode); return false; } - + this.configuration.QueueSave(); return true; }); } - + private delegate nint CrashDebugDelegate(nint self); - + /// /// Gets the number of frames since Dalamud has loaded. /// @@ -319,7 +319,7 @@ internal class DalamudInterface : IInternalDisposableService this.pluginStatWindow.IsOpen = true; this.pluginStatWindow.BringToFront(); } - + /// /// Opens the on the plugin installed. /// @@ -384,7 +384,7 @@ internal class DalamudInterface : IInternalDisposableService this.profilerWindow.IsOpen = true; this.profilerWindow.BringToFront(); } - + /// /// Opens the . /// @@ -696,7 +696,7 @@ internal class DalamudInterface : IInternalDisposableService ImGui.EndMenu(); } - + var logSynchronously = this.configuration.LogSynchronously; if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously)) { @@ -788,14 +788,14 @@ internal class DalamudInterface : IInternalDisposableService } ImGui.Separator(); - + if (ImGui.BeginMenu("Crash game")) { if (ImGui.MenuItem("Access Violation")) { Marshal.ReadByte(IntPtr.Zero); - } - + } + if (ImGui.MenuItem("Set UiModule to NULL")) { unsafe @@ -804,7 +804,7 @@ internal class DalamudInterface : IInternalDisposableService framework->UIModule = (UIModule*)0; } } - + if (ImGui.MenuItem("Set UiModule to invalid ptr")) { unsafe @@ -813,7 +813,7 @@ internal class DalamudInterface : IInternalDisposableService framework->UIModule = (UIModule*)0x12345678; } } - + if (ImGui.MenuItem("Deref nullptr in Hook")) { unsafe @@ -834,7 +834,7 @@ internal class DalamudInterface : IInternalDisposableService ImGui.PopStyleVar(); ImGui.PopStyleVar(); } - + ImGui.EndMenu(); } @@ -850,7 +850,7 @@ internal class DalamudInterface : IInternalDisposableService { this.OpenBranchSwitcher(); } - + ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false); ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false); ImGui.MenuItem($"CLR: {Environment.Version}", false); @@ -867,10 +867,16 @@ internal class DalamudInterface : IInternalDisposableService ImGui.Separator(); - var val = this.interfaceManager.ShowAsserts; - if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) + var showAsserts = this.interfaceManager.ShowAsserts; + if (ImGui.MenuItem("Enable assert popups", string.Empty, ref showAsserts)) { - this.interfaceManager.ShowAsserts = val; + this.interfaceManager.ShowAsserts = showAsserts; + } + + var enableVerboseAsserts = this.interfaceManager.EnableVerboseAssertLogging; + if (ImGui.MenuItem("Enable verbose assert logging", string.Empty, ref enableVerboseAsserts)) + { + this.interfaceManager.EnableVerboseAssertLogging = enableVerboseAsserts; } var assertsEnabled = this.configuration.ImGuiAssertsEnabledAtStartup ?? false; @@ -880,6 +886,8 @@ internal class DalamudInterface : IInternalDisposableService this.configuration.QueueSave(); } + ImGui.Separator(); + if (ImGui.MenuItem("Clear focus")) { ImGui.SetWindowFocus(null); @@ -927,7 +935,7 @@ internal class DalamudInterface : IInternalDisposableService { this.configuration.ShowDevBarInfo = !this.configuration.ShowDevBarInfo; } - + ImGui.Separator(); if (ImGui.MenuItem("Show loading window")) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 65f2da705..2908be34a 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -73,7 +73,7 @@ internal partial class InterfaceManager : IInternalDisposableService public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f; private static readonly ModuleLog Log = new("INTERFACE"); - + private readonly ConcurrentBag deferredDisposeTextures = new(); private readonly ConcurrentBag deferredDisposeDisposables = new(); @@ -93,7 +93,7 @@ internal partial class InterfaceManager : IInternalDisposableService private readonly ConcurrentQueue runBeforeImGuiRender = new(); private readonly ConcurrentQueue runAfterImGuiRender = new(); - + private readonly AssertHandler assertHandler = new(); private RawDX11Scene? scene; @@ -276,6 +276,13 @@ internal partial class InterfaceManager : IInternalDisposableService set => this.assertHandler.ShowAsserts = value; } + /// + public bool EnableVerboseAssertLogging + { + get => this.assertHandler.EnableVerboseLogging; + set => this.assertHandler.EnableVerboseLogging = value; + } + /// /// Dispose of managed and unmanaged resources. /// @@ -809,14 +816,14 @@ internal partial class InterfaceManager : IInternalDisposableService }); }; } - + // This will wait for scene on its own. We just wait for this.dalamudAtlas.BuildTask in this.InitScene. _ = this.dalamudAtlas.BuildFontsAsync(); SwapChainHelper.BusyWaitForGameDeviceSwapChain(); var swapChainDesc = default(DXGI_SWAP_CHAIN_DESC); if (SwapChainHelper.GameDeviceSwapChain->GetDesc(&swapChainDesc).SUCCEEDED) - this.gameWindowHandle = swapChainDesc.OutputWindow; + this.gameWindowHandle = swapChainDesc.OutputWindow; try { @@ -959,7 +966,7 @@ internal partial class InterfaceManager : IInternalDisposableService switch (this.dalamudConfiguration.SwapChainHookMode) { - case SwapChainHelper.HookMode.ByteCode: + case SwapChainHelper.HookMode.ByteCode: default: { Log.Information("Hooking using bytecode..."); @@ -1149,7 +1156,7 @@ internal partial class InterfaceManager : IInternalDisposableService catch (Exception ex) { Log.Error(ex, "Error when invoking global Draw"); - + // We should always handle this in the callbacks. Util.Fatal("An internal error occurred while drawing the Dalamud UI and the game must close.\nPlease report this error.", "Dalamud"); } From 55bfe9eeb0e021c015cc12229d431a13bee0cc02 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 27 Dec 2024 18:19:45 +0100 Subject: [PATCH 227/375] upgrade cimgui, fix IME and image rendering --- Dalamud/Interface/Internal/DalamudIme.cs | 27 ++++++++---------------- lib/cimgui | 2 +- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index 7beb5e58b..a1c4a0a95 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -37,9 +37,6 @@ namespace Dalamud.Interface.Internal; [ServiceManager.EarlyLoadedService] internal sealed unsafe class DalamudIme : IInternalDisposableService { - private const int CImGuiStbTextCreateUndoOffset = 0xB57A0; - private const int CImGuiStbTextUndoOffset = 0xB59C0; - private const int ImePageSize = 9; private static readonly Dictionary WmNames = @@ -69,11 +66,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService UnicodeRanges.HangulJamoExtendedB, }; - private static readonly delegate* unmanaged - StbTextMakeUndoReplace; - - private static readonly delegate* unmanaged StbTextUndo; - [ServiceManager.ServiceDependency] private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); @@ -134,13 +126,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService { return; } - - StbTextMakeUndoReplace = - (delegate* unmanaged) - (cimgui + CImGuiStbTextCreateUndoOffset); - StbTextUndo = - (delegate* unmanaged) - (cimgui + CImGuiStbTextUndoOffset); } [ServiceManager.ServiceConstructor] @@ -1031,14 +1016,14 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService (s, e) = (e, s); } - public void Undo() => StbTextUndo(this.ThisPtr, &this.ThisPtr->Stb); + public void Undo() => CustomNativeFunctions.igCustom_StbTextUndo(this.ThisPtr); public bool MakeUndoReplace(int offset, int oldLength, int newLength) { if (oldLength == 0 && newLength == 0) return false; - StbTextMakeUndoReplace(this.ThisPtr, &this.ThisPtr->Stb, offset, oldLength, newLength); + CustomNativeFunctions.igCustom_StbTextMakeUndoReplace(this.ThisPtr, offset, oldLength, newLength); return true; } @@ -1116,9 +1101,15 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService private static class CustomNativeFunctions { - [DllImport("cimgui")] #pragma warning disable SA1300 + [DllImport("cimgui")] public static extern ImGuiInputTextState* igCustom_GetInputTextState(); + + [DllImport("cimgui")] + public static extern void igCustom_StbTextMakeUndoReplace(ImGuiInputTextState* str, int where, int old_length, int new_length); + + [DllImport("cimgui")] + public static extern void igCustom_StbTextUndo(ImGuiInputTextState* str); #pragma warning restore SA1300 } diff --git a/lib/cimgui b/lib/cimgui index fd2377934..7002b2884 160000 --- a/lib/cimgui +++ b/lib/cimgui @@ -1 +1 @@ -Subproject commit fd2377934f2cc007982e21ab82e54b41955cb658 +Subproject commit 7002b2884e9216d8bef3e792722d88abe31788f8 From 4af9bc05dc0cd02a56fc9d93469c69cf7939d73e Mon Sep 17 00:00:00 2001 From: goat Date: Sat, 28 Dec 2024 14:00:25 +0100 Subject: [PATCH 228/375] don't rebuild Dalamud.Boot every time, copy out of output directory instead --- Dalamud.Boot/Dalamud.Boot.vcxproj | 12 ++++++---- .../Internal/Asserts/RenderScopes.cs | 23 ------------------- 2 files changed, 7 insertions(+), 28 deletions(-) delete mode 100644 Dalamud/Interface/Internal/Asserts/RenderScopes.cs diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 80435cd67..c18045027 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -28,7 +28,7 @@ v143 false Unicode - ..\bin\$(Configuration)\ + bin\$(Configuration)\ obj\$(Configuration)\ @@ -200,8 +200,10 @@ - - - + + + + + - \ No newline at end of file + diff --git a/Dalamud/Interface/Internal/Asserts/RenderScopes.cs b/Dalamud/Interface/Internal/Asserts/RenderScopes.cs deleted file mode 100644 index 1e0448e87..000000000 --- a/Dalamud/Interface/Internal/Asserts/RenderScopes.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Diagnostics; - -namespace Dalamud.Interface.Internal.Asserts; - -public static class RenderScopes -{ - private static RenderScopeFrame currentFrame = new(); - private static RenderScopeFrame lastFrame = new(); - - public static RenderScopeFrame GetLastFrame() => lastFrame; - - public static RenderScopeFrame GetCurrentFrame() => currentFrame; - - public static void NewFrame() - { - //Debug.Assert(currentFrame.IsRoot, "NewFrame() but we didn't pop all the way to ."); - } - - public class RenderScopeFrame - { - - } -} From d200c12c2f40c012c1e7283ab1b1ec39b30d16e8 Mon Sep 17 00:00:00 2001 From: goat Date: Sat, 28 Dec 2024 14:02:06 +0100 Subject: [PATCH 229/375] copy cimgui DLLs in their vcxproj instead of Dalamud --- Dalamud/Dalamud.csproj | 14 -------------- external/cimgui/cimgui.vcxproj | 9 +++++++-- external/cimguizmo/cimguizmo.vcxproj | 9 +++++++-- external/cimplot/cimplot.vcxproj | 9 +++++++-- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 1cd9fc336..d869f76ba 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -205,18 +205,4 @@ - - - - - - - - - - - - - - diff --git a/external/cimgui/cimgui.vcxproj b/external/cimgui/cimgui.vcxproj index a7a7ddb0e..55e737595 100644 --- a/external/cimgui/cimgui.vcxproj +++ b/external/cimgui/cimgui.vcxproj @@ -56,7 +56,8 @@ - ..\$(Platform)\$(Configuration)\ + bin\$(Configuration)\ + obj\$(Configuration)\ @@ -99,4 +100,8 @@ - \ No newline at end of file + + + + + diff --git a/external/cimguizmo/cimguizmo.vcxproj b/external/cimguizmo/cimguizmo.vcxproj index 48b432327..3014786b8 100644 --- a/external/cimguizmo/cimguizmo.vcxproj +++ b/external/cimguizmo/cimguizmo.vcxproj @@ -60,7 +60,8 @@ - ..\$(Platform)\$(Configuration)\ + bin\$(Configuration)\ + obj\$(Configuration)\ @@ -105,4 +106,8 @@ - \ No newline at end of file + + + + + diff --git a/external/cimplot/cimplot.vcxproj b/external/cimplot/cimplot.vcxproj index c7ae46a6b..c2b468fab 100644 --- a/external/cimplot/cimplot.vcxproj +++ b/external/cimplot/cimplot.vcxproj @@ -58,7 +58,8 @@ - ..\$(Platform)\$(Configuration)\ + bin\$(Configuration)\ + obj\$(Configuration)\ @@ -103,4 +104,8 @@ - \ No newline at end of file + + + + + From 01980c3133a3486acacbd80262fa7a1fb672aadc Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 12:42:43 +0100 Subject: [PATCH 230/375] plugin installer: add linebreak after ban reason --- .../PluginInstaller/PluginInstallerWindow.cs | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 61f4bd1fc..4daccab3b 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -120,7 +120,7 @@ internal class PluginInstallerWindow : Window, IDisposable private List pluginListUpdatable = new(); private bool hasDevPlugins = false; private bool hasHiddenPlugins = false; - + private string searchText = string.Empty; private bool isSearchTextPrefilled = false; @@ -137,7 +137,7 @@ internal class PluginInstallerWindow : Window, IDisposable private LoadingIndicatorKind loadingIndicatorKind = LoadingIndicatorKind.Unknown; private string verifiedCheckmarkHoveredPlugin = string.Empty; - + private string? staleDalamudNewVersion = null; /// @@ -215,8 +215,8 @@ internal class PluginInstallerWindow : Window, IDisposable ProfileOrNot, SearchScore, } - - [Flags] + + [Flags] private enum PluginHeaderFlags { None = 0, @@ -236,7 +236,7 @@ internal class PluginInstallerWindow : Window, IDisposable Updateable, Dev, } - + private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress || this.updateStatus == OperationStatus.InProgress || this.enableDisableStatus == OperationStatus.InProgress; @@ -304,7 +304,7 @@ internal class PluginInstallerWindow : Window, IDisposable { if (!t.IsCompletedSuccessfully) return; - + var versionInfo = t.Result; if (versionInfo.AssemblyVersion != Util.GetScmVersion() && versionInfo.Track != "release" && @@ -413,7 +413,7 @@ internal class PluginInstallerWindow : Window, IDisposable { if (!task.IsFaulted && !task.IsCanceled) return true; - + var newErrorMessage = state as string; if (task.Exception != null) @@ -438,7 +438,7 @@ internal class PluginInstallerWindow : Window, IDisposable } } } - + if (task.IsCanceled) Log.Error("A task was cancelled"); @@ -446,14 +446,14 @@ internal class PluginInstallerWindow : Window, IDisposable return false; } - + private static void EnsureHaveTestingOptIn(IPluginManifest manifest) { var configuration = Service.Get(); - + if (configuration.PluginTestingOptIns.Any(x => x.InternalName == manifest.InternalName)) return; - + configuration.PluginTestingOptIns.Add(new PluginTestingOptIn(manifest.InternalName)); configuration.QueueSave(); } @@ -490,7 +490,7 @@ internal class PluginInstallerWindow : Window, IDisposable throw new ArgumentOutOfRangeException(nameof(kind), kind, null); } } - + private void DrawProgressOverlay() { var pluginManager = Service.Get(); @@ -733,7 +733,7 @@ internal class PluginInstallerWindow : Window, IDisposable } } } - + private void DrawFooter() { var configuration = Service.Get(); @@ -802,7 +802,7 @@ internal class PluginInstallerWindow : Window, IDisposable { this.updateStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; - + var toUpdate = this.pluginListUpdatable .Where(x => x.InstalledPlugin.IsWantedByAnyProfile) .ToList(); @@ -994,7 +994,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.Text(Locs.DeletePluginConfigWarningModal_ExplainTesting()); ImGui.PopStyleColor(); } - + ImGui.Text(Locs.DeletePluginConfigWarningModal_Body(this.deletePluginConfigWarningModalPluginName)); ImGui.Spacing(); @@ -1264,7 +1264,7 @@ internal class PluginInstallerWindow : Window, IDisposable plugin.Manifest.RepoUrl == availableManifest.RepoUrl && !plugin.IsDev); - // We "consumed" this plugin from the pile and remove it. + // We "consumed" this plugin from the pile and remove it. if (plugin != null) { installedPlugins.Remove(plugin); @@ -1296,7 +1296,7 @@ internal class PluginInstallerWindow : Window, IDisposable return isHidden; return !isHidden; } - + // Filter out plugins that are not hidden proxies = proxies.Where(IsProxyHidden).ToList(); @@ -1328,14 +1328,14 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PopID(); } - + // Reset the category to "All" if we're on the "Hidden" category and there are no hidden plugins (we removed the last one) if (i == 0 && this.categoryManager.CurrentCategoryKind == PluginCategoryManager.CategoryKind.Hidden) { this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.All; } } - + private void DrawInstalledPluginList(InstalledPluginListFilter filter) { var pluginList = this.pluginListInstalled; @@ -1363,7 +1363,7 @@ internal class PluginInstallerWindow : Window, IDisposable { if (filter == InstalledPluginListFilter.Testing && !manager.HasTestingOptIn(plugin.Manifest)) continue; - + // Find applicable update and manifest, if we have them AvailablePluginUpdate? update = null; RemotePluginManifest? remoteManifest = null; @@ -1383,11 +1383,11 @@ internal class PluginInstallerWindow : Window, IDisposable { continue; } - + this.DrawInstalledPlugin(plugin, i++, remoteManifest, update); drewAny = true; } - + if (!drewAny) { var text = filter switch @@ -1398,7 +1398,7 @@ internal class PluginInstallerWindow : Window, IDisposable InstalledPluginListFilter.Dev => Locs.TabBody_NoPluginsDev, _ => throw new ArgumentException(null, nameof(filter)), }; - + ImGuiHelpers.ScaledDummy(60); using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) @@ -1490,7 +1490,7 @@ internal class PluginInstallerWindow : Window, IDisposable foreach (var categoryKind in groupInfo.Categories) { var categoryInfo = this.categoryManager.CategoryList.First(x => x.CategoryKind == categoryKind); - + switch (categoryInfo.Condition) { case PluginCategoryManager.CategoryInfo.AppearCondition.None: @@ -1549,7 +1549,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PopFont(); ImGui.PopStyleColor(); } - + void DrawLinesCentered(string text) { var lines = text.Split('\n'); @@ -1558,7 +1558,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.CenteredText(line); } } - + var pm = Service.Get(); if (pm.SafeMode) { @@ -1623,7 +1623,7 @@ internal class PluginInstallerWindow : Window, IDisposable case PluginCategoryManager.CategoryKind.IsTesting: this.DrawInstalledPluginList(InstalledPluginListFilter.Testing); break; - + case PluginCategoryManager.CategoryKind.UpdateablePlugins: this.DrawInstalledPluginList(InstalledPluginListFilter.Updateable); break; @@ -1631,7 +1631,7 @@ internal class PluginInstallerWindow : Window, IDisposable case PluginCategoryManager.CategoryKind.PluginProfiles: this.profileManagerWidget.Draw(); break; - + default: ImGui.TextUnformatted("You found a secret category. Please feel a sense of pride and accomplishment."); break; @@ -1652,7 +1652,7 @@ internal class PluginInstallerWindow : Window, IDisposable case PluginCategoryManager.CategoryKind.PluginChangelogs: this.DrawChangelogList(false, true); break; - + default: ImGui.TextUnformatted("You found a quiet category. Please don't wake it up."); break; @@ -1979,9 +1979,9 @@ internal class PluginInstallerWindow : Window, IDisposable var sectionSize = ImGuiHelpers.GlobalScale * 66; var tapeCursor = ImGui.GetCursorPos(); - + ImGui.Separator(); - + var startCursor = ImGui.GetCursorPos(); if (flags.HasFlag(PluginHeaderFlags.IsTesting)) @@ -1992,9 +1992,9 @@ internal class PluginInstallerWindow : Window, IDisposable var windowPos = ImGui.GetWindowPos(); var scroll = new Vector2(ImGui.GetScrollX(), ImGui.GetScrollY()); - + var adjustedPosition = windowPos + position - scroll; - + var yellow = ImGui.ColorConvertFloat4ToU32(new Vector4(1.0f, 0.9f, 0.0f, 0.10f)); var numStripes = (int)(size.X / stripeWidth) + (int)(size.Y / skewAmount) + 1; // +1 to cover partial stripe @@ -2004,19 +2004,19 @@ internal class PluginInstallerWindow : Window, IDisposable var x1 = x0 + stripeWidth; var y0 = adjustedPosition.Y; var y1 = y0 + size.Y; - + var p0 = new Vector2(x0, y0); var p1 = new Vector2(x1, y0); var p2 = new Vector2(x1 - skewAmount, y1); var p3 = new Vector2(x0 - skewAmount, y1); - + if (i % 2 != 0) continue; - + wdl.AddQuadFilled(p0, p1, p2, p3, yellow); } } - + DrawCautionTape(tapeCursor + new Vector2(0, 1), new Vector2(ImGui.GetWindowWidth(), sectionSize + ImGui.GetStyle().ItemSpacing.Y), ImGuiHelpers.GlobalScale * 40, 20); } @@ -2025,7 +2025,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - + ImGui.SetCursorPos(tapeCursor); if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetContentRegionAvail().X, sectionSize + ImGui.GetStyle().ItemSpacing.Y))) @@ -2194,7 +2194,7 @@ internal class PluginInstallerWindow : Window, IDisposable bodyText += " "; if (flags.HasFlag(PluginHeaderFlags.UpdateAvailable)) - bodyText += Locs.PluginBody_Outdated_CanNowUpdate; + bodyText += "\n" + Locs.PluginBody_Outdated_CanNowUpdate; else bodyText += Locs.PluginBody_Outdated_WaitForUpdate; @@ -2368,11 +2368,11 @@ internal class PluginInstallerWindow : Window, IDisposable { label += Locs.PluginTitleMod_TestingAvailable; } - + var isThirdParty = manifest.SourceRepo.IsThirdParty; ImGui.PushID($"available{index}{manifest.InternalName}"); - + var flags = PluginHeaderFlags.None; if (isThirdParty) flags |= PluginHeaderFlags.IsThirdParty; @@ -2382,7 +2382,7 @@ internal class PluginInstallerWindow : Window, IDisposable flags |= PluginHeaderFlags.IsInstallableOutdated; if (useTesting || manifest.IsTestingExclusive) flags |= PluginHeaderFlags.IsTesting; - + if (this.DrawPluginCollapsingHeader(label, null, manifest, flags, () => this.DrawAvailablePluginContextMenu(manifest), index)) { if (!wasSeen) @@ -2478,7 +2478,7 @@ internal class PluginInstallerWindow : Window, IDisposable EnsureHaveTestingOptIn(manifest); this.StartInstall(manifest, true); } - + ImGui.Separator(); } @@ -2676,9 +2676,9 @@ internal class PluginInstallerWindow : Window, IDisposable availablePluginUpdate.UseTesting ? availablePluginUpdate.UpdateManifest.TestingAssemblyVersion : availablePluginUpdate.UpdateManifest.AssemblyVersion; - + availableChangelog = - availablePluginUpdate.UseTesting ? + availablePluginUpdate.UseTesting ? availablePluginUpdate.UpdateManifest.TestingChangelog : availablePluginUpdate.UpdateManifest.Changelog; } @@ -2835,7 +2835,7 @@ internal class PluginInstallerWindow : Window, IDisposable { this.DrawInstalledPluginChangelog(applicableChangelog); } - + if (this.categoryManager.CurrentCategoryKind == PluginCategoryManager.CategoryKind.UpdateablePlugins && !availableChangelog.IsNullOrWhitespace() && !didDrawAvailableChangelogInsideCollapsible) @@ -3689,7 +3689,7 @@ internal class PluginInstallerWindow : Window, IDisposable this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); this.ResortPlugins(); } - + this.hasHiddenPlugins = this.pluginListAvailable.Any(x => configuration.HiddenPluginInternalName.Contains(x.InternalName)); this.UpdateCategoriesOnPluginsChange(); @@ -3943,16 +3943,16 @@ internal class PluginInstallerWindow : Window, IDisposable public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed."); public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins."); - + public static string TabBody_NoPluginsTesting => Loc.Localize("InstallerNoPluginsTesting", "You aren't testing any plugins at the moment!\nYou can opt in to testing versions in the plugin context menu."); - + public static string TabBody_NoPluginsInstalled => string.Format(Loc.Localize("InstallerNoPluginsInstalled", "You don't have any plugins installed yet!\nYou can install them from the \"{0}\" tab."), PluginCategoryManager.Locs.Category_All); - + public static string TabBody_NoPluginsUpdateable => Loc.Localize("InstallerNoPluginsUpdate", "No plugins have updates available at the moment."); - + public static string TabBody_NoPluginsDev => Loc.Localize("InstallerNoPluginsDev", "You don't have any dev plugins. Add them from the settings."); - + #endregion #region Search text @@ -4014,11 +4014,11 @@ internal class PluginInstallerWindow : Window, IDisposable public static string PluginContext_TestingOptIn => Loc.Localize("InstallerTestingOptIn", "Receive plugin testing versions"); public static string PluginContext_InstallTestingVersion => Loc.Localize("InstallerInstallTestingVersion", "Install testing version"); - + public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen"); public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer"); - + public static string PluginContext_UnhidePlugin => Loc.Localize("InstallerUnhidePlugin", "Unhide from installer"); public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin data"); From c79b1cd83a22c114ab592db4d0ba2d38866c22a9 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 13:17:03 +0100 Subject: [PATCH 231/375] config: save asynchronously to prevent hitches --- .../Internal/DalamudConfiguration.cs | 24 ++++- .../Internal/Windows/Data/DataWindow.cs | 1 + .../Windows/Data/Widgets/VfsWidget.cs | 102 ++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 22fb2f448..850aeb7e9 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Dalamud.Game.Text; using Dalamud.Interface; @@ -45,6 +46,8 @@ internal sealed class DalamudConfiguration : IInternalDisposableService [JsonIgnore] private bool isSaveQueued; + private Task? writeTask; + /// /// Delegate for the event that occurs when the dalamud configuration is saved. /// @@ -560,6 +563,9 @@ internal sealed class DalamudConfiguration : IInternalDisposableService { // Make sure that we save, if a save is queued while we are shutting down this.Update(); + + // Wait for the write task to finish + this.writeTask?.Wait(); } /// @@ -614,8 +620,22 @@ internal sealed class DalamudConfiguration : IInternalDisposableService if (this.configPath is null) throw new InvalidOperationException("configPath is not set."); - Service.Get().WriteAllText( - this.configPath, JsonConvert.SerializeObject(this, SerializerSettings)); + // Wait for previous write to finish + this.writeTask?.Wait(); + + this.writeTask = Task.Run(() => + { + Service.Get().WriteAllText( + this.configPath, + JsonConvert.SerializeObject(this, SerializerSettings)); + }).ContinueWith(t => + { + if (t.IsFaulted) + { + Log.Error(t.Exception, "Failed to save DalamudConfiguration to {Path}", this.configPath); + } + }); + this.DalamudConfigurationSaved?.Invoke(this); } } diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index 7678b395e..f3ec882fc 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -60,6 +60,7 @@ internal class DataWindow : Window, IDisposable new ToastWidget(), new UiColorWidget(), new UldWidget(), + new VfsWidget(), }; private readonly IOrderedEnumerable orderedModules; diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs new file mode 100644 index 000000000..019d743bc --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/VfsWidget.cs @@ -0,0 +1,102 @@ +using System.Diagnostics; +using System.IO; + +using Dalamud.Configuration.Internal; +using Dalamud.Storage; +using ImGuiNET; +using Serilog; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for displaying configuration info. +/// +internal class VfsWidget : IDataWindowWidget +{ + private int numBytes = 1024; + private int reps = 1; + + /// + public string[]? CommandShortcuts { get; init; } = { "vfs" }; + + /// + public string DisplayName { get; init; } = "VFS Performance"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.Ready = true; + } + + /// + public void Draw() + { + var service = Service.Get(); + var dalamud = Service.Get(); + + ImGui.InputInt("Num bytes", ref this.numBytes); + ImGui.InputInt("Reps", ref this.reps); + + var path = Path.Combine(dalamud.StartInfo.WorkingDirectory!, "test.bin"); + + if (ImGui.Button("Write")) + { + Log.Information("=== WRITING ==="); + var data = new byte[this.numBytes]; + var stopwatch = new Stopwatch(); + var acc = 0L; + + for (var i = 0; i < this.reps; i++) + { + stopwatch.Restart(); + service.WriteAllBytes(path, data); + stopwatch.Stop(); + acc += stopwatch.ElapsedMilliseconds; + Log.Information("Turn {Turn} took {Ms}ms", i, stopwatch.ElapsedMilliseconds); + } + + Log.Information("Took {Ms}ms in total", acc); + } + + if (ImGui.Button("Read")) + { + Log.Information("=== READING ==="); + var stopwatch = new Stopwatch(); + var acc = 0L; + + for (var i = 0; i < this.reps; i++) + { + stopwatch.Restart(); + service.ReadAllBytes(path); + stopwatch.Stop(); + acc += stopwatch.ElapsedMilliseconds; + Log.Information("Turn {Turn} took {Ms}ms", i, stopwatch.ElapsedMilliseconds); + } + + Log.Information("Took {Ms}ms in total", acc); + } + + if (ImGui.Button("Test Config")) + { + var config = Service.Get(); + + Log.Information("=== READING ==="); + var stopwatch = new Stopwatch(); + var acc = 0L; + + for (var i = 0; i < this.reps; i++) + { + stopwatch.Restart(); + config.ForceSave(); + stopwatch.Stop(); + acc += stopwatch.ElapsedMilliseconds; + Log.Information("Turn {Turn} took {Ms}ms", i, stopwatch.ElapsedMilliseconds); + } + + Log.Information("Took {Ms}ms in total", acc); + } + } +} From 2e77e905322a568017001c5dd531f65d346ba43f Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 13:49:31 +0100 Subject: [PATCH 232/375] title screen menu: some visual fixes Use SeStringRenderer to render menu entries Clamp alpha values to avoid flickering --- .../Internal/Windows/TitleScreenMenuWindow.cs | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 88f291954..979f68bf8 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -11,6 +11,7 @@ using Dalamud.Game.ClientState; using Dalamud.Game.Gui; using Dalamud.Game.Text; using Dalamud.Interface.Animation.EasingFunctions; +using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Textures.TextureWraps; @@ -50,11 +51,11 @@ internal class TitleScreenMenuWindow : Window, IDisposable private readonly Lazy myFontHandle; private readonly Lazy shadeTexture; private readonly AddonLifecycleEventListener versionStringListener; - + private readonly Dictionary shadeEasings = new(); private readonly Dictionary moveEasings = new(); private readonly Dictionary logoEasings = new(); - + private readonly IConsoleVariable showTsm; private InOutCubic? fadeOutEasing; @@ -62,7 +63,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable private State state = State.Hide; private int lastLoadedPluginCount = -1; - + /// /// Initializes a new instance of the class. /// @@ -91,7 +92,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus) { this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true); - + this.clientState = clientState; this.configuration = configuration; this.gameGui = gameGui; @@ -124,7 +125,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable framework.Update += this.FrameworkOnUpdate; this.scopedFinalizer.Add(() => framework.Update -= this.FrameworkOnUpdate); - + this.versionStringListener = new AddonLifecycleEventListener(AddonEvent.PreDraw, "_TitleRevision", this.OnVersionStringDraw); addonLifecycle.RegisterListener(this.versionStringListener); this.scopedFinalizer.Add(() => addonLifecycle.UnregisterListener(this.versionStringListener)); @@ -136,7 +137,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable Show, FadeOut, } - + /// /// Gets or sets a value indicating whether drawing is allowed. /// @@ -165,7 +166,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable { if (!this.AllowDrawing || !this.showTsm.Value) return; - + var scale = ImGui.GetIO().FontGlobalScale; var entries = this.titleScreenMenu.PluginEntries; @@ -174,7 +175,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); Service.Get().OverrideGameCursor = !hovered; - + switch (this.state) { case State.Show: @@ -251,7 +252,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.fadeOutEasing.Update(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value)) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)Math.Max(this.fadeOutEasing.Value, 0))) { var i = 0; foreach (var entry in entries) @@ -392,21 +393,14 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)Math.Min(logoEasing.Value, 1) : 0f); } // Drop shadow - using (ImRaii.PushColor(ImGuiCol.Text, 0xFF000000)) - { - for (int i = 0, to = (int)Math.Ceiling(1 * scale); i < to; i++) - { - ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i)); - ImGui.Text(entry.Name); - } - } - ImGui.SetCursorPos(cursor); - ImGui.Text(entry.Name); + var renderStyle = default(SeStringDrawParams); + renderStyle.FontSize = TargetFontSizePx; + ImGuiHelpers.CompileSeStringWrapped($"{entry.Name}", renderStyle); if (overrideAlpha) { @@ -439,7 +433,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable var addon = (AtkUnitBase*)drawArgs.Addon; var textNode = addon->GetTextNodeById(3); - + // look and feel init. should be harmless to set. textNode->TextFlags |= (byte)TextFlags.MultiLine; textNode->AlignmentType = AlignmentType.TopLeft; From 1aada983931d9e45a250eebbc17c8b782d07701b Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 16:53:16 +0100 Subject: [PATCH 233/375] clamp value of all easings by default --- Dalamud/Interface/Animation/Easing.cs | 10 ++++++++-- .../Interface/Animation/EasingFunctions/InCirc.cs | 2 +- .../Interface/Animation/EasingFunctions/InCubic.cs | 2 +- .../Animation/EasingFunctions/InElastic.cs | 10 +++++----- .../Animation/EasingFunctions/InOutCirc.cs | 6 +++--- .../Animation/EasingFunctions/InOutCubic.cs | 2 +- .../Animation/EasingFunctions/InOutElastic.cs | 14 +++++++------- .../Animation/EasingFunctions/InOutQuint.cs | 2 +- .../Animation/EasingFunctions/InOutSine.cs | 2 +- .../Interface/Animation/EasingFunctions/InQuint.cs | 2 +- .../Interface/Animation/EasingFunctions/InSine.cs | 2 +- .../Interface/Animation/EasingFunctions/OutCirc.cs | 2 +- .../Animation/EasingFunctions/OutCubic.cs | 2 +- .../Animation/EasingFunctions/OutElastic.cs | 10 +++++----- .../Animation/EasingFunctions/OutQuint.cs | 2 +- .../Interface/Animation/EasingFunctions/OutSine.cs | 2 +- .../Internal/Windows/TitleScreenMenuWindow.cs | 4 ++-- 17 files changed, 41 insertions(+), 35 deletions(-) diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index edab25149..a48300a22 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -43,9 +43,15 @@ public abstract class Easing public bool IsInverse { get; set; } /// - /// Gets or sets the current value of the animation, from 0 to 1. + /// Gets the current value of the animation, from 0 to 1. /// - public double Value + public double Value => Math.Clamp(this.ValueUnclamped, 0, 1); + + /// + /// Gets or sets the current value of the animation, not limited to a range of 0 to 1. + /// Will return numbers outside of this range if accessed beyond animation time. + /// + public double ValueUnclamped { get { diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs index c467104c5..d94e9fc9f 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -19,6 +19,6 @@ public class InCirc : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); + this.ValueUnclamped = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs index 78f6774ac..64ebc5ba3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -19,6 +19,6 @@ public class InCubic : Easing public override void Update() { var p = this.Progress; - this.Value = p * p * p; + this.ValueUnclamped = p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs index c53c3d587..2e834e41c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -21,10 +21,10 @@ public class InElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs index 71a598dfb..a63ab648a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -19,8 +19,8 @@ public class InOutCirc : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 - ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 - : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; + this.ValueUnclamped = p < 0.5 + ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 + : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs index 07bcfa28d..4083265b7 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -19,6 +19,6 @@ public class InOutCubic : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); + this.ValueUnclamped = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs index f78f9f336..f27726038 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -21,12 +21,12 @@ public class InOutElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : p < 0.5 - ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 - : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : p < 0.5 + ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 + : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs index 64ab98b16..e08129b25 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -19,6 +19,6 @@ public class InOutQuint : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); + this.ValueUnclamped = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs index 2f347ff80..cb940d87d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -19,6 +19,6 @@ public class InOutSine : Easing public override void Update() { var p = this.Progress; - this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; + this.ValueUnclamped = -(Math.Cos(Math.PI * p) - 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs index a5ab5a22c..827e0e21b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -19,6 +19,6 @@ public class InQuint : Easing public override void Update() { var p = this.Progress; - this.Value = p * p * p * p * p; + this.ValueUnclamped = p * p * p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs index fa079baad..61affa10a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -19,6 +19,6 @@ public class InSine : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Cos((p * Math.PI) / 2); + this.ValueUnclamped = 1 - Math.Cos((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs index b0d3b895a..980e29a81 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -19,6 +19,6 @@ public class OutCirc : Easing public override void Update() { var p = this.Progress; - this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); + this.ValueUnclamped = Math.Sqrt(1 - Math.Pow(p - 1, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs index 9c1bb57dc..e1a79c35b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -19,6 +19,6 @@ public class OutCubic : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 3); + this.ValueUnclamped = 1 - Math.Pow(1 - p, 3); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs index 6a4fcd6dc..1f525b404 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -21,10 +21,10 @@ public class OutElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs index a3174e762..24a2255d3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -19,6 +19,6 @@ public class OutQuint : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 5); + this.ValueUnclamped = 1 - Math.Pow(1 - p, 5); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs index ba82232b3..a376d7f57 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -19,6 +19,6 @@ public class OutSine : Easing public override void Update() { var p = this.Progress; - this.Value = Math.Sin((p * Math.PI) / 2); + this.ValueUnclamped = Math.Sin((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 979f68bf8..c63efca64 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -252,7 +252,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.fadeOutEasing.Update(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)Math.Max(this.fadeOutEasing.Value, 0))) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value)) { var i = 0; foreach (var entry in entries) @@ -393,7 +393,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)Math.Min(logoEasing.Value, 1) : 0f); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); } // Drop shadow From 6dd85c9e3ee3643b3b408a4661f6c3ddb215420e Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 16:56:31 +0100 Subject: [PATCH 234/375] remove some unused stuff in Util, fix warnings --- Dalamud/Utility/Util.cs | 53 ----------------------------------------- 1 file changed, 53 deletions(-) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index d53828858..7724c68e0 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -65,7 +65,6 @@ public static class Util private static readonly Type GenericSpanType = typeof(Span<>); private static string? scmVersionInternal; private static string? gitHashInternal; - private static int? gitCommitCountInternal; private static string? gitHashClientStructsInternal; private static ulong moduleStartAddr; @@ -77,58 +76,6 @@ public static class Util public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - /// - /// Check two byte arrays for equality. - /// - /// The first byte array. - /// The second byte array. - /// Whether or not the byte arrays are equal. - public static unsafe bool FastByteArrayCompare(byte[]? a1, byte[]? a2) - { - // Copyright (c) 2008-2013 Hafthor Stefansson - // Distributed under the MIT/X11 software license - // Ref: http://www.opensource.org/licenses/mit-license.php. - // https://stackoverflow.com/a/8808245 - - if (a1 == a2) return true; - if (a1 == null || a2 == null || a1.Length != a2.Length) - return false; - fixed (byte* p1 = a1, p2 = a2) - { - byte* x1 = p1, x2 = p2; - var l = a1.Length; - for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) - { - if (*((long*)x1) != *((long*)x2)) - return false; - } - - if ((l & 4) != 0) - { - if (*((int*)x1) != *((int*)x2)) - return false; - x1 += 4; - x2 += 4; - } - - if ((l & 2) != 0) - { - if (*((short*)x1) != *((short*)x2)) - return false; - x1 += 2; - x2 += 2; - } - - if ((l & 1) != 0) - { - if (*((byte*)x1) != *((byte*)x2)) - return false; - } - - return true; - } - } - /// /// Gets the SCM Version from the assembly, or null if it cannot be found. This method will generally return /// the git describe output for this build, which will be a raw version if this is a stable build or an From 0cac90b03285b9654c8a45506acc419a8ff794b0 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 19:01:49 +0100 Subject: [PATCH 235/375] plugin installer: only allow feedback for non-outdated plugins --- .../Windows/PluginInstaller/PluginInstallerWindow.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 4daccab3b..4e60d4be8 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -2444,7 +2444,7 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.ScaledDummy(3); } - if (!manifest.SourceRepo.IsThirdParty && manifest.AcceptsFeedback) + if (!manifest.SourceRepo.IsThirdParty && manifest.AcceptsFeedback && !isOutdated) { ImGui.SameLine(); this.DrawSendFeedbackButton(manifest, false, true); @@ -2724,8 +2724,8 @@ internal class PluginInstallerWindow : Window, IDisposable var canFeedback = !isThirdParty && !plugin.IsDev && !plugin.IsOrphaned && - (plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel - || plugin.Manifest.TestingDalamudApiLevel == PluginManager.DalamudApiLevel) && + (plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel || + (plugin.Manifest.TestingDalamudApiLevel == PluginManager.DalamudApiLevel && hasTestingAvailable)) && acceptsFeedback && availablePluginUpdate == default; From b36bdb2086ed27244c11e31cc17a3f7d62c76a30 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 30 Dec 2024 13:59:59 +0100 Subject: [PATCH 236/375] Revert "clamp value of all easings by default" This reverts commit 1aada983931d9e45a250eebbc17c8b782d07701b. Breaks the API. --- Dalamud/Interface/Animation/Easing.cs | 10 ++-------- .../Interface/Animation/EasingFunctions/InCirc.cs | 2 +- .../Interface/Animation/EasingFunctions/InCubic.cs | 2 +- .../Animation/EasingFunctions/InElastic.cs | 10 +++++----- .../Animation/EasingFunctions/InOutCirc.cs | 6 +++--- .../Animation/EasingFunctions/InOutCubic.cs | 2 +- .../Animation/EasingFunctions/InOutElastic.cs | 14 +++++++------- .../Animation/EasingFunctions/InOutQuint.cs | 2 +- .../Animation/EasingFunctions/InOutSine.cs | 2 +- .../Interface/Animation/EasingFunctions/InQuint.cs | 2 +- .../Interface/Animation/EasingFunctions/InSine.cs | 2 +- .../Interface/Animation/EasingFunctions/OutCirc.cs | 2 +- .../Animation/EasingFunctions/OutCubic.cs | 2 +- .../Animation/EasingFunctions/OutElastic.cs | 10 +++++----- .../Animation/EasingFunctions/OutQuint.cs | 2 +- .../Interface/Animation/EasingFunctions/OutSine.cs | 2 +- .../Internal/Windows/TitleScreenMenuWindow.cs | 4 ++-- 17 files changed, 35 insertions(+), 41 deletions(-) diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index a48300a22..edab25149 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -43,15 +43,9 @@ public abstract class Easing public bool IsInverse { get; set; } /// - /// Gets the current value of the animation, from 0 to 1. + /// Gets or sets the current value of the animation, from 0 to 1. /// - public double Value => Math.Clamp(this.ValueUnclamped, 0, 1); - - /// - /// Gets or sets the current value of the animation, not limited to a range of 0 to 1. - /// Will return numbers outside of this range if accessed beyond animation time. - /// - public double ValueUnclamped + public double Value { get { diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs index d94e9fc9f..c467104c5 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -19,6 +19,6 @@ public class InCirc : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); + this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs index 64ebc5ba3..78f6774ac 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -19,6 +19,6 @@ public class InCubic : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p * p * p; + this.Value = p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs index 2e834e41c..c53c3d587 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -21,10 +21,10 @@ public class InElastic : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p == 0 - ? 0 - : p == 1 - ? 1 - : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs index a63ab648a..71a598dfb 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -19,8 +19,8 @@ public class InOutCirc : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p < 0.5 - ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 - : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; + this.Value = p < 0.5 + ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 + : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs index 4083265b7..07bcfa28d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -19,6 +19,6 @@ public class InOutCubic : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); + this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs index f27726038..f78f9f336 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -21,12 +21,12 @@ public class InOutElastic : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p == 0 - ? 0 - : p == 1 - ? 1 - : p < 0.5 - ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 - : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : p < 0.5 + ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 + : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs index e08129b25..64ab98b16 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -19,6 +19,6 @@ public class InOutQuint : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); + this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs index cb940d87d..2f347ff80 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -19,6 +19,6 @@ public class InOutSine : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = -(Math.Cos(Math.PI * p) - 1) / 2; + this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs index 827e0e21b..a5ab5a22c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -19,6 +19,6 @@ public class InQuint : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p * p * p * p * p; + this.Value = p * p * p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs index 61affa10a..fa079baad 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -19,6 +19,6 @@ public class InSine : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = 1 - Math.Cos((p * Math.PI) / 2); + this.Value = 1 - Math.Cos((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs index 980e29a81..b0d3b895a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -19,6 +19,6 @@ public class OutCirc : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = Math.Sqrt(1 - Math.Pow(p - 1, 2)); + this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs index e1a79c35b..9c1bb57dc 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -19,6 +19,6 @@ public class OutCubic : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = 1 - Math.Pow(1 - p, 3); + this.Value = 1 - Math.Pow(1 - p, 3); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs index 1f525b404..6a4fcd6dc 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -21,10 +21,10 @@ public class OutElastic : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = p == 0 - ? 0 - : p == 1 - ? 1 - : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs index 24a2255d3..a3174e762 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -19,6 +19,6 @@ public class OutQuint : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = 1 - Math.Pow(1 - p, 5); + this.Value = 1 - Math.Pow(1 - p, 5); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs index a376d7f57..ba82232b3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -19,6 +19,6 @@ public class OutSine : Easing public override void Update() { var p = this.Progress; - this.ValueUnclamped = Math.Sin((p * Math.PI) / 2); + this.Value = Math.Sin((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index c63efca64..979f68bf8 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -252,7 +252,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.fadeOutEasing.Update(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value)) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)Math.Max(this.fadeOutEasing.Value, 0))) { var i = 0; foreach (var entry in entries) @@ -393,7 +393,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)Math.Min(logoEasing.Value, 1) : 0f); } // Drop shadow From 335b3d1ed5edf1325bbbed67984f6d0be2e92222 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 30 Dec 2024 14:01:12 +0100 Subject: [PATCH 237/375] add todo to re-apply easings behavior change --- Dalamud/Interface/Animation/Easing.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index edab25149..473e3d5e1 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -1,11 +1,14 @@ using System.Diagnostics; using System.Numerics; +using Dalamud.Utility; + namespace Dalamud.Interface.Animation; /// /// Base class facilitating the implementation of easing functions. /// +[Api11ToDo("Re-apply https://github.com/goatcorp/Dalamud/commit/1aada983931d9e45a250eebbc17c8b782d07701b")] public abstract class Easing { // TODO: Use game delta time here instead From 49a18e3c1ed328f74c559657635b66e1008ad26c Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 30 Dec 2024 14:01:50 +0100 Subject: [PATCH 238/375] Api11ToDo -> Api12ToDo --- Dalamud/Game/ClientState/Objects/ObjectTable.cs | 2 +- Dalamud/Game/Gui/Dtr/DtrBarEntry.cs | 2 +- Dalamud/Interface/Animation/Easing.cs | 2 +- .../{Api11ToDoAttribute.cs => Api12ToDoAttribute.cs} | 6 +++--- Dalamud/Utility/SeStringExtensions.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) rename Dalamud/Utility/{Api11ToDoAttribute.cs => Api12ToDoAttribute.cs} (75%) diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 8ea1b582f..6aa7fd8ad 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -155,7 +155,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable }; } - [Api11ToDo("Use ThreadSafety.AssertMainThread() instead of this.")] + [Api12ToDo("Use ThreadSafety.AssertMainThread() instead of this.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool WarnMultithreadedUsage() { diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index 8ba46f999..49a2cbb73 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -145,7 +145,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry } /// - [Api11ToDo("Maybe make this config scoped to internalname?")] + [Api12ToDo("Maybe make this config scoped to internalname?")] public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false; /// diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index 473e3d5e1..c6d6149af 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -8,7 +8,7 @@ namespace Dalamud.Interface.Animation; /// /// Base class facilitating the implementation of easing functions. /// -[Api11ToDo("Re-apply https://github.com/goatcorp/Dalamud/commit/1aada983931d9e45a250eebbc17c8b782d07701b")] +[Api12ToDo("Re-apply https://github.com/goatcorp/Dalamud/commit/1aada983931d9e45a250eebbc17c8b782d07701b")] public abstract class Easing { // TODO: Use game delta time here instead diff --git a/Dalamud/Utility/Api11ToDoAttribute.cs b/Dalamud/Utility/Api12ToDoAttribute.cs similarity index 75% rename from Dalamud/Utility/Api11ToDoAttribute.cs rename to Dalamud/Utility/Api12ToDoAttribute.cs index 0336120ba..9f871274d 100644 --- a/Dalamud/Utility/Api11ToDoAttribute.cs +++ b/Dalamud/Utility/Api12ToDoAttribute.cs @@ -4,7 +4,7 @@ namespace Dalamud.Utility; /// Utility class for marking something to be changed for API 11, for ease of lookup. /// [AttributeUsage(AttributeTargets.All, Inherited = false)] -internal sealed class Api11ToDoAttribute : Attribute +internal sealed class Api12ToDoAttribute : Attribute { /// /// Marks that this should be made internal. @@ -12,11 +12,11 @@ internal sealed class Api11ToDoAttribute : Attribute public const string MakeInternal = "Make internal."; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The explanation. /// The explanation 2. - public Api11ToDoAttribute(string what, string what2 = "") + public Api12ToDoAttribute(string what, string what2 = "") { _ = what; _ = what2; diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 34ebb1450..057759e1e 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -43,7 +43,7 @@ public static class SeStringExtensions /// Macro string in UTF-8 to compile and append to . /// this for method chaining. [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.", true)] - [Api11ToDo("Remove")] + [Api12ToDo("Remove")] public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); @@ -52,7 +52,7 @@ public static class SeStringExtensions /// Macro string in UTF-16 to compile and append to . /// this for method chaining. [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.", true)] - [Api11ToDo("Remove")] + [Api12ToDo("Remove")] public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); From 35b49823e5e5883f25220f4ab651487fc4344ff4 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 30 Dec 2024 21:14:08 +0100 Subject: [PATCH 239/375] WindowSystem: fix clickthrough option not applying to child windows, persist options Persistence is pretty WIP. I want to offer multiple presets in the future, and save more things like window positions. --- .../Internal/DalamudConfiguration.cs | 9 +- .../Interface/Internal/InterfaceManager.cs | 3 + .../Settings/Tabs/SettingsTabExperimental.cs | 16 +- .../Windows/Settings/Tabs/SettingsTabLook.cs | 20 +- .../Windowing/Persistence/PresetModel.cs | 53 +++++ .../Persistence/WindowSystemPersistence.cs | 47 ++++ Dalamud/Interface/Windowing/Window.cs | 213 +++++++++++++----- Dalamud/Interface/Windowing/WindowSystem.cs | 18 +- lib/cimgui | 2 +- 9 files changed, 301 insertions(+), 80 deletions(-) create mode 100644 Dalamud/Interface/Windowing/Persistence/PresetModel.cs create mode 100644 Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 850aeb7e9..515556b7e 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -12,6 +12,7 @@ using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.ReShadeHandling; using Dalamud.Interface.Style; +using Dalamud.Interface.Windowing.Persistence; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal.AutoUpdate; using Dalamud.Plugin.Internal.Profiles; @@ -264,8 +265,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// Gets or sets a value indicating whether or not an additional button allowing pinning and clickthrough options should be shown /// on plugin title bars when using the Window System. /// - [JsonProperty("EnablePluginUiAdditionalOptionsExperimental")] - public bool EnablePluginUiAdditionalOptions { get; set; } = false; + public bool EnablePluginUiAdditionalOptions { get; set; } = true; /// /// Gets or sets a value indicating whether viewports should always be disabled. @@ -351,6 +351,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// public bool ProfilesHasSeenTutorial { get; set; } = false; + /// + /// Gets or sets the default UI preset. + /// + public PresetModel DefaultUiPreset { get; set; } = new(); + /// /// Gets or sets the order of DTR elements, by title. /// diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 2908be34a..1cd45f4f4 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -27,6 +27,8 @@ using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Style; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; +using Dalamud.Interface.Windowing.Persistence; +using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Utility; using Dalamud.Utility.Timing; @@ -60,6 +62,7 @@ namespace Dalamud.Interface.Internal; /// This class manages interaction with the ImGui interface. /// [ServiceManager.EarlyLoadedService] +[InherentDependency] // Used by window system windows to restore state from the configuration internal partial class InterfaceManager : IInternalDisposableService { /// diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs index 4354daffe..1aae0dfb3 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabExperimental.cs @@ -39,18 +39,6 @@ public class SettingsTabExperimental : SettingsTab new GapSettingsEntry(5), - new SettingsEntry( - Loc.Localize( - "DalamudSettingEnablePluginUIAdditionalOptions", - "Add a button to the title bar of plugin windows to open additional options"), - Loc.Localize( - "DalamudSettingEnablePluginUIAdditionalOptionsHint", - "This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."), - c => c.EnablePluginUiAdditionalOptions, - (v, c) => c.EnablePluginUiAdditionalOptions = v), - - new GapSettingsEntry(5), - new ButtonSettingsEntry( Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"), Loc.Localize( @@ -65,7 +53,7 @@ public class SettingsTabExperimental : SettingsTab new GapSettingsEntry(5, true), new DevPluginsSettingsEntry(), - + new SettingsEntry( Loc.Localize( "DalamudSettingEnableImGuiAsserts", @@ -75,7 +63,7 @@ public class SettingsTabExperimental : SettingsTab "If this setting is enabled, a window containing further details will be shown when an internal assertion in ImGui fails.\nWe recommend enabling this when developing plugins."), c => Service.Get().ShowAsserts, (v, _) => Service.Get().ShowAsserts = v), - + new SettingsEntry( Loc.Localize( "DalamudSettingEnableImGuiAssertsAtStartup", diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs index 61ab00936..7f75dbf29 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs @@ -24,7 +24,7 @@ namespace Dalamud.Interface.Internal.Windows.Settings.Tabs; [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")] public class SettingsTabLook : SettingsTab { - private static readonly (string, float)[] GlobalUiScalePresets = + private static readonly (string, float)[] GlobalUiScalePresets = { ("80%##DalamudSettingsGlobalUiScaleReset96", 0.8f), ("100%##DalamudSettingsGlobalUiScaleReset12", 1f), @@ -107,7 +107,17 @@ public class SettingsTabLook : SettingsTab Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows."), c => c.IsDocking, (v, c) => c.IsDocking = v), - + + new SettingsEntry( + Loc.Localize( + "DalamudSettingEnablePluginUIAdditionalOptions", + "Add a button to the title bar of plugin windows to open additional options"), + Loc.Localize( + "DalamudSettingEnablePluginUIAdditionalOptionsHint", + "This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."), + c => c.EnablePluginUiAdditionalOptions, + (v, c) => c.EnablePluginUiAdditionalOptions = v), + new SettingsEntry( Loc.Localize("DalamudSettingEnablePluginUISoundEffects", "Enable sound effects for plugin windows"), Loc.Localize("DalamudSettingEnablePluginUISoundEffectsHint", "This will allow you to enable or disable sound effects generated by plugin user interfaces.\nThis is affected by your in-game `System Sounds` volume settings."), @@ -125,19 +135,19 @@ public class SettingsTabLook : SettingsTab Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen.\nDisabling this will also hide the Dalamud version text on the title screen."), c => c.ShowTsm, (v, c) => c.ShowTsm = v), - + new SettingsEntry( Loc.Localize("DalamudSettingInstallerOpenDefault", "Open the Plugin Installer to the \"Installed Plugins\" tab by default"), Loc.Localize("DalamudSettingInstallerOpenDefaultHint", "This will allow you to open the Plugin Installer to the \"Installed Plugins\" tab by default, instead of the \"Available Plugins\" tab."), c => c.PluginInstallerOpen == PluginInstallerOpenKind.InstalledPlugins, (v, c) => c.PluginInstallerOpen = v ? PluginInstallerOpenKind.InstalledPlugins : PluginInstallerOpenKind.AllPlugins), - + new SettingsEntry( Loc.Localize("DalamudSettingReducedMotion", "Reduce motions"), Loc.Localize("DalamudSettingReducedMotionHint", "This will suppress certain animations from Dalamud, such as the notification popup."), c => c.ReduceMotions ?? false, (v, c) => c.ReduceMotions = v), - + new SettingsEntry( Loc.Localize("DalamudSettingImeStateIndicatorOpacity", "IME State Indicator Opacity (CJK only)"), Loc.Localize("DalamudSettingImeStateIndicatorOpacityHint", "When any of CJK IMEs is in use, the state of IME will be shown with the opacity specified here."), diff --git a/Dalamud/Interface/Windowing/Persistence/PresetModel.cs b/Dalamud/Interface/Windowing/Persistence/PresetModel.cs new file mode 100644 index 000000000..db91bad8a --- /dev/null +++ b/Dalamud/Interface/Windowing/Persistence/PresetModel.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Dalamud.Interface.Windowing.Persistence; + +/// +/// Class representing a Window System preset. +/// +internal class PresetModel +{ + /// + /// Gets or sets the ID of this preset. + /// + [JsonProperty("id")] + public Guid Id { get; set; } + + /// + /// Gets or sets the name of this preset. + /// + [JsonProperty("n")] + public string Name { get; set; } = "New Preset"; + + /// + /// Gets or sets a dictionary containing the windows in the preset, mapping their ID to the preset. + /// + [JsonProperty("w")] + public Dictionary Windows { get; set; } = new(); + + /// + /// Class representing a window in a preset. + /// + internal class PresetWindow + { + /// + /// Gets or sets a value indicating whether the window is pinned. + /// + [JsonProperty("p")] + public bool IsPinned { get; set; } + + /// + /// Gets or sets a value indicating whether the window is clickthrough. + /// + [JsonProperty("ct")] + public bool IsClickThrough { get; set; } + + /// + /// Gets or sets the window's opacity override. + /// + [JsonProperty("a")] + public float? Alpha { get; set; } + } +} diff --git a/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs b/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs new file mode 100644 index 000000000..8b884b0e5 --- /dev/null +++ b/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs @@ -0,0 +1,47 @@ +using Dalamud.Configuration.Internal; + +namespace Dalamud.Interface.Windowing.Persistence; + +/// +/// Class handling persistence for window system windows. +/// +[ServiceManager.EarlyLoadedService] +internal class WindowSystemPersistence : IServiceType +{ + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration config = Service.Get(); + + /// + /// Initializes a new instance of the class. + /// + [ServiceManager.ServiceConstructor] + public WindowSystemPersistence() + { + } + + /// + /// Gets the active window system preset. + /// + public PresetModel ActivePreset => this.config.DefaultUiPreset; + + /// + /// Get or add a window to the active preset. + /// + /// The ID of the window. + /// The preset window instance, or null if the preset does not contain this window. + public PresetModel.PresetWindow? GetWindow(uint id) + { + return this.ActivePreset.Windows.TryGetValue(id, out var window) ? window : null; + } + + /// + /// Persist the state of a window to the active preset. + /// + /// The ID of the window. + /// The preset window instance. + public void SaveWindow(uint id, PresetModel.PresetWindow window) + { + this.ActivePreset.Windows[id] = window; + this.config.QueueSave(); + } +} diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index 589217a73..6219de852 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -1,15 +1,17 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using CheapLoc; -using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; +using Dalamud.Interface.Windowing.Persistence; using Dalamud.Logging.Internal; using FFXIVClientStructs.FFXIV.Client.UI; @@ -26,7 +28,7 @@ public abstract class Window private static readonly ModuleLog Log = new("WindowSystem"); private static bool wasEscPressedLastFrame = false; - + private bool internalLastIsOpen = false; private bool internalIsOpen = false; private bool internalIsPinned = false; @@ -35,15 +37,19 @@ public abstract class Window private float? internalAlpha = null; private bool nextFrameBringToFront = false; + private bool hasInitializedFromPreset = false; + private PresetModel.PresetWindow? presetWindow; + private bool presetDirty = false; + /// /// Initializes a new instance of the class. /// /// The name/ID of this window. /// If you have multiple windows with the same name, you will need to - /// append an unique ID to it by specifying it after "###" behind the window title. + /// append a unique ID to it by specifying it after "###" behind the window title. /// /// The of this window. - /// Whether or not this window should be limited to the main game window. + /// Whether this window should be limited to the main game window. protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) { this.WindowName = name; @@ -51,6 +57,33 @@ public abstract class Window this.ForceMainWindow = forceMainWindow; } + /// + /// Flags to control window behavior. + /// + [Flags] + internal enum WindowDrawFlags + { + /// + /// Nothing. + /// + None = 0, + + /// + /// Enable window opening/closing sound effects. + /// + UseSoundEffects = 1 << 0, + + /// + /// Hook into the game's focus management. + /// + UseFocusManagement = 1 << 1, + + /// + /// Enable the built-in "additional options" menu on the title bar. + /// + UseAdditionalOptions = 1 << 2, + } + /// /// Gets or sets the namespace of the window. /// @@ -87,7 +120,7 @@ public abstract class Window /// Gets or sets a value representing the sound effect id to be played when the window is closed. /// public uint OnCloseSfxId { get; set; } = 24u; - + /// /// Gets or sets the position of this window. /// @@ -155,7 +188,7 @@ public abstract class Window /// /// Gets or sets a list of available title bar buttons. - /// + /// /// If or are set to true, and this features is not /// disabled globally by the user, an internal title bar button to manage these is added when drawing, but it will /// not appear in this collection. If you wish to remove this button, set both of these values to false. @@ -170,7 +203,7 @@ public abstract class Window get => this.internalIsOpen; set => this.internalIsOpen = value; } - + private bool CanShowCloseButton => this.ShowCloseButton && !this.internalIsClickthrough; /// @@ -267,17 +300,16 @@ public abstract class Window public virtual void Update() { } - + /// /// Draw the window via ImGui. /// - /// Configuration instance used to check if certain window management features should be enabled. - internal void DrawInternal(DalamudConfiguration? configuration) + /// Flags controlling window behavior. + /// Handler for window persistence data. + internal void DrawInternal(WindowDrawFlags internalDrawFlags, WindowSystemPersistence? persistence) { this.PreOpenCheck(); - var doSoundEffects = configuration?.EnablePluginUISoundEffects ?? false; - if (!this.IsOpen) { if (this.internalIsOpen != this.internalLastIsOpen) @@ -286,8 +318,9 @@ public abstract class Window this.OnClose(); this.IsFocused = false; - - if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnCloseSfxId); + + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds) + UIGlobals.PlaySoundEffect(this.OnCloseSfxId); } return; @@ -301,13 +334,16 @@ public abstract class Window if (hasNamespace) ImGui.PushID(this.Namespace); - + + this.PreHandlePreset(persistence); + if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) { this.internalLastIsOpen = this.internalIsOpen; this.OnOpen(); - if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnOpenSfxId); + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds) + UIGlobals.PlaySoundEffect(this.OnOpenSfxId); } this.PreDraw(); @@ -340,6 +376,8 @@ public abstract class Window if (this.CanShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags)) { + ImGuiNativeAdditions.igCustom_WindowSetInheritNoInputs(this.internalIsClickthrough); + // Draw the actual window contents try { @@ -355,7 +393,7 @@ public abstract class Window var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) && !flags.HasFlag(ImGuiWindowFlags.NoTitleBar); var showAdditions = (this.AllowPinning || this.AllowClickthrough) && - (configuration?.EnablePluginUiAdditionalOptions ?? true) && + internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) && flagsApplicableForTitleBarIcons; if (showAdditions) { @@ -364,10 +402,10 @@ public abstract class Window if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove)) { var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport(); - + if (!isAvailable) ImGui.BeginDisabled(); - + if (this.internalIsClickthrough) ImGui.BeginDisabled(); @@ -375,36 +413,51 @@ public abstract class Window { var showAsPinned = this.internalIsPinned || this.internalIsClickthrough; if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref showAsPinned)) + { this.internalIsPinned = showAsPinned; + this.presetDirty = true; + } + + ImGuiComponents.HelpMarker( + Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them.")); } if (this.internalIsClickthrough) ImGui.EndDisabled(); if (this.AllowClickthrough) - ImGui.Checkbox(Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"), ref this.internalIsClickthrough); + { + if (ImGui.Checkbox( + Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"), + ref this.internalIsClickthrough)) + { + this.presetDirty = true; + } + + ImGuiComponents.HelpMarker( + Loc.Localize("WindowSystemContextActionClickthroughHint", "Clickthrough windows will not receive mouse input, move or resize. They are completely inert.")); + } var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f; if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f, 100f)) { this.internalAlpha = alpha / 100f; + this.presetDirty = true; } ImGui.SameLine(); if (ImGui.Button(Loc.Localize("WindowSystemContextActionReset", "Reset"))) { this.internalAlpha = null; + this.presetDirty = true; } if (isAvailable) { ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("WindowSystemContextActionClickthroughDisclaimer", - "Open this menu again to disable clickthrough.")); - ImGui.TextColored(ImGuiColors.DalamudGrey, - Loc.Localize("WindowSystemContextActionDisclaimer", - "These options may not work for all plugins at the moment.")); + "Open this menu again by clicking the three dashes to disable clickthrough.")); } else { @@ -415,7 +468,7 @@ public abstract class Window if (!isAvailable) ImGui.EndDisabled(); - + ImGui.EndPopup(); } @@ -457,8 +510,7 @@ public abstract class Window this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); - var isAllowed = configuration?.IsFocusManagementEnabled ?? false; - if (isAllowed) + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement)) { var escapeDown = Service.Get()[VirtualKey.ESCAPE]; if (escapeDown && this.IsFocused && !wasEscPressedLastFrame && this.RespectCloseHotkey) @@ -476,6 +528,8 @@ public abstract class Window this.PostDraw(); + this.PostHandlePreset(persistence); + if (hasNamespace) ImGui.PopID(); } @@ -511,7 +565,7 @@ public abstract class Window { ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); } - + // Manually set alpha takes precedence, if devs don't want that, they should turn it off if (this.internalAlpha.HasValue) { @@ -519,21 +573,65 @@ public abstract class Window } } + private void PreHandlePreset(WindowSystemPersistence? persistence) + { + if (persistence == null || this.hasInitializedFromPreset) + return; + + var id = ImGui.GetID(this.WindowName); + this.presetWindow = persistence.GetWindow(id); + + this.hasInitializedFromPreset = true; + + // Fresh preset - don't apply anything + if (this.presetWindow == null) + { + this.presetWindow = new PresetModel.PresetWindow(); + this.presetDirty = true; + return; + } + + this.internalIsPinned = this.presetWindow.IsPinned; + this.internalIsClickthrough = this.presetWindow.IsClickThrough; + this.internalAlpha = this.presetWindow.Alpha; + } + + private void PostHandlePreset(WindowSystemPersistence? persistence) + { + if (persistence == null) + return; + + Debug.Assert(this.presetWindow != null, "this.presetWindow != null"); + + if (this.presetDirty) + { + this.presetWindow.IsPinned = this.internalIsPinned; + this.presetWindow.IsClickThrough = this.internalIsClickthrough; + this.presetWindow.Alpha = this.internalAlpha; + + var id = ImGui.GetID(this.WindowName); + persistence.SaveWindow(id, this.presetWindow!); + this.presetDirty = false; + + Log.Verbose("Saved preset for {WindowName}", this.WindowName); + } + } + private unsafe void DrawTitleBarButtons(void* window, ImGuiWindowFlags flags, Vector4 titleBarRect, IEnumerable buttons) { ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false); - + var style = ImGui.GetStyle(); var fontSize = ImGui.GetFontSize(); var drawList = ImGui.GetWindowDrawList(); - + var padR = 0f; var buttonSize = ImGui.GetFontSize(); var numNativeButtons = 0; if (this.CanShowCloseButton) numNativeButtons++; - + if (!flags.HasFlag(ImGuiWindowFlags.NoCollapse) && style.WindowMenuButtonPosition == ImGuiDir.Right) numNativeButtons++; @@ -543,15 +641,15 @@ public abstract class Window // Pad to the left, to get out of the way of the native buttons padR += numNativeButtons * (buttonSize + style.ItemInnerSpacing.X); - - Vector2 GetCenter(Vector4 rect) => new((rect.X + rect.Z) * 0.5f, (rect.Y + rect.W) * 0.5f); + + Vector2 GetCenter(Vector4 rect) => new((rect.X + rect.Z) * 0.5f, (rect.Y + rect.W) * 0.5f); var numButtons = 0; bool DrawButton(TitleBarButton button, Vector2 pos) { var id = ImGui.GetID($"###CustomTbButton{numButtons}"); numButtons++; - + var min = pos; var max = pos + new Vector2(fontSize, fontSize); Vector4 bb = new(min.X, min.Y, max.X, max.Y); @@ -563,12 +661,12 @@ public abstract class Window { hovered = false; held = false; - + // ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves if (ImGui.IsMouseHoveringRect(min, max)) { hovered = true; - + // We can't use ImGui native functions here, because they don't work with clickthrough if ((User32.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0) { @@ -581,7 +679,7 @@ public abstract class Window { pressed = ImGuiNativeAdditions.igButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags.None); } - + if (isClipped) return pressed; @@ -590,10 +688,10 @@ public abstract class Window var textCol = ImGui.GetColorU32(ImGuiCol.Text); if (hovered || held) drawList.AddCircleFilled(GetCenter(bb) + new Vector2(0.0f, -0.5f), (fontSize * 0.5f) + 1.0f, bgCol); - + var offset = button.IconOffset * ImGuiHelpers.GlobalScale; drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.X + offset.X, bb.Y + offset.Y), textCol, button.Icon.ToIconString()); - + if (hovered) button.ShowTooltip?.Invoke(); @@ -608,14 +706,14 @@ public abstract class Window { if (this.internalIsClickthrough && !button.AvailableClickthrough) return; - + Vector2 position = new(titleBarRect.Z - padR - buttonSize, titleBarRect.Y + style.FramePadding.Y); padR += buttonSize + style.ItemInnerSpacing.X; - + if (DrawButton(button, position)) button.Click?.Invoke(ImGuiMouseButton.Left); } - + ImGui.PopClipRect(); } @@ -625,7 +723,7 @@ public abstract class Window public struct WindowSizeConstraints { private Vector2 internalMaxSize = new(float.MaxValue); - + /// /// Initializes a new instance of the struct. /// @@ -637,7 +735,7 @@ public abstract class Window /// Gets or sets the minimum size of the window. /// public Vector2 MinimumSize { get; set; } = new(0); - + /// /// Gets or sets the maximum size of the window. /// @@ -646,12 +744,12 @@ public abstract class Window get => this.GetSafeMaxSize(); set => this.internalMaxSize = value; } - + private Vector2 GetSafeMaxSize() { var currentMin = this.MinimumSize; - if (this.internalMaxSize.X < currentMin.X || this.internalMaxSize.Y < currentMin.Y) + if (this.internalMaxSize.X < currentMin.X || this.internalMaxSize.Y < currentMin.Y) return new Vector2(float.MaxValue); return this.internalMaxSize; @@ -667,53 +765,56 @@ public abstract class Window /// Gets or sets the icon of the button. /// public FontAwesomeIcon Icon { get; set; } - + /// /// Gets or sets a vector by which the position of the icon within the button shall be offset. /// Automatically scaled by the global font scale for you. /// public Vector2 IconOffset { get; set; } - + /// /// Gets or sets an action that is called when a tooltip shall be drawn. /// May be null if no tooltip shall be drawn. /// public Action? ShowTooltip { get; set; } - + /// /// Gets or sets an action that is called when the button is clicked. /// public Action Click { get; set; } - + /// /// Gets or sets the priority the button shall be shown in. /// Lower = closer to ImGui default buttons. /// public int Priority { get; set; } - + /// /// Gets or sets a value indicating whether or not the button shall be clickable /// when the respective window is set to clickthrough. /// public bool AvailableClickthrough { get; set; } } - + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "imports")] private static unsafe class ImGuiNativeAdditions { [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern bool igItemAdd(Vector4 bb, uint id, Vector4* navBb, uint flags); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern bool igButtonBehavior(Vector4 bb, uint id, bool* outHovered, bool* outHeld, ImGuiButtonFlags flags); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern void* igGetCurrentWindow(); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern void igStartMouseMovingWindow(void* window); - + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] public static extern void ImGuiWindow_TitleBarRect(Vector4* pOut, void* window); + + [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)] + public static extern void igCustom_WindowSetInheritNoInputs(bool inherit); } } diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index a3eab2a60..f79eea025 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Configuration.Internal; +using Dalamud.Interface.Windowing.Persistence; using ImGuiNET; using Serilog; @@ -103,15 +104,28 @@ public class WindowSystem if (hasNamespace) ImGui.PushID(this.Namespace); + // These must be nullable, people are using stock WindowSystems and Windows without Dalamud for tests var config = Service.GetNullable(); + var persistence = Service.GetNullable(); + + var flags = Window.WindowDrawFlags.None; + + if (config?.EnablePluginUISoundEffects ?? false) + flags |= Window.WindowDrawFlags.UseSoundEffects; + + if (config?.EnablePluginUiAdditionalOptions ?? false) + flags |= Window.WindowDrawFlags.UseAdditionalOptions; + + if (config?.IsFocusManagementEnabled ?? false) + flags |= Window.WindowDrawFlags.UseFocusManagement; // Shallow clone the list of windows so that we can edit it without modifying it while the loop is iterating foreach (var window in this.windows.ToArray()) { #if DEBUG - // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); + // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif - window.DrawInternal(config); + window.DrawInternal(flags, persistence); } var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); diff --git a/lib/cimgui b/lib/cimgui index 7002b2884..122ee1681 160000 --- a/lib/cimgui +++ b/lib/cimgui @@ -1 +1 @@ -Subproject commit 7002b2884e9216d8bef3e792722d88abe31788f8 +Subproject commit 122ee16819437eea7eefe0c04398b44174106d86 From cac76f045b6acd6124415172e27f9eb050f4ea4c Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 2 Jan 2025 15:10:15 +0100 Subject: [PATCH 240/375] remove useless PathMap in c# projects --- Dalamud.Injector/Dalamud.Injector.csproj | 4 ---- Dalamud/Dalamud.csproj | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 1ff29ea66..9cc063916 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -45,10 +45,6 @@ DEBUG;TRACE - - $(MSBuildProjectDirectory)\ - $(AppOutputBase)=C:\goatsoft\companysecrets\injector\ - IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702 diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index d869f76ba..89db02fa0 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -47,10 +47,6 @@ DEBUG;TRACE - - $(MSBuildProjectDirectory)\ - $(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\ - IDE0002;IDE0003;IDE1006;IDE0044;CA1822;CS1591;CS1701;CS1702 From d22ff8fad8d00acb1b1e106053cda1e4fad52a17 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 2 Jan 2025 17:21:57 +0100 Subject: [PATCH 241/375] don't save windows that haven't changed --- .../Interface/Windowing/Persistence/PresetModel.cs | 8 ++++++++ .../Windowing/Persistence/WindowSystemPersistence.cs | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Windowing/Persistence/PresetModel.cs b/Dalamud/Interface/Windowing/Persistence/PresetModel.cs index db91bad8a..f7910e0b2 100644 --- a/Dalamud/Interface/Windowing/Persistence/PresetModel.cs +++ b/Dalamud/Interface/Windowing/Persistence/PresetModel.cs @@ -49,5 +49,13 @@ internal class PresetModel /// [JsonProperty("a")] public float? Alpha { get; set; } + + /// + /// Gets a value indicating whether this preset is in the default state. + /// + public bool IsDefault => + !this.IsPinned && + !this.IsClickThrough && + !this.Alpha.HasValue; } } diff --git a/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs b/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs index 8b884b0e5..a64928003 100644 --- a/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs +++ b/Dalamud/Interface/Windowing/Persistence/WindowSystemPersistence.cs @@ -41,7 +41,17 @@ internal class WindowSystemPersistence : IServiceType /// The preset window instance. public void SaveWindow(uint id, PresetModel.PresetWindow window) { - this.ActivePreset.Windows[id] = window; + // If the window is in the default state, don't save it to avoid saving every possible window + // if the user has not customized anything. + if (window.IsDefault) + { + this.ActivePreset.Windows.Remove(id); + } + else + { + this.ActivePreset.Windows[id] = window; + } + this.config.QueueSave(); } } From e0eca2ac70e8c28679de62849b9fac76062155e7 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 2 Jan 2025 17:29:49 +0100 Subject: [PATCH 242/375] don't auto-enable imgui asserts for now, until we have better tracing --- Dalamud/Plugin/Internal/PluginManager.cs | 89 ++++++++++++------------ 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index b70b74b03..7a05a7549 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -53,7 +53,7 @@ internal class PluginManager : IInternalDisposableService private readonly object pluginListLock = new(); private readonly DirectoryInfo pluginDirectory; private readonly BannedPlugin[]? bannedPlugins; - + private readonly List installedPluginsList = new(); private readonly List availablePluginsList = new(); private readonly List updatablePluginsList = new(); @@ -207,7 +207,7 @@ internal class PluginManager : IInternalDisposableService } } } - + /// /// Gets a copy of the list of all plugins with an available update. /// @@ -261,7 +261,7 @@ internal class PluginManager : IInternalDisposableService /// Gets or sets a value indicating whether banned plugins will be loaded. /// public bool LoadBannedPlugins { get; set; } - + /// /// Gets a tracker for plugins that are loading at startup, used to display information to the user. /// @@ -479,7 +479,7 @@ internal class PluginManager : IInternalDisposableService Log.Error("No DLL found for plugin at {Path}", versionDir.FullName); continue; } - + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); if (!manifestFile.Exists) { @@ -506,7 +506,7 @@ internal class PluginManager : IInternalDisposableService } this.configuration.QueueSave(); - + if (versionsDefs.Count == 0) { Log.Verbose("No versions found for plugin: {Name}", pluginDir.Name); @@ -552,7 +552,7 @@ internal class PluginManager : IInternalDisposableService Log.Error("DLL at {DllPath} has no manifest, this is no longer valid", dllFile.FullName); continue; } - + var manifest = LocalPluginManifest.Load(manifestFile); if (manifest == null) { @@ -761,7 +761,7 @@ internal class PluginManager : IInternalDisposableService .SelectMany(repo => repo.PluginMaster) .Where(this.IsManifestEligible) .Where(IsManifestVisible)); - + if (notify) { this.NotifyAvailablePluginsChanged(); @@ -783,7 +783,7 @@ internal class PluginManager : IInternalDisposableService { if (!setting.IsEnabled) continue; - + Log.Verbose("Scanning dev plugins at {Path}", setting.Path); if (File.Exists(setting.Path)) @@ -810,7 +810,7 @@ internal class PluginManager : IInternalDisposableService Log.Error("DLL at {DllPath} has no manifest, this is no longer valid", dllFile.FullName); continue; } - + var manifest = LocalPluginManifest.Load(manifestFile); if (manifest == null) { @@ -854,7 +854,7 @@ internal class PluginManager : IInternalDisposableService var stream = await this.DownloadPluginAsync(repoManifest, useTesting); return await this.InstallPluginInternalAsync(repoManifest, useTesting, reason, stream, inheritedWorkingPluginId); } - + /// /// Remove a plugin. /// @@ -1048,7 +1048,7 @@ internal class PluginManager : IInternalDisposableService Status = PluginUpdateStatus.StatusKind.Success, HasChangelog = !metadata.UpdateManifest.Changelog.IsNullOrWhitespace(), }; - + // Check if this plugin is already up to date (=> AvailablePluginUpdate was stale) lock (this.installedPluginsList) { @@ -1075,7 +1075,7 @@ internal class PluginManager : IInternalDisposableService updateStatus.Status = PluginUpdateStatus.StatusKind.FailedDownload; return updateStatus; } - + // Unload if loaded if (plugin.State is PluginState.Loaded or PluginState.LoadError or PluginState.DependencyResolutionFailed) { @@ -1305,7 +1305,7 @@ internal class PluginManager : IInternalDisposableService { if (serviceType == typeof(PluginManager)) continue; - + // Scoped plugin services lifetime is tied to their scopes. They go away when LocalPlugin goes away. // Nonetheless, their direct dependencies must be considered. if (serviceType.GetServiceKind() == ServiceManager.ServiceKind.ScopedService) @@ -1313,19 +1313,19 @@ internal class PluginManager : IInternalDisposableService var typeAsServiceT = ServiceHelpers.GetAsService(serviceType); var dependencies = ServiceHelpers.GetDependencies(typeAsServiceT, false); ServiceManager.Log.Verbose("Found dependencies of scoped plugin service {Type} ({Cnt})", serviceType.FullName!, dependencies!.Count); - + foreach (var scopedDep in dependencies) { if (scopedDep == typeof(PluginManager)) throw new Exception("Scoped plugin services cannot depend on PluginManager."); - + ServiceManager.Log.Verbose("PluginManager MUST depend on {Type} via {BaseType}", scopedDep.FullName!, serviceType.FullName!); yield return scopedDep; } continue; } - + var pluginInterfaceAttribute = serviceType.GetCustomAttribute(true); if (pluginInterfaceAttribute == null) continue; @@ -1336,12 +1336,12 @@ internal class PluginManager : IInternalDisposableService } /// - /// Check if there are any inconsistencies with our plugins, their IDs, and our profiles. + /// Check if there are any inconsistencies with our plugins, their IDs, and our profiles. /// private void ParanoiaValidatePluginsAndProfiles() { var seenIds = new List(); - + foreach (var installedPlugin in this.InstalledPlugins) { if (installedPlugin.EffectiveWorkingPluginId == Guid.Empty) @@ -1352,13 +1352,13 @@ internal class PluginManager : IInternalDisposableService throw new Exception( $"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has a duplicate WorkingPluginId '{installedPlugin.EffectiveWorkingPluginId}'"); } - + seenIds.Add(installedPlugin.EffectiveWorkingPluginId); } - + this.profileManager.ParanoiaValidateProfiles(); } - + private async Task DownloadPluginAsync(RemotePluginManifest repoManifest, bool useTesting) { var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall; @@ -1391,7 +1391,7 @@ internal class PluginManager : IInternalDisposableService { var version = useTesting ? repoManifest.TestingAssemblyVersion : repoManifest.AssemblyVersion; Log.Debug($"Installing plugin {repoManifest.Name} (testing={useTesting}, version={version}, reason={reason})"); - + // If this plugin is in the default profile for whatever reason, delete the state // If it was in multiple profiles and is still, the user uninstalled it and chose to keep it in there, // or the user removed the plugin manually in which case we don't care @@ -1425,7 +1425,7 @@ internal class PluginManager : IInternalDisposableService // If we are doing anything other than a fresh install, not having a workingPluginId is an error that must be fixed Debug.Assert(inheritedWorkingPluginId != null, "inheritedWorkingPluginId != null"); } - + // Ensure that we have a testing opt-in for this plugin if we are installing a testing version if (useTesting && this.configuration.PluginTestingOptIns!.All(x => x.InternalName != repoManifest.InternalName)) { @@ -1534,7 +1534,7 @@ internal class PluginManager : IInternalDisposableService this.NotifyinstalledPluginsListChanged(); return plugin; } - + /// /// Load a plugin. /// @@ -1562,16 +1562,17 @@ internal class PluginManager : IInternalDisposableService { Log.Information($"Loading dev plugin {name}"); plugin = new LocalDevPlugin(dllFile, manifest); - + // This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet - this.configuration.ImGuiAssertsEnabledAtStartup ??= true; + // TODO(goat): Re-enable this when we have better tracing for what was rendering when + // this.configuration.ImGuiAssertsEnabledAtStartup ??= true; } else { Log.Information($"Loading plugin {name}"); plugin = new LocalPlugin(dllFile, manifest); } - + // Perform a migration from InternalName to GUIDs. The plugin should definitely have a GUID here. // This will also happen if you are installing a plugin with the installer, and that's intended! // It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will @@ -1579,7 +1580,7 @@ internal class PluginManager : IInternalDisposableService if (plugin.EffectiveWorkingPluginId == Guid.Empty) throw new Exception("Plugin should have a WorkingPluginId at this point"); this.profileManager.MigrateProfilesToGuidsForPlugin(plugin.Manifest.InternalName, plugin.EffectiveWorkingPluginId); - + var wantedByAnyProfile = false; // Now, if this is a devPlugin, figure out if we want to load it @@ -1595,11 +1596,11 @@ internal class PluginManager : IInternalDisposableService // We don't know about this plugin, so we don't want to do anything here. // The code below will take care of it and add it with the default value. Log.Verbose("DevPlugin {Name} not wanted in default plugin", plugin.Manifest.InternalName); - + // Check if any profile wants this plugin. We need to do this here, since we want to allow loading a dev plugin if a non-default profile wants it active. // Note that this will not add the plugin to the default profile. That's done below in any other case. wantedByAnyProfile = await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); - + // If it is wanted by any other profile, we do want to load it. if (wantedByAnyProfile) loadPlugin = true; @@ -1646,12 +1647,12 @@ internal class PluginManager : IInternalDisposableService #pragma warning disable CS0618 var defaultState = manifest?.Disabled != true && loadPlugin; #pragma warning restore CS0618 - + // Plugins that aren't in any profile will be added to the default profile with this call. // We are skipping a double-lookup for dev plugins that are wanted by non-default profiles, as noted above. wantedByAnyProfile = wantedByAnyProfile || await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, defaultState); Log.Information("{Name} defaultState: {State} wantedByAnyProfile: {WantedByAny} loadPlugin: {LoadPlugin}", plugin.Manifest.InternalName, defaultState, wantedByAnyProfile, loadPlugin); - + if (loadPlugin) { try @@ -1735,11 +1736,11 @@ internal class PluginManager : IInternalDisposableService private void DetectAvailablePluginUpdates() { Log.Debug("Starting plugin update check..."); - + lock (this.pluginListLock) { this.updatablePluginsList.Clear(); - + foreach (var plugin in this.installedPluginsList) { var installedVersion = plugin.IsTesting @@ -1778,12 +1779,12 @@ internal class PluginManager : IInternalDisposableService } } } - + Log.Debug("Update check found {updateCount} available updates.", this.updatablePluginsList.Count); } private void NotifyAvailablePluginsChanged() - { + { this.DetectAvailablePluginUpdates(); this.OnAvailablePluginsChanged?.InvokeSafely(); @@ -1831,7 +1832,7 @@ internal class PluginManager : IInternalDisposableService using (Timings.Start("PM Load Sync Plugins")) { var loadAllPlugins = Task.Run(this.LoadAllPlugins); - + // We wait for all blocking services and tasks to finish before kicking off the main thread in any mode. // This means that we don't want to block here if this stupid thing isn't enabled. if (this.configuration.IsResumeGameAfterPluginLoad) @@ -1850,12 +1851,12 @@ internal class PluginManager : IInternalDisposableService Log.Error(ex, "Plugin load failed"); } } - + /// /// Class representing progress of an update operation. /// public record PluginUpdateProgress(int PluginsProcessed, int TotalPlugins, IPluginManifest CurrentPluginManifest); - + /// /// Simple class that tracks the internal names and public names of plugins that we are planning to load at startup, /// and are still actively loading. @@ -1865,12 +1866,12 @@ internal class PluginManager : IInternalDisposableService private readonly Dictionary internalToPublic = new(); private readonly ConcurrentBag allInternalNames = new(); private readonly ConcurrentBag finishedInternalNames = new(); - + /// /// Gets a value indicating the total load progress. /// public float Progress => (float)this.finishedInternalNames.Count / this.allInternalNames.Count; - + /// /// Calculate a set of internal names that are still pending. /// @@ -1881,7 +1882,7 @@ internal class PluginManager : IInternalDisposableService pending.ExceptWith(this.finishedInternalNames); return pending; } - + /// /// Track a new plugin. /// @@ -1892,7 +1893,7 @@ internal class PluginManager : IInternalDisposableService this.internalToPublic[internalName] = publicName; this.allInternalNames.Add(internalName); } - + /// /// Mark a plugin as finished loading. /// @@ -1901,7 +1902,7 @@ internal class PluginManager : IInternalDisposableService { this.finishedInternalNames.Add(internalName); } - + /// /// Get the public name for a given internal name. /// From b74f4fba01dffd7f858b91e067733c55ad42c5e6 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:07:57 +0100 Subject: [PATCH 243/375] Update ClientStructs (#2151) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index cc98a564d..7ba7ab4fd 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit cc98a564d0787813d4be082bf75f5bb98e0ed12f +Subproject commit 7ba7ab4fdd90b05631807ebdaeec9613f33397bd From 6d0c9d4f4a40b297506cfbc88361e2126d36c55e Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 3 Jan 2025 17:00:03 +0100 Subject: [PATCH 244/375] build: 11.0.5.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 89db02fa0..2297355a1 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.4.0 + 11.0.5.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From e999a8f678ead9cdd48d7df6e63f24f7ef6e7f16 Mon Sep 17 00:00:00 2001 From: wolfcomp <4028289+wolfcomp@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:57:34 +0100 Subject: [PATCH 245/375] Add timezone offset to system time in crash log (#2142) --- DalamudCrashHandler/DalamudCrashHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 4b1d4a6e5..62ccdd20a 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -941,7 +941,7 @@ int main() { log << std::format(L"Dump at: {}", dumpPath.wstring()) << std::endl; else log << std::format(L"Dump error: {}", dumpError) << std::endl; - log << L"System Time: " << std::chrono::system_clock::now() << std::endl; + log << std::format(L"System Time: {0:%F} {0:%T} {0:%Ez}", std::chrono::system_clock::now()) << std::endl; log << L"\n" << stackTrace << std::endl; if (pProgressDialog) From a690ccbeef68189282e036743c4307f877a89396 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 3 Jan 2025 18:32:34 +0100 Subject: [PATCH 246/375] build: 11.0.5.1 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 2297355a1..c4f99ec34 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.5.0 + 11.0.5.1 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 27b6dfcbea30c94ff65361883f61cf9bd3de2b9f Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 9 Jan 2025 21:58:23 +0100 Subject: [PATCH 247/375] don't allow window additions for multi-monitor windows --- Dalamud/Interface/Windowing/Window.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index 6219de852..79bb63685 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -378,6 +378,16 @@ public abstract class Window { ImGuiNativeAdditions.igCustom_WindowSetInheritNoInputs(this.internalIsClickthrough); + // Not supported yet on non-main viewports + if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) && + ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID) + { + this.internalAlpha = null; + this.internalIsPinned = false; + this.internalIsClickthrough = false; + this.presetDirty = true; + } + // Draw the actual window contents try { From da8be031248940f8bc8ee6bb178bd4c491d60a65 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 9 Jan 2025 22:00:53 +0100 Subject: [PATCH 248/375] don't log imgui asserts unless we've shown them this session --- Dalamud/Interface/Internal/Asserts/AssertHandler.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs index c1dc12206..376aaed5b 100644 --- a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -24,6 +24,8 @@ internal class AssertHandler : IDisposable // Store callback to avoid it from being GC'd private readonly AssertCallbackDelegate callback; + private bool everShownAssertThisSession = false; + /// /// Initializes a new instance of the class. /// @@ -76,6 +78,10 @@ internal class AssertHandler : IDisposable if (this.ignoredAsserts.Contains(key)) return; + // Don't log unless we've ever shown an assert this session + if (!this.ShowAsserts && !this.everShownAssertThisSession) + return; + Lazy stackTrace = new(() => new StackTrace(3).ToString()); if (!this.EnableVerboseLogging) @@ -110,6 +116,8 @@ internal class AssertHandler : IDisposable if (!this.ShowAsserts) return; + this.everShownAssertThisSession = true; + string? GetRepoUrl() { // TODO: implot, imguizmo? From a656fefb2bf349a4073717c8c2219abded095b2b Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 9 Jan 2025 13:01:46 -0800 Subject: [PATCH 249/375] feat: Allow /xldev to disable Safe Mode (#2166) - Adds new menu item to /xldev to disable Safe Mode, allowing users to load plugins again. - Safe mode cannot be re-enabled once disabled. - Add new ModuleLog.Create for eventual ILogger magic - Make safe mode writable - Remove redundant check in CheckPolicy --- .../Interface/Internal/DalamudInterface.cs | 5 ++++ Dalamud/Logging/Internal/ModuleLog.cs | 25 ++++++++++++++++++- Dalamud/Plugin/Internal/PluginManager.cs | 6 ++--- Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 15 +++++------ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 0381164c9..f3f2564af 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -1012,6 +1012,11 @@ internal class DalamudInterface : IInternalDisposableService pluginManager.LoadBannedPlugins = !pluginManager.LoadBannedPlugins; } + if (pluginManager.SafeMode && ImGui.MenuItem("Disable Safe Mode")) + { + pluginManager.SafeMode = false; + } + ImGui.Separator(); ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count(), false); diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs index bcbb6e2b1..bb5af4ffe 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -1,5 +1,6 @@ using Serilog; using Serilog.Core; +using Serilog.Core.Enrichers; using Serilog.Events; namespace Dalamud.Logging.Internal; @@ -11,7 +12,7 @@ public class ModuleLog { private readonly string moduleName; private readonly ILogger moduleLogger; - + // FIXME (v9): Deprecate this class in favor of using contextualized ILoggers with proper formatting. // We can keep this class around as a Serilog helper, but ModuleLog should no longer be a returned // type, instead returning a (prepared) ILogger appropriately. @@ -27,6 +28,28 @@ public class ModuleLog this.moduleLogger = Log.ForContext("Dalamud.ModuleName", this.moduleName); } + /// + /// Initializes a new instance of the class. + /// This class will properly attach SourceContext and other attributes per Serilog standards. + /// + /// The type of the class this logger is for. + public ModuleLog(Type type) + { + this.moduleName = type.Name; + this.moduleLogger = Log.ForContext( + [ + new PropertyEnricher(Constants.SourceContextPropertyName, type.FullName), + new PropertyEnricher("Dalamud.ModuleName", this.moduleName) + ]); + } + + /// + /// Helper method to create a new instance based on a type. + /// + /// The class to create this ModuleLog for. + /// Returns a ModuleLog with name set. + internal static ModuleLog Create() => new(typeof(T)); + /// /// Log a templated verbose message to the in-game debug log. /// diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 7a05a7549..5cab004ed 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -48,7 +48,7 @@ internal class PluginManager : IInternalDisposableService /// public const int PluginWaitBeforeFreeDefault = 1000; // upped from 500ms, seems more stable - private static readonly ModuleLog Log = new("PLUGINM"); + private static readonly ModuleLog Log = ModuleLog.Create(); private readonly object pluginListLock = new(); private readonly DirectoryInfo pluginDirectory; @@ -243,9 +243,9 @@ internal class PluginManager : IInternalDisposableService public bool ReposReady { get; private set; } /// - /// Gets a value indicating whether the plugin manager started in safe mode. + /// Gets or sets a value indicating whether the plugin manager started in safe mode. /// - public bool SafeMode { get; init; } + public bool SafeMode { get; set; } /// /// Gets the object used when initializing plugins. diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 43bba0a5b..a86a39905 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -30,7 +30,7 @@ internal class LocalPlugin : IAsyncDisposable #pragma warning disable SA1401 protected LocalPluginManifest manifest; #pragma warning restore SA1401 - + private static readonly ModuleLog Log = new("LOCALPLUGIN"); private readonly FileInfo manifestFile; @@ -314,7 +314,7 @@ internal class LocalPlugin : IAsyncDisposable this.State = PluginState.Loading; Log.Information($"Loading {this.DllFile.Name}"); - + this.EnsureLoader(); if (this.DllFile.DirectoryName != null && @@ -413,7 +413,7 @@ internal class LocalPlugin : IAsyncDisposable if (ex is not InvalidPluginOperationException) this.State = PluginState.LoadError; - // If a precondition fails, don't record it as an error, as it isn't really. + // If a precondition fails, don't record it as an error, as it isn't really. if (ex is PluginPreconditionFailedException) Log.Warning(ex.Message); else @@ -510,9 +510,6 @@ internal class LocalPlugin : IAsyncDisposable var startInfo = Service.Get().StartInfo; var manager = Service.Get(); - if (startInfo.NoLoadPlugins) - return false; - if (startInfo.NoLoadThirdPartyPlugins && this.manifest.IsThirdParty) return false; @@ -556,7 +553,7 @@ internal class LocalPlugin : IAsyncDisposable /// /// Why it should be saved. protected void SaveManifest(string reason) => this.manifest.Save(this.manifestFile, reason); - + /// /// Called before a plugin is reloaded. /// @@ -595,7 +592,7 @@ internal class LocalPlugin : IAsyncDisposable // but plugins may load other versions of assemblies that Dalamud depends on. config.SharedAssemblies.Add((typeof(EntryPoint).Assembly.GetName(), false)); config.SharedAssemblies.Add((typeof(Common.DalamudStartInfo).Assembly.GetName(), false)); - + // Pin Lumina since we expose it as an API surface. Before anyone removes this again, please see #1598. // Changes to Lumina should be upstreamed if feasible, and if there is a desire to re-add unpinned Lumina we // will need to put this behind some kind of feature flag somewhere. @@ -607,7 +604,7 @@ internal class LocalPlugin : IAsyncDisposable { if (this.loader != null) return; - + try { this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig); From bed591d890f5be69b5178d5f8d12df75bfe636fe Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 9 Jan 2025 13:11:41 -0800 Subject: [PATCH 250/375] fix: Forcefully load stacktrace if assert UI is shown (#2164) - Add DiagnosticUtil to automatically filter out the "boring" stack entries. Co-authored-by: goat <16760685+goaaats@users.noreply.github.com> --- .../Internal/Asserts/AssertHandler.cs | 5 ++- Dalamud/Utility/DiagnosticUtil.cs | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Utility/DiagnosticUtil.cs diff --git a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs index 376aaed5b..56050cdfb 100644 --- a/Dalamud/Interface/Internal/Asserts/AssertHandler.cs +++ b/Dalamud/Interface/Internal/Asserts/AssertHandler.cs @@ -82,7 +82,7 @@ internal class AssertHandler : IDisposable if (!this.ShowAsserts && !this.everShownAssertThisSession) return; - Lazy stackTrace = new(() => new StackTrace(3).ToString()); + Lazy stackTrace = new(() => DiagnosticUtil.GetUsefulTrace(new StackTrace()).ToString()); if (!this.EnableVerboseLogging) { @@ -133,6 +133,9 @@ internal class AssertHandler : IDisposable return $"https://github.com/{userName}/{repoName}/blob/{branch}/{fileName}#L{line}"; } + // grab the stack trace now that we've decided to show UI. + _ = stackTrace.Value; + var gitHubUrl = GetRepoUrl(); var showOnGitHubButton = new TaskDialogButton { diff --git a/Dalamud/Utility/DiagnosticUtil.cs b/Dalamud/Utility/DiagnosticUtil.cs new file mode 100644 index 000000000..9c9718c4e --- /dev/null +++ b/Dalamud/Utility/DiagnosticUtil.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using System.Linq; + +namespace Dalamud.Utility; + +/// +/// A set of utilities for diagnostics. +/// +public static class DiagnosticUtil +{ + private static readonly string[] IgnoredNamespaces = [ + nameof(System), + nameof(ImGuiNET.ImGuiNative) + ]; + + /// + /// Gets a stack trace that filters out irrelevant frames. + /// + /// The source stacktrace to filter. + /// Returns a stack trace with "extra" frames removed. + public static StackTrace GetUsefulTrace(StackTrace source) + { + var frames = source.GetFrames().SkipWhile( + f => + { + var frameNs = f.GetMethod()?.DeclaringType?.Namespace; + return frameNs == null || IgnoredNamespaces.Any(i => frameNs.StartsWith(i, true, null)); + }); + + return new StackTrace(frames); + } +} From 0b9a2fd9c8752a35ebe1a6d091f8e338c1a026df Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:12:21 +0100 Subject: [PATCH 251/375] Update ClientStructs (#2163) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 7ba7ab4fd..9543f553e 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 7ba7ab4fdd90b05631807ebdaeec9613f33397bd +Subproject commit 9543f553e609f1682451c62c0ffcf5303359cf24 From f2c132c7d8723c93b53008afa0866076136fb44d Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Thu, 9 Jan 2025 22:15:14 +0100 Subject: [PATCH 252/375] Allow setting a DisplayOrder on commands (#2162) * Allow setting a DisplayOrder on commands * Linq on demand * Sort commands in /help alphabetically * note default in xmldoc --------- Co-authored-by: goat <16760685+goaaats@users.noreply.github.com> --- Dalamud/Game/Command/CommandInfo.cs | 10 +++++++++- Dalamud/Interface/Internal/DalamudCommands.cs | 2 +- .../Windows/PluginInstaller/PluginInstallerWindow.cs | 7 ++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Dalamud/Game/Command/CommandInfo.cs b/Dalamud/Game/Command/CommandInfo.cs index 8aed817d0..16462a831 100644 --- a/Dalamud/Game/Command/CommandInfo.cs +++ b/Dalamud/Game/Command/CommandInfo.cs @@ -11,7 +11,7 @@ public interface IReadOnlyCommandInfo /// The command itself. /// The arguments supplied to the command, ready for parsing. public delegate void HandlerDelegate(string command, string arguments); - + /// /// Gets a which will be called when the command is dispatched. /// @@ -26,6 +26,11 @@ public interface IReadOnlyCommandInfo /// Gets a value indicating whether if this command should be shown in the help output. /// bool ShowInHelp { get; } + + /// + /// Gets the display order of this command. Defaults to alphabetical ordering. + /// + int DisplayOrder { get; } } /// @@ -51,4 +56,7 @@ public sealed class CommandInfo : IReadOnlyCommandInfo /// public bool ShowInHelp { get; set; } = true; + + /// + public int DisplayOrder { get; set; } = -1; } diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index 00997c1d5..636f71bfa 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -178,7 +178,7 @@ internal class DalamudCommands : IServiceType if (arguments.IsNullOrWhitespace()) { chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:")); - foreach (var cmd in commandManager.Commands) + foreach (var cmd in commandManager.Commands.OrderBy(cInfo => cInfo.Key)) { if (!cmd.Value.ShowInHelp) continue; diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 4e60d4be8..3abb1bb39 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -2762,13 +2762,14 @@ internal class PluginInstallerWindow : Window, IDisposable var commands = commandManager.Commands .Where(cInfo => cInfo.Value is { ShowInHelp: true } && - commandManager.GetHandlerAssemblyName(cInfo.Key, cInfo.Value) == plugin.Manifest.InternalName) - .ToArray(); + commandManager.GetHandlerAssemblyName(cInfo.Key, cInfo.Value) == plugin.Manifest.InternalName); if (commands.Any()) { ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f)); - foreach (var command in commands) + foreach (var command in commands + .OrderBy(cInfo => cInfo.Value.DisplayOrder) + .ThenBy(cInfo => cInfo.Key)) { ImGuiHelpers.SafeTextWrapped($"{command.Key} → {command.Value.HelpMessage}"); } From d932f2f06e7ac4585bc300c7d31abcb60b58302b Mon Sep 17 00:00:00 2001 From: srkizer Date: Sat, 11 Jan 2025 01:52:54 +0900 Subject: [PATCH 253/375] fix: apply scale to TSM entry (#2169) --- .../Internal/Windows/TitleScreenMenuWindow.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 979f68bf8..ce8c192a4 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -11,7 +11,6 @@ using Dalamud.Game.ClientState; using Dalamud.Game.Gui; using Dalamud.Game.Text; using Dalamud.Interface.Animation.EasingFunctions; -using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Textures.TextureWraps; @@ -398,9 +397,14 @@ internal class TitleScreenMenuWindow : Window, IDisposable // Drop shadow ImGui.SetCursorPos(cursor); - var renderStyle = default(SeStringDrawParams); - renderStyle.FontSize = TargetFontSizePx; - ImGuiHelpers.CompileSeStringWrapped($"{entry.Name}", renderStyle); + ImGuiHelpers.SeStringWrapped( + ReadOnlySeString.FromText(entry.Name), + new() + { + FontSize = TargetFontSizePx * ImGui.GetIO().FontGlobalScale, + Edge = true, + Shadow = true, + }); if (overrideAlpha) { From d7279f5f216a4f4f362d01027dc4b624b126dab1 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 10 Jan 2025 19:35:54 +0100 Subject: [PATCH 254/375] don't build cimgui components if we're building for docs --- build/DalamudBuild.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 6340c36fa..c616dd03e 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -71,6 +71,10 @@ public class DalamudBuild : NukeBuild Target CompileCImGui => _ => _ .Executes(() => { + // Not necessary, and does not build on Linux + if (IsDocsBuild) + return; + MSBuildTasks.MSBuild(s => s .SetTargetPath(CImGuiProjectFile) .SetConfiguration(Configuration) @@ -80,6 +84,10 @@ public class DalamudBuild : NukeBuild Target CompileCImPlot => _ => _ .Executes(() => { + // Not necessary, and does not build on Linux + if (IsDocsBuild) + return; + MSBuildTasks.MSBuild(s => s .SetTargetPath(CImPlotProjectFile) .SetConfiguration(Configuration) @@ -89,6 +97,10 @@ public class DalamudBuild : NukeBuild Target CompileCImGuizmo => _ => _ .Executes(() => { + // Not necessary, and does not build on Linux + if (IsDocsBuild) + return; + MSBuildTasks.MSBuild(s => s .SetTargetPath(CImGuizmoProjectFile) .SetConfiguration(Configuration) From d779408fdcbd99b492814cb7fb5d33adcc97fae2 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 10 Jan 2025 19:57:08 +0100 Subject: [PATCH 255/375] don't set IsDocsBuild flag on project --- build/DalamudBuild.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index c616dd03e..67a812916 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -126,12 +126,12 @@ public class DalamudBuild : NukeBuild // We need to emit compiler generated files for the docs build, since docfx can't run generators directly // TODO: This fails every build after this because of redefinitions... - if (IsDocsBuild) - { - Log.Warning("Building for documentation, emitting compiler generated files. This can cause issues on Windows due to path-length limitations"); - s = s - .SetProperty("IsDocsBuild", "true"); - } + // if (IsDocsBuild) + // { + // Log.Warning("Building for documentation, emitting compiler generated files. This can cause issues on Windows due to path-length limitations"); + // s = s + // .SetProperty("IsDocsBuild", "true"); + // } return s; }); From 56acb7dead5bd4a41d25a4a34c84f6dc8cfa66f5 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 20 Jan 2025 22:03:51 +0100 Subject: [PATCH 256/375] build: 11.0.5.2 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index c4f99ec34..5ca193c08 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.5.1 + 11.0.5.2 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From a72dc92411ee8a8f9bdc4d75058d9131b445cf59 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 20 Jan 2025 22:11:17 +0100 Subject: [PATCH 257/375] make window config dirty when disabling clickthrough with title bar button --- Dalamud/Interface/Windowing/Window.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index 79bb63685..eaa3561f5 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -498,6 +498,7 @@ public abstract class Window Click = _ => { this.internalIsClickthrough = false; + this.presetDirty = false; ImGui.OpenPopup(additionsPopupName); }, Priority = int.MinValue, From 121b3d9a003992aa47dcd0c96e717f11b3ffa052 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Mon, 20 Jan 2025 22:26:52 +0100 Subject: [PATCH 258/375] Update ClientStructs (#2168) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 9543f553e..d5c13c5d3 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 9543f553e609f1682451c62c0ffcf5303359cf24 +Subproject commit d5c13c5d35fc3721626449c10eda605183351044 From 9d2264eec3aa8f61848b411f1951f7982a10dd12 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:24:51 +0100 Subject: [PATCH 259/375] Update ClientStructs (#2173) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index d5c13c5d3..6a34c7073 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit d5c13c5d35fc3721626449c10eda605183351044 +Subproject commit 6a34c7073da9170ca522deca9fbb4903ce8bc3f5 From 724d14248d13ea000c45748b77f8ab0f318a092b Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:22:56 +0100 Subject: [PATCH 260/375] Update ClientStructs (#2174) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 6a34c7073..8f2c18af4 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 6a34c7073da9170ca522deca9fbb4903ce8bc3f5 +Subproject commit 8f2c18af4dcd225fecee2e7713a3dead48ca5011 From 6434972b7de0631e659294bbb2d19ade7eb86f86 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Tue, 21 Jan 2025 11:55:26 -0800 Subject: [PATCH 261/375] deps: Bump Lumina to 5.6.1 (#2171) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6ee6f4b11..354dedd60 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 5.6.0 + 5.6.1 7.1.3 13.0.3 From af1ddf5bfb08759d88850b070893076b9872eaaf Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 21 Jan 2025 20:59:02 +0100 Subject: [PATCH 262/375] build: 11.0.5.3 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 5ca193c08..d6de19de9 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.5.2 + 11.0.5.3 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 5dd097d72b65b6e471d5a9eb1176f31ad9ec0edc Mon Sep 17 00:00:00 2001 From: Lyna <19539165+Blooym@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:55:06 +0000 Subject: [PATCH 263/375] Pinning window disables close on escape key down (#2178) * Pinning window disables close on escape key down * Update tooltip for window pinning --- Dalamud/Interface/Windowing/Window.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index eaa3561f5..b3a505c1d 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Runtime.InteropServices; using CheapLoc; + using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; @@ -15,7 +16,9 @@ using Dalamud.Interface.Windowing.Persistence; using Dalamud.Logging.Internal; using FFXIVClientStructs.FFXIV.Client.UI; + using ImGuiNET; + using PInvoke; namespace Dalamud.Interface.Windowing; @@ -429,7 +432,7 @@ public abstract class Window } ImGuiComponents.HelpMarker( - Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them.")); + Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them, nor will they close when escape is pressed.")); } if (this.internalIsClickthrough) @@ -521,7 +524,7 @@ public abstract class Window this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); - if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement)) + if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement) && !this.internalIsPinned) { var escapeDown = Service.Get()[VirtualKey.ESCAPE]; if (escapeDown && this.IsFocused && !wasEscPressedLastFrame && this.RespectCloseHotkey) @@ -701,7 +704,7 @@ public abstract class Window drawList.AddCircleFilled(GetCenter(bb) + new Vector2(0.0f, -0.5f), (fontSize * 0.5f) + 1.0f, bgCol); var offset = button.IconOffset * ImGuiHelpers.GlobalScale; - drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.X + offset.X, bb.Y + offset.Y), textCol, button.Icon.ToIconString()); + drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.X + offset.X, bb.Y + offset.Y), textCol, button.Icon.ToIconString()); if (hovered) button.ShowTooltip?.Invoke(); From f8a5fbac39b819420598174690fb74a60e904351 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:46:21 +0100 Subject: [PATCH 264/375] Update ClientStructs (#2175) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 8f2c18af4..4d78d23b9 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 8f2c18af4dcd225fecee2e7713a3dead48ca5011 +Subproject commit 4d78d23b9a3a84222795e7aaa18785dca12ae5de From 0ada421460ceb7767a64d48e7419dbe1e05c0a30 Mon Sep 17 00:00:00 2001 From: Jackson <9527380+Jaksuhn@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:58:04 +0100 Subject: [PATCH 265/375] Expose more of `LocalPlugin` to `ExposedPlugin` (#2177) --- Dalamud/Plugin/InstalledPluginState.cs | 68 +++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/Dalamud/Plugin/InstalledPluginState.cs b/Dalamud/Plugin/InstalledPluginState.cs index 6700a1f8d..64c8e40a5 100644 --- a/Dalamud/Plugin/InstalledPluginState.cs +++ b/Dalamud/Plugin/InstalledPluginState.cs @@ -1,4 +1,5 @@ -using Dalamud.Plugin.Internal.Types; +using Dalamud.Plugin.Internal.Types; +using Dalamud.Plugin.Internal.Types.Manifest; namespace Dalamud.Plugin; @@ -22,6 +23,47 @@ public interface IExposedPlugin /// bool IsLoaded { get; } + /// + /// Gets a value indicating whether this plugin's API level is out of date. + /// + bool IsOutdated { get; } + + /// + /// Gets a value indicating whether the plugin is for testing use only. + /// + bool IsTesting { get; } + + /// + /// Gets a value indicating whether or not this plugin is orphaned(belongs to a repo) or not. + /// + bool IsOrphaned { get; } + + /// + /// Gets a value indicating whether or not this plugin is serviced(repo still exists, but plugin no longer does). + /// + bool IsDecommissioned { get; } + + /// + /// Gets a value indicating whether this plugin has been banned. + /// + bool IsBanned { get; } + + /// + /// Gets a value indicating whether this plugin is dev plugin. + /// + bool IsDev { get; } + + /// + /// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party + /// repo. + /// + bool IsThirdParty { get; } + + /// + /// Gets the plugin manifest. + /// + ILocalPluginManifest Manifest { get; } + /// /// Gets the version of the plugin. /// @@ -74,6 +116,30 @@ internal sealed class ExposedPlugin(LocalPlugin plugin) : IExposedPlugin /// public bool HasConfigUi => plugin.DalamudInterface?.LocalUiBuilder.HasConfigUi ?? false; + /// + public bool IsOutdated => plugin.IsOutdated; + + /// + public bool IsTesting => plugin.IsTesting; + + /// + public bool IsOrphaned => plugin.IsOrphaned; + + /// + public bool IsDecommissioned => plugin.IsDecommissioned; + + /// + public bool IsBanned => plugin.IsBanned; + + /// + public bool IsDev => plugin.IsDev; + + /// + public bool IsThirdParty => plugin.IsThirdParty; + + /// + public ILocalPluginManifest Manifest => plugin.Manifest; + /// public void OpenMainUi() { From 03562189e4161c52037c968c471f4b3a03cbc659 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sun, 16 Feb 2025 15:34:50 +0100 Subject: [PATCH 266/375] build: 11.0.6.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index d6de19de9..21086f969 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.5.3 + 11.0.6.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From df78a4ae04c6426810ec3d1813980e69fe1f2eed Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 25 Feb 2025 19:04:36 +0100 Subject: [PATCH 267/375] Update ClientStructs (#2179) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 4d78d23b9..4a727b491 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 4d78d23b9a3a84222795e7aaa18785dca12ae5de +Subproject commit 4a727b491721b02a4e7b9ded289f35388758394f From 3bd380c85442a8875e516d0d957682f75a7d4fc7 Mon Sep 17 00:00:00 2001 From: goaaats Date: Wed, 26 Feb 2025 00:06:33 +0100 Subject: [PATCH 268/375] build: 11.0.7.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 21086f969..e30109a99 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.6.0 + 11.0.7.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 3340bfd205e5d5dfb1cc9181514c8ce3a1032472 Mon Sep 17 00:00:00 2001 From: goaaats Date: Wed, 26 Feb 2025 21:09:15 +0100 Subject: [PATCH 269/375] Remove confusing comment about IDisposable behavior from ISharedImmediateTexture, it no longer inherits from IDisposable --- Dalamud/Interface/Textures/ISharedImmediateTexture.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Dalamud/Interface/Textures/ISharedImmediateTexture.cs b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs index 0ceb92171..b6aa4da83 100644 --- a/Dalamud/Interface/Textures/ISharedImmediateTexture.cs +++ b/Dalamud/Interface/Textures/ISharedImmediateTexture.cs @@ -11,7 +11,6 @@ namespace Dalamud.Interface.Textures; /// A texture with a backing instance of that is shared across multiple /// requesters. /// -/// Calling on this interface is a no-op. /// and may stop returning the intended texture at any point. /// Use to lock the texture for use in any thread for any duration. /// From defa49865ccc017f5a914e2edb8bc236ff2971fa Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 27 Feb 2025 21:37:05 +0100 Subject: [PATCH 270/375] Fix warning --- Dalamud/Logging/Internal/ModuleLog.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs index bb5af4ffe..00173b09d 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -43,13 +43,6 @@ public class ModuleLog ]); } - /// - /// Helper method to create a new instance based on a type. - /// - /// The class to create this ModuleLog for. - /// Returns a ModuleLog with name set. - internal static ModuleLog Create() => new(typeof(T)); - /// /// Log a templated verbose message to the in-game debug log. /// @@ -183,4 +176,11 @@ public class ModuleLog messageTemplate: $"[{this.moduleName}] {messageTemplate}", values); } + + /// + /// Helper method to create a new instance based on a type. + /// + /// The class to create this ModuleLog for. + /// Returns a ModuleLog with name set. + internal static ModuleLog Create() => new(typeof(T)); } From dd4714753871edfc07fd1c4b30cd027c43dd9449 Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 27 Feb 2025 21:38:06 +0100 Subject: [PATCH 271/375] Installer: if main-repo cross update is available, adjust warning message --- .../PluginInstaller/PluginInstallerWindow.cs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 3abb1bb39..d01063156 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -223,10 +223,11 @@ internal class PluginInstallerWindow : Window, IDisposable IsThirdParty = 1 << 0, HasTrouble = 1 << 1, UpdateAvailable = 1 << 2, - IsNew = 1 << 3, - IsInstallableOutdated = 1 << 4, - IsOrphan = 1 << 5, - IsTesting = 1 << 6, + MainRepoCrossUpdate = 1 << 3, + IsNew = 1 << 4, + IsInstallableOutdated = 1 << 5, + IsOrphan = 1 << 6, + IsTesting = 1 << 7, } private enum InstalledPluginListFilter @@ -2217,7 +2218,12 @@ internal class PluginInstallerWindow : Window, IDisposable else if (plugin is { IsDecommissioned: true, IsThirdParty: true }) { ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_NoServiceThird); + + ImGui.TextWrapped( + flags.HasFlag(PluginHeaderFlags.MainRepoCrossUpdate) + ? Locs.PluginBody_NoServiceThirdCrossUpdate + : Locs.PluginBody_NoServiceThird); + ImGui.PopStyleColor(); } else if (plugin != null && !plugin.CheckPolicy()) @@ -2602,7 +2608,10 @@ internal class PluginInstallerWindow : Window, IDisposable availablePluginUpdate = null; // Update available - if (availablePluginUpdate != default) + var isMainRepoCrossUpdate = availablePluginUpdate != null && + availablePluginUpdate.UpdateManifest.RepoUrl != plugin.Manifest.RepoUrl && + availablePluginUpdate.UpdateManifest.RepoUrl == PluginRepository.MainRepoUrl; + if (availablePluginUpdate != null) { label += Locs.PluginTitleMod_HasUpdate; } @@ -2612,7 +2621,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (this.updatedPlugins != null && !plugin.IsDev) { var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName); - if (update != default) + if (update != null) { if (update.Status == PluginUpdateStatus.StatusKind.Success) { @@ -2640,8 +2649,8 @@ internal class PluginInstallerWindow : Window, IDisposable trouble = true; } - // Orphaned - if (plugin.IsOrphaned) + // Orphaned, if we don't have a cross-repo update + if (plugin.IsOrphaned && !isMainRepoCrossUpdate) { label += Locs.PluginTitleMod_OrphanedError; trouble = true; @@ -2670,7 +2679,7 @@ internal class PluginInstallerWindow : Window, IDisposable string? availableChangelog = null; var didDrawAvailableChangelogInsideCollapsible = false; - if (availablePluginUpdate != default) + if (availablePluginUpdate != null) { availablePluginUpdateVersion = availablePluginUpdate.UseTesting ? @@ -2688,8 +2697,10 @@ internal class PluginInstallerWindow : Window, IDisposable flags |= PluginHeaderFlags.IsThirdParty; if (trouble) flags |= PluginHeaderFlags.HasTrouble; - if (availablePluginUpdate != default) + if (availablePluginUpdate != null) flags |= PluginHeaderFlags.UpdateAvailable; + if (isMainRepoCrossUpdate) + flags |= PluginHeaderFlags.MainRepoCrossUpdate; if (plugin.IsOrphaned) flags |= PluginHeaderFlags.IsOrphan; if (plugin.IsTesting) @@ -4056,6 +4067,8 @@ internal class PluginInstallerWindow : Window, IDisposable public static string PluginBody_NoServiceThird => Loc.Localize("InstallerNoServiceThirdPluginBody", "This plugin is no longer being serviced by its source repo. You may have to look for an updated version in another repo."); + public static string PluginBody_NoServiceThirdCrossUpdate => Loc.Localize("InstallerNoServiceThirdCrossUpdatePluginBody", "This plugin is no longer being serviced by its source repo. An update is available and will update it to a version from the official repository."); + public static string PluginBody_LoadFailed => Loc.Localize("InstallerLoadFailedPluginBody ", "This plugin failed to load. Please contact the author for more information."); public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin was automatically disabled due to incompatibilities and is not available."); From cd03ef7d02ac19aebaf5da1acbc7fdadb2122e64 Mon Sep 17 00:00:00 2001 From: nil <25884226+voidstar0@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:37:58 -0500 Subject: [PATCH 272/375] fix: always scan for dev plugins when opening the plugin installer (#2184) * fix: scan for dev plugins on plugin installer window open * fix: show scan dev plugins button based on plugin locations and not installed plugins --- .../Windows/PluginInstaller/PluginInstallerWindow.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index d01063156..66f338586 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -283,6 +283,7 @@ internal class PluginInstallerWindow : Window, IDisposable var pluginManager = Service.Get(); _ = pluginManager.ReloadPluginMastersAsync(); + Service.Get().ScanDevPlugins(); if (!this.isSearchTextPrefilled) this.searchText = string.Empty; this.sortKind = PluginSortKind.Alphabetical; @@ -755,8 +756,9 @@ internal class PluginInstallerWindow : Window, IDisposable Service.Get().OpenSettings(); } - // If any dev plugins are installed, allow a shortcut for the /xldev menu item - if (this.hasDevPlugins) + // If any dev plugin locations exist, allow a shortcut for the /xldev menu item + var hasDevPluginLocations = configuration.DevPluginLoadLocations.Count > 0; + if (hasDevPluginLocations) { ImGui.SameLine(); if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) From 3a9dc48c116bc73d9f1ee32bd95404be00a5774a Mon Sep 17 00:00:00 2001 From: Cytraen <60638768+Cytraen@users.noreply.github.com> Date: Sat, 1 Mar 2025 08:38:14 -0500 Subject: [PATCH 273/375] fix: trim quotes from dev plugin before validation (#2186) --- .../Windows/Settings/Widgets/DevPluginsSettingsEntry.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs index 4a78735f6..720d2a70f 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs @@ -206,6 +206,7 @@ public class DevPluginsSettingsEntry : SettingsEntry private void AddDevPlugin() { + this.devPluginTempLocation = this.devPluginTempLocation.Trim('"'); if (this.devPluginLocations.Any( r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) { @@ -224,7 +225,7 @@ public class DevPluginsSettingsEntry : SettingsEntry this.devPluginLocations.Add( new DevPluginLocationSettings { - Path = this.devPluginTempLocation.Replace("\"", string.Empty), + Path = this.devPluginTempLocation, IsEnabled = true, }); this.devPluginLocationsChanged = true; From e6017f96c09b8cde20e02371914ec25cfa989ef7 Mon Sep 17 00:00:00 2001 From: Cytraen <60638768+Cytraen@users.noreply.github.com> Date: Sat, 1 Mar 2025 08:39:13 -0500 Subject: [PATCH 274/375] fix: only enable ImGui asserts when adding a valid dev plugin path (#2185) --- .../Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs index 720d2a70f..4c5dc8b83 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/DevPluginsSettingsEntry.cs @@ -219,6 +219,7 @@ public class DevPluginsSettingsEntry : SettingsEntry "DalamudDevPluginInvalid", "The entered value is not a valid path to a potential Dev Plugin.\nDid you mean to enter it as a custom plugin repository in the fields below instead?"); Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + return; } else { From d255a8ff277a3e1c16b9d603cf8aea73c681c338 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:32:34 +0100 Subject: [PATCH 275/375] Update ClientStructs (#2181) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 4a727b491..484b1b588 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 4a727b491721b02a4e7b9ded289f35388758394f +Subproject commit 484b1b588be5e685855392f090ec9e3eea7b3a71 From 01b3a5428e372e02dbe3c271a918e45055d32f67 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 4 Mar 2025 20:25:47 +0100 Subject: [PATCH 276/375] build: 11.0.8.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index e30109a99..b24eb07ed 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -10,7 +10,7 @@ XIV Launcher addon framework - 11.0.7.0 + 11.0.8.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 89dfd72e24e9e43bea0a6d32c76c0a86aa9e3c56 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:13:54 +0100 Subject: [PATCH 277/375] ci: build with 9.0.200 SDK --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e053e565..e46f6f693 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: uses: microsoft/setup-msbuild@v1.0.2 - uses: actions/setup-dotnet@v3 with: - dotnet-version: '9.0.100' + dotnet-version: '9.0.200' - name: Define VERSION run: | $env:COMMIT = $env:GITHUB_SHA.Substring(0, 7) From cf1d8a81c6a958de6ac2da21b1fc6d5d480ba567 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 8 Mar 2025 16:27:10 +0100 Subject: [PATCH 278/375] Remove obsolete ServicePointManager TLS version override --- Dalamud/EntryPoint.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 6d5c219dd..89e4e0e5e 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -178,20 +178,17 @@ public sealed class EntryPoint throw new Exception("Working directory was invalid"); Reloaded.Hooks.Tools.Utilities.FasmBasePath = new DirectoryInfo(info.WorkingDirectory); - + // Apply common fixes for culture issues CultureFixes.Apply(); - // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; - if (!Util.IsWine()) InitSymbolHandler(info); var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent); - Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]", - Util.GetScmVersion(), - Util.GetGitHashClientStructs(), + Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash} [{CsVersion}]", + Util.GetScmVersion(), + Util.GetGitHashClientStructs(), FFXIVClientStructs.ThisAssembly.Git.Commits); dalamud.WaitForUnload(); From b21a0c929839d917f54d0ffcde3438b47858ce27 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sun, 9 Mar 2025 19:34:26 +0100 Subject: [PATCH 279/375] Add console argument/env var to disable legacy corrupted state exceptions policy --- Dalamud.Boot/DalamudStartInfo.cpp | 2 ++ Dalamud.Boot/DalamudStartInfo.h | 3 ++- Dalamud.Boot/dllmain.cpp | 23 ++++++++++++----------- Dalamud.Common/DalamudStartInfo.cs | 7 ++++++- Dalamud.Injector.Boot/main.cpp | 1 + Dalamud.Injector/EntryPoint.cs | 3 +++ lib/CoreCLR/boot.cpp | 10 ++++++++-- lib/CoreCLR/boot.h | 1 + 8 files changed, 35 insertions(+), 15 deletions(-) diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 985332966..52f201fea 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -123,6 +123,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.BootVehEnabled = json.value("BootVehEnabled", config.BootVehEnabled); config.BootVehFull = json.value("BootVehFull", config.BootVehFull); config.BootEnableEtw = json.value("BootEnableEtw", config.BootEnableEtw); + config.BootDisableLegacyCorruptedStateExceptions = json.value("BootDisableLegacyCorruptedStateExceptions", config.BootDisableLegacyCorruptedStateExceptions); config.BootDotnetOpenProcessHookMode = json.value("BootDotnetOpenProcessHookMode", config.BootDotnetOpenProcessHookMode); if (const auto it = json.find("BootEnabledGameFixes"); it != json.end() && it->is_array()) { config.BootEnabledGameFixes.clear(); @@ -148,6 +149,7 @@ void DalamudStartInfo::from_envvars() { BootVehEnabled = utils::get_env(L"DALAMUD_IS_VEH"); BootVehFull = utils::get_env(L"DALAMUD_IS_VEH_FULL"); BootEnableEtw = utils::get_env(L"DALAMUD_ENABLE_ETW"); + BootDisableLegacyCorruptedStateExceptions = utils::get_env(L"DALAMUD_DISABLE_LEGACY_CORRUPTED_STATE_EXCEPTIONS"); BootDotnetOpenProcessHookMode = static_cast(utils::get_env(L"DALAMUD_DOTNET_OPENPROCESS_HOOKMODE")); for (const auto& item : utils::get_env_list(L"DALAMUD_GAMEFIX_LIST")) BootEnabledGameFixes.insert(unicode::convert(item, &unicode::lower)); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index cc31ba2c5..d5a6a6aec 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -17,7 +17,7 @@ struct DalamudStartInfo { DirectHook = 1, }; friend void from_json(const nlohmann::json&, DotNetOpenProcessHookMode&); - + enum class ClientLanguage : int { Japanese, English, @@ -61,6 +61,7 @@ struct DalamudStartInfo { bool BootVehEnabled = false; bool BootVehFull = false; bool BootEnableEtw = false; + bool BootDisableLegacyCorruptedStateExceptions = false; DotNetOpenProcessHookMode BootDotnetOpenProcessHookMode = DotNetOpenProcessHookMode::ImportHooks; std::set BootEnabledGameFixes{}; std::set BootUnhookDlls{}; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 5c7c00b68..f608c626e 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -11,7 +11,7 @@ HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr); HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { g_startInfo.from_envvars(); - + std::string jsonParseError; try { from_json(nlohmann::json::parse(std::string_view(static_cast(lpParam))), g_startInfo); @@ -21,7 +21,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { if (g_startInfo.BootShowConsole) ConsoleSetup(L"Dalamud Boot"); - + logging::update_dll_load_status(true); const auto logFilePath = unicode::convert(g_startInfo.BootLogPath); @@ -29,16 +29,16 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { auto attemptFallbackLog = false; if (logFilePath.empty()) { attemptFallbackLog = true; - + logging::I("No log file path given; not logging to file."); } else { try { logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole); logging::I("Logging to file: {}", logFilePath); - + } catch (const std::exception& e) { attemptFallbackLog = true; - + logging::E("Couldn't open log file: {}", logFilePath); logging::E("Error: {} / {}", errno, e.what()); } @@ -59,15 +59,15 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { SYSTEMTIME st; GetLocalTime(&st); logFilePath += std::format(L"Dalamud.Boot.{:04}{:02}{:02}.{:02}{:02}{:02}.{:03}.{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentProcessId()); - + try { logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole); logging::I("Logging to fallback log file: {}", logFilePath); - + } catch (const std::exception& e) { if (!g_startInfo.BootShowConsole && !g_startInfo.BootDisableFallbackConsole) ConsoleSetup(L"Dalamud Boot - Fallback Console"); - + logging::E("Couldn't open fallback log file: {}", logFilePath); logging::E("Error: {} / {}", errno, e.what()); } @@ -83,7 +83,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { } else { logging::E("Failed to initialize MinHook (status={}({}))", MH_StatusToString(mhStatus), static_cast(mhStatus)); } - + logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors"); logging::I("Built at: " __DATE__ "@" __TIME__); @@ -117,6 +117,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { const auto result = InitializeClrAndGetEntryPoint( g_hModule, g_startInfo.BootEnableEtw, + !g_startInfo.BootDisableLegacyCorruptedStateExceptions, runtimeconfig_path, module_path, L"Dalamud.EntryPoint, Dalamud", @@ -174,11 +175,11 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese case DLL_PROCESS_DETACH: // do not show debug message boxes on abort() here _set_abort_behavior(0, _WRITE_ABORT_MSG); - + // process is terminating; don't bother cleaning up if (lpReserved) return TRUE; - + logging::update_dll_load_status(false); xivfixes::apply_all(false); diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index c3cc33a12..ca81d1281 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -120,10 +120,15 @@ public record DalamudStartInfo public bool BootVehFull { get; set; } /// - /// Gets or sets a value indicating whether or not ETW should be enabled. + /// Gets or sets a value indicating whether ETW should be enabled. /// public bool BootEnableEtw { get; set; } + /// + /// Gets or sets a value indicating whether to enable legacy corrupted state exceptions. + /// + public bool BootDisableLegacyCorruptedStateExceptions { get; set; } + /// /// Gets or sets a value choosing the OpenProcess hookmode. /// diff --git a/Dalamud.Injector.Boot/main.cpp b/Dalamud.Injector.Boot/main.cpp index df4120009..50555a09a 100644 --- a/Dalamud.Injector.Boot/main.cpp +++ b/Dalamud.Injector.Boot/main.cpp @@ -27,6 +27,7 @@ int wmain(int argc, wchar_t** argv) const auto result = InitializeClrAndGetEntryPoint( GetModuleHandleW(nullptr), false, + false, runtimeconfig_path, module_path, L"Dalamud.Injector.EntryPoint, Dalamud.Injector", diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 6507436c2..9e7033c4d 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -94,6 +94,7 @@ namespace Dalamud.Injector args.Remove("--msgbox2"); args.Remove("--msgbox3"); args.Remove("--etw"); + args.Remove("--no-legacy-corrupted-state-exceptions"); args.Remove("--veh"); args.Remove("--veh-full"); args.Remove("--no-plugin"); @@ -399,6 +400,7 @@ namespace Dalamud.Injector // Set boot defaults startInfo.BootShowConsole = args.Contains("--console"); startInfo.BootEnableEtw = args.Contains("--etw"); + startInfo.BootDisableLegacyCorruptedStateExceptions = args.Contains("--no-legacy-corrupted-state-exceptions"); startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName); startInfo.BootEnabledGameFixes = new() { @@ -470,6 +472,7 @@ namespace Dalamud.Injector Console.WriteLine("Verbose logging:\t[-v]"); Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]"); Console.WriteLine("Enable ETW:\t[--etw]"); + Console.WriteLine("Disable legacy corrupted state exceptions:\t[--no-legacy-corrupted-state-exceptions]"); Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]"); Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]"); Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]"); diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index 54276aad1..723a1ed21 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -30,6 +30,7 @@ std::optional g_clr; HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, + bool enable_legacy_corrupted_state_exception_policy, std::wstring runtimeconfig_path, std::wstring module_path, std::wstring entrypoint_assembly_name, @@ -41,8 +42,13 @@ HRESULT InitializeClrAndGetEntryPoint( int result; SetEnvironmentVariable(L"DOTNET_MULTILEVEL_LOOKUP", L"0"); - SetEnvironmentVariable(L"COMPlus_legacyCorruptedStateExceptionsPolicy", L"1"); - SetEnvironmentVariable(L"DOTNET_legacyCorruptedStateExceptionsPolicy", L"1"); + + if (enable_legacy_corrupted_state_exception_policy) + { + SetEnvironmentVariable(L"COMPlus_legacyCorruptedStateExceptionsPolicy", L"1"); + SetEnvironmentVariable(L"DOTNET_legacyCorruptedStateExceptionsPolicy", L"1"); + } + SetEnvironmentVariable(L"COMPLUS_ForceENC", L"1"); SetEnvironmentVariable(L"DOTNET_ForceENC", L"1"); diff --git a/lib/CoreCLR/boot.h b/lib/CoreCLR/boot.h index 33bc58bbf..b227a3438 100644 --- a/lib/CoreCLR/boot.h +++ b/lib/CoreCLR/boot.h @@ -4,6 +4,7 @@ void ConsoleTeardown(); HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, + bool enable_legacy_corrupted_state_exception_policy, std::wstring runtimeconfig_path, std::wstring module_path, std::wstring entrypoint_assembly_name, From 3a990b77f05a6084a0f27b2ef7027c90cdc70be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Valli=C3=A8re?= <6031413+AlexValliere@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:51:33 +0100 Subject: [PATCH 280/375] feat: handle UNC paths in FileDialog (#2191) --- .../Interface/ImGuiFileDialog/FileDialog.Files.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs index 121ec8890..705c0f100 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs @@ -36,6 +36,20 @@ public partial class FileDialog private static string ComposeNewPath(List decomp) { + // Handle UNC paths (network paths) + if (decomp.Count >= 2 && string.IsNullOrEmpty(decomp[0]) && string.IsNullOrEmpty(decomp[1])) + { + var pathParts = new List(decomp); + pathParts.RemoveRange(0, 2); + // Can not access server level or UNC root + if (pathParts.Count <= 1) + { + return string.Empty; + } + + return $"\\\\{string.Join('\\', pathParts)}"; + } + if (decomp.Count == 1) { var drivePath = decomp[0]; From 97add3fc90cb2eb8763228899ebecd0c9de87af0 Mon Sep 17 00:00:00 2001 From: goaaats Date: Wed, 12 Mar 2025 23:23:31 +0100 Subject: [PATCH 281/375] Globally disable legacy corrupted state exceptions for now --- Dalamud.Boot/dllmain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index f608c626e..68f04b84b 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -117,7 +117,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { const auto result = InitializeClrAndGetEntryPoint( g_hModule, g_startInfo.BootEnableEtw, - !g_startInfo.BootDisableLegacyCorruptedStateExceptions, + false, // !g_startInfo.BootDisableLegacyCorruptedStateExceptions, runtimeconfig_path, module_path, L"Dalamud.EntryPoint, Dalamud", From e47b8164957aceddaa09f62e7be7c3093aa2e471 Mon Sep 17 00:00:00 2001 From: Blair Date: Thu, 13 Mar 2025 08:26:37 +1000 Subject: [PATCH 282/375] Remove command from assembly map when removed from command map (#2183) --- Dalamud/Game/Command/CommandManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index 078ce8c50..fdaa5833b 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -10,6 +10,7 @@ using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; @@ -149,6 +150,11 @@ internal sealed unsafe class CommandManager : IInternalDisposableService, IComma /// public bool RemoveHandler(string command) { + if (this.commandAssemblyNameMap.FindFirst(c => c.Key.Item1 == command, out var assemblyKeyValuePair)) + { + this.commandAssemblyNameMap.TryRemove(assemblyKeyValuePair.Key, out _); + } + return this.commandMap.Remove(command, out _); } From 2e2c73f707afd589aeea3e5de11835b9d4dbde53 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Wed, 12 Mar 2025 23:27:12 +0100 Subject: [PATCH 283/375] [net9] Rollup changes from master (#2193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: handle UNC paths in FileDialog (#2191) * Remove command from assembly map when removed from command map (#2183) --------- Co-authored-by: Alex Vallière <6031413+AlexValliere@users.noreply.github.com> Co-authored-by: Blair Co-authored-by: github-actions[bot] --- Dalamud/Game/Command/CommandManager.cs | 6 ++++++ .../Interface/ImGuiFileDialog/FileDialog.Files.cs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index 078ce8c50..fdaa5833b 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -10,6 +10,7 @@ using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; @@ -149,6 +150,11 @@ internal sealed unsafe class CommandManager : IInternalDisposableService, IComma /// public bool RemoveHandler(string command) { + if (this.commandAssemblyNameMap.FindFirst(c => c.Key.Item1 == command, out var assemblyKeyValuePair)) + { + this.commandAssemblyNameMap.TryRemove(assemblyKeyValuePair.Key, out _); + } + return this.commandMap.Remove(command, out _); } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs index 121ec8890..705c0f100 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs @@ -36,6 +36,20 @@ public partial class FileDialog private static string ComposeNewPath(List decomp) { + // Handle UNC paths (network paths) + if (decomp.Count >= 2 && string.IsNullOrEmpty(decomp[0]) && string.IsNullOrEmpty(decomp[1])) + { + var pathParts = new List(decomp); + pathParts.RemoveRange(0, 2); + // Can not access server level or UNC root + if (pathParts.Count <= 1) + { + return string.Empty; + } + + return $"\\\\{string.Join('\\', pathParts)}"; + } + if (decomp.Count == 1) { var drivePath = decomp[0]; From 2f0c31d024e8a3e82b7cbc12c6c1fc4e6fd0dee0 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 13 Mar 2025 12:34:13 -0700 Subject: [PATCH 284/375] fix: Use builtin method for target management (#2194) - Properly validate target preconditions --- Dalamud/Game/ClientState/Objects/TargetManager.cs | 12 ++++++------ lib/FFXIVClientStructs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index 5d7ae8945..f81154693 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -20,7 +20,7 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager { [ServiceManager.ServiceDependency] private readonly ObjectTable objectTable = Service.Get(); - + [ServiceManager.ServiceConstructor] private TargetManager() { @@ -29,8 +29,8 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager /// public IGameObject? Target { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target); - set => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; + get => this.objectTable.CreateObjectReference((IntPtr)Struct->GetHardTarget()); + set => Struct->SetHardTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address); } /// @@ -57,8 +57,8 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager /// public IGameObject? SoftTarget { - get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget); - set => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; + get => this.objectTable.CreateObjectReference((IntPtr)Struct->GetSoftTarget()); + set => Struct->SetSoftTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address); } /// @@ -67,7 +67,7 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget); set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address; } - + /// public IGameObject? MouseOverNameplateTarget { diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 484b1b588..2c3e84640 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 484b1b588be5e685855392f090ec9e3eea7b3a71 +Subproject commit 2c3e84640af5220b78b944a06fdca79c52144075 From ee2c8dd9ccb726b2d6753446d0a0e6cb3e80d24f Mon Sep 17 00:00:00 2001 From: Nathan C <39100023+nathanctech@users.noreply.github.com> Date: Thu, 13 Mar 2025 17:01:39 -0400 Subject: [PATCH 285/375] Build adjustments for code annotation (#2155) * Drop special formatting, allowing annotations to work properly. * Suppress duplicate warnings, adding a prefix to prevent annotation * Tweak message, don't rebuild on test * Move testing into same job step * Only run PR build on newly opened PRs * flip build order, derp * Test suppressing summary for annotations... * Get the build order right, testing without conditionals... * Run tests after compile, suppress warnings from test * Reverted previous change to `main.yml`. * Drop special formatting, allowing annotations to work properly. * Suppress duplicate warnings, adding a prefix to prevent annotation * Tweak message, don't rebuild on test * Move testing into same job step * Only run PR build on newly opened PRs * flip build order, derp * Test suppressing summary for annotations... * Get the build order right, testing without conditionals... * Run tests after compile, suppress warnings from test * Reverted previous change to `main.yml`. * Drop special formatting, allowing annotations to work properly. * Suppress duplicate warnings, adding a prefix to prevent annotation * Tweak message, don't rebuild on test * Move testing into same job step * Only run PR build on newly opened PRs * flip build order, derp * Test suppressing summary for annotations... * Get the build order right, testing without conditionals... * Run tests after compile, suppress warnings from test * Reverted previous change to `main.yml`. * Add conditional for CI builds, add --skip-tests to make up for the combined build/test step. * Behavior change, now requires arg `ci` to be passed to trigger tests. Tests can also be manually triggered with `test`. --- .github/workflows/main.yml | 27 +++++++++++++------------ .nuke/build.schema.json | 4 ++++ build/DalamudBuild.cs | 40 +++++++++++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 930adf8ed..f1267baca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,6 @@ name: Build Dalamud on: [push, pull_request, workflow_dispatch] + concurrency: group: build_dalamud_${{ github.ref_name }} cancel-in-progress: true @@ -32,10 +33,8 @@ jobs: ($env:REPO_NAME) >> VERSION ($env:BRANCH) >> VERSION ($env:COMMIT) >> VERSION - - name: Build Dalamud - run: .\build.ps1 compile - - name: Test Dalamud - run: .\build.ps1 test + - name: Build and Test Dalamud + run: .\build.ps1 ci - name: Sign Dalamud if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }} env: @@ -87,9 +86,9 @@ jobs: - name: "Verify Compatibility" run: | $FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll" - + $retcode = 0 - + foreach ($file in $FILES_TO_VALIDATE) { $testout = "" Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ===" @@ -100,7 +99,7 @@ jobs: $retcode = 1 } } - + exit $retcode deploy_stg: @@ -129,18 +128,18 @@ jobs: GH_BRANCH: ${{ steps.extract_branch.outputs.branch }} run: | Compress-Archive .\scratch\* .\canary.zip # Recreate the release zip - + $branchName = $env:GH_BRANCH - + if ($branchName -eq "master") { $branchName = "stg" } - + $newVersion = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\TEMP_gitver.txt") $revision = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\revision.txt") $commitHash = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\commit_hash.txt") Remove-Item -Force -Recurse .\scratch - + if (Test-Path -Path $branchName) { $versionData = Get-Content ".\${branchName}\version" | ConvertFrom-Json $oldVersion = $versionData.AssemblyVersion @@ -159,7 +158,7 @@ jobs: Write-Host "Deployment folder doesn't exist. Not doing anything." Remove-Item .\canary.zip } - + - name: Commit changes shell: bash env: @@ -167,8 +166,8 @@ jobs: run: | git config --global user.name "Actions User" git config --global user.email "actions@github.com" - + git add . git commit -m "[CI] Update staging for ${DVER} on ${GH_BRANCH}" || true - + git push origin main || true diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index e7e1a446a..8331affcc 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -76,6 +76,7 @@ "items": { "type": "string", "enum": [ + "CI", "Clean", "Compile", "CompileCImGui", @@ -88,6 +89,7 @@ "CompileInjector", "CompileInjectorBoot", "Restore", + "SetCILogging", "Test" ] } @@ -102,6 +104,7 @@ "items": { "type": "string", "enum": [ + "CI", "Clean", "Compile", "CompileCImGui", @@ -114,6 +117,7 @@ "CompileInjector", "CompileInjectorBoot", "Restore", + "SetCILogging", "Test" ] } diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 67a812916..d374c79f8 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using Nuke.Common; @@ -5,6 +6,7 @@ using Nuke.Common.Execution; using Nuke.Common.Git; using Nuke.Common.IO; using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.MSBuild; using Serilog; @@ -61,6 +63,9 @@ public class DalamudBuild : NukeBuild private static Dictionary EnvironmentVariables => new(EnvironmentInfo.Variables); + private static string ConsoleTemplate => "{Message:l}{NewLine}{Exception}"; + private static bool IsCIBuild => Environment.GetEnvironmentVariable("CI") == "true"; + Target Restore => _ => _ .Executes(() => { @@ -123,16 +128,20 @@ public class DalamudBuild : NukeBuild .SetProjectFile(DalamudProjectFile) .SetConfiguration(Configuration) .EnableNoRestore(); - + if (IsCIBuild) + { + s = s + .SetProcessArgumentConfigurator(a => a.Add("/clp:NoSummary")); // Disable MSBuild summary on CI builds + } // We need to emit compiler generated files for the docs build, since docfx can't run generators directly // TODO: This fails every build after this because of redefinitions... + // if (IsDocsBuild) // { // Log.Warning("Building for documentation, emitting compiler generated files. This can cause issues on Windows due to path-length limitations"); // s = s // .SetProperty("IsDocsBuild", "true"); // } - return s; }); }); @@ -171,12 +180,28 @@ public class DalamudBuild : NukeBuild .SetConfiguration(Configuration)); }); + Target SetCILogging => _ => _ + .DependentFor(Compile) + .OnlyWhenStatic(() => IsCIBuild) + .Executes(() => + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console(outputTemplate: ConsoleTemplate) + .CreateLogger(); + }); + Target Compile => _ => _ - .DependsOn(CompileDalamud) - .DependsOn(CompileDalamudBoot) - .DependsOn(CompileDalamudCrashHandler) - .DependsOn(CompileInjector) - .DependsOn(CompileInjectorBoot); + .DependsOn(CompileDalamud) + .DependsOn(CompileDalamudBoot) + .DependsOn(CompileDalamudCrashHandler) + .DependsOn(CompileInjector) + .DependsOn(CompileInjectorBoot) + ; + + Target CI => _ => _ + .DependsOn(Compile) + .Triggers(Test); Target Test => _ => _ .DependsOn(Compile) @@ -185,6 +210,7 @@ public class DalamudBuild : NukeBuild DotNetTasks.DotNetTest(s => s .SetProjectFile(TestProjectFile) .SetConfiguration(Configuration) + .AddProperty("WarningLevel", "0") .EnableNoRestore()); }); From 577977350f1a2350ba4d4768061a1985f29c5252 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 13 Mar 2025 14:16:28 -0700 Subject: [PATCH 286/375] fix: Attempt to better handle hook disposal (#1803) - Use a Weak Concurrent Collection to track scoped hooks - Make `Hook`s remove themselves from the Tracked Hook list. --- Dalamud/Hooking/AsmHook.cs | 8 +++- Dalamud/Hooking/Hook.cs | 5 +++ .../Internal/FunctionPointerVariableHook.cs | 4 +- .../GameInteropProviderPluginScoped.cs | 6 +-- Dalamud/Hooking/Internal/MinHookHook.cs | 4 +- Dalamud/Hooking/Internal/ReloadedHook.cs | 4 +- .../Internal/Windows/PluginStatWindow.cs | 13 ------ Dalamud/Utility/WeakConcurrentCollection.cs | 44 +++++++++++++++++++ 8 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 Dalamud/Utility/WeakConcurrentCollection.cs diff --git a/Dalamud/Hooking/AsmHook.cs b/Dalamud/Hooking/AsmHook.cs index 4ee96bb15..f1ed7fd11 100644 --- a/Dalamud/Hooking/AsmHook.cs +++ b/Dalamud/Hooking/AsmHook.cs @@ -20,6 +20,8 @@ public sealed class AsmHook : IDisposable, IDalamudHook private bool isEnabled = false; private DynamicMethod statsMethod; + + private Guid hookId = Guid.NewGuid(); /// /// Initializes a new instance of the class. @@ -44,7 +46,7 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); var dele = this.statsMethod.CreateDelegate(typeof(Action)); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, dele, Assembly.GetCallingAssembly())); + HookManager.TrackedHooks.TryAdd(this.hookId, new HookInfo(this, dele, Assembly.GetCallingAssembly())); } /// @@ -70,7 +72,7 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); var dele = this.statsMethod.CreateDelegate(typeof(Action)); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, dele, Assembly.GetCallingAssembly())); + HookManager.TrackedHooks.TryAdd(this.hookId, new HookInfo(this, dele, Assembly.GetCallingAssembly())); } /// @@ -116,6 +118,8 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.IsDisposed = true; + HookManager.TrackedHooks.TryRemove(this.hookId, out _); + if (this.isEnabled) { this.isEnabled = false; diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index da65fedc7..20796bbf0 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -71,6 +71,11 @@ public abstract class Hook : IDalamudHook where T : Delegate /// public virtual string BackendName => throw new NotImplementedException(); + /// + /// Gets the unique GUID for this hook. + /// + protected Guid HookId { get; } = Guid.NewGuid(); + /// /// Remove a hook from the current process. /// diff --git a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs index 7a177206f..40a33fc1b 100644 --- a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs +++ b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs @@ -100,7 +100,7 @@ internal class FunctionPointerVariableHook : Hook unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -137,6 +137,8 @@ internal class FunctionPointerVariableHook : Hook this.Disable(); + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); HookManager.MultiHookTracker[this.Address][index] = null; diff --git a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs index f7c4db00a..52d266335 100644 --- a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs +++ b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Diagnostics; +using System.Diagnostics; using System.Linq; using Dalamud.Game; @@ -7,6 +6,7 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Dalamud.Utility.Signatures; using Serilog; @@ -25,7 +25,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternal private readonly LocalPlugin plugin; private readonly SigScanner scanner; - private readonly ConcurrentBag trackedHooks = new(); + private readonly WeakConcurrentCollection trackedHooks = new(); /// /// Initializes a new instance of the class. diff --git a/Dalamud/Hooking/Internal/MinHookHook.cs b/Dalamud/Hooking/Internal/MinHookHook.cs index 4cebca0d0..0305f3c84 100644 --- a/Dalamud/Hooking/Internal/MinHookHook.cs +++ b/Dalamud/Hooking/Internal/MinHookHook.cs @@ -35,7 +35,7 @@ internal class MinHookHook : Hook where T : Delegate unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -76,6 +76,8 @@ internal class MinHookHook : Hook where T : Delegate HookManager.MultiHookTracker[this.Address][index] = null; } + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + base.Dispose(); } diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs index bf441a86d..2b0a4e9ce 100644 --- a/Dalamud/Hooking/Internal/ReloadedHook.cs +++ b/Dalamud/Hooking/Internal/ReloadedHook.cs @@ -30,7 +30,7 @@ internal class ReloadedHook : Hook where T : Delegate unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -63,6 +63,8 @@ internal class ReloadedHook : Hook where T : Delegate if (this.IsDisposed) return; + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + this.Disable(); base.Dispose(); diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index 314f023da..eeafa98e7 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -258,8 +258,6 @@ internal class PluginStatWindow : Window ImGui.EndTabItem(); } - var toRemove = new List(); - if (ImGui.BeginTabItem("Hooks")) { ImGui.Checkbox("Show Dalamud Hooks", ref this.showDalamudHooks); @@ -291,9 +289,6 @@ internal class PluginStatWindow : Window { try { - if (trackedHook.Hook.IsDisposed) - toRemove.Add(guid); - if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) continue; @@ -355,14 +350,6 @@ internal class PluginStatWindow : Window } } - if (ImGui.IsWindowAppearing()) - { - foreach (var guid in toRemove) - { - HookManager.TrackedHooks.TryRemove(guid, out _); - } - } - ImGui.EndTabBar(); } } diff --git a/Dalamud/Utility/WeakConcurrentCollection.cs b/Dalamud/Utility/WeakConcurrentCollection.cs new file mode 100644 index 000000000..a3bc04651 --- /dev/null +++ b/Dalamud/Utility/WeakConcurrentCollection.cs @@ -0,0 +1,44 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Dalamud.Utility; + +/// +/// An implementation of a weak concurrent set based on a . +/// +/// The type of object that we're tracking. +public class WeakConcurrentCollection : ICollection where T : class +{ + private readonly ConditionalWeakTable cwt = new(); + + /// + public int Count => this.cwt.Count(); + + /// + public bool IsReadOnly => false; + + private IEnumerable Keys => this.cwt.Select(pair => pair.Key); + + /// + public IEnumerator GetEnumerator() => this.cwt.Select(pair => pair.Key).GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => this.cwt.Select(pair => pair.Key).GetEnumerator(); + + /// + public void Add(T item) => this.cwt.AddOrUpdate(item, null); + + /// + public void Clear() => this.cwt.Clear(); + + /// + public bool Contains(T item) => this.Keys.Contains(item); + + /// + public void CopyTo(T[] array, int arrayIndex) => this.Keys.ToArray().CopyTo(array, arrayIndex); + + /// + public bool Remove(T item) => this.cwt.Remove(item); +} From 203d80c602dd814082bced51d1116b983e3c6181 Mon Sep 17 00:00:00 2001 From: Nathan C <39100023+nathanctech@users.noreply.github.com> Date: Thu, 13 Mar 2025 17:01:39 -0400 Subject: [PATCH 287/375] Build adjustments for code annotation (#2155) * Drop special formatting, allowing annotations to work properly. * Suppress duplicate warnings, adding a prefix to prevent annotation * Tweak message, don't rebuild on test * Move testing into same job step * Only run PR build on newly opened PRs * flip build order, derp * Test suppressing summary for annotations... * Get the build order right, testing without conditionals... * Run tests after compile, suppress warnings from test * Reverted previous change to `main.yml`. * Drop special formatting, allowing annotations to work properly. * Suppress duplicate warnings, adding a prefix to prevent annotation * Tweak message, don't rebuild on test * Move testing into same job step * Only run PR build on newly opened PRs * flip build order, derp * Test suppressing summary for annotations... * Get the build order right, testing without conditionals... * Run tests after compile, suppress warnings from test * Reverted previous change to `main.yml`. * Drop special formatting, allowing annotations to work properly. * Suppress duplicate warnings, adding a prefix to prevent annotation * Tweak message, don't rebuild on test * Move testing into same job step * Only run PR build on newly opened PRs * flip build order, derp * Test suppressing summary for annotations... * Get the build order right, testing without conditionals... * Run tests after compile, suppress warnings from test * Reverted previous change to `main.yml`. * Add conditional for CI builds, add --skip-tests to make up for the combined build/test step. * Behavior change, now requires arg `ci` to be passed to trigger tests. Tests can also be manually triggered with `test`. --- .github/workflows/main.yml | 27 +++++++++++++------------ .nuke/build.schema.json | 4 ++++ build/DalamudBuild.cs | 40 +++++++++++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e46f6f693..be44afacc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,6 @@ name: Build Dalamud on: [push, pull_request, workflow_dispatch] + concurrency: group: build_dalamud_${{ github.ref_name }} cancel-in-progress: true @@ -32,10 +33,8 @@ jobs: ($env:REPO_NAME) >> VERSION ($env:BRANCH) >> VERSION ($env:COMMIT) >> VERSION - - name: Build Dalamud - run: .\build.ps1 compile - - name: Test Dalamud - run: .\build.ps1 test + - name: Build and Test Dalamud + run: .\build.ps1 ci - name: Sign Dalamud if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }} env: @@ -87,9 +86,9 @@ jobs: - name: "Verify Compatibility" run: | $FILES_TO_VALIDATE = "Dalamud.dll","FFXIVClientStructs.dll","Lumina.dll","Lumina.Excel.dll" - + $retcode = 0 - + foreach ($file in $FILES_TO_VALIDATE) { $testout = "" Write-Output "::group::=== API COMPATIBILITY CHECK: ${file} ===" @@ -100,7 +99,7 @@ jobs: $retcode = 1 } } - + exit $retcode deploy_stg: @@ -129,18 +128,18 @@ jobs: GH_BRANCH: ${{ steps.extract_branch.outputs.branch }} run: | Compress-Archive .\scratch\* .\canary.zip # Recreate the release zip - + $branchName = $env:GH_BRANCH - + if ($branchName -eq "master") { $branchName = "stg" } - + $newVersion = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\TEMP_gitver.txt") $revision = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\revision.txt") $commitHash = [System.IO.File]::ReadAllText("$(Get-Location)\scratch\commit_hash.txt") Remove-Item -Force -Recurse .\scratch - + if (Test-Path -Path $branchName) { $versionData = Get-Content ".\${branchName}\version" | ConvertFrom-Json $oldVersion = $versionData.AssemblyVersion @@ -159,7 +158,7 @@ jobs: Write-Host "Deployment folder doesn't exist. Not doing anything." Remove-Item .\canary.zip } - + - name: Commit changes shell: bash env: @@ -167,8 +166,8 @@ jobs: run: | git config --global user.name "Actions User" git config --global user.email "actions@github.com" - + git add . git commit -m "[CI] Update staging for ${DVER} on ${GH_BRANCH}" || true - + git push origin main || true diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index e7e1a446a..8331affcc 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -76,6 +76,7 @@ "items": { "type": "string", "enum": [ + "CI", "Clean", "Compile", "CompileCImGui", @@ -88,6 +89,7 @@ "CompileInjector", "CompileInjectorBoot", "Restore", + "SetCILogging", "Test" ] } @@ -102,6 +104,7 @@ "items": { "type": "string", "enum": [ + "CI", "Clean", "Compile", "CompileCImGui", @@ -114,6 +117,7 @@ "CompileInjector", "CompileInjectorBoot", "Restore", + "SetCILogging", "Test" ] } diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 67a812916..d374c79f8 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using Nuke.Common; @@ -5,6 +6,7 @@ using Nuke.Common.Execution; using Nuke.Common.Git; using Nuke.Common.IO; using Nuke.Common.ProjectModel; +using Nuke.Common.Tooling; using Nuke.Common.Tools.DotNet; using Nuke.Common.Tools.MSBuild; using Serilog; @@ -61,6 +63,9 @@ public class DalamudBuild : NukeBuild private static Dictionary EnvironmentVariables => new(EnvironmentInfo.Variables); + private static string ConsoleTemplate => "{Message:l}{NewLine}{Exception}"; + private static bool IsCIBuild => Environment.GetEnvironmentVariable("CI") == "true"; + Target Restore => _ => _ .Executes(() => { @@ -123,16 +128,20 @@ public class DalamudBuild : NukeBuild .SetProjectFile(DalamudProjectFile) .SetConfiguration(Configuration) .EnableNoRestore(); - + if (IsCIBuild) + { + s = s + .SetProcessArgumentConfigurator(a => a.Add("/clp:NoSummary")); // Disable MSBuild summary on CI builds + } // We need to emit compiler generated files for the docs build, since docfx can't run generators directly // TODO: This fails every build after this because of redefinitions... + // if (IsDocsBuild) // { // Log.Warning("Building for documentation, emitting compiler generated files. This can cause issues on Windows due to path-length limitations"); // s = s // .SetProperty("IsDocsBuild", "true"); // } - return s; }); }); @@ -171,12 +180,28 @@ public class DalamudBuild : NukeBuild .SetConfiguration(Configuration)); }); + Target SetCILogging => _ => _ + .DependentFor(Compile) + .OnlyWhenStatic(() => IsCIBuild) + .Executes(() => + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console(outputTemplate: ConsoleTemplate) + .CreateLogger(); + }); + Target Compile => _ => _ - .DependsOn(CompileDalamud) - .DependsOn(CompileDalamudBoot) - .DependsOn(CompileDalamudCrashHandler) - .DependsOn(CompileInjector) - .DependsOn(CompileInjectorBoot); + .DependsOn(CompileDalamud) + .DependsOn(CompileDalamudBoot) + .DependsOn(CompileDalamudCrashHandler) + .DependsOn(CompileInjector) + .DependsOn(CompileInjectorBoot) + ; + + Target CI => _ => _ + .DependsOn(Compile) + .Triggers(Test); Target Test => _ => _ .DependsOn(Compile) @@ -185,6 +210,7 @@ public class DalamudBuild : NukeBuild DotNetTasks.DotNetTest(s => s .SetProjectFile(TestProjectFile) .SetConfiguration(Configuration) + .AddProperty("WarningLevel", "0") .EnableNoRestore()); }); From a2124bb73dde7f8eeb86f50c5c72d7413fc9e893 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 13 Mar 2025 14:16:28 -0700 Subject: [PATCH 288/375] fix: Attempt to better handle hook disposal (#1803) - Use a Weak Concurrent Collection to track scoped hooks - Make `Hook`s remove themselves from the Tracked Hook list. --- Dalamud/Hooking/AsmHook.cs | 8 +++- Dalamud/Hooking/Hook.cs | 5 +++ .../Internal/FunctionPointerVariableHook.cs | 4 +- .../GameInteropProviderPluginScoped.cs | 6 +-- Dalamud/Hooking/Internal/MinHookHook.cs | 4 +- Dalamud/Hooking/Internal/ReloadedHook.cs | 4 +- .../Internal/Windows/PluginStatWindow.cs | 13 ------ Dalamud/Utility/WeakConcurrentCollection.cs | 44 +++++++++++++++++++ 8 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 Dalamud/Utility/WeakConcurrentCollection.cs diff --git a/Dalamud/Hooking/AsmHook.cs b/Dalamud/Hooking/AsmHook.cs index 4ee96bb15..f1ed7fd11 100644 --- a/Dalamud/Hooking/AsmHook.cs +++ b/Dalamud/Hooking/AsmHook.cs @@ -20,6 +20,8 @@ public sealed class AsmHook : IDisposable, IDalamudHook private bool isEnabled = false; private DynamicMethod statsMethod; + + private Guid hookId = Guid.NewGuid(); /// /// Initializes a new instance of the class. @@ -44,7 +46,7 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); var dele = this.statsMethod.CreateDelegate(typeof(Action)); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, dele, Assembly.GetCallingAssembly())); + HookManager.TrackedHooks.TryAdd(this.hookId, new HookInfo(this, dele, Assembly.GetCallingAssembly())); } /// @@ -70,7 +72,7 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); var dele = this.statsMethod.CreateDelegate(typeof(Action)); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, dele, Assembly.GetCallingAssembly())); + HookManager.TrackedHooks.TryAdd(this.hookId, new HookInfo(this, dele, Assembly.GetCallingAssembly())); } /// @@ -116,6 +118,8 @@ public sealed class AsmHook : IDisposable, IDalamudHook this.IsDisposed = true; + HookManager.TrackedHooks.TryRemove(this.hookId, out _); + if (this.isEnabled) { this.isEnabled = false; diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index da65fedc7..20796bbf0 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -71,6 +71,11 @@ public abstract class Hook : IDalamudHook where T : Delegate /// public virtual string BackendName => throw new NotImplementedException(); + /// + /// Gets the unique GUID for this hook. + /// + protected Guid HookId { get; } = Guid.NewGuid(); + /// /// Remove a hook from the current process. /// diff --git a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs index 7a177206f..40a33fc1b 100644 --- a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs +++ b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs @@ -100,7 +100,7 @@ internal class FunctionPointerVariableHook : Hook unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -137,6 +137,8 @@ internal class FunctionPointerVariableHook : Hook this.Disable(); + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + var index = HookManager.MultiHookTracker[this.Address].IndexOf(this); HookManager.MultiHookTracker[this.Address][index] = null; diff --git a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs index f7c4db00a..52d266335 100644 --- a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs +++ b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Diagnostics; +using System.Diagnostics; using System.Linq; using Dalamud.Game; @@ -7,6 +6,7 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Dalamud.Utility.Signatures; using Serilog; @@ -25,7 +25,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternal private readonly LocalPlugin plugin; private readonly SigScanner scanner; - private readonly ConcurrentBag trackedHooks = new(); + private readonly WeakConcurrentCollection trackedHooks = new(); /// /// Initializes a new instance of the class. diff --git a/Dalamud/Hooking/Internal/MinHookHook.cs b/Dalamud/Hooking/Internal/MinHookHook.cs index 4cebca0d0..0305f3c84 100644 --- a/Dalamud/Hooking/Internal/MinHookHook.cs +++ b/Dalamud/Hooking/Internal/MinHookHook.cs @@ -35,7 +35,7 @@ internal class MinHookHook : Hook where T : Delegate unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -76,6 +76,8 @@ internal class MinHookHook : Hook where T : Delegate HookManager.MultiHookTracker[this.Address][index] = null; } + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + base.Dispose(); } diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs index bf441a86d..2b0a4e9ce 100644 --- a/Dalamud/Hooking/Internal/ReloadedHook.cs +++ b/Dalamud/Hooking/Internal/ReloadedHook.cs @@ -30,7 +30,7 @@ internal class ReloadedHook : Hook where T : Delegate unhooker.TrimAfterHook(); - HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); + HookManager.TrackedHooks.TryAdd(this.HookId, new HookInfo(this, detour, callingAssembly)); } } @@ -63,6 +63,8 @@ internal class ReloadedHook : Hook where T : Delegate if (this.IsDisposed) return; + HookManager.TrackedHooks.TryRemove(this.HookId, out _); + this.Disable(); base.Dispose(); diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index 314f023da..eeafa98e7 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -258,8 +258,6 @@ internal class PluginStatWindow : Window ImGui.EndTabItem(); } - var toRemove = new List(); - if (ImGui.BeginTabItem("Hooks")) { ImGui.Checkbox("Show Dalamud Hooks", ref this.showDalamudHooks); @@ -291,9 +289,6 @@ internal class PluginStatWindow : Window { try { - if (trackedHook.Hook.IsDisposed) - toRemove.Add(guid); - if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) continue; @@ -355,14 +350,6 @@ internal class PluginStatWindow : Window } } - if (ImGui.IsWindowAppearing()) - { - foreach (var guid in toRemove) - { - HookManager.TrackedHooks.TryRemove(guid, out _); - } - } - ImGui.EndTabBar(); } } diff --git a/Dalamud/Utility/WeakConcurrentCollection.cs b/Dalamud/Utility/WeakConcurrentCollection.cs new file mode 100644 index 000000000..a3bc04651 --- /dev/null +++ b/Dalamud/Utility/WeakConcurrentCollection.cs @@ -0,0 +1,44 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Dalamud.Utility; + +/// +/// An implementation of a weak concurrent set based on a . +/// +/// The type of object that we're tracking. +public class WeakConcurrentCollection : ICollection where T : class +{ + private readonly ConditionalWeakTable cwt = new(); + + /// + public int Count => this.cwt.Count(); + + /// + public bool IsReadOnly => false; + + private IEnumerable Keys => this.cwt.Select(pair => pair.Key); + + /// + public IEnumerator GetEnumerator() => this.cwt.Select(pair => pair.Key).GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => this.cwt.Select(pair => pair.Key).GetEnumerator(); + + /// + public void Add(T item) => this.cwt.AddOrUpdate(item, null); + + /// + public void Clear() => this.cwt.Clear(); + + /// + public bool Contains(T item) => this.Keys.Contains(item); + + /// + public void CopyTo(T[] array, int arrayIndex) => this.Keys.ToArray().CopyTo(array, arrayIndex); + + /// + public bool Remove(T item) => this.cwt.Remove(item); +} From 82dd4629cb9deaac3ce2d1b81d828325a0035f5e Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Sun, 23 Mar 2025 16:13:27 -0600 Subject: [PATCH 289/375] SMNGauge: Fix typo, update CS (#2197) * chore(SMNGauge): update gauge to latest CS struct * chore(SMNGauge): Fix `AttunementTimerRemaining` typo * refactor(SMNGauge): Cast `AttunementType` to a new enum instead of leaving it raw * fix(SMNGauge): Use `AttunementType` for `IsAttuned` --- .../JobGauge/Enums/SummonAttunement.cs | 27 ++++++++++++++++++ .../ClientState/JobGauge/Types/SMNGauge.cs | 28 +++++++++++++++---- 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs new file mode 100644 index 000000000..e0b65966c --- /dev/null +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs @@ -0,0 +1,27 @@ +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +public enum SummonAttunement +{ + /// + /// No attunement. + /// + NONE = 0, + + /// + /// Attuned to the summon Ifrit. + /// Same as . + /// + IFRIT = 1, + + /// + /// Attuned to the summon Titan. + /// Same as . + /// + TITAN = 2, + + /// + /// Attuned to the summon Garuda. + /// Same as . + /// + GARUDA = 3, +} diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index 81be0e762..4c27ae83a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -22,10 +22,13 @@ public unsafe class SMNGauge : JobGaugeBase /// public ushort SummonTimerRemaining => this.Struct->SummonTimer; + [Obsolete("Typo fixed. Use AttunementTimerRemaining instead.", true)] + public ushort AttunmentTimerRemaining => this.AttunementTimerRemaining; + /// /// Gets the time remaining for the current attunement. /// - public ushort AttunmentTimerRemaining => this.Struct->AttunementTimer; + public ushort AttunementTimerRemaining => this.Struct->AttunementTimer; /// /// Gets the summon that will return after the current summon expires. @@ -40,10 +43,25 @@ public unsafe class SMNGauge : JobGaugeBase public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; /// - /// Gets the amount of aspected Attunment remaining. + /// Gets the amount of aspected Attunement remaining. /// + /// + /// As of 7.01, this should be treated as a bit field. + /// Use and instead. + /// public byte Attunement => this.Struct->Attunement; + /// + /// Gets the count of attunement cost resource available. + /// + public byte AttunementCount => this.Struct->AttunementCount; + + /// + /// Gets the type of attunement available. + /// Use the summon attuned accessors instead. + /// + public SummonAttunement AttunementType => (SummonAttunement)this.Struct->AttunementType; + /// /// Gets the current aether flags. /// Use the summon accessors instead. @@ -84,19 +102,19 @@ public unsafe class SMNGauge : JobGaugeBase /// Gets a value indicating whether if Ifrit is currently attuned. /// /// true or false. - public bool IsIfritAttuned => this.AetherFlags.HasFlag(AetherFlags.IfritAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + public bool IsIfritAttuned => this.AttunementType == SummonAttunement.IFRIT; /// /// Gets a value indicating whether if Titan is currently attuned. /// /// true or false. - public bool IsTitanAttuned => this.AetherFlags.HasFlag(AetherFlags.TitanAttuned) && !this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + public bool IsTitanAttuned => this.AttunementType == SummonAttunement.TITAN; /// /// Gets a value indicating whether if Garuda is currently attuned. /// /// true or false. - public bool IsGarudaAttuned => this.AetherFlags.HasFlag(AetherFlags.GarudaAttuned); + public bool IsGarudaAttuned => this.AttunementType == SummonAttunement.GARUDA; /// /// Gets a value indicating whether there are any Aetherflow stacks available. From 23c87015b635ce7fa58a6b3608a8e9b99c3ca3c3 Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Sun, 23 Mar 2025 16:14:16 -0600 Subject: [PATCH 290/375] add(DRKGauge): Add `DeliriumComboStep`, and its enum `DeliriumStep` (#2198) --- .../JobGauge/Enums/DeliriumStep.cs | 19 +++++++++++++++++++ .../ClientState/JobGauge/Types/DRKGauge.cs | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs new file mode 100644 index 000000000..cb35a83d2 --- /dev/null +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs @@ -0,0 +1,19 @@ +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +public enum DeliriumStep +{ + /// + /// Scarlet Delirium can be used. + /// + SCARLET_DELIRIUM = 0, + + /// + /// Comeuppance can be used. + /// + COMEUPPANCE = 1, + + /// + /// Torcleaver can be used. + /// + TORCLEAVER = 2, +} diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs index 834087040..c56d03db0 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs @@ -1,9 +1,12 @@ +using Dalamud.Game.ClientState.JobGauge.Enums; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; + namespace Dalamud.Game.ClientState.JobGauge.Types; /// /// In-memory DRK job gauge. /// -public unsafe class DRKGauge : JobGaugeBase +public unsafe class DRKGauge : JobGaugeBase { /// /// Initializes a new instance of the class. @@ -34,4 +37,16 @@ public unsafe class DRKGauge : JobGaugeBase /// true or false. public bool HasDarkArts => this.Struct->DarkArtsState > 0; + + /// + /// Gets the step of the Delirium Combo (Scarlet Delirium, Comeuppance, + /// Torcleaver) that the player is on.
+ /// Does not in any way consider whether the player is still under Delirium, or + /// if the player still has stacks of Delirium to use. + ///
+ /// + /// Value will persist until combo is finished OR + /// if the combo is not completed then the value will stay until about halfway into Delirium's cooldown. + /// + public DeliriumStep DeliriumComboStep => (DeliriumStep)this.Struct->DeliriumStep; } From 6a1cea731f5c33dd9e8789933eca43c5b2acd4eb Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 23 Mar 2025 18:11:03 -0700 Subject: [PATCH 291/375] chore: remove obsoleted AppendMacroString methods --- Dalamud/Utility/SeStringExtensions.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 057759e1e..f50f19d8e 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -38,24 +38,6 @@ public static class SeStringExtensions /// The re-parsed Dalamud SeString. public static DSeString ToDalamudString(this ReadOnlySeStringSpan originalString) => DSeString.Parse(originalString.Data); - /// Compiles and appends a macro string. - /// Target SeString builder. - /// Macro string in UTF-8 to compile and append to . - /// this for method chaining. - [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.", true)] - [Api12ToDo("Remove")] - public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => - ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); - - /// Compiles and appends a macro string. - /// Target SeString builder. - /// Macro string in UTF-16 to compile and append to . - /// this for method chaining. - [Obsolete($"Use {nameof(LSeStringBuilder)}.{nameof(LSeStringBuilder.AppendMacroString)} directly instead.", true)] - [Api12ToDo("Remove")] - public static LSeStringBuilder AppendMacroString(this LSeStringBuilder ssb, ReadOnlySpan macroString) => - ssb.AppendMacroString(macroString, new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError }); - /// Compiles and appends a macro string. /// Target SeString builder. /// Macro string in UTF-8 to compile and append to . From c3df199e29866ffd7cb99061338ad12611c75e3a Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 23 Mar 2025 18:12:41 -0700 Subject: [PATCH 292/375] chore: Obsolete accessing ObjectTable off main thread --- .../Game/ClientState/Objects/ObjectTable.cs | 42 ++++--------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 6aa7fd8ad..6b7f5f193 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -66,7 +66,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable { get { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); return (nint)(&CSGameObjectManager.Instance()->Objects); } @@ -80,7 +80,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable { get { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); return (index >= objectTableLength || index < 0) ? null : this.cachedObjectTable[index].Update(); } @@ -89,7 +89,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable /// public IGameObject? SearchById(ulong gameObjectId) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); if (gameObjectId is 0) return null; @@ -106,7 +106,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable /// public IGameObject? SearchByEntityId(uint entityId) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); if (entityId is 0 or 0xE0000000) return null; @@ -123,7 +123,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable /// public unsafe nint GetObjectAddress(int index) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); return (index >= objectTableLength || index < 0) ? nint.Zero : (nint)this.cachedObjectTable[index].Address; } @@ -131,7 +131,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable /// public unsafe IGameObject? CreateObjectReference(nint address) { - _ = this.WarnMultithreadedUsage(); + ThreadSafety.AssertMainThread(); if (this.clientState.LocalContentId == 0) return null; @@ -155,27 +155,6 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable }; } - [Api12ToDo("Use ThreadSafety.AssertMainThread() instead of this.")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool WarnMultithreadedUsage() - { - if (ThreadSafety.IsMainThread) - return false; - - var n = Environment.TickCount64; - if (this.nextMultithreadedUsageWarnTime < n) - { - this.nextMultithreadedUsageWarnTime = n + 30000; - - Log.Warning( - "{plugin} is accessing {objectTable} outside the main thread. This is deprecated.", - Service.Get().FindCallingPlugin()?.Name ?? "", - nameof(ObjectTable)); - } - - return true; - } - /// Stores an object table entry, with preallocated concrete types. /// Initializes a new instance of the struct. /// A pointer to the object table entry this entry should be pointing to. @@ -228,14 +207,7 @@ internal sealed partial class ObjectTable /// public IEnumerator GetEnumerator() { - // If something's trying to enumerate outside the framework thread, we use the ObjectPool. - if (this.WarnMultithreadedUsage()) - { - // let's not - var e = this.multiThreadedEnumerators.Get(); - e.InitializeForPooledObjects(this); - return e; - } + ThreadSafety.AssertMainThread(); // If we're on the framework thread, see if there's an already allocated enumerator available for use. foreach (ref var x in this.frameworkThreadEnumerators.AsSpan()) From 1676522ea8f0a7f3447c3e5ff2a026260d8034d4 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 29 Dec 2024 16:53:16 +0100 Subject: [PATCH 293/375] clamp value of all easings by default (cherry picked from commit 1aada983931d9e45a250eebbc17c8b782d07701b) --- Dalamud/Interface/Animation/Easing.cs | 13 ++++++++----- .../Interface/Animation/EasingFunctions/InCirc.cs | 2 +- .../Interface/Animation/EasingFunctions/InCubic.cs | 2 +- .../Animation/EasingFunctions/InElastic.cs | 10 +++++----- .../Animation/EasingFunctions/InOutCirc.cs | 6 +++--- .../Animation/EasingFunctions/InOutCubic.cs | 2 +- .../Animation/EasingFunctions/InOutElastic.cs | 14 +++++++------- .../Animation/EasingFunctions/InOutQuint.cs | 2 +- .../Animation/EasingFunctions/InOutSine.cs | 2 +- .../Interface/Animation/EasingFunctions/InQuint.cs | 2 +- .../Interface/Animation/EasingFunctions/InSine.cs | 2 +- .../Interface/Animation/EasingFunctions/OutCirc.cs | 2 +- .../Animation/EasingFunctions/OutCubic.cs | 2 +- .../Animation/EasingFunctions/OutElastic.cs | 10 +++++----- .../Animation/EasingFunctions/OutQuint.cs | 2 +- .../Interface/Animation/EasingFunctions/OutSine.cs | 2 +- .../Internal/Windows/TitleScreenMenuWindow.cs | 4 ++-- 17 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index c6d6149af..a48300a22 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -1,14 +1,11 @@ using System.Diagnostics; using System.Numerics; -using Dalamud.Utility; - namespace Dalamud.Interface.Animation; /// /// Base class facilitating the implementation of easing functions. /// -[Api12ToDo("Re-apply https://github.com/goatcorp/Dalamud/commit/1aada983931d9e45a250eebbc17c8b782d07701b")] public abstract class Easing { // TODO: Use game delta time here instead @@ -46,9 +43,15 @@ public abstract class Easing public bool IsInverse { get; set; } /// - /// Gets or sets the current value of the animation, from 0 to 1. + /// Gets the current value of the animation, from 0 to 1. /// - public double Value + public double Value => Math.Clamp(this.ValueUnclamped, 0, 1); + + /// + /// Gets or sets the current value of the animation, not limited to a range of 0 to 1. + /// Will return numbers outside of this range if accessed beyond animation time. + /// + public double ValueUnclamped { get { diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs index c467104c5..d94e9fc9f 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -19,6 +19,6 @@ public class InCirc : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); + this.ValueUnclamped = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs index 78f6774ac..64ebc5ba3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -19,6 +19,6 @@ public class InCubic : Easing public override void Update() { var p = this.Progress; - this.Value = p * p * p; + this.ValueUnclamped = p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs index c53c3d587..2e834e41c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -21,10 +21,10 @@ public class InElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs index 71a598dfb..a63ab648a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -19,8 +19,8 @@ public class InOutCirc : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 - ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 - : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; + this.ValueUnclamped = p < 0.5 + ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 + : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs index 07bcfa28d..4083265b7 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -19,6 +19,6 @@ public class InOutCubic : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); + this.ValueUnclamped = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs index f78f9f336..f27726038 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -21,12 +21,12 @@ public class InOutElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : p < 0.5 - ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 - : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : p < 0.5 + ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 + : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs index 64ab98b16..e08129b25 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -19,6 +19,6 @@ public class InOutQuint : Easing public override void Update() { var p = this.Progress; - this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); + this.ValueUnclamped = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs index 2f347ff80..cb940d87d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -19,6 +19,6 @@ public class InOutSine : Easing public override void Update() { var p = this.Progress; - this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; + this.ValueUnclamped = -(Math.Cos(Math.PI * p) - 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs index a5ab5a22c..827e0e21b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -19,6 +19,6 @@ public class InQuint : Easing public override void Update() { var p = this.Progress; - this.Value = p * p * p * p * p; + this.ValueUnclamped = p * p * p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs index fa079baad..61affa10a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -19,6 +19,6 @@ public class InSine : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Cos((p * Math.PI) / 2); + this.ValueUnclamped = 1 - Math.Cos((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs index b0d3b895a..980e29a81 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -19,6 +19,6 @@ public class OutCirc : Easing public override void Update() { var p = this.Progress; - this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); + this.ValueUnclamped = Math.Sqrt(1 - Math.Pow(p - 1, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs index 9c1bb57dc..e1a79c35b 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -19,6 +19,6 @@ public class OutCubic : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 3); + this.ValueUnclamped = 1 - Math.Pow(1 - p, 3); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs index 6a4fcd6dc..1f525b404 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -21,10 +21,10 @@ public class OutElastic : Easing public override void Update() { var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; + this.ValueUnclamped = p == 0 + ? 0 + : p == 1 + ? 1 + : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs index a3174e762..24a2255d3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -19,6 +19,6 @@ public class OutQuint : Easing public override void Update() { var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 5); + this.ValueUnclamped = 1 - Math.Pow(1 - p, 5); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs index ba82232b3..a376d7f57 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -19,6 +19,6 @@ public class OutSine : Easing public override void Update() { var p = this.Progress; - this.Value = Math.Sin((p * Math.PI) / 2); + this.ValueUnclamped = Math.Sin((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index ce8c192a4..c1f0b2a67 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -251,7 +251,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.fadeOutEasing.Update(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)Math.Max(this.fadeOutEasing.Value, 0))) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value)) { var i = 0; foreach (var entry in entries) @@ -392,7 +392,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)Math.Min(logoEasing.Value, 1) : 0f); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); } // Drop shadow From 3074115b345e51ad354674adec30cbaa2de58efe Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 23 Mar 2025 18:27:48 -0700 Subject: [PATCH 294/375] chore: Add api 13 tracker --- Dalamud/Utility/Api13ToDoAttribute.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Dalamud/Utility/Api13ToDoAttribute.cs diff --git a/Dalamud/Utility/Api13ToDoAttribute.cs b/Dalamud/Utility/Api13ToDoAttribute.cs new file mode 100644 index 000000000..576401cda --- /dev/null +++ b/Dalamud/Utility/Api13ToDoAttribute.cs @@ -0,0 +1,24 @@ +namespace Dalamud.Utility; + +/// +/// Utility class for marking something to be changed for API 13, for ease of lookup. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false)] +internal sealed class Api13ToDoAttribute : Attribute +{ + /// + /// Marks that this should be made internal. + /// + public const string MakeInternal = "Make internal."; + + /// + /// Initializes a new instance of the class. + /// + /// The explanation. + /// The explanation 2. + public Api13ToDoAttribute(string what, string what2 = "") + { + _ = what; + _ = what2; + } +} From e84654005e18e80ccf654df9372a96817c0aebae Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 23 Mar 2025 18:28:07 -0700 Subject: [PATCH 295/375] fix: Force devs to choose between ValueClamped and ValueUnclamped --- Dalamud/Interface/Animation/Easing.cs | 11 ++++++++++- .../Internal/ActiveNotification.ImGui.cs | 12 ++++++------ .../ImGuiNotification/Internal/ActiveNotification.cs | 2 +- .../Internal/Windows/ComponentDemoWindow.cs | 2 +- .../Internal/Windows/TitleScreenMenuWindow.cs | 10 +++++----- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index a48300a22..8191487f4 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -1,6 +1,8 @@ using System.Diagnostics; using System.Numerics; +using Dalamud.Utility; + namespace Dalamud.Interface.Animation; /// @@ -42,10 +44,17 @@ public abstract class Easing /// public bool IsInverse { get; set; } + /// + /// Gets the current value of the animation, following unclamped logic. + /// + [Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)] + [Api13ToDo("Map this field to ValueClamped, probably.")] + public double Value => this.ValueUnclamped; + /// /// Gets the current value of the animation, from 0 to 1. /// - public double Value => Math.Clamp(this.ValueUnclamped, 0, 1); + public double ValueClamped => Math.Clamp(this.ValueUnclamped, 0, 1); /// /// Gets or sets the current value of the animation, not limited to a range of 0 to 1. diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs index 16d58bea5..ab41c5521 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs @@ -21,8 +21,8 @@ internal sealed partial class ActiveNotification var opacity = Math.Clamp( (float)(this.hideEasing.IsRunning - ? (this.hideEasing.IsDone || ReducedMotions ? 0 : 1f - this.hideEasing.Value) - : (this.showEasing.IsDone || ReducedMotions ? 1 : this.showEasing.Value)), + ? (this.hideEasing.IsDone || ReducedMotions ? 0 : 1f - this.hideEasing.ValueClamped) + : (this.showEasing.IsDone || ReducedMotions ? 1 : this.showEasing.ValueClamped)), 0f, 1f); if (opacity <= 0) @@ -106,7 +106,7 @@ internal sealed partial class ActiveNotification } else if (this.expandoEasing.IsRunning) { - var easedValue = ReducedMotions ? 1f : (float)this.expandoEasing.Value; + var easedValue = ReducedMotions ? 1f : (float)this.expandoEasing.ValueClamped; if (this.underlyingNotification.Minimized) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, opacity * (1f - easedValue)); else @@ -295,8 +295,8 @@ internal sealed partial class ActiveNotification { relativeOpacity = this.underlyingNotification.Minimized - ? 1f - (float)this.expandoEasing.Value - : (float)this.expandoEasing.Value; + ? 1f - (float)this.expandoEasing.ValueClamped + : (float)this.expandoEasing.ValueClamped; } else { @@ -543,7 +543,7 @@ internal sealed partial class ActiveNotification float barL, barR; if (this.DismissReason is not null) { - var v = this.hideEasing.IsDone || ReducedMotions ? 0f : 1f - (float)this.hideEasing.Value; + var v = this.hideEasing.IsDone || ReducedMotions ? 0f : 1f - (float)this.hideEasing.ValueClamped; var midpoint = (this.prevProgressL + this.prevProgressR) / 2f; var length = (this.prevProgressR - this.prevProgressL) / 2f; barL = midpoint - (length * v); diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs index 607c7c49d..89a175a9e 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs @@ -226,7 +226,7 @@ internal sealed partial class ActiveNotification : IActiveNotification if (Math.Abs(underlyingProgress - this.progressBefore) < 0.000001f || this.progressEasing.IsDone || ReducedMotions) return underlyingProgress; - var state = ReducedMotions ? 1f : Math.Clamp((float)this.progressEasing.Value, 0f, 1f); + var state = ReducedMotions ? 1f : Math.Clamp((float)this.progressEasing.ValueClamped, 0f, 1f); return this.progressBefore + (state * (underlyingProgress - this.progressBefore)); } } diff --git a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs index 0b704990b..c0d2e4c61 100644 --- a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs @@ -147,7 +147,7 @@ internal sealed class ComponentDemoWindow : Window ImGui.Bullet(); ImGui.SetCursorPos(cursor + new Vector2(0, 10)); - ImGui.Text($"{easing.GetType().Name} ({easing.Value})"); + ImGui.Text($"{easing.GetType().Name} ({easing.ValueClamped})"); ImGuiHelpers.ScaledDummy(5); } } diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index c1f0b2a67..3b6140b8c 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -204,7 +204,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable moveEasing.Update(); var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale; - var pos = moveEasing.Value * finalPos; + var pos = moveEasing.ValueClamped * finalPos; // FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment. if (moveEasing.IsDone) @@ -251,7 +251,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.fadeOutEasing.Update(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value)) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.ValueClamped)) { var i = 0; foreach (var entry in entries) @@ -317,7 +317,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable var initialCursor = ImGui.GetCursorPos(); - using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.Value)) + using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.ValueClamped)) { var texture = this.shadeTexture.Value; ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height) * scale); @@ -367,7 +367,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.Value); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, isFirst ? 1f : (float)logoEasing.ValueClamped); } else if (isFirst) { @@ -392,7 +392,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (overrideAlpha) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.Value : 0f); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, showText ? (float)logoEasing.ValueClamped : 0f); } // Drop shadow From aaa3e33a8b14c6f43602dfad73759a1623c1a12b Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 23 Mar 2025 18:37:44 -0700 Subject: [PATCH 296/375] chore: PluginLoadReason is flags --- Dalamud/Plugin/PluginLoadReason.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dalamud/Plugin/PluginLoadReason.cs b/Dalamud/Plugin/PluginLoadReason.cs index d4c1a3b26..de84e3368 100644 --- a/Dalamud/Plugin/PluginLoadReason.cs +++ b/Dalamud/Plugin/PluginLoadReason.cs @@ -3,6 +3,7 @@ namespace Dalamud.Plugin; /// /// This enum reflects reasons for loading a plugin. /// +[Flags] public enum PluginLoadReason { /// @@ -30,5 +31,3 @@ public enum PluginLoadReason /// Boot, } - -// TODO(api9): This should be a mask, so that we can combine Installer | ProfileLoaded From 8939cac80fc72349e26d555d2417483021c2cb83 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sun, 23 Mar 2025 18:45:15 -0700 Subject: [PATCH 297/375] fix: actually add values --- Dalamud/Plugin/PluginLoadReason.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dalamud/Plugin/PluginLoadReason.cs b/Dalamud/Plugin/PluginLoadReason.cs index de84e3368..2b494c549 100644 --- a/Dalamud/Plugin/PluginLoadReason.cs +++ b/Dalamud/Plugin/PluginLoadReason.cs @@ -9,25 +9,25 @@ public enum PluginLoadReason /// /// We don't know why this plugin was loaded. /// - Unknown, + Unknown = 1 << 0, /// /// This plugin was loaded because it was installed with the plugin installer. /// - Installer, + Installer = 1 << 1, /// /// This plugin was loaded because it was just updated. /// - Update, + Update = 1 << 2, /// /// This plugin was loaded because it was told to reload. /// - Reload, + Reload = 1 << 3, /// /// This plugin was loaded because the game was started or Dalamud was reinjected. /// - Boot, + Boot = 1 << 4, } From 9815cf1d88de1c6eca39b6ef5683e6564b13eb68 Mon Sep 17 00:00:00 2001 From: Vanillaaaa Date: Mon, 24 Mar 2025 12:38:47 +0800 Subject: [PATCH 298/375] perf: show whether plugin toggle is enable or disable (#2189) --- .../Profiles/PluginManagementCommandHandler.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs index ad5aad286..09cceebcb 100644 --- a/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs +++ b/Dalamud/Plugin/Internal/Profiles/PluginManagementCommandHandler.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -216,7 +216,12 @@ internal class PluginManagementCommandHandler : IInternalDisposableService this.chat.Print(onSuccess); } - + + if (operation == PluginCommandOperation.Toggle) + { + operation = plugin.State == PluginState.Loaded ? PluginCommandOperation.Disable : PluginCommandOperation.Enable; + } + switch (operation) { case PluginCommandOperation.Enable: @@ -235,14 +240,6 @@ internal class PluginManagementCommandHandler : IInternalDisposableService Loc.Localize("PluginCommandsDisableFailed", "Failed to disable plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) .ConfigureAwait(false); break; - case PluginCommandOperation.Toggle: - this.chat.Print(Loc.Localize("PluginCommandsToggling", "Toggling plugin \"{0}\"...").Format(plugin.Name)); - Task.Run(() => plugin.State == PluginState.Loaded ? plugin.UnloadAsync() : plugin.LoadAsync(PluginLoadReason.Installer)) - .ContinueWith(t => Continuation(t, - Loc.Localize("PluginCommandsToggleSuccess", "Plugin \"{0}\" toggled.").Format(plugin.Name), - Loc.Localize("PluginCommandsToggleFailed", "Failed to toggle plugin \"{0}\". Please check the console for errors.").Format(plugin.Name))) - .ConfigureAwait(false); - break; default: throw new ArgumentOutOfRangeException(nameof(operation), operation, null); } From 7cac19ce814e3e2b7ccec9b3c4eb3fb93babf1bf Mon Sep 17 00:00:00 2001 From: marzent Date: Mon, 24 Mar 2025 16:42:24 +0100 Subject: [PATCH 299/375] Implement dalamud-platform launch argument (#1452) * implement dalamud platform launch arg * implement cross-platform gamePath fallback * refactor platform detection heuristic * add cross platform dalamud runtime detection --- Dalamud.Boot/DalamudStartInfo.cpp | 1 + Dalamud.Boot/DalamudStartInfo.h | 1 + Dalamud.Boot/utils.cpp | 5 + Dalamud.Common/DalamudStartInfo.cs | 9 +- Dalamud.Common/OSPlatformConverter.cs | 78 ++++++++++++ Dalamud.Injector/EntryPoint.cs | 113 ++++++++++++++---- Dalamud.Injector/NativeFunctions.cs | 42 +++++++ .../Internal/EnvironmentConfiguration.cs | 5 - Dalamud/EntryPoint.cs | 4 + Dalamud/Utility/Util.cs | 50 +------- lib/CoreCLR/boot.cpp | 92 +++++++++----- 11 files changed, 299 insertions(+), 101 deletions(-) create mode 100644 Dalamud.Common/OSPlatformConverter.cs diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 52f201fea..4aa7d46dd 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -109,6 +109,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory); config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory); config.Language = json.value("Language", config.Language); + config.Platform = json.value("Platform", config.Platform); config.GameVersion = json.value("GameVersion", config.GameVersion); config.TroubleshootingPackData = json.value("TroubleshootingPackData", std::string{}); config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index d5a6a6aec..64450e290 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -47,6 +47,7 @@ struct DalamudStartInfo { std::string PluginDirectory; std::string AssetDirectory; ClientLanguage Language = ClientLanguage::English; + std::string Platform; std::string GameVersion; std::string TroubleshootingPackData; int DelayInitializeMs = 0; diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index bbe47db82..dbfcf39ee 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include "DalamudStartInfo.h" #include "utils.h" @@ -584,6 +585,10 @@ std::vector utils::get_env_list(const wchar_t* pcszName) { return res; } +bool utils::is_running_on_wine() { + return g_startInfo.Platform != "WINDOWS"; +} + std::filesystem::path utils::get_module_path(HMODULE hModule) { std::wstring buf(MAX_PATH, L'\0'); while (true) { diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index ca81d1281..eb2410cfd 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using Dalamud.Common.Game; using Newtonsoft.Json; @@ -15,7 +16,7 @@ public record DalamudStartInfo /// public DalamudStartInfo() { - // ignored + this.Platform = OSPlatform.Create("UNKNOWN"); } /// @@ -58,6 +59,12 @@ public record DalamudStartInfo /// public ClientLanguage Language { get; set; } = ClientLanguage.English; + /// + /// Gets or sets the underlying platform�Dalamud runs on. + /// + [JsonConverter(typeof(OSPlatformConverter))] + public OSPlatform Platform { get; set; } + /// /// Gets or sets the current game version code. /// diff --git a/Dalamud.Common/OSPlatformConverter.cs b/Dalamud.Common/OSPlatformConverter.cs new file mode 100644 index 000000000..62d2996d4 --- /dev/null +++ b/Dalamud.Common/OSPlatformConverter.cs @@ -0,0 +1,78 @@ +using System.Runtime.InteropServices; +using Newtonsoft.Json; + +namespace Dalamud.Common; + +/// +/// Converts a to and from a string (e.g. "FreeBSD"). +/// +public sealed class OSPlatformConverter : JsonConverter +{ + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + else if (value is OSPlatform) + { + writer.WriteValue(value.ToString()); + } + else + { + throw new JsonSerializationException("Expected OSPlatform object value"); + } + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing property value of the JSON that is being converted. + /// The calling serializer. + /// The object value. + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + else + { + if (reader.TokenType == JsonToken.String) + { + try + { + return OSPlatform.Create((string)reader.Value!); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error parsing OSPlatform string: {reader.Value}", ex); + } + } + else + { + throw new JsonSerializationException($"Unexpected token or value when parsing OSPlatform. Token: {reader.TokenType}, Value: {reader.Value}"); + } + } + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(OSPlatform); + } +} diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 9e7033c4d..0ee2e5507 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -263,6 +263,35 @@ namespace Dalamud.Injector } } + private static OSPlatform DetectPlatformHeuristic() + { + var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll"); + var wineServerCallPtr = NativeFunctions.GetProcAddress(ntdll, "wine_server_call"); + var wineGetHostVersionPtr = NativeFunctions.GetProcAddress(ntdll, "wine_get_host_version"); + var winePlatform = GetWinePlatform(wineGetHostVersionPtr); + var isWine = wineServerCallPtr != nint.Zero; + + static unsafe string? GetWinePlatform(nint wineGetHostVersionPtr) + { + if (wineGetHostVersionPtr == nint.Zero) return null; + + var methodDelegate = (delegate* unmanaged[Fastcall])wineGetHostVersionPtr; + methodDelegate(out var platformPtr, out var _); + + if (platformPtr == null) return null; + + return Marshal.PtrToStringAnsi((nint)platformPtr); + } + + if (!isWine) + return OSPlatform.Windows; + + if (winePlatform == "Darwin") + return OSPlatform.OSX; + + return OSPlatform.Linux; + } + private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List args) { int len; @@ -278,6 +307,7 @@ namespace Dalamud.Injector var logName = startInfo.LogName; var logPath = startInfo.LogPath; var languageStr = startInfo.Language.ToString().ToLowerInvariant(); + var platformStr = startInfo.Platform.ToString().ToLowerInvariant(); var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant(); var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; @@ -307,6 +337,10 @@ namespace Dalamud.Injector { languageStr = args[i][key.Length..].ToLowerInvariant(); } + else if (args[i].StartsWith(key = "--dalamud-platform=")) + { + platformStr = args[i][key.Length..].ToLowerInvariant(); + } else if (args[i].StartsWith(key = "--dalamud-tspack-b64=")) { troubleshootingData = Encoding.UTF8.GetString(Convert.FromBase64String(args[i][key.Length..])); @@ -378,11 +412,35 @@ namespace Dalamud.Injector throw new CommandLineException($"\"{languageStr}\" is not a valid supported language."); } + OSPlatform platform; + if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len]) // covers both win32 and Windows + { + platform = OSPlatform.Windows; + } + else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "linux").Length))] == key[0..len]) + { + platform = OSPlatform.Linux; + } + else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "macos").Length))] == key[0..len]) + { + platform = OSPlatform.OSX; + } + else if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "osx").Length))] == key[0..len]) + { + platform = OSPlatform.OSX; + } + else + { + platform = DetectPlatformHeuristic(); + Log.Warning("Heuristically determined host system platform as {platform}", platform); + } + startInfo.WorkingDirectory = workingDirectory; startInfo.ConfigurationPath = configurationPath; startInfo.PluginDirectory = pluginDirectory; startInfo.AssetDirectory = assetDirectory; startInfo.Language = clientLanguage; + startInfo.Platform = platform; startInfo.DelayInitializeMs = delayInitializeMs; startInfo.GameVersion = null; startInfo.TroubleshootingPackData = troubleshootingData; @@ -465,7 +523,7 @@ namespace Dalamud.Injector } Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]"); - Console.WriteLine(" [--dalamud-plugin-directory=path]"); + Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-platform=win32|linux|macOS]"); Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]"); Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]"); @@ -732,15 +790,42 @@ namespace Dalamud.Injector { try { - var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); - var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); - gamePath = Path.Combine(JsonSerializer.CreateDefault().Deserialize>(new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], "game", "ffxiv_dx11.exe"); - Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); + if (dalamudStartInfo.Platform == OSPlatform.Windows) + { + var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var xivlauncherDir = Path.Combine(appDataDir, "XIVLauncher"); + var launcherConfigPath = Path.Combine(xivlauncherDir, "launcherConfigV3.json"); + gamePath = Path.Combine( + JsonSerializer.CreateDefault() + .Deserialize>( + new JsonTextReader(new StringReader(File.ReadAllText(launcherConfigPath))))["GamePath"], + "game", + "ffxiv_dx11.exe"); + Log.Information("Using game installation path configuration from from XIVLauncher: {0}", gamePath); + } + else if (dalamudStartInfo.Platform == OSPlatform.Linux) + { + var homeDir = $"Z:\\home\\{Environment.UserName}"; + var xivlauncherDir = Path.Combine(homeDir, ".xlcore"); + var launcherConfigPath = Path.Combine(xivlauncherDir, "launcher.ini"); + var config = File.ReadAllLines(launcherConfigPath) + .Where(line => line.Contains('=')) + .ToDictionary(line => line.Split('=')[0], line => line.Split('=')[1]); + gamePath = Path.Combine("Z:" + config["GamePath"].Replace('/', '\\'), "game", "ffxiv_dx11.exe"); + Log.Information("Using game installation path configuration from from XIVLauncher Core: {0}", gamePath); + } + else + { + var homeDir = $"Z:\\Users\\{Environment.UserName}"; + var xomlauncherDir = Path.Combine(homeDir, "Library", "Application Support", "XIV on Mac"); + // we could try to parse the binary plist file here if we really wanted to... + gamePath = Path.Combine(xomlauncherDir, "ffxiv", "game", "ffxiv_dx11.exe"); + Log.Information("Using default game installation path from XOM: {0}", gamePath); + } } catch (Exception) { - Log.Error("Failed to read launcherConfigV3.json to get the set-up game path, please specify one using -g"); + Log.Error("Failed to read launcher config to get the set-up game path, please specify one using -g"); return -1; } @@ -795,20 +880,6 @@ namespace Dalamud.Injector if (encryptArguments) { var rawTickCount = (uint)Environment.TickCount; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - [System.Runtime.InteropServices.DllImport("c")] -#pragma warning disable SA1300 - static extern ulong clock_gettime_nsec_np(int clockId); -#pragma warning restore SA1300 - - const int CLOCK_MONOTONIC_RAW = 4; - var rawTickCountFixed = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000; - Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed); - rawTickCount = (uint)rawTickCountFixed; - } - var ticks = rawTickCount & 0xFFFF_FFFFu; var key = ticks & 0xFFFF_0000u; gameArguments.Insert(0, $"T={ticks}"); diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs index 2a4654aaf..06add3acc 100644 --- a/Dalamud.Injector/NativeFunctions.cs +++ b/Dalamud.Injector/NativeFunctions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Dalamud.Injector @@ -910,5 +911,46 @@ namespace Dalamud.Injector uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. + /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To + /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. + /// + /// + /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default + /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate + /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure + /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules + /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns + /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve + /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return + /// value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetModuleHandleW(string lpModuleName); + + /// + /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// + /// + /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary, + /// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules + /// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be + /// in the low-order word; the high-order word must be zero. + /// + /// + /// If the function succeeds, the return value is the address of the exported function or variable. If the function + /// fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); } } diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs index 2df9ec5fe..11a8d3567 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -5,11 +5,6 @@ namespace Dalamud.Configuration.Internal; ///
internal class EnvironmentConfiguration { - /// - /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. - /// - public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); - /// /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. /// diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 89e4e0e5e..ebfc975ff 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -181,7 +181,11 @@ public sealed class EntryPoint // Apply common fixes for culture issues CultureFixes.Apply(); + + // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; + // Currently VEH is not fully functional on WINE if (!Util.IsWine()) InitSymbolHandler(info); diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 7724c68e0..2fcc4806a 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -19,6 +19,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; +using Dalamud.Logging.Internal; using Dalamud.Interface.Utility.Raii; using Dalamud.Support; using ImGuiNET; @@ -500,55 +501,14 @@ public static class Util /// Determine if Dalamud is currently running within a Wine context (e.g. either on macOS or Linux). This method /// will not return information about the host operating system. ///
- /// Returns true if Wine is detected, false otherwise. - public static bool IsWine() - { - if (EnvironmentConfiguration.XlWineOnLinux) return true; - if (Environment.GetEnvironmentVariable("XL_PLATFORM") is not null and not "Windows") return true; - - var ntdll = NativeFunctions.GetModuleHandleW("ntdll.dll"); - - // Test to see if any Wine specific exports exist. If they do, then we are running on Wine. - // The exports "wine_get_version", "wine_get_build_id", and "wine_get_host_version" will tend to be hidden - // by most Linux users (else FFXIV will want a macOS license), so we will additionally check some lesser-known - // exports as well. - return AnyProcExists( - ntdll, - "wine_get_version", - "wine_get_build_id", - "wine_get_host_version", - "wine_server_call", - "wine_unix_to_nt_file_name"); - - bool AnyProcExists(nint handle, params string[] procs) => - procs.Any(p => NativeFunctions.GetProcAddress(handle, p) != nint.Zero); - } + /// Returns true if running on Wine, false otherwise. + public static bool IsWine() => Service.Get().StartInfo.Platform != OSPlatform.Windows; /// - /// Gets the best guess for the current host's platform based on the XL_PLATFORM environment variable or - /// heuristics. + /// Gets the current host's platform based on the injector launch arguments or heuristics. /// - /// - /// macOS users running without XL_PLATFORM being set will be reported as Linux users. Due to the way our - /// Wines work, there isn't a great (consistent) way to split the two apart if we're not told. - /// /// Returns the that Dalamud is currently running on. - public static OSPlatform GetHostPlatform() - { - switch (Environment.GetEnvironmentVariable("XL_PLATFORM")) - { - case "Windows": return OSPlatform.Windows; - case "MacOS": return OSPlatform.OSX; - case "Linux": return OSPlatform.Linux; - } - - // n.b. we had some fancy code here to check if the Wine host version returned "Darwin" but apparently - // *all* our Wines report Darwin if exports aren't hidden. As such, it is effectively impossible (without some - // (very cursed and inaccurate heuristics) to determine if we're on macOS or Linux unless we're explicitly told - // by our launcher. See commit a7aacb15e4603a367e2f980578271a9a639d8852 for the old check. - - return IsWine() ? OSPlatform.Linux : OSPlatform.Windows; - } + public static OSPlatform GetHostPlatform() => Service.Get().StartInfo.Platform; /// /// Heuristically determine if the Windows version is higher than Windows 11's first build. diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index 723a1ed21..f5e626c3f 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "CoreCLR.h" #include "..\..\Dalamud.Boot\logging.h" @@ -27,6 +28,64 @@ void ConsoleTeardown() std::optional g_clr; +static wchar_t* GetRuntimePath() +{ + int result; + std::wstring buffer; + wchar_t* runtime_path; + wchar_t* _appdata; + DWORD username_len = UNLEN + 1; + wchar_t username[UNLEN + 1]; + + buffer.resize(0); + result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0); + + if (result) + { + buffer.resize(result); // The first pass returns the required length + result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); + return _wcsdup(buffer.c_str()); + } + + // Detect Windows first + result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata); + + if (result != 0) + { + logging::E("Unable to get RoamingAppData path (err={})", result); + return NULL; + } + + std::filesystem::path fs_app_data(_appdata); + runtime_path = _wcsdup(fs_app_data.append("XIVLauncher").append("runtime").c_str()); + if (std::filesystem::exists(runtime_path)) + return runtime_path; + free(runtime_path); + + // Next XLCore on Linux + result = GetUserNameW(username, &username_len); + if (result != 0) + { + logging::E("Unable to get user name (err={})", result); + return NULL; + } + + std::filesystem::path homeDir = L"Z:\\home\\" + std::wstring(username); + runtime_path = _wcsdup(homeDir.append(".xlcore").append("runtime").c_str()); + if (std::filesystem::exists(runtime_path)) + return runtime_path; + free(runtime_path); + + // Finally XOM + homeDir = L"Z:\\Users\\" + std::wstring(username); + runtime_path = _wcsdup(homeDir.append("Library").append("Application Suppor").append("XIV on Mac").append("runtime").c_str()); + if (std::filesystem::exists(runtime_path)) + return runtime_path; + free(runtime_path); + + return NULL; +} + HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, @@ -62,31 +121,12 @@ HRESULT InitializeClrAndGetEntryPoint( SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0"); - wchar_t* dotnet_path; - wchar_t* _appdata; + wchar_t* dotnet_path = GetRuntimePath(); - std::wstring buffer; - buffer.resize(0); - result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], 0); - - if (result) + if (!dotnet_path || !std::filesystem::exists(dotnet_path)) { - buffer.resize(result); // The first pass returns the required length - result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); - dotnet_path = _wcsdup(buffer.c_str()); - } - else - { - result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata); - - if (result != 0) - { - logging::E("Unable to get RoamingAppData path (err={})", result); - return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); - } - - std::filesystem::path fs_app_data(_appdata); - dotnet_path = _wcsdup(fs_app_data.append("XIVLauncher").append("runtime").c_str()); + logging::E("Error: Unable to find .NET runtime path"); + return 1; } // =========================================================================== // @@ -95,12 +135,6 @@ HRESULT InitializeClrAndGetEntryPoint( logging::I("with config_path: {}", runtimeconfig_path); logging::I("with module_path: {}", module_path); - if (!std::filesystem::exists(dotnet_path)) - { - logging::E("Error: Unable to find .NET runtime path"); - return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); - } - get_hostfxr_parameters init_parameters { sizeof(get_hostfxr_parameters), From fdbfdbb2cdc348e1c7bb114287cc93c16cb34967 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Mon, 24 Mar 2025 17:00:27 +0100 Subject: [PATCH 300/375] Add SeStringEvaluator service (#2188) * Add SeStringEvaluator service * Move DrawCopyableText into WidgetUtil * Use Icon2RemapTable in SeStringRenderer * Beautify some code * Make sure to use the correct language * Add SeString Creator widget * Fix getting local parameters * Update expressionNames * misc changes * Use InvariantCulture in TryResolveSheet * Add SeStringEvaluatorAgingStep * Fix item id comparisons * Add SheetRedirectResolverAgingStep * Add NounProcessorAgingStep * Update SeString.CreateItemLink This also adds the internal ItemUtil class. * Fix name of SeStringCreator widget * Add Global Parameters tab to SeStringCreatorWidget * Load widgets on demand * Update SeStringCreatorWidget * Resizable SeStringCreatorWidget panels * Update GamepadStateAgingStep * Experimental status was removed in #2144 * Update SheetRedirectResolver, rewrite Noun params * Fixes for 4 am code * Remove incorrect column offset I have no idea how that happened. * Draw names of linked things --------- Co-authored-by: Soreepeong <3614868+Soreepeong@users.noreply.github.com> Co-authored-by: KazWolfe --- Dalamud/Game/ActionKind.cs | 89 + Dalamud/Game/Gui/GameGui.cs | 2 +- .../Internal/SeStringBuilderIconWrap.cs | 30 + .../Evaluator/Internal/SeStringContext.cs | 83 + .../Evaluator/Internal/SheetRedirectFlags.cs | 49 + .../Internal/SheetRedirectResolver.cs | 232 ++ .../Game/Text/Evaluator/SeStringEvaluator.cs | 1995 +++++++++++++++++ .../Game/Text/Evaluator/SeStringParameter.cs | 79 + .../Text/Noun/Enums/EnglishArticleType.cs | 17 + .../Game/Text/Noun/Enums/FrenchArticleType.cs | 32 + .../Game/Text/Noun/Enums/GermanArticleType.cs | 37 + .../Text/Noun/Enums/JapaneseArticleType.cs | 17 + Dalamud/Game/Text/Noun/NounParams.cs | 73 + Dalamud/Game/Text/Noun/NounProcessor.cs | 461 ++++ .../SeStringHandling/Payloads/ItemPayload.cs | 20 +- .../Game/Text/SeStringHandling/SeString.cs | 80 +- .../Internal/SeStringRenderer.cs | 74 +- .../Internal/Windows/Data/DataWindow.cs | 12 +- .../Internal/Windows/Data/WidgetUtil.cs | 34 + .../Data/Widgets/AtkArrayDataBrowserWidget.cs | 42 +- .../Windows/Data/Widgets/FateTableWidget.cs | 28 +- .../Data/Widgets/NounProcessorWidget.cs | 207 ++ .../Data/Widgets/SeStringCreatorWidget.cs | 1276 +++++++++++ .../AgingSteps/GamepadStateAgingStep.cs | 40 +- .../AgingSteps/NounProcessorAgingStep.cs | 259 +++ .../AgingSteps/SeStringEvaluatorAgingStep.cs | 92 + .../SheetRedirectResolverAgingStep.cs | 130 ++ .../Windows/SelfTest/SelfTestWindow.cs | 3 + Dalamud/Interface/Utility/ImGuiHelpers.cs | 4 - Dalamud/Plugin/Services/ISeStringEvaluator.cs | 79 + Dalamud/Utility/ActionKindExtensions.cs | 26 + Dalamud/Utility/ClientLanguageExtensions.cs | 36 + Dalamud/Utility/ItemUtil.cs | 159 ++ Dalamud/Utility/ObjectKindExtensions.cs | 33 + Dalamud/Utility/SeStringExtensions.cs | 152 ++ Dalamud/Utility/StringExtensions.cs | 45 + 36 files changed, 5831 insertions(+), 196 deletions(-) create mode 100644 Dalamud/Game/ActionKind.cs create mode 100644 Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs create mode 100644 Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs create mode 100644 Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs create mode 100644 Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs create mode 100644 Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs create mode 100644 Dalamud/Game/Text/Evaluator/SeStringParameter.cs create mode 100644 Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs create mode 100644 Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs create mode 100644 Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs create mode 100644 Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs create mode 100644 Dalamud/Game/Text/Noun/NounParams.cs create mode 100644 Dalamud/Game/Text/Noun/NounProcessor.cs create mode 100644 Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs create mode 100644 Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs create mode 100644 Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs create mode 100644 Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs create mode 100644 Dalamud/Plugin/Services/ISeStringEvaluator.cs create mode 100644 Dalamud/Utility/ActionKindExtensions.cs create mode 100644 Dalamud/Utility/ItemUtil.cs create mode 100644 Dalamud/Utility/ObjectKindExtensions.cs diff --git a/Dalamud/Game/ActionKind.cs b/Dalamud/Game/ActionKind.cs new file mode 100644 index 000000000..9a574f9a8 --- /dev/null +++ b/Dalamud/Game/ActionKind.cs @@ -0,0 +1,89 @@ +namespace Dalamud.Game; + +/// +/// Enum describing possible action kinds. +/// +public enum ActionKind +{ + /// + /// A Trait. + /// + Trait = 0, + + /// + /// An Action. + /// + Action = 1, + + /// + /// A usable Item. + /// + Item = 2, // does not work? + + /// + /// A usable EventItem. + /// + EventItem = 3, // does not work? + + /// + /// An EventAction. + /// + EventAction = 4, + + /// + /// A GeneralAction. + /// + GeneralAction = 5, + + /// + /// A BuddyAction. + /// + BuddyAction = 6, + + /// + /// A MainCommand. + /// + MainCommand = 7, + + /// + /// A Companion. + /// + Companion = 8, // unresolved?! + + /// + /// A CraftAction. + /// + CraftAction = 9, + + /// + /// An Action (again). + /// + Action2 = 10, // what's the difference? + + /// + /// A PetAction. + /// + PetAction = 11, + + /// + /// A CompanyAction. + /// + CompanyAction = 12, + + /// + /// A Mount. + /// + Mount = 13, + + // 14-18 unused + + /// + /// A BgcArmyAction. + /// + BgcArmyAction = 19, + + /// + /// An Ornament. + /// + Ornament = 20, +} diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 7cd6d7360..1041464a7 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -323,7 +323,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui return ret; } - private void HandleActionHoverDetour(AgentActionDetail* hoverState, ActionKind actionKind, uint actionId, int a4, byte a5) + private void HandleActionHoverDetour(AgentActionDetail* hoverState, FFXIVClientStructs.FFXIV.Client.UI.Agent.ActionKind actionKind, uint actionId, int a4, byte a5) { this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); this.HoveredAction.ActionKind = (HoverActionKind)actionKind; diff --git a/Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs b/Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs new file mode 100644 index 000000000..65567d240 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SeStringBuilderIconWrap.cs @@ -0,0 +1,30 @@ +using Lumina.Text; + +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// Wraps payloads in an open and close icon, for example the Auto Translation open/close brackets. +/// +internal readonly struct SeStringBuilderIconWrap : IDisposable +{ + private readonly SeStringBuilder builder; + private readonly uint iconClose; + + /// + /// Initializes a new instance of the struct.
+ /// Appends an icon macro with on creation, and an icon macro with + /// on disposal. + ///
+ /// The builder to use. + /// The open icon id. + /// The close icon id. + public SeStringBuilderIconWrap(SeStringBuilder builder, uint iconOpen, uint iconClose) + { + this.builder = builder; + this.iconClose = iconClose; + this.builder.AppendIcon(iconOpen); + } + + /// + public void Dispose() => this.builder.AppendIcon(this.iconClose); +} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs b/Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs new file mode 100644 index 000000000..a32702f6c --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SeStringContext.cs @@ -0,0 +1,83 @@ +using System.Globalization; + +using Dalamud.Utility; + +using Lumina.Text; +using Lumina.Text.ReadOnly; + +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// A context wrapper used in . +/// +internal readonly ref struct SeStringContext +{ + /// + /// The to append text and macros to. + /// + internal readonly SeStringBuilder Builder; + + /// + /// A list of local parameters. + /// + internal readonly Span LocalParameters; + + /// + /// The target language, used for sheet lookups. + /// + internal readonly ClientLanguage Language; + + /// + /// Initializes a new instance of the struct. + /// + /// The to append text and macros to. + /// A list of local parameters. + /// The target language, used for sheet lookups. + internal SeStringContext(SeStringBuilder builder, Span localParameters, ClientLanguage language) + { + this.Builder = builder; + this.LocalParameters = localParameters; + this.Language = language; + } + + /// + /// Gets the of the current target . + /// + internal CultureInfo CultureInfo => Localization.GetCultureInfoFromLangCode(this.Language.ToCode()); + + /// + /// Tries to get a number from the local parameters at the specified index. + /// + /// The index in the list. + /// The local parameter number. + /// true if the local parameters list contained a parameter at given index, false otherwise. + internal bool TryGetLNum(int index, out uint value) + { + if (index >= 0 && this.LocalParameters.Length > index) + { + value = this.LocalParameters[index].UIntValue; + return true; + } + + value = 0; + return false; + } + + /// + /// Tries to get a string from the local parameters at the specified index. + /// + /// The index in the list. + /// The local parameter string. + /// true if the local parameters list contained a parameter at given index, false otherwise. + internal bool TryGetLStr(int index, out ReadOnlySeString value) + { + if (index >= 0 && this.LocalParameters.Length > index) + { + value = this.LocalParameters[index].StringValue; + return true; + } + + value = default; + return false; + } +} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs new file mode 100644 index 000000000..1c1171873 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectFlags.cs @@ -0,0 +1,49 @@ +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// An enum providing additional information about the sheet redirect. +/// +[Flags] +internal enum SheetRedirectFlags +{ + /// + /// No flags. + /// + None = 0, + + /// + /// Resolved to a sheet related with items. + /// + Item = 1, + + /// + /// Resolved to the EventItem sheet. + /// + EventItem = 2, + + /// + /// Resolved to a high quality item. + /// + /// + /// Append Addon#9. + /// + HighQuality = 4, + + /// + /// Resolved to a collectible item. + /// + /// + /// Append Addon#150. + /// + Collectible = 8, + + /// + /// Resolved to a sheet related with actions. + /// + Action = 16, + + /// + /// Resolved to the Action sheet. + /// + ActionSheet = 32, +} diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs new file mode 100644 index 000000000..57a58c80d --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs @@ -0,0 +1,232 @@ +using Dalamud.Data; +using Dalamud.Utility; + +using Lumina.Extensions; + +using ItemKind = Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload.ItemKind; +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Game.Text.Evaluator.Internal; + +/// +/// A service to resolve sheet redirects in expressions. +/// +[ServiceManager.EarlyLoadedService] +internal class SheetRedirectResolver : IServiceType +{ + private static readonly (string SheetName, uint ColumnIndex, bool ReturnActionSheetFlag)[] ActStrSheets = + [ + (nameof(LSheets.Trait), 0, false), + (nameof(LSheets.Action), 0, true), + (nameof(LSheets.Item), 0, false), + (nameof(LSheets.EventItem), 0, false), + (nameof(LSheets.EventAction), 0, false), + (nameof(LSheets.GeneralAction), 0, false), + (nameof(LSheets.BuddyAction), 0, false), + (nameof(LSheets.MainCommand), 5, false), + (nameof(LSheets.Companion), 0, false), + (nameof(LSheets.CraftAction), 0, false), + (nameof(LSheets.Action), 0, true), + (nameof(LSheets.PetAction), 0, false), + (nameof(LSheets.CompanyAction), 0, false), + (nameof(LSheets.Mount), 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (string.Empty, 0, false), + (nameof(LSheets.BgcArmyAction), 1, false), + (nameof(LSheets.Ornament), 8, false), + ]; + + private static readonly string[] ObjStrSheetNames = + [ + nameof(LSheets.BNpcName), + nameof(LSheets.ENpcResident), + nameof(LSheets.Treasure), + nameof(LSheets.Aetheryte), + nameof(LSheets.GatheringPointName), + nameof(LSheets.EObjName), + nameof(LSheets.Mount), + nameof(LSheets.Companion), + string.Empty, + string.Empty, + nameof(LSheets.Item), + ]; + + [ServiceManager.ServiceDependency] + private readonly DataManager dataManager = Service.Get(); + + [ServiceManager.ServiceConstructor] + private SheetRedirectResolver() + { + } + + /// + /// Resolves the sheet redirect, if any is present. + /// + /// The sheet name. + /// The row id. + /// The column index. Use ushort.MaxValue as default. + /// Flags giving additional information about the redirect. + internal SheetRedirectFlags Resolve(ref string sheetName, ref uint rowId, ref uint colIndex) + { + var flags = SheetRedirectFlags.None; + + switch (sheetName) + { + case nameof(LSheets.Item) or "ItemHQ" or "ItemMP": + { + flags |= SheetRedirectFlags.Item; + + var (itemId, kind) = ItemUtil.GetBaseId(rowId); + + if (kind == ItemKind.Hq || sheetName == "ItemHQ") + { + flags |= SheetRedirectFlags.HighQuality; + } + else if (kind == ItemKind.Collectible || sheetName == "ItemMP") // MP for Masterpiece?! + { + flags |= SheetRedirectFlags.Collectible; + } + + if (kind == ItemKind.EventItem && + rowId - 2_000_000 <= this.dataManager.GetExcelSheet().Count) + { + flags |= SheetRedirectFlags.EventItem; + sheetName = nameof(LSheets.EventItem); + } + else + { + sheetName = nameof(LSheets.Item); + rowId = itemId; + } + + if (colIndex is >= 4 and <= 7) + return SheetRedirectFlags.None; + + break; + } + + case "ActStr": + { + var returnActionSheetFlag = false; + (var index, rowId) = uint.DivRem(rowId, 1000000); + if (index < ActStrSheets.Length) + (sheetName, colIndex, returnActionSheetFlag) = ActStrSheets[index]; + + if (sheetName != nameof(LSheets.Companion) && colIndex != 13) + flags |= SheetRedirectFlags.Action; + + if (returnActionSheetFlag) + flags |= SheetRedirectFlags.ActionSheet; + + break; + } + + case "ObjStr": + { + (var index, rowId) = uint.DivRem(rowId, 1000000); + if (index < ObjStrSheetNames.Length) + sheetName = ObjStrSheetNames[index]; + + colIndex = 0; + + switch (index) + { + case 0: // BNpcName + if (rowId >= 100000) + rowId += 900000; + break; + + case 1: // ENpcResident + rowId += 1000000; + break; + + case 2: // Treasure + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var treasureRow) && + treasureRow.Unknown0.IsEmpty) + rowId = 0; // defaulting to "Treasure Coffer" + break; + + case 3: // Aetheryte + rowId = this.dataManager.GetExcelSheet() + .TryGetRow(rowId, out var aetheryteRow) && aetheryteRow.IsAetheryte + ? 0u // "Aetheryte" + : 1; // "Aethernet Shard" + break; + + case 5: // EObjName + rowId += 2000000; + break; + } + + break; + } + + case nameof(LSheets.EObj) when colIndex is <= 7 or ushort.MaxValue: + sheetName = nameof(LSheets.EObjName); + break; + + case nameof(LSheets.Treasure) + when this.dataManager.GetExcelSheet().TryGetRow(rowId, out var treasureRow) && + treasureRow.Unknown0.IsEmpty: + rowId = 0; // defaulting to "Treasure Coffer" + break; + + case "WeatherPlaceName": + { + sheetName = nameof(LSheets.PlaceName); + + var placeNameSubId = rowId; + if (this.dataManager.GetExcelSheet().TryGetFirst( + r => r.PlaceNameSub.RowId == placeNameSubId, + out var row)) + rowId = row.PlaceNameParent.RowId; + break; + } + + case nameof(LSheets.InstanceContent) when colIndex == 3: + { + sheetName = nameof(LSheets.ContentFinderCondition); + colIndex = 43; + + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) + rowId = row.Order; + break; + } + + case nameof(LSheets.PartyContent) when colIndex == 2: + { + sheetName = nameof(LSheets.ContentFinderCondition); + colIndex = 43; + + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) + rowId = row.ContentFinderCondition.RowId; + break; + } + + case nameof(LSheets.PublicContent) when colIndex == 3: + { + sheetName = nameof(LSheets.ContentFinderCondition); + colIndex = 43; + + if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) + rowId = row.ContentFinderCondition.RowId; + break; + } + + case nameof(LSheets.AkatsukiNote): + { + sheetName = nameof(LSheets.AkatsukiNoteString); + colIndex = 0; + + if (this.dataManager.Excel.GetSubrowSheet().TryGetRow(rowId, out var row)) + rowId = (uint)row[0].Unknown2; + break; + } + } + + return flags; + } +} diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs new file mode 100644 index 000000000..723dbcb41 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -0,0 +1,1995 @@ +using System.Collections.Concurrent; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.Config; +using Dalamud.Game.Text.Evaluator.Internal; +using Dalamud.Game.Text.Noun; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Logging.Internal; +using Dalamud.Plugin.Services; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Info; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using Lumina.Data.Structs.Excel; +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Lumina.Extensions; +using Lumina.Text; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using AddonSheet = Lumina.Excel.Sheets.Addon; + +namespace Dalamud.Game.Text.Evaluator; + +#pragma warning disable SeStringEvaluator + +/// +/// Evaluator for SeStrings. +/// +[ServiceManager.EarlyLoadedService] +internal class SeStringEvaluator : IServiceType, ISeStringEvaluator +{ + private static readonly ModuleLog Log = new("SeStringEvaluator"); + + [ServiceManager.ServiceDependency] + private readonly DataManager dataManager = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly GameConfig gameConfig = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly NounProcessor nounProcessor = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly SheetRedirectResolver sheetRedirectResolver = Service.Get(); + + private readonly ConcurrentDictionary, string> actStrCache = []; + private readonly ConcurrentDictionary, string> objStrCache = []; + + [ServiceManager.ServiceConstructor] + private SeStringEvaluator() + { + } + + /// + public ReadOnlySeString Evaluate( + ReadOnlySeString str, + Span localParameters = default, + ClientLanguage? language = null) + { + return this.Evaluate(str.AsSpan(), localParameters, language); + } + + /// + public ReadOnlySeString Evaluate( + ReadOnlySeStringSpan str, + Span localParameters = default, + ClientLanguage? language = null) + { + if (str.IsTextOnly()) + return new(str); + + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + // TODO: remove culture info toggling after supporting CultureInfo for SeStringBuilder.Append, + // and then remove try...finally block (discard builder from the pool on exception) + var previousCulture = CultureInfo.CurrentCulture; + var builder = SeStringBuilder.SharedPool.Get(); + try + { + CultureInfo.CurrentCulture = Localization.GetCultureInfoFromLangCode(lang.ToCode()); + return this.EvaluateAndAppendTo(builder, str, localParameters, lang).ToReadOnlySeString(); + } + finally + { + CultureInfo.CurrentCulture = previousCulture; + SeStringBuilder.SharedPool.Return(builder); + } + } + + /// + public ReadOnlySeString EvaluateFromAddon( + uint addonId, + Span localParameters = default, + ClientLanguage? language = null) + { + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + if (!this.dataManager.GetExcelSheet(lang).TryGetRow(addonId, out var addonRow)) + return default; + + return this.Evaluate(addonRow.Text.AsSpan(), localParameters, lang); + } + + /// + public ReadOnlySeString EvaluateFromLobby( + uint lobbyId, + Span localParameters = default, + ClientLanguage? language = null) + { + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + if (!this.dataManager.GetExcelSheet(lang).TryGetRow(lobbyId, out var lobbyRow)) + return default; + + return this.Evaluate(lobbyRow.Text.AsSpan(), localParameters, lang); + } + + /// + public ReadOnlySeString EvaluateFromLogMessage( + uint logMessageId, + Span localParameters = default, + ClientLanguage? language = null) + { + var lang = language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage(); + + if (!this.dataManager.GetExcelSheet(lang).TryGetRow(logMessageId, out var logMessageRow)) + return default; + + return this.Evaluate(logMessageRow.Text.AsSpan(), localParameters, lang); + } + + /// + public string EvaluateActStr(ActionKind actionKind, uint id, ClientLanguage? language = null) => + this.actStrCache.GetOrAdd( + new(actionKind, id, language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage()), + static (key, t) => t.EvaluateFromAddon(2026, [key.Kind.GetActStrId(key.Id)], key.Language) + .ExtractText() + .StripSoftHyphen(), + this); + + /// + public string EvaluateObjStr(ObjectKind objectKind, uint id, ClientLanguage? language = null) => + this.objStrCache.GetOrAdd( + new(objectKind, id, language ?? this.dalamudConfiguration.EffectiveLanguage.ToClientLanguage()), + static (key, t) => t.EvaluateFromAddon(2025, [key.Kind.GetObjStrId(key.Id)], key.Language) + .ExtractText() + .StripSoftHyphen(), + this); + + // TODO: move this to MapUtil? + private static uint ConvertRawToMapPos(Lumina.Excel.Sheets.Map map, short offset, float value) + { + var scale = map.SizeFactor / 100.0f; + return (uint)(10 - (int)(((((value + offset) * scale) + 1024f) * -0.2f) / scale)); + } + + private static uint ConvertRawToMapPosX(Lumina.Excel.Sheets.Map map, float x) + => ConvertRawToMapPos(map, map.OffsetX, x); + + private static uint ConvertRawToMapPosY(Lumina.Excel.Sheets.Map map, float y) + => ConvertRawToMapPos(map, map.OffsetY, y); + + private SeStringBuilder EvaluateAndAppendTo( + SeStringBuilder builder, + ReadOnlySeStringSpan str, + Span localParameters, + ClientLanguage language) + { + var context = new SeStringContext(builder, localParameters, language); + + foreach (var payload in str) + { + if (!this.ResolvePayload(in context, payload)) + { + context.Builder.Append(payload); + } + } + + return builder; + } + + private bool ResolvePayload(in SeStringContext context, ReadOnlySePayloadSpan payload) + { + if (payload.Type != ReadOnlySePayloadType.Macro) + return false; + + // if (context.HandlePayload(payload, in context)) + // return true; + + switch (payload.MacroCode) + { + case MacroCode.SetResetTime: + return this.TryResolveSetResetTime(in context, payload); + + case MacroCode.SetTime: + return this.TryResolveSetTime(in context, payload); + + case MacroCode.If: + return this.TryResolveIf(in context, payload); + + case MacroCode.Switch: + return this.TryResolveSwitch(in context, payload); + + case MacroCode.PcName: + return this.TryResolvePcName(in context, payload); + + case MacroCode.IfPcGender: + return this.TryResolveIfPcGender(in context, payload); + + case MacroCode.IfPcName: + return this.TryResolveIfPcName(in context, payload); + + // case MacroCode.Josa: + // case MacroCode.Josaro: + + case MacroCode.IfSelf: + return this.TryResolveIfSelf(in context, payload); + + // case MacroCode.NewLine: // pass through + // case MacroCode.Wait: // pass through + // case MacroCode.Icon: // pass through + + case MacroCode.Color: + return this.TryResolveColor(in context, payload); + + case MacroCode.EdgeColor: + return this.TryResolveEdgeColor(in context, payload); + + case MacroCode.ShadowColor: + return this.TryResolveShadowColor(in context, payload); + + // case MacroCode.SoftHyphen: // pass through + // case MacroCode.Key: + // case MacroCode.Scale: + + case MacroCode.Bold: + return this.TryResolveBold(in context, payload); + + case MacroCode.Italic: + return this.TryResolveItalic(in context, payload); + + // case MacroCode.Edge: + // case MacroCode.Shadow: + // case MacroCode.NonBreakingSpace: // pass through + // case MacroCode.Icon2: // pass through + // case MacroCode.Hyphen: // pass through + + case MacroCode.Num: + return this.TryResolveNum(in context, payload); + + case MacroCode.Hex: + return this.TryResolveHex(in context, payload); + + case MacroCode.Kilo: + return this.TryResolveKilo(in context, payload); + + // case MacroCode.Byte: + + case MacroCode.Sec: + return this.TryResolveSec(in context, payload); + + // case MacroCode.Time: + + case MacroCode.Float: + return this.TryResolveFloat(in context, payload); + + // case MacroCode.Link: // pass through + + case MacroCode.Sheet: + return this.TryResolveSheet(in context, payload); + + case MacroCode.String: + return this.TryResolveString(in context, payload); + + case MacroCode.Caps: + return this.TryResolveCaps(in context, payload); + + case MacroCode.Head: + return this.TryResolveHead(in context, payload); + + case MacroCode.Split: + return this.TryResolveSplit(in context, payload); + + case MacroCode.HeadAll: + return this.TryResolveHeadAll(in context, payload); + + case MacroCode.Fixed: + return this.TryResolveFixed(in context, payload); + + case MacroCode.Lower: + return this.TryResolveLower(in context, payload); + + case MacroCode.JaNoun: + return this.TryResolveNoun(ClientLanguage.Japanese, in context, payload); + + case MacroCode.EnNoun: + return this.TryResolveNoun(ClientLanguage.English, in context, payload); + + case MacroCode.DeNoun: + return this.TryResolveNoun(ClientLanguage.German, in context, payload); + + case MacroCode.FrNoun: + return this.TryResolveNoun(ClientLanguage.French, in context, payload); + + // case MacroCode.ChNoun: + + case MacroCode.LowerHead: + return this.TryResolveLowerHead(in context, payload); + + case MacroCode.ColorType: + return this.TryResolveColorType(in context, payload); + + case MacroCode.EdgeColorType: + return this.TryResolveEdgeColorType(in context, payload); + + // case MacroCode.Ruby: + + case MacroCode.Digit: + return this.TryResolveDigit(in context, payload); + + case MacroCode.Ordinal: + return this.TryResolveOrdinal(in context, payload); + + // case MacroCode.Sound: // pass through + + case MacroCode.LevelPos: + return this.TryResolveLevelPos(in context, payload); + + default: + return false; + } + } + + private unsafe bool TryResolveSetResetTime(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + DateTime date; + + if (payload.TryGetExpression(out var eHour, out var eWeekday) + && this.TryResolveInt(in context, eHour, out var eHourVal) + && this.TryResolveInt(in context, eWeekday, out var eWeekdayVal)) + { + var t = DateTime.UtcNow.AddDays(((eWeekdayVal - (int)DateTime.UtcNow.DayOfWeek) + 7) % 7); + date = new DateTime(t.Year, t.Month, t.Day, eHourVal, 0, 0, DateTimeKind.Utc).ToLocalTime(); + } + else if (payload.TryGetExpression(out eHour) + && this.TryResolveInt(in context, eHour, out eHourVal)) + { + var t = DateTime.UtcNow; + date = new DateTime(t.Year, t.Month, t.Day, eHourVal, 0, 0, DateTimeKind.Utc).ToLocalTime(); + } + else + { + return false; + } + + MacroDecoder.GetMacroTime()->SetTime(date); + + return true; + } + + private unsafe bool TryResolveSetTime(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eTime) || !this.TryResolveUInt(in context, eTime, out var eTimeVal)) + return false; + + var date = DateTimeOffset.FromUnixTimeSeconds(eTimeVal).LocalDateTime; + MacroDecoder.GetMacroTime()->SetTime(date); + + return true; + } + + private bool TryResolveIf(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + return + payload.TryGetExpression(out var eCond, out var eTrue, out var eFalse) + && this.ResolveStringExpression( + context, + this.TryResolveBool(in context, eCond, out var eCondVal) && eCondVal + ? eTrue + : eFalse); + } + + private bool TryResolveSwitch(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + var cond = -1; + foreach (var e in payload) + { + switch (cond) + { + case -1: + cond = this.TryResolveUInt(in context, e, out var eVal) ? (int)eVal : 0; + break; + case > 1: + cond--; + break; + default: + return this.ResolveStringExpression(in context, e); + } + } + + return false; + } + + private unsafe bool TryResolvePcName(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId)) + return false; + + // TODO: handle LogNameType + + NameCache.CharacterInfo characterInfo = default; + if (NameCache.Instance()->TryGetCharacterInfoByEntityId(entityId, &characterInfo)) + { + context.Builder.Append((ReadOnlySeStringSpan)characterInfo.Name.AsSpan()); + + if (characterInfo.HomeWorldId != AgentLobby.Instance()->LobbyData.HomeWorldId && + WorldHelper.Instance()->AllWorlds.TryGetValue((ushort)characterInfo.HomeWorldId, out var world, false)) + { + context.Builder.AppendIcon(88); + + if (this.gameConfig.UiConfig.TryGetUInt("LogCrossWorldName", out var logCrossWorldName) && + logCrossWorldName == 1) + context.Builder.Append((ReadOnlySeStringSpan)world.Name); + } + + return true; + } + + // TODO: lookup via InstanceContentCrystallineConflictDirector + // TODO: lookup via MJIManager + + return false; + } + + private unsafe bool TryResolveIfPcGender(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId, out var eMale, out var eFemale)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId)) + return false; + + NameCache.CharacterInfo characterInfo = default; + if (NameCache.Instance()->TryGetCharacterInfoByEntityId(entityId, &characterInfo)) + return this.ResolveStringExpression(in context, characterInfo.Sex == 0 ? eMale : eFemale); + + // TODO: lookup via InstanceContentCrystallineConflictDirector + + return false; + } + + private unsafe bool TryResolveIfPcName(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId, out var eName, out var eTrue, out var eFalse)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId) || !eName.TryGetString(out var name)) + return false; + + name = this.Evaluate(name, context.LocalParameters, context.Language).AsSpan(); + + NameCache.CharacterInfo characterInfo = default; + return NameCache.Instance()->TryGetCharacterInfoByEntityId(entityId, &characterInfo) && + this.ResolveStringExpression( + context, + name.Equals(characterInfo.Name.AsSpan()) + ? eTrue + : eFalse); + } + + private unsafe bool TryResolveIfSelf(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEntityId, out var eTrue, out var eFalse)) + return false; + + if (!this.TryResolveUInt(in context, eEntityId, out var entityId)) + return false; + + // the game uses LocalPlayer here, but using PlayerState seems more safe. + return this.ResolveStringExpression(in context, PlayerState.Instance()->EntityId == entityId ? eTrue : eFalse); + } + + private bool TryResolveColor(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColor)) + return false; + + if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor) + context.Builder.PopColor(); + else if (this.TryResolveUInt(in context, eColor, out var eColorVal)) + context.Builder.PushColorBgra(eColorVal); + + return true; + } + + private bool TryResolveEdgeColor(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColor)) + return false; + + if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor) + context.Builder.PopEdgeColor(); + else if (this.TryResolveUInt(in context, eColor, out var eColorVal)) + context.Builder.PushEdgeColorBgra(eColorVal); + + return true; + } + + private bool TryResolveShadowColor(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColor)) + return false; + + if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor) + context.Builder.PopShadowColor(); + else if (this.TryResolveUInt(in context, eColor, out var eColorVal)) + context.Builder.PushShadowColorBgra(eColorVal); + + return true; + } + + private bool TryResolveBold(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEnable) || + !this.TryResolveBool(in context, eEnable, out var eEnableVal)) + return false; + + context.Builder.AppendSetBold(eEnableVal); + + return true; + } + + private bool TryResolveItalic(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eEnable) || + !this.TryResolveBool(in context, eEnable, out var eEnableVal)) + return false; + + context.Builder.AppendSetItalic(eEnableVal); + + return true; + } + + private bool TryResolveNum(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eInt) || !this.TryResolveInt(in context, eInt, out var eIntVal)) + { + context.Builder.Append('0'); + return true; + } + + context.Builder.Append(eIntVal.ToString()); + + return true; + } + + private bool TryResolveHex(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eUInt) || !this.TryResolveUInt(in context, eUInt, out var eUIntVal)) + { + // TODO: throw? + // ERROR: mismatch parameter type ('' is not numeric) + return false; + } + + context.Builder.Append("0x{0:X08}".Format(eUIntVal)); + + return true; + } + + private bool TryResolveKilo(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eInt, out var eSep) || + !this.TryResolveInt(in context, eInt, out var eIntVal)) + { + context.Builder.Append('0'); + return true; + } + + if (eIntVal == int.MinValue) + { + // -2147483648 + context.Builder.Append("-2"u8); + this.ResolveStringExpression(in context, eSep); + context.Builder.Append("147"u8); + this.ResolveStringExpression(in context, eSep); + context.Builder.Append("483"u8); + this.ResolveStringExpression(in context, eSep); + context.Builder.Append("648"u8); + return true; + } + + if (eIntVal < 0) + { + context.Builder.Append('-'); + eIntVal = -eIntVal; + } + + if (eIntVal == 0) + { + context.Builder.Append('0'); + return true; + } + + var anyDigitPrinted = false; + for (var i = 1_000_000_000; i > 0; i /= 10) + { + var digit = (eIntVal / i) % 10; + switch (anyDigitPrinted) + { + case false when digit == 0: + continue; + case true when i % 3 == 0: + this.ResolveStringExpression(in context, eSep); + break; + } + + anyDigitPrinted = true; + context.Builder.Append((char)('0' + digit)); + } + + return true; + } + + private bool TryResolveSec(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eInt) || !this.TryResolveUInt(in context, eInt, out var eIntVal)) + { + // TODO: throw? + // ERROR: mismatch parameter type ('' is not numeric) + return false; + } + + context.Builder.Append("{0:00}".Format(eIntVal)); + return true; + } + + private bool TryResolveFloat(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eValue, out var eRadix, out var eSeparator) + || !this.TryResolveInt(in context, eValue, out var eValueVal) + || !this.TryResolveInt(in context, eRadix, out var eRadixVal)) + { + return false; + } + + var (integerPart, fractionalPart) = int.DivRem(eValueVal, eRadixVal); + if (fractionalPart < 0) + { + integerPart--; + fractionalPart += eRadixVal; + } + + context.Builder.Append(integerPart.ToString()); + this.ResolveStringExpression(in context, eSeparator); + + // brain fried code + Span fractionalDigits = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + var pos = fractionalDigits.Length - 1; + for (var r = eRadixVal; r > 1; r /= 10) + { + fractionalDigits[pos--] = (byte)('0' + (fractionalPart % 10)); + fractionalPart /= 10; + } + + context.Builder.Append(fractionalDigits[(pos + 1)..]); + + return true; + } + + private bool TryResolveSheet(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + var enu = payload.GetEnumerator(); + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var eSheetNameStr)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eRowIdValue)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eColIndexValue)) + return false; + + var eColParamValue = 0u; + if (enu.MoveNext()) + this.TryResolveUInt(in context, enu.Current, out eColParamValue); + + var resolvedSheetName = this.Evaluate(eSheetNameStr, context.LocalParameters, context.Language).ExtractText(); + + this.sheetRedirectResolver.Resolve(ref resolvedSheetName, ref eRowIdValue, ref eColIndexValue); + + if (string.IsNullOrEmpty(resolvedSheetName)) + return false; + + if (!this.dataManager.Excel.SheetNames.Contains(resolvedSheetName)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language, resolvedSheetName) + .TryGetRow(eRowIdValue, out var row)) + return false; + + if (eColIndexValue >= row.Columns.Count) + return false; + + var column = row.Columns[(int)eColIndexValue]; + switch (column.Type) + { + case ExcelColumnDataType.String: + context.Builder.Append(this.Evaluate(row.ReadString(column.Offset), [eColParamValue], context.Language)); + return true; + case ExcelColumnDataType.Bool: + context.Builder.Append((row.ReadBool(column.Offset) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int8: + context.Builder.Append(row.ReadInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt8: + context.Builder.Append(row.ReadUInt8(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int16: + context.Builder.Append(row.ReadInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt16: + context.Builder.Append(row.ReadUInt16(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int32: + context.Builder.Append(row.ReadInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt32: + context.Builder.Append(row.ReadUInt32(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Float32: + context.Builder.Append(row.ReadFloat32(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.Int64: + context.Builder.Append(row.ReadInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.UInt64: + context.Builder.Append(row.ReadUInt64(column.Offset).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool0: + context.Builder.Append((row.ReadPackedBool(column.Offset, 0) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool1: + context.Builder.Append((row.ReadPackedBool(column.Offset, 1) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool2: + context.Builder.Append((row.ReadPackedBool(column.Offset, 2) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool3: + context.Builder.Append((row.ReadPackedBool(column.Offset, 3) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool4: + context.Builder.Append((row.ReadPackedBool(column.Offset, 4) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool5: + context.Builder.Append((row.ReadPackedBool(column.Offset, 5) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool6: + context.Builder.Append((row.ReadPackedBool(column.Offset, 6) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + case ExcelColumnDataType.PackedBool7: + context.Builder.Append((row.ReadPackedBool(column.Offset, 7) ? 1u : 0).ToString("D", CultureInfo.InvariantCulture)); + return true; + default: + return false; + } + } + + private bool TryResolveString(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + return payload.TryGetExpression(out var eStr) && this.ResolveStringExpression(in context, eStr); + } + + private bool TryResolveCaps(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + var pIdx = 0; + + foreach (var p in str) + { + pIdx++; + + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToUpper(context.CultureInfo)); + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveHead(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + var pIdx = 0; + + foreach (var p in str) + { + pIdx++; + + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToUpper(context.CultureInfo)); + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveSplit(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eText, out var eSeparator, out var eIndex)) + return false; + + if (!eSeparator.TryGetString(out var eSeparatorVal) || !eIndex.TryGetUInt(out var eIndexVal) || eIndexVal <= 0) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eText)) + return false; + + var separator = eSeparatorVal.ExtractText(); + if (separator.Length < 1) + return false; + + var splitted = builder.ToReadOnlySeString().ExtractText().Split(separator[0]); + if (eIndexVal <= splitted.Length) + { + context.Builder.Append(splitted[eIndexVal - 1]); + return true; + } + + return false; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveHeadAll(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + + foreach (var p in str) + { + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append( + context.CultureInfo.TextInfo.ToTitleCase(Encoding.UTF8.GetString(p.Body.Span))); + + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveFixed(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + // This is handled by the second function in Client::UI::Misc::PronounModule_ProcessString + + var enu = payload.GetEnumerator(); + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var e0Val)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var e1Val)) + return false; + + return e0Val switch + { + 100 or 200 => e1Val switch + { + 1 => this.TryResolveFixedPlayerLink(in context, ref enu), + 2 => this.TryResolveFixedClassJobLevel(in context, ref enu), + 3 => this.TryResolveFixedMapLink(in context, ref enu), + 4 => this.TryResolveFixedItemLink(in context, ref enu), + 5 => this.TryResolveFixedChatSoundEffect(in context, ref enu), + 6 => this.TryResolveFixedObjStr(in context, ref enu), + 7 => this.TryResolveFixedString(in context, ref enu), + 8 => this.TryResolveFixedTimeRemaining(in context, ref enu), + // Reads a uint and saves it to PronounModule+0x3AC + // TODO: handle this? looks like it's for the mentor/beginner icon of the player link in novice network + // see "FF 50 50 8B B0" + 9 => true, + 10 => this.TryResolveFixedStatusLink(in context, ref enu), + 11 => this.TryResolveFixedPartyFinderLink(in context, ref enu), + 12 => this.TryResolveFixedQuestLink(in context, ref enu), + _ => false, + }, + _ => this.TryResolveFixedAutoTranslation(in context, payload, e0Val, e1Val), + }; + } + + private unsafe bool TryResolveFixedPlayerLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var worldId)) + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var playerName)) + return false; + + if (UIGlobals.IsValidPlayerCharacterName(playerName.ExtractText())) + { + var flags = 0u; + if (InfoModule.Instance()->IsInCrossWorldDuty()) + flags |= 0x10; + + context.Builder.PushLink(LinkMacroPayloadType.Character, flags, worldId, 0u, playerName); + context.Builder.Append(playerName); + context.Builder.PopLink(); + } + else + { + context.Builder.Append(playerName); + } + + if (worldId == AgentLobby.Instance()->LobbyData.HomeWorldId) + return true; + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetRow(worldId, out var worldRow)) + return false; + + context.Builder.AppendIcon(88); + context.Builder.Append(worldRow.Name); + + return true; + } + + private bool TryResolveFixedClassJobLevel(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var classJobId) || classJobId <= 0) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var level)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language) + .TryGetRow((uint)classJobId, out var classJobRow)) + return false; + + context.Builder.Append(classJobRow.Name); + + if (level != 0) + context.Builder.Append(context.CultureInfo, $"({level:D})"); + + return true; + } + + private bool TryResolveFixedMapLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var territoryTypeId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var packedIds)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var rawX)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var rawY)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var rawZ)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var placeNameIdInt)) + return false; + + var instance = packedIds >> 0x10; + var mapId = packedIds & 0xFF; + + if (this.dataManager.GetExcelSheet(context.Language) + .TryGetRow(territoryTypeId, out var territoryTypeRow)) + { + if (!this.dataManager.GetExcelSheet(context.Language) + .TryGetRow( + placeNameIdInt == 0 ? territoryTypeRow.PlaceName.RowId : placeNameIdInt, + out var placeNameRow)) + return false; + + if (!this.dataManager.GetExcelSheet().TryGetRow(mapId, out var mapRow)) + return false; + + var sb = SeStringBuilder.SharedPool.Get(); + + sb.Append(placeNameRow.Name); + if (instance is > 0 and <= 9) + sb.Append((char)((char)0xE0B0 + (char)instance)); + + var placeNameWithInstance = sb.ToReadOnlySeString(); + SeStringBuilder.SharedPool.Return(sb); + + var mapPosX = ConvertRawToMapPosX(mapRow, rawX / 1000f); + var mapPosY = ConvertRawToMapPosY(mapRow, rawY / 1000f); + + var linkText = rawZ == -30000 + ? this.EvaluateFromAddon( + 1635, + [placeNameWithInstance, mapPosX, mapPosY], + context.Language) + : this.EvaluateFromAddon( + 1636, + [placeNameWithInstance, mapPosX, mapPosY, rawZ / (rawZ >= 0 ? 10 : -10), rawZ], + context.Language); + + context.Builder.PushLinkMapPosition(territoryTypeId, mapId, rawX, rawY); + context.Builder.Append(this.EvaluateFromAddon(371, [linkText], context.Language)); + context.Builder.PopLink(); + + return true; + } + + var rowId = mapId switch + { + 0 => 875u, // "(No location set for map link)" + 1 => 874u, // "(Map link unavailable in this area)" + 2 => 13743u, // "(Unable to set map link)" + _ => 0u, + }; + if (rowId == 0u) + return false; + if (this.dataManager.GetExcelSheet(context.Language).TryGetRow(rowId, out var addonRow)) + context.Builder.Append(addonRow.Text); + return true; + } + + private bool TryResolveFixedItemLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var itemId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var rarity)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var unk2)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var unk3)) + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var itemName)) // TODO: unescape?? + return false; + + // rarity color start + context.Builder.Append(this.EvaluateFromAddon(6, [rarity], context.Language)); + + var v2 = (ushort)((unk2 & 0xFF) + (unk3 << 0x10)); // TODO: find out what this does + + context.Builder.PushLink(LinkMacroPayloadType.Item, itemId, rarity, v2); + + // arrow and item name + context.Builder.Append(this.EvaluateFromAddon(371, [itemName], context.Language)); + + context.Builder.PopLink(); + context.Builder.PopColor(); + + return true; + } + + private bool TryResolveFixedChatSoundEffect(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var soundEffectId)) + return false; + + context.Builder.Append($""); + + // the game would play it here + + return true; + } + + private bool TryResolveFixedObjStr(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var objStrId)) + return false; + + context.Builder.Append(this.EvaluateFromAddon(2025, [objStrId], context.Language)); + + return true; + } + + private bool TryResolveFixedString(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !enu.Current.TryGetString(out var text)) + return false; + + // formats it through vsprintf using "%s"?? + context.Builder.Append(text.ExtractText()); + + return true; + } + + private bool TryResolveFixedTimeRemaining(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var seconds)) + return false; + + if (seconds != 0) + { + context.Builder.Append(this.EvaluateFromAddon(33, [seconds / 60, seconds % 60], context.Language)); + } + else + { + if (this.dataManager.GetExcelSheet(context.Language).TryGetRow(48, out var addonRow)) + context.Builder.Append(addonRow.Text); + } + + return true; + } + + private bool TryResolveFixedStatusLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var statusId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveBool(in context, enu.Current, out var hasOverride)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language) + .TryGetRow(statusId, out var statusRow)) + return false; + + ReadOnlySeStringSpan statusName; + ReadOnlySeStringSpan statusDescription; + + if (hasOverride) + { + if (!enu.MoveNext() || !enu.Current.TryGetString(out statusName)) + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out statusDescription)) + return false; + } + else + { + statusName = statusRow.Name.AsSpan(); + statusDescription = statusRow.Description.AsSpan(); + } + + var sb = SeStringBuilder.SharedPool.Get(); + + switch (statusRow.StatusCategory) + { + case 1: + sb.Append(this.EvaluateFromAddon(376, default, context.Language)); + break; + + case 2: + sb.Append(this.EvaluateFromAddon(377, default, context.Language)); + break; + } + + sb.Append(statusName); + + var linkText = sb.ToReadOnlySeString(); + SeStringBuilder.SharedPool.Return(sb); + + context.Builder + .BeginMacro(MacroCode.Link) + .AppendUIntExpression((uint)LinkMacroPayloadType.Status) + .AppendUIntExpression(statusId) + .AppendUIntExpression(0) + .AppendUIntExpression(0) + .AppendStringExpression(statusName) + .AppendStringExpression(statusDescription) + .EndMacro(); + + context.Builder.Append(this.EvaluateFromAddon(371, [linkText], context.Language)); + + context.Builder.PopLink(); + + return true; + } + + private bool TryResolveFixedPartyFinderLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var listingId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var unk1)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var worldId)) + return false; + + if (!enu.MoveNext() || !this.TryResolveInt( + context, + enu.Current, + out var crossWorldFlag)) // 0 = cross world, 1 = not cross world + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var playerName)) + return false; + + context.Builder + .BeginMacro(MacroCode.Link) + .AppendUIntExpression((uint)LinkMacroPayloadType.PartyFinder) + .AppendUIntExpression(listingId) + .AppendUIntExpression(unk1) + .AppendUIntExpression((uint)(crossWorldFlag << 0x10) + worldId) + .EndMacro(); + + context.Builder.Append( + this.EvaluateFromAddon( + 371, + [this.EvaluateFromAddon(2265, [playerName, crossWorldFlag], context.Language)], + context.Language)); + + context.Builder.PopLink(); + + return true; + } + + private bool TryResolveFixedQuestLink(in SeStringContext context, ref ReadOnlySePayloadSpan.Enumerator enu) + { + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var questId)) + return false; + + if (!enu.MoveNext() || !enu.MoveNext() || !enu.MoveNext()) // unused + return false; + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var questName)) + return false; + + /* TODO: hide incomplete, repeatable special event quest names + if (!QuestManager.IsQuestComplete(questId) && !QuestManager.Instance()->IsQuestAccepted(questId)) + { + var questRecompleteManager = QuestRecompleteManager.Instance(); + if (questRecompleteManager == null || !questRecompleteManager->"E8 ?? ?? ?? ?? 0F B6 57 FF"(questId)) { + if (_excelService.TryGetRow(5497, context.Language, out var addonRow)) + questName = addonRow.Text.AsSpan(); + } + } + */ + + context.Builder + .BeginMacro(MacroCode.Link) + .AppendUIntExpression((uint)LinkMacroPayloadType.Quest) + .AppendUIntExpression(questId) + .AppendUIntExpression(0) + .AppendUIntExpression(0) + .EndMacro(); + + context.Builder.Append(this.EvaluateFromAddon(371, [questName], context.Language)); + + context.Builder.PopLink(); + + return true; + } + + private bool TryResolveFixedAutoTranslation( + in SeStringContext context, in ReadOnlySePayloadSpan payload, int e0Val, int e1Val) + { + // Auto-Translation / Completion + var group = (uint)(e0Val + 1); + var rowId = (uint)e1Val; + + using var icons = new SeStringBuilderIconWrap(context.Builder, 54, 55); + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetFirst( + row => row.Group == group && !row.LookupTable.IsEmpty, + out var groupRow)) + return false; + + var lookupTable = ( + groupRow.LookupTable.IsTextOnly() + ? groupRow.LookupTable + : this.Evaluate( + groupRow.LookupTable.AsSpan(), + context.LocalParameters, + context.Language)).ExtractText(); + + // Completion sheet + if (lookupTable.Equals("@")) + { + if (this.dataManager.GetExcelSheet(context.Language).TryGetRow(rowId, out var completionRow)) + { + context.Builder.Append(completionRow.Text); + } + + return true; + } + + // CategoryDataCache + if (lookupTable.Equals("#")) + { + // couldn't find any, so we don't handle them :p + context.Builder.Append(payload); + return false; + } + + // All other sheets + var rangesStart = lookupTable.IndexOf('['); + // Sheet without ranges + if (rangesStart == -1) + { + if (this.dataManager.GetExcelSheet(context.Language, lookupTable).TryGetRow(rowId, out var row)) + { + context.Builder.Append(row.ReadStringColumn(0)); + return true; + } + } + + var sheetName = lookupTable[..rangesStart]; + var ranges = lookupTable[(rangesStart + 1)..^1]; + if (ranges.Length == 0) + return true; + + var isNoun = false; + var col = 0; + + if (ranges.StartsWith("noun")) + { + isNoun = true; + } + else if (ranges.StartsWith("col")) + { + var colRangeEnd = ranges.IndexOf(','); + if (colRangeEnd == -1) + colRangeEnd = ranges.Length; + + col = int.Parse(ranges[4..colRangeEnd]); + } + else if (ranges.StartsWith("tail")) + { + // couldn't find any, so we don't handle them :p + context.Builder.Append(payload); + return false; + } + + if (isNoun && context.Language == ClientLanguage.German && sheetName == "Companion") + { + context.Builder.Append(this.nounProcessor.ProcessNoun(new NounParams() + { + Language = ClientLanguage.German, + SheetName = sheetName, + RowId = rowId, + Quantity = 1, + ArticleType = (int)GermanArticleType.ZeroArticle, + })); + } + else if (this.dataManager.GetExcelSheet(context.Language, sheetName).TryGetRow(rowId, out var row)) + { + context.Builder.Append(row.ReadStringColumn(col)); + } + + return true; + } + + private bool TryResolveLower(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + + foreach (var p in str) + { + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.ToArray()).ToLower(context.CultureInfo)); + + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveNoun(ClientLanguage language, in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + var eAmountVal = 1; + var eCaseVal = 1; + + var enu = payload.GetEnumerator(); + + if (!enu.MoveNext() || !enu.Current.TryGetString(out var eSheetNameStr)) + return false; + + var sheetName = this.Evaluate(eSheetNameStr, context.LocalParameters, context.Language).ExtractText(); + + if (!enu.MoveNext() || !this.TryResolveInt(in context, enu.Current, out var eArticleTypeVal)) + return false; + + if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eRowIdVal)) + return false; + + uint colIndex = ushort.MaxValue; + var flags = this.sheetRedirectResolver.Resolve(ref sheetName, ref eRowIdVal, ref colIndex); + + if (string.IsNullOrEmpty(sheetName)) + return false; + + // optional arguments + if (enu.MoveNext()) + { + if (!this.TryResolveInt(in context, enu.Current, out eAmountVal)) + return false; + + if (enu.MoveNext()) + { + if (!this.TryResolveInt(in context, enu.Current, out eCaseVal)) + return false; + + // For Chinese texts? + /* + if (enu.MoveNext()) + { + var eUnkInt5 = enu.Current; + if (!TryResolveInt(context,eUnkInt5, out eUnkInt5Val)) + return false; + } + */ + } + } + + context.Builder.Append( + this.nounProcessor.ProcessNoun(new NounParams() + { + Language = language, + SheetName = sheetName, + RowId = eRowIdVal, + Quantity = eAmountVal, + ArticleType = eArticleTypeVal, + GrammaticalCase = eCaseVal - 1, + IsActionSheet = flags.HasFlag(SheetRedirectFlags.Action), + })); + + return true; + } + + private bool TryResolveLowerHead(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eStr)) + return false; + + var builder = SeStringBuilder.SharedPool.Get(); + + try + { + var headContext = new SeStringContext(builder, context.LocalParameters, context.Language); + + if (!this.ResolveStringExpression(headContext, eStr)) + return false; + + var str = builder.ToReadOnlySeString(); + var pIdx = 0; + + foreach (var p in str) + { + pIdx++; + + if (p.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (pIdx == 1 && p.Type == ReadOnlySePayloadType.Text) + { + context.Builder.Append(Encoding.UTF8.GetString(p.Body.Span).FirstCharToLower(context.CultureInfo)); + continue; + } + + context.Builder.Append(p); + } + + return true; + } + finally + { + SeStringBuilder.SharedPool.Return(builder); + } + } + + private bool TryResolveColorType(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColorType) || + !this.TryResolveUInt(in context, eColorType, out var eColorTypeVal)) + return false; + + if (eColorTypeVal == 0) + context.Builder.PopColor(); + else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) + context.Builder.PushColorBgra((row.UIForeground >> 8) | (row.UIForeground << 24)); + + return true; + } + + private bool TryResolveEdgeColorType(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eColorType) || + !this.TryResolveUInt(in context, eColorType, out var eColorTypeVal)) + return false; + + if (eColorTypeVal == 0) + context.Builder.PopEdgeColor(); + else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) + context.Builder.PushEdgeColorBgra((row.UIForeground >> 8) | (row.UIForeground << 24)); + + return true; + } + + private bool TryResolveDigit(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eValue, out var eTargetLength)) + return false; + + if (!this.TryResolveInt(in context, eValue, out var eValueVal)) + return false; + + if (!this.TryResolveInt(in context, eTargetLength, out var eTargetLengthVal)) + return false; + + context.Builder.Append(eValueVal.ToString(new string('0', eTargetLengthVal))); + + return true; + } + + private bool TryResolveOrdinal(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eValue) || !this.TryResolveUInt(in context, eValue, out var eValueVal)) + return false; + + // TODO: Culture support? + context.Builder.Append( + $"{eValueVal}{(eValueVal % 10) switch + { + _ when eValueVal is >= 10 and <= 19 => "th", + 1 => "st", + 2 => "nd", + 3 => "rd", + _ => "th", + }}"); + return true; + } + + private bool TryResolveLevelPos(in SeStringContext context, in ReadOnlySePayloadSpan payload) + { + if (!payload.TryGetExpression(out var eLevel) || !this.TryResolveUInt(in context, eLevel, out var eLevelVal)) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetRow(eLevelVal, out var level) || + !level.Map.IsValid) + return false; + + if (!this.dataManager.GetExcelSheet(context.Language).TryGetRow( + level.Map.Value.PlaceName.RowId, + out var placeName)) + return false; + + var mapPosX = ConvertRawToMapPosX(level.Map.Value, level.X); + var mapPosY = ConvertRawToMapPosY(level.Map.Value, level.Z); // Z is [sic] + + context.Builder.Append( + this.EvaluateFromAddon( + 1637, + [placeName.Name, mapPosX, mapPosY], + context.Language)); + + return true; + } + + private unsafe bool TryGetGNumDefault(uint parameterIndex, out uint value) + { + value = 0u; + + var rtm = RaptureTextModule.Instance(); + if (rtm is null) + return false; + + if (!ThreadSafety.IsMainThread) + { + Log.Error("Global parameters may only be used from the main thread."); + return false; + } + + ref var gp = ref rtm->TextModule.MacroDecoder.GlobalParameters; + if (parameterIndex >= gp.MySize) + return false; + + var p = rtm->TextModule.MacroDecoder.GlobalParameters[parameterIndex]; + switch (p.Type) + { + case TextParameterType.Integer: + value = (uint)p.IntValue; + return true; + + case TextParameterType.ReferencedUtf8String: + Log.Error("Requested a number; Utf8String global parameter at {parameterIndex}.", parameterIndex); + return false; + + case TextParameterType.String: + Log.Error("Requested a number; string global parameter at {parameterIndex}.", parameterIndex); + return false; + + case TextParameterType.Uninitialized: + Log.Error("Requested a number; uninitialized global parameter at {parameterIndex}.", parameterIndex); + return false; + + default: + return false; + } + } + + private unsafe bool TryProduceGStrDefault(SeStringBuilder builder, ClientLanguage language, uint parameterIndex) + { + var rtm = RaptureTextModule.Instance(); + if (rtm is null) + return false; + + ref var gp = ref rtm->TextModule.MacroDecoder.GlobalParameters; + if (parameterIndex >= gp.MySize) + return false; + + if (!ThreadSafety.IsMainThread) + { + Log.Error("Global parameters may only be used from the main thread."); + return false; + } + + var p = rtm->TextModule.MacroDecoder.GlobalParameters[parameterIndex]; + switch (p.Type) + { + case TextParameterType.Integer: + builder.Append($"{p.IntValue:D}"); + return true; + + case TextParameterType.ReferencedUtf8String: + this.EvaluateAndAppendTo( + builder, + p.ReferencedUtf8StringValue->Utf8String.AsSpan(), + null, + language); + return false; + + case TextParameterType.String: + this.EvaluateAndAppendTo(builder, new(p.StringValue), null, language); + return false; + + case TextParameterType.Uninitialized: + default: + return false; + } + } + + private unsafe bool TryResolveUInt( + in SeStringContext context, in ReadOnlySeExpressionSpan expression, out uint value) + { + if (expression.TryGetUInt(out value)) + return true; + + if (expression.TryGetPlaceholderExpression(out var exprType)) + { + // if (context.TryGetPlaceholderNum(exprType, out value)) + // return true; + + switch ((ExpressionType)exprType) + { + case ExpressionType.Millisecond: + value = (uint)DateTime.Now.Millisecond; + return true; + case ExpressionType.Second: + value = (uint)MacroDecoder.GetMacroTime()->tm_sec; + return true; + case ExpressionType.Minute: + value = (uint)MacroDecoder.GetMacroTime()->tm_min; + return true; + case ExpressionType.Hour: + value = (uint)MacroDecoder.GetMacroTime()->tm_hour; + return true; + case ExpressionType.Day: + value = (uint)MacroDecoder.GetMacroTime()->tm_mday; + return true; + case ExpressionType.Weekday: + value = (uint)MacroDecoder.GetMacroTime()->tm_wday; + return true; + case ExpressionType.Month: + value = (uint)MacroDecoder.GetMacroTime()->tm_mon + 1; + return true; + case ExpressionType.Year: + value = (uint)MacroDecoder.GetMacroTime()->tm_year + 1900; + return true; + default: + return false; + } + } + + if (expression.TryGetParameterExpression(out exprType, out var operand1)) + { + if (!this.TryResolveUInt(in context, operand1, out var paramIndex)) + return false; + if (paramIndex == 0) + return false; + paramIndex--; + return (ExpressionType)exprType switch + { + ExpressionType.LocalNumber => context.TryGetLNum((int)paramIndex, out value), // lnum + ExpressionType.GlobalNumber => this.TryGetGNumDefault(paramIndex, out value), // gnum + _ => false, // gstr, lstr + }; + } + + if (expression.TryGetBinaryExpression(out exprType, out operand1, out var operand2)) + { + switch ((ExpressionType)exprType) + { + case ExpressionType.GreaterThanOrEqualTo: + case ExpressionType.GreaterThan: + case ExpressionType.LessThanOrEqualTo: + case ExpressionType.LessThan: + if (!this.TryResolveInt(in context, operand1, out var value1) + || !this.TryResolveInt(in context, operand2, out var value2)) + { + return false; + } + + value = (ExpressionType)exprType switch + { + ExpressionType.GreaterThanOrEqualTo => value1 >= value2 ? 1u : 0u, + ExpressionType.GreaterThan => value1 > value2 ? 1u : 0u, + ExpressionType.LessThanOrEqualTo => value1 <= value2 ? 1u : 0u, + ExpressionType.LessThan => value1 < value2 ? 1u : 0u, + _ => 0u, + }; + return true; + + case ExpressionType.Equal: + case ExpressionType.NotEqual: + if (this.TryResolveInt(in context, operand1, out value1) && + this.TryResolveInt(in context, operand2, out value2)) + { + if ((ExpressionType)exprType == ExpressionType.Equal) + value = value1 == value2 ? 1u : 0u; + else + value = value1 == value2 ? 0u : 1u; + return true; + } + + if (operand1.TryGetString(out var strval1) && operand2.TryGetString(out var strval2)) + { + var resolvedStr1 = this.EvaluateAndAppendTo( + SeStringBuilder.SharedPool.Get(), + strval1, + context.LocalParameters, + context.Language); + var resolvedStr2 = this.EvaluateAndAppendTo( + SeStringBuilder.SharedPool.Get(), + strval2, + context.LocalParameters, + context.Language); + var equals = resolvedStr1.GetViewAsSpan().SequenceEqual(resolvedStr2.GetViewAsSpan()); + SeStringBuilder.SharedPool.Return(resolvedStr1); + SeStringBuilder.SharedPool.Return(resolvedStr2); + + if ((ExpressionType)exprType == ExpressionType.Equal) + value = equals ? 1u : 0u; + else + value = equals ? 0u : 1u; + return true; + } + + // compare int with string, string with int?? + + return true; + + default: + return false; + } + } + + if (expression.TryGetString(out var str)) + { + var evaluatedStr = this.Evaluate(str, context.LocalParameters, context.Language); + + foreach (var payload in evaluatedStr) + { + if (!payload.TryGetExpression(out var expr)) + return false; + + return this.TryResolveUInt(in context, expr, out value); + } + + return false; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryResolveInt(in SeStringContext context, in ReadOnlySeExpressionSpan expression, out int value) + { + if (this.TryResolveUInt(in context, expression, out var u32)) + { + value = (int)u32; + return true; + } + + value = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryResolveBool(in SeStringContext context, in ReadOnlySeExpressionSpan expression, out bool value) + { + if (this.TryResolveUInt(in context, expression, out var u32)) + { + value = u32 != 0; + return true; + } + + value = false; + return false; + } + + private bool ResolveStringExpression(in SeStringContext context, in ReadOnlySeExpressionSpan expression) + { + uint u32; + + if (expression.TryGetString(out var innerString)) + { + context.Builder.Append(this.Evaluate(innerString, context.LocalParameters, context.Language)); + return true; + } + + /* + if (expression.TryGetPlaceholderExpression(out var exprType)) + { + if (context.TryProducePlaceholder(context,exprType)) + return true; + } + */ + + if (expression.TryGetParameterExpression(out var exprType, out var operand1)) + { + if (!this.TryResolveUInt(in context, operand1, out var paramIndex)) + return false; + if (paramIndex == 0) + return false; + paramIndex--; + switch ((ExpressionType)exprType) + { + case ExpressionType.LocalNumber: // lnum + if (!context.TryGetLNum((int)paramIndex, out u32)) + return false; + + context.Builder.Append(unchecked((int)u32).ToString()); + return true; + + case ExpressionType.LocalString: // lstr + if (!context.TryGetLStr((int)paramIndex, out var str)) + return false; + + context.Builder.Append(str); + return true; + + case ExpressionType.GlobalNumber: // gnum + if (!this.TryGetGNumDefault(paramIndex, out u32)) + return false; + + context.Builder.Append(unchecked((int)u32).ToString()); + return true; + + case ExpressionType.GlobalString: // gstr + return this.TryProduceGStrDefault(context.Builder, context.Language, paramIndex); + + default: + return false; + } + } + + // Handles UInt and Binary expressions + if (!this.TryResolveUInt(in context, expression, out u32)) + return false; + + context.Builder.Append(((int)u32).ToString()); + return true; + } + + private readonly record struct StringCacheKey(TK Kind, uint Id, ClientLanguage Language) + where TK : struct, Enum; +} diff --git a/Dalamud/Game/Text/Evaluator/SeStringParameter.cs b/Dalamud/Game/Text/Evaluator/SeStringParameter.cs new file mode 100644 index 000000000..c1f238f56 --- /dev/null +++ b/Dalamud/Game/Text/Evaluator/SeStringParameter.cs @@ -0,0 +1,79 @@ +using System.Globalization; + +using Lumina.Text.ReadOnly; + +using DSeString = Dalamud.Game.Text.SeStringHandling.SeString; +using LSeString = Lumina.Text.SeString; + +namespace Dalamud.Game.Text.Evaluator; + +/// +/// A wrapper for a local parameter, holding either a number or a string. +/// +public readonly struct SeStringParameter +{ + private readonly uint num; + private readonly ReadOnlySeString str; + + /// + /// Initializes a new instance of the struct for a number parameter. + /// + /// The number value. + public SeStringParameter(uint value) + { + this.num = value; + } + + /// + /// Initializes a new instance of the struct for a string parameter. + /// + /// The string value. + public SeStringParameter(ReadOnlySeString value) + { + this.str = value; + this.IsString = true; + } + + /// + /// Initializes a new instance of the struct for a string parameter. + /// + /// The string value. + public SeStringParameter(string value) + { + this.str = new ReadOnlySeString(value); + this.IsString = true; + } + + /// + /// Gets a value indicating whether the backing type of this parameter is a string. + /// + public bool IsString { get; } + + /// + /// Gets a numeric value. + /// + public uint UIntValue => + !this.IsString + ? this.num + : uint.TryParse(this.str.ExtractText(), out var value) ? value : 0; + + /// + /// Gets a string value. + /// + public ReadOnlySeString StringValue => + this.IsString ? this.str : new(this.num.ToString("D", CultureInfo.InvariantCulture)); + + public static implicit operator SeStringParameter(int value) => new((uint)value); + + public static implicit operator SeStringParameter(uint value) => new(value); + + public static implicit operator SeStringParameter(ReadOnlySeString value) => new(value); + + public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value)); + + public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData)); + + public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode())); + + public static implicit operator SeStringParameter(string value) => new(value); +} diff --git a/Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs b/Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs new file mode 100644 index 000000000..9214bea0b --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/EnglishArticleType.cs @@ -0,0 +1,17 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum EnglishArticleType +{ + /// + /// Indefinite article (a, an). + /// + Indefinite = 1, + + /// + /// Definite article (the). + /// + Definite = 2, +} diff --git a/Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs b/Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs new file mode 100644 index 000000000..3b6d6a63e --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/FrenchArticleType.cs @@ -0,0 +1,32 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum FrenchArticleType +{ + /// + /// Indefinite article (une, des). + /// + Indefinite = 1, + + /// + /// Definite article (le, la, les). + /// + Definite = 2, + + /// + /// Possessive article (mon, mes). + /// + PossessiveFirstPerson = 3, + + /// + /// Possessive article (ton, tes). + /// + PossessiveSecondPerson = 4, + + /// + /// Possessive article (son, ses). + /// + PossessiveThirdPerson = 5, +} diff --git a/Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs b/Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs new file mode 100644 index 000000000..29124e172 --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/GermanArticleType.cs @@ -0,0 +1,37 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum GermanArticleType +{ + /// + /// Unbestimmter Artikel (ein, eine, etc.). + /// + Indefinite = 1, + + /// + /// Bestimmter Artikel (der, die, das, etc.). + /// + Definite = 2, + + /// + /// Possessivartikel "dein" (dein, deine, etc.). + /// + Possessive = 3, + + /// + /// Negativartikel "kein" (kein, keine, etc.). + /// + Negative = 4, + + /// + /// Nullartikel. + /// + ZeroArticle = 5, + + /// + /// Demonstrativpronomen "dieser" (dieser, diese, etc.). + /// + Demonstrative = 6, +} diff --git a/Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs b/Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs new file mode 100644 index 000000000..14a29c4ff --- /dev/null +++ b/Dalamud/Game/Text/Noun/Enums/JapaneseArticleType.cs @@ -0,0 +1,17 @@ +namespace Dalamud.Game.Text.Noun.Enums; + +/// +/// Article types for . +/// +public enum JapaneseArticleType +{ + /// + /// Near listener (それら). + /// + NearListener = 1, + + /// + /// Distant from both speaker and listener (あれら). + /// + Distant = 2, +} diff --git a/Dalamud/Game/Text/Noun/NounParams.cs b/Dalamud/Game/Text/Noun/NounParams.cs new file mode 100644 index 000000000..3d5c424be --- /dev/null +++ b/Dalamud/Game/Text/Noun/NounParams.cs @@ -0,0 +1,73 @@ +using Dalamud.Game.Text.Noun.Enums; + +using Lumina.Text.ReadOnly; + +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Game.Text.Noun; + +/// +/// Parameters for noun processing. +/// +internal record struct NounParams() +{ + /// + /// The language of the sheet to be processed. + /// + public required ClientLanguage Language; + + /// + /// The name of the sheet containing the row to process. + /// + public required string SheetName = string.Empty; + + /// + /// The row id within the sheet to process. + /// + public required uint RowId; + + /// + /// The quantity of the entity (default is 1). Used to determine grammatical number (e.g., singular or plural). + /// + public int Quantity = 1; + + /// + /// The article type. + /// + /// + /// Depending on the , this has different meanings.
+ /// See , , , . + ///
+ public int ArticleType = 1; + + /// + /// The grammatical case (e.g., Nominative, Genitive, Dative, Accusative) used for German texts (default is 0). + /// + public int GrammaticalCase = 0; + + /// + /// An optional string that is placed in front of the text that should be linked, such as item names (default is an empty string; the game uses "//"). + /// + public ReadOnlySeString LinkMarker = default; + + /// + /// An indicator that this noun will be processed from an Action sheet. Only used for German texts. + /// + public bool IsActionSheet; + + /// + /// Gets the column offset. + /// + public readonly int ColumnOffset => this.SheetName switch + { + // See "E8 ?? ?? ?? ?? 44 8B 6B 08" + nameof(LSheets.BeastTribe) => 10, + nameof(LSheets.DeepDungeonItem) => 1, + nameof(LSheets.DeepDungeonEquipment) => 1, + nameof(LSheets.DeepDungeonMagicStone) => 1, + nameof(LSheets.DeepDungeonDemiclone) => 1, + nameof(LSheets.Glasses) => 4, + nameof(LSheets.GlassesStyle) => 15, + _ => 0, + }; +} diff --git a/Dalamud/Game/Text/Noun/NounProcessor.cs b/Dalamud/Game/Text/Noun/NounProcessor.cs new file mode 100644 index 000000000..18f8cd4a9 --- /dev/null +++ b/Dalamud/Game/Text/Noun/NounProcessor.cs @@ -0,0 +1,461 @@ +using System.Collections.Concurrent; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Logging.Internal; +using Dalamud.Utility; + +using Lumina.Excel; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Game.Text.Noun; + +/* +Attributive sheet: + Japanese: + Unknown0 = Singular Demonstrative + Unknown1 = Plural Demonstrative + English: + Unknown2 = Article before a singular noun beginning with a consonant sound + Unknown3 = Article before a generic noun beginning with a consonant sound + Unknown4 = N/A + Unknown5 = Article before a singular noun beginning with a vowel sound + Unknown6 = Article before a generic noun beginning with a vowel sound + Unknown7 = N/A + German: + Unknown8 = Nominative Masculine + Unknown9 = Nominative Feminine + Unknown10 = Nominative Neutral + Unknown11 = Nominative Plural + Unknown12 = Genitive Masculine + Unknown13 = Genitive Feminine + Unknown14 = Genitive Neutral + Unknown15 = Genitive Plural + Unknown16 = Dative Masculine + Unknown17 = Dative Feminine + Unknown18 = Dative Neutral + Unknown19 = Dative Plural + Unknown20 = Accusative Masculine + Unknown21 = Accusative Feminine + Unknown22 = Accusative Neutral + Unknown23 = Accusative Plural + French (unsure): + Unknown24 = Singular Article + Unknown25 = Singular Masculine Article + Unknown26 = Plural Masculine Article + Unknown27 = ? + Unknown28 = ? + Unknown29 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown30 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown31 = ? + Unknown32 = ? + Unknown33 = Singular Feminine Article + Unknown34 = Plural Feminine Article + Unknown35 = ? + Unknown36 = ? + Unknown37 = Singular Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown38 = Plural Masculine/Feminine Article, before a noun beginning in a vowel or an h + Unknown39 = ? + Unknown40 = ? + +Placeholders: + [t] = article or grammatical gender (EN: the, DE: der, die, das) + [n] = amount (number) + [a] = declension + [p] = plural + [pa] = ? +*/ + +/// +/// Provides functionality to process texts from sheets containing grammatical placeholders. +/// +[ServiceManager.EarlyLoadedService] +internal class NounProcessor : IServiceType +{ + // column names from ExdSchema, most likely incorrect + private const int SingularColumnIdx = 0; + private const int AdjectiveColumnIdx = 1; + private const int PluralColumnIdx = 2; + private const int PossessivePronounColumnIdx = 3; + private const int StartsWithVowelColumnIdx = 4; + private const int Unknown5ColumnIdx = 5; // probably used in Chinese texts + private const int PronounColumnIdx = 6; + private const int ArticleColumnIdx = 7; + + private static readonly ModuleLog Log = new("NounProcessor"); + + [ServiceManager.ServiceDependency] + private readonly DataManager dataManager = Service.Get(); + + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); + + private readonly ConcurrentDictionary cache = []; + + [ServiceManager.ServiceConstructor] + private NounProcessor() + { + } + + /// + /// Processes a specific row from a sheet and generates a formatted string based on grammatical and language-specific rules. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + public ReadOnlySeString ProcessNoun(NounParams nounParams) + { + if (nounParams.GrammaticalCase < 0 || nounParams.GrammaticalCase > 5) + return default; + + if (this.cache.TryGetValue(nounParams, out var value)) + return value; + + var output = nounParams.Language switch + { + ClientLanguage.Japanese => this.ResolveNounJa(nounParams), + ClientLanguage.English => this.ResolveNounEn(nounParams), + ClientLanguage.German => this.ResolveNounDe(nounParams), + ClientLanguage.French => this.ResolveNounFr(nounParams), + _ => default, + }; + + this.cache.TryAdd(nounParams, output); + + return output; + } + + /// + /// Resolves noun placeholders in Japanese text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounJa.Resolve. + /// + private ReadOnlySeString ResolveNounJa(NounParams nounParams) + { + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + + // Ko-So-A-Do + var ksad = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(nounParams.Quantity > 1 ? 1 : 0); + if (!ksad.IsEmpty) + { + builder.Append(ksad); + + if (nounParams.Quantity > 1) + { + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + } + } + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset); + if (!text.IsEmpty) + builder.Append(text); + + var ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + /// + /// Resolves noun placeholders in English text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounEn.Resolve. + /// + private ReadOnlySeString ResolveNounEn(NounParams nounParams) + { + /* + a1->Offsets[0] = SingularColumnIdx + a1->Offsets[1] = PluralColumnIdx + a1->Offsets[2] = StartsWithVowelColumnIdx + a1->Offsets[3] = PossessivePronounColumnIdx + a1->Offsets[4] = ArticleColumnIdx + */ + + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + + var isProperNounColumn = nounParams.ColumnOffset + ArticleColumnIdx; + var isProperNoun = isProperNounColumn >= 0 ? row.ReadInt8Column(isProperNounColumn) : ~isProperNounColumn; + if (isProperNoun == 0) + { + var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx; + var startsWithVowel = startsWithVowelColumn >= 0 + ? row.ReadInt8Column(startsWithVowelColumn) + : ~startsWithVowelColumn; + + var articleColumn = startsWithVowel + (2 * (startsWithVowel + 1)); + var grammaticalNumberColumnOffset = nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx; + var article = attributiveSheet.GetRow((uint)nounParams.ArticleType) + .ReadStringColumn(articleColumn + grammaticalNumberColumnOffset); + if (!article.IsEmpty) + builder.Append(article); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + } + + var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx)); + if (!text.IsEmpty) + builder.Append(text); + + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + var ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + /// + /// Resolves noun placeholders in German text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounDe.Resolve. + /// + private ReadOnlySeString ResolveNounDe(NounParams nounParams) + { + /* + a1->Offsets[0] = SingularColumnIdx + a1->Offsets[1] = PluralColumnIdx + a1->Offsets[2] = PronounColumnIdx + a1->Offsets[3] = AdjectiveColumnIdx + a1->Offsets[4] = PossessivePronounColumnIdx + a1->Offsets[5] = Unknown5ColumnIdx + a1->Offsets[6] = ArticleColumnIdx + */ + + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + ReadOnlySeString ross; + + if (nounParams.IsActionSheet) + { + builder.Append(row.ReadStringColumn(nounParams.GrammaticalCase)); + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + var genderIndexColumn = nounParams.ColumnOffset + PronounColumnIdx; + var genderIndex = genderIndexColumn >= 0 ? row.ReadInt8Column(genderIndexColumn) : ~genderIndexColumn; + + var articleIndexColumn = nounParams.ColumnOffset + ArticleColumnIdx; + var articleIndex = articleIndexColumn >= 0 ? row.ReadInt8Column(articleIndexColumn) : ~articleIndexColumn; + + var caseColumnOffset = (4 * nounParams.GrammaticalCase) + 8; + + var caseRowOffsetColumn = nounParams.ColumnOffset + (nounParams.Quantity == 1 ? AdjectiveColumnIdx : PossessivePronounColumnIdx); + var caseRowOffset = caseRowOffsetColumn >= 0 + ? row.ReadInt8Column(caseRowOffsetColumn) + : (sbyte)~caseRowOffsetColumn; + + if (nounParams.Quantity != 1) + genderIndex = 3; + + var hasT = false; + var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity == 1 ? SingularColumnIdx : PluralColumnIdx)); + if (!text.IsEmpty) + { + hasT = text.ContainsText("[t]"u8); + + if (articleIndex == 0 && !hasT) + { + var grammaticalGender = attributiveSheet.GetRow((uint)nounParams.ArticleType) + .ReadStringColumn(caseColumnOffset + genderIndex); // Genus + if (!grammaticalGender.IsEmpty) + builder.Append(grammaticalGender); + } + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + builder.Append(text); + + var plural = attributiveSheet.GetRow((uint)(caseRowOffset + 26)) + .ReadStringColumn(caseColumnOffset + genderIndex); + if (builder.ContainsText("[p]"u8)) + builder.ReplaceText("[p]"u8, plural); + else + builder.Append(plural); + + if (hasT) + { + var article = + attributiveSheet.GetRow(39).ReadStringColumn(caseColumnOffset + genderIndex); // Definiter Artikel + builder.ReplaceText("[t]"u8, article); + } + } + + var pa = attributiveSheet.GetRow(24).ReadStringColumn(caseColumnOffset + genderIndex); + builder.ReplaceText("[pa]"u8, pa); + + RawRow declensionRow; + + declensionRow = (GermanArticleType)nounParams.ArticleType switch + { + // Schwache Flexion eines Adjektivs?! + GermanArticleType.Possessive or GermanArticleType.Demonstrative => attributiveSheet.GetRow(25), + _ when hasT => attributiveSheet.GetRow(25), + + // Starke Deklination + GermanArticleType.ZeroArticle => attributiveSheet.GetRow(38), + + // Gemischte Deklination + GermanArticleType.Definite => attributiveSheet.GetRow(37), + + // Starke Flexion eines Artikels?! + GermanArticleType.Indefinite or GermanArticleType.Negative => attributiveSheet.GetRow(26), + _ => attributiveSheet.GetRow(26), + }; + + var declension = declensionRow.ReadStringColumn(caseColumnOffset + genderIndex); + builder.ReplaceText("[a]"u8, declension); + + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + /// + /// Resolves noun placeholders in French text. + /// + /// Parameters for processing. + /// A ReadOnlySeString representing the processed text. + /// + /// This is a C# implementation of Component::Text::Localize::NounFr.Resolve. + /// + private ReadOnlySeString ResolveNounFr(NounParams nounParams) + { + /* + a1->Offsets[0] = SingularColumnIdx + a1->Offsets[1] = PluralColumnIdx + a1->Offsets[2] = StartsWithVowelColumnIdx + a1->Offsets[3] = PronounColumnIdx + a1->Offsets[4] = Unknown5ColumnIdx + a1->Offsets[5] = ArticleColumnIdx + */ + + var sheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nounParams.SheetName); + if (!sheet.TryGetRow(nounParams.RowId, out var row)) + { + Log.Warning("Sheet {SheetName} does not contain row #{RowId}", nounParams.SheetName, nounParams.RowId); + return default; + } + + var attributiveSheet = this.dataManager.Excel.GetSheet(nounParams.Language.ToLumina(), nameof(LSheets.Attributive)); + + var builder = LSeStringBuilder.SharedPool.Get(); + ReadOnlySeString ross; + + var startsWithVowelColumn = nounParams.ColumnOffset + StartsWithVowelColumnIdx; + var startsWithVowel = startsWithVowelColumn >= 0 + ? row.ReadInt8Column(startsWithVowelColumn) + : ~startsWithVowelColumn; + + var pronounColumn = nounParams.ColumnOffset + PronounColumnIdx; + var pronoun = pronounColumn >= 0 ? row.ReadInt8Column(pronounColumn) : ~pronounColumn; + + var articleColumn = nounParams.ColumnOffset + ArticleColumnIdx; + var article = articleColumn >= 0 ? row.ReadInt8Column(articleColumn) : ~articleColumn; + + var v20 = 4 * (startsWithVowel + 6 + (2 * pronoun)); + + if (article != 0) + { + var v21 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20); + if (!v21.IsEmpty) + builder.Append(v21); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset + (nounParams.Quantity <= 1 ? SingularColumnIdx : PluralColumnIdx)); + if (!text.IsEmpty) + builder.Append(text); + + if (nounParams.Quantity <= 1) + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } + + var v17 = row.ReadInt8Column(nounParams.ColumnOffset + Unknown5ColumnIdx); + if (v17 != 0 && (nounParams.Quantity > 1 || v17 == 2)) + { + var v29 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + 2); + if (!v29.IsEmpty) + { + builder.Append(v29); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset + PluralColumnIdx); + if (!text.IsEmpty) + builder.Append(text); + } + } + else + { + var v27 = attributiveSheet.GetRow((uint)nounParams.ArticleType).ReadStringColumn(v20 + (v17 != 0 ? 1 : 3)); + if (!v27.IsEmpty) + builder.Append(v27); + + if (!nounParams.LinkMarker.IsEmpty) + builder.Append(nounParams.LinkMarker); + + var text = row.ReadStringColumn(nounParams.ColumnOffset + SingularColumnIdx); + if (!text.IsEmpty) + builder.Append(text); + } + + builder.ReplaceText("[n]"u8, ReadOnlySeString.FromText(nounParams.Quantity.ToString())); + + ross = builder.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(builder); + return ross; + } +} diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index c31707ff2..d6fd897b8 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using Dalamud.Data; +using Dalamud.Utility; + using Lumina.Excel; using Lumina.Excel.Sheets; using Newtonsoft.Json; @@ -73,6 +75,7 @@ public class ItemPayload : Payload /// /// Kinds of items that can be fetched from this payload. /// + [Api12ToDo("Move this out of ItemPayload. It's used in other classes too.")] public enum ItemKind : uint { /// @@ -121,7 +124,7 @@ public class ItemPayload : Payload /// Gets the actual item ID of this payload. /// [JsonIgnore] - public uint ItemId => GetAdjustedId(this.rawItemId).ItemId; + public uint ItemId => ItemUtil.GetBaseId(this.rawItemId).ItemId; /// /// Gets the raw, unadjusted item ID of this payload. @@ -161,7 +164,7 @@ public class ItemPayload : Payload /// The created item payload. public static ItemPayload FromRaw(uint rawItemId, string? displayNameOverride = null) { - var (id, kind) = GetAdjustedId(rawItemId); + var (id, kind) = ItemUtil.GetBaseId(rawItemId); return new ItemPayload(id, kind, displayNameOverride); } @@ -230,7 +233,7 @@ public class ItemPayload : Payload protected override void DecodeImpl(BinaryReader reader, long endOfStream) { this.rawItemId = GetInteger(reader); - this.Kind = GetAdjustedId(this.rawItemId).Kind; + this.Kind = ItemUtil.GetBaseId(this.rawItemId).Kind; if (reader.BaseStream.Position + 3 < endOfStream) { @@ -255,15 +258,4 @@ public class ItemPayload : Payload this.displayName = Encoding.UTF8.GetString(itemNameBytes); } } - - private static (uint ItemId, ItemKind Kind) GetAdjustedId(uint rawItemId) - { - return rawItemId switch - { - > 500_000 and < 1_000_000 => (rawItemId - 500_000, ItemKind.Collectible), - > 1_000_000 and < 2_000_000 => (rawItemId - 1_000_000, ItemKind.Hq), - > 2_000_000 => (rawItemId, ItemKind.EventItem), // EventItem IDs are NOT adjusted - _ => (rawItemId, ItemKind.Normal), - }; - } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index 7f1955da5..b7618305a 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -5,11 +5,16 @@ using System.Runtime.InteropServices; using System.Text; using Dalamud.Data; +using Dalamud.Game.Text.Evaluator; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Utility; + using Lumina.Excel.Sheets; + using Newtonsoft.Json; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + namespace Dalamud.Game.Text.SeStringHandling; /// @@ -187,57 +192,32 @@ public class SeString /// An SeString containing all the payloads necessary to display an item link in the chat log. public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null) { - var data = Service.Get(); + var clientState = Service.Get(); + var seStringEvaluator = Service.Get(); - var displayName = displayNameOverride; - var rarity = 1; // default: white - if (displayName == null) - { - switch (kind) - { - case ItemPayload.ItemKind.Normal: - case ItemPayload.ItemKind.Collectible: - case ItemPayload.ItemKind.Hq: - var item = data.GetExcelSheet()?.GetRowOrDefault(itemId); - displayName = item?.Name.ExtractText(); - rarity = item?.Rarity ?? 1; - break; - case ItemPayload.ItemKind.EventItem: - displayName = data.GetExcelSheet()?.GetRowOrDefault(itemId)?.Name.ExtractText(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(kind), kind, null); - } - } + var rawId = ItemUtil.GetRawId(itemId, kind); - if (displayName == null) - { + var displayName = displayNameOverride ?? ItemUtil.GetItemName(rawId); + if (displayName.IsEmpty) throw new Exception("Invalid item ID specified, could not determine item name."); - } - if (kind == ItemPayload.ItemKind.Hq) - { - displayName += $" {(char)SeIconChar.HighQuality}"; - } - else if (kind == ItemPayload.ItemKind.Collectible) - { - displayName += $" {(char)SeIconChar.Collectible}"; - } + var copyName = ItemUtil.GetItemName(rawId, false).ExtractText(); + var textColor = ItemUtil.GetItemRarityColorType(rawId); + var textEdgeColor = textColor + 1u; - var textColor = (ushort)(549 + ((rarity - 1) * 2)); - var textGlowColor = (ushort)(textColor + 1); + var sb = LSeStringBuilder.SharedPool.Get(); + var itemLink = sb + .PushColorType(textColor) + .PushEdgeColorType(textEdgeColor) + .PushLinkItem(rawId, copyName) + .Append(displayName) + .PopLink() + .PopEdgeColorType() + .PopColorType() + .ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(sb); - // Note: `SeStringBuilder.AddItemLink` uses this function, so don't call it here! - return new SeStringBuilder() - .AddUiForeground(textColor) - .AddUiGlow(textGlowColor) - .Add(new ItemPayload(itemId, kind)) - .Append(TextArrowPayloads) - .AddText(displayName) - .AddUiGlowOff() - .AddUiForegroundOff() - .Add(RawPayload.LinkTerminator) - .Build(); + return SeString.Parse(seStringEvaluator.EvaluateFromAddon(371, [itemLink], clientState.ClientLanguage)); } /// @@ -301,7 +281,7 @@ public class SeString public static SeString CreateMapLink( uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => CreateMapLinkWithInstance(territoryId, mapId, null, xCoord, yCoord, fudgeFactor); - + /// /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. /// @@ -340,7 +320,7 @@ public class SeString /// An SeString containing all of the payloads necessary to display a map link in the chat log. public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => CreateMapLinkWithInstance(placeName, null, xCoord, yCoord, fudgeFactor); - + /// /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. /// Returns null if no corresponding PlaceName was found. @@ -511,7 +491,7 @@ public class SeString { messageBytes.AddRange(p.Encode()); } - + // Add Null Terminator messageBytes.Add(0); @@ -526,7 +506,7 @@ public class SeString { return this.TextValue; } - + private static string GetMapLinkNameString(string placeName, int? instance, string coordinateString) { var instanceString = string.Empty; @@ -534,7 +514,7 @@ public class SeString { instanceString = (SeIconChar.Instance1 + instance.Value - 1).ToIconString(); } - + return $"{placeName}{instanceString} {coordinateString}"; } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 4937e4af0..9221c2dc5 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -7,7 +7,6 @@ using BitFaster.Caching.Lru; using Dalamud.Data; using Dalamud.Game; -using Dalamud.Game.Config; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.Interface.Utility; @@ -44,9 +43,6 @@ internal unsafe class SeStringRenderer : IInternalDisposableService /// of this placeholder. On its own, usually displayed like [OBJ]. private const int ObjectReplacementCharacter = '\uFFFC'; - [ServiceManager.ServiceDependency] - private readonly GameConfig gameConfig = Service.Get(); - /// Cache of compiled SeStrings from . private readonly ConcurrentLru cache = new(1024); @@ -570,70 +566,16 @@ internal unsafe class SeStringRenderer : IInternalDisposableService // Apply gamepad key mapping to icons. case MacroCode.Icon2 when payload.TryGetExpression(out var icon) && icon.TryGetInt(out var iconId): - var configName = (BitmapFontIcon)iconId switch + ref var iconMapping = ref RaptureAtkModule.Instance()->AtkFontManager.Icon2RemapTable; + for (var i = 0; i < 30; i++) { - ControllerShoulderLeft => SystemConfigOption.PadButton_L1, - ControllerShoulderRight => SystemConfigOption.PadButton_R1, - ControllerTriggerLeft => SystemConfigOption.PadButton_L2, - ControllerTriggerRight => SystemConfigOption.PadButton_R2, - ControllerButton3 => SystemConfigOption.PadButton_Triangle, - ControllerButton1 => SystemConfigOption.PadButton_Cross, - ControllerButton0 => SystemConfigOption.PadButton_Circle, - ControllerButton2 => SystemConfigOption.PadButton_Square, - ControllerStart => SystemConfigOption.PadButton_Start, - ControllerBack => SystemConfigOption.PadButton_Select, - ControllerAnalogLeftStick => SystemConfigOption.PadButton_LS, - ControllerAnalogLeftStickIn => SystemConfigOption.PadButton_LS, - ControllerAnalogLeftStickUpDown => SystemConfigOption.PadButton_LS, - ControllerAnalogLeftStickLeftRight => SystemConfigOption.PadButton_LS, - ControllerAnalogRightStick => SystemConfigOption.PadButton_RS, - ControllerAnalogRightStickIn => SystemConfigOption.PadButton_RS, - ControllerAnalogRightStickUpDown => SystemConfigOption.PadButton_RS, - ControllerAnalogRightStickLeftRight => SystemConfigOption.PadButton_RS, - _ => (SystemConfigOption?)null, - }; - - if (configName is null || !this.gameConfig.TryGet(configName.Value, out PadButtonValue pb)) - return (BitmapFontIcon)iconId; - - return pb switch - { - PadButtonValue.Autorun_Support => ControllerShoulderLeft, - PadButtonValue.Hotbar_Set_Change => ControllerShoulderRight, - PadButtonValue.XHB_Left_Start => ControllerTriggerLeft, - PadButtonValue.XHB_Right_Start => ControllerTriggerRight, - PadButtonValue.Jump => ControllerButton3, - PadButtonValue.Accept => ControllerButton0, - PadButtonValue.Cancel => ControllerButton1, - PadButtonValue.Map_Sub => ControllerButton2, - PadButtonValue.MainCommand => ControllerStart, - PadButtonValue.HUD_Select => ControllerBack, - PadButtonValue.Move_Operation => (BitmapFontIcon)iconId switch + if (iconMapping[i].IconId == iconId) { - ControllerAnalogLeftStick => ControllerAnalogLeftStick, - ControllerAnalogLeftStickIn => ControllerAnalogLeftStickIn, - ControllerAnalogLeftStickUpDown => ControllerAnalogLeftStickUpDown, - ControllerAnalogLeftStickLeftRight => ControllerAnalogLeftStickLeftRight, - ControllerAnalogRightStick => ControllerAnalogLeftStick, - ControllerAnalogRightStickIn => ControllerAnalogLeftStickIn, - ControllerAnalogRightStickUpDown => ControllerAnalogLeftStickUpDown, - ControllerAnalogRightStickLeftRight => ControllerAnalogLeftStickLeftRight, - _ => (BitmapFontIcon)iconId, - }, - PadButtonValue.Camera_Operation => (BitmapFontIcon)iconId switch - { - ControllerAnalogLeftStick => ControllerAnalogRightStick, - ControllerAnalogLeftStickIn => ControllerAnalogRightStickIn, - ControllerAnalogLeftStickUpDown => ControllerAnalogRightStickUpDown, - ControllerAnalogLeftStickLeftRight => ControllerAnalogRightStickLeftRight, - ControllerAnalogRightStick => ControllerAnalogRightStick, - ControllerAnalogRightStickIn => ControllerAnalogRightStickIn, - ControllerAnalogRightStickUpDown => ControllerAnalogRightStickUpDown, - ControllerAnalogRightStickLeftRight => ControllerAnalogRightStickLeftRight, - _ => (BitmapFontIcon)iconId, - }, - _ => (BitmapFontIcon)iconId, - }; + return (BitmapFontIcon)iconMapping[i].RemappedIconId; + } + } + + return (BitmapFontIcon)iconId; } return None; diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index f3ec882fc..7326f6745 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -47,11 +47,13 @@ internal class DataWindow : Window, IDisposable new KeyStateWidget(), new MarketBoardWidget(), new NetworkMonitorWidget(), + new NounProcessorWidget(), new ObjectTableWidget(), new PartyListWidget(), new PluginIpcWidget(), new SeFontTestWidget(), new ServicesWidget(), + new SeStringCreatorWidget(), new SeStringRendererTestWidget(), new StartInfoWidget(), new TargetWidget(), @@ -68,6 +70,7 @@ internal class DataWindow : Window, IDisposable private bool isExcept; private bool selectionCollapsed; private IDataWindowWidget currentWidget; + private bool isLoaded; /// /// Initializes a new instance of the class. @@ -81,8 +84,6 @@ internal class DataWindow : Window, IDisposable this.RespectCloseHotkey = false; this.orderedModules = this.modules.OrderBy(module => module.DisplayName); this.currentWidget = this.orderedModules.First(); - - this.Load(); } /// @@ -91,6 +92,7 @@ internal class DataWindow : Window, IDisposable /// public override void OnOpen() { + this.Load(); } /// @@ -183,6 +185,7 @@ internal class DataWindow : Window, IDisposable if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync)) { + this.isLoaded = false; this.Load(); } @@ -236,6 +239,11 @@ internal class DataWindow : Window, IDisposable private void Load() { + if (this.isLoaded) + return; + + this.isLoaded = true; + foreach (var widget in this.modules) { widget.Load(); diff --git a/Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs b/Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs new file mode 100644 index 000000000..209970e24 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/WidgetUtil.cs @@ -0,0 +1,34 @@ +using Dalamud.Interface.Utility; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Data; + +/// +/// Common utilities used in Widgets. +/// +internal class WidgetUtil +{ + /// + /// Draws text that can be copied on click. + /// + /// The text shown and to be copied. + /// The text in the tooltip. + internal static void DrawCopyableText(string text, string tooltipText = "Copy") + { + ImGuiHelpers.SafeTextWrapped(text); + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltipText); + ImGui.EndTooltip(); + } + + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText(text); + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs index 791dc5310..c3499570c 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AtkArrayDataBrowserWidget.cs @@ -57,24 +57,6 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget this.DrawExtendArrayTab(); } - private static void DrawCopyableText(string text, string tooltipText) - { - ImGuiHelpers.SafeTextWrapped(text); - - if (ImGui.IsItemHovered()) - { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - ImGui.BeginTooltip(); - ImGui.TextUnformatted(tooltipText); - ImGui.EndTooltip(); - } - - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText(text); - } - } - private void DrawArrayList(Type? arrayType, int arrayCount, short* arrayKeys, AtkArrayData** arrays, ref int selectedIndex) { using var table = ImRaii.Table("ArkArrayTable", 3, ImGuiTableFlags.ScrollY | ImGuiTableFlags.Borders, new Vector2(300, -1)); @@ -162,7 +144,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget ImGui.SameLine(); ImGui.TextUnformatted("Address: "); ImGui.SameLine(0, 0); - DrawCopyableText($"0x{(nint)array:X}", "Copy address"); + WidgetUtil.DrawCopyableText($"0x{(nint)array:X}", "Copy address"); if (array->SubscribedAddonsCount > 0) { @@ -238,22 +220,22 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget var ptr = &array->IntArray[i]; ImGui.TableNextColumn(); // Address - DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address"); + WidgetUtil.DrawCopyableText($"0x{(nint)ptr:X}", "Copy entry address"); ImGui.TableNextColumn(); // Integer - DrawCopyableText((*ptr).ToString(), "Copy value"); + WidgetUtil.DrawCopyableText((*ptr).ToString(), "Copy value"); ImGui.TableNextColumn(); // Short - DrawCopyableText((*(short*)ptr).ToString(), "Copy as short"); + WidgetUtil.DrawCopyableText((*(short*)ptr).ToString(), "Copy as short"); ImGui.TableNextColumn(); // Byte - DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte"); + WidgetUtil.DrawCopyableText((*(byte*)ptr).ToString(), "Copy as byte"); ImGui.TableNextColumn(); // Float - DrawCopyableText((*(float*)ptr).ToString(), "Copy as float"); + WidgetUtil.DrawCopyableText((*(float*)ptr).ToString(), "Copy as float"); ImGui.TableNextColumn(); // Hex - DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex"); + WidgetUtil.DrawCopyableText($"0x{array->IntArray[i]:X2}", "Copy Hex"); } } @@ -333,11 +315,11 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget if (this.showTextAddress) { if (!isNull) - DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address"); + WidgetUtil.DrawCopyableText($"0x{(nint)array->StringArray[i]:X}", "Copy text address"); } else { - DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address"); + WidgetUtil.DrawCopyableText($"0x{(nint)(&array->StringArray[i]):X}", "Copy entry address"); } ImGui.TableNextColumn(); // Managed @@ -351,7 +333,7 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget { if (this.showMacroString) { - DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text"); + WidgetUtil.DrawCopyableText(new ReadOnlySeStringSpan(array->StringArray[i]).ToString(), "Copy text"); } else { @@ -408,11 +390,11 @@ internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget ImGui.TextUnformatted($"#{i}"); ImGui.TableNextColumn(); // Address - DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address"); + WidgetUtil.DrawCopyableText($"0x{(nint)(&array->DataArray[i]):X}", "Copy entry address"); ImGui.TableNextColumn(); // Pointer if (!isNull) - DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address"); + WidgetUtil.DrawCopyableText($"0x{(nint)array->DataArray[i]:X}", "Copy address"); } } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs index 1a43f2b2d..34b04dae0 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/FateTableWidget.cs @@ -69,10 +69,10 @@ internal class FateTableWidget : IDataWindowWidget ImGui.TextUnformatted($"#{i}"); ImGui.TableNextColumn(); // Address - DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address"); + WidgetUtil.DrawCopyableText($"0x{fate.Address:X}", "Click to copy Address"); ImGui.TableNextColumn(); // FateId - DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)"); + WidgetUtil.DrawCopyableText(fate.FateId.ToString(), "Click to copy FateId (RowId of Fate sheet)"); ImGui.TableNextColumn(); // State ImGui.TextUnformatted(fate.State.ToString()); @@ -140,7 +140,7 @@ internal class FateTableWidget : IDataWindowWidget ImGui.TableNextColumn(); // Name - DrawCopyableText(fate.Name.ToString(), "Click to copy Name"); + WidgetUtil.DrawCopyableText(fate.Name.ToString(), "Click to copy Name"); ImGui.TableNextColumn(); // Progress ImGui.TextUnformatted($"{fate.Progress}%"); @@ -156,28 +156,10 @@ internal class FateTableWidget : IDataWindowWidget ImGui.TextUnformatted(fate.HasBonus.ToString()); ImGui.TableNextColumn(); // Position - DrawCopyableText(fate.Position.ToString(), "Click to copy Position"); + WidgetUtil.DrawCopyableText(fate.Position.ToString(), "Click to copy Position"); ImGui.TableNextColumn(); // Radius - DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius"); - } - } - - private static void DrawCopyableText(string text, string tooltipText) - { - ImGuiHelpers.SafeTextWrapped(text); - - if (ImGui.IsItemHovered()) - { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - ImGui.BeginTooltip(); - ImGui.TextUnformatted(tooltipText); - ImGui.EndTooltip(); - } - - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText(text); + WidgetUtil.DrawCopyableText(fate.Radius.ToString(), "Click to copy Radius"); } } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs new file mode 100644 index 000000000..bc0bd0ac9 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NounProcessorWidget.cs @@ -0,0 +1,207 @@ +using System.Linq; +using System.Text; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.ClientState; +using Dalamud.Game.Text.Noun; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Interface.Utility.Raii; + +using ImGuiNET; +using Lumina.Data; +using Lumina.Excel; +using Lumina.Excel.Sheets; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget for the NounProcessor service. +/// +internal class NounProcessorWidget : IDataWindowWidget +{ + /// A list of German grammatical cases. + internal static readonly string[] GermanCases = ["Nominative", "Genitive", "Dative", "Accusative"]; + + private static readonly Type[] NounSheets = [ + typeof(Aetheryte), + typeof(BNpcName), + typeof(BeastTribe), + typeof(DeepDungeonEquipment), + typeof(DeepDungeonItem), + typeof(DeepDungeonMagicStone), + typeof(DeepDungeonDemiclone), + typeof(ENpcResident), + typeof(EObjName), + typeof(EurekaAetherItem), + typeof(EventItem), + typeof(GCRankGridaniaFemaleText), + typeof(GCRankGridaniaMaleText), + typeof(GCRankLimsaFemaleText), + typeof(GCRankLimsaMaleText), + typeof(GCRankUldahFemaleText), + typeof(GCRankUldahMaleText), + typeof(GatheringPointName), + typeof(Glasses), + typeof(GlassesStyle), + typeof(HousingPreset), + typeof(Item), + typeof(MJIName), + typeof(Mount), + typeof(Ornament), + typeof(TripleTriadCard), + ]; + + private ClientLanguage[] languages = []; + private string[] languageNames = []; + + private int selectedSheetNameIndex = 0; + private int selectedLanguageIndex = 0; + private int rowId = 1; + private int amount = 1; + + /// + public string[]? CommandShortcuts { get; init; } = { "noun" }; + + /// + public string DisplayName { get; init; } = "Noun Processor"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.languages = Enum.GetValues(); + this.languageNames = Enum.GetNames(); + this.selectedLanguageIndex = (int)Service.Get().ClientLanguage; + + this.Ready = true; + } + + /// + public void Draw() + { + var nounProcessor = Service.Get(); + var dataManager = Service.Get(); + var clientState = Service.Get(); + + var sheetType = NounSheets.ElementAt(this.selectedSheetNameIndex); + var language = this.languages[this.selectedLanguageIndex]; + + ImGui.SetNextItemWidth(300); + if (ImGui.Combo("###SelectedSheetName", ref this.selectedSheetNameIndex, NounSheets.Select(t => t.Name).ToArray(), NounSheets.Length)) + { + this.rowId = 1; + } + + ImGui.SameLine(); + + ImGui.SetNextItemWidth(120); + if (ImGui.Combo("###SelectedLanguage", ref this.selectedLanguageIndex, this.languageNames, this.languageNames.Length)) + { + language = this.languages[this.selectedLanguageIndex]; + this.rowId = 1; + } + + ImGui.SetNextItemWidth(120); + var sheet = dataManager.Excel.GetSheet(Language.English, sheetType.Name); + var minRowId = (int)sheet.FirstOrDefault().RowId; + var maxRowId = (int)sheet.LastOrDefault().RowId; + if (ImGui.InputInt("RowId###RowId", ref this.rowId, 1, 10, ImGuiInputTextFlags.AutoSelectAll)) + { + if (this.rowId < minRowId) + this.rowId = minRowId; + + if (this.rowId >= maxRowId) + this.rowId = maxRowId; + } + + ImGui.SameLine(); + ImGui.TextUnformatted($"(Range: {minRowId} - {maxRowId})"); + + ImGui.SetNextItemWidth(120); + if (ImGui.InputInt("Amount###Amount", ref this.amount, 1, 10, ImGuiInputTextFlags.AutoSelectAll)) + { + if (this.amount <= 0) + this.amount = 1; + } + + var articleTypeEnumType = language switch + { + ClientLanguage.Japanese => typeof(JapaneseArticleType), + ClientLanguage.German => typeof(GermanArticleType), + ClientLanguage.French => typeof(FrenchArticleType), + _ => typeof(EnglishArticleType), + }; + + var numCases = language == ClientLanguage.German ? 4 : 1; + +#if DEBUG + if (ImGui.Button("Copy as self-test entry")) + { + var sb = new StringBuilder(); + + foreach (var articleType in Enum.GetValues(articleTypeEnumType)) + { + for (var grammaticalCase = 0; grammaticalCase < numCases; grammaticalCase++) + { + var nounParams = new NounParams() + { + SheetName = sheetType.Name, + RowId = (uint)this.rowId, + Language = language, + Quantity = this.amount, + ArticleType = (int)articleType, + GrammaticalCase = grammaticalCase, + }; + var output = nounProcessor.ProcessNoun(nounParams).ExtractText().Replace("\"", "\\\""); + var caseParam = language == ClientLanguage.German ? $"(int)GermanCases.{GermanCases[grammaticalCase]}" : "1"; + sb.AppendLine($"new(nameof(LSheets.{sheetType.Name}), {this.rowId}, ClientLanguage.{language}, {this.amount}, (int){articleTypeEnumType.Name}.{Enum.GetName(articleTypeEnumType, articleType)}, {caseParam}, \"{output}\"),"); + } + } + + ImGui.SetClipboardText(sb.ToString()); + } +#endif + + using var table = ImRaii.Table("TextDecoderTable", 1 + numCases, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("ArticleType", ImGuiTableColumnFlags.WidthFixed, 150); + for (var i = 0; i < numCases; i++) + ImGui.TableSetupColumn(language == ClientLanguage.German ? GermanCases[i] : "Text"); + ImGui.TableSetupScrollFreeze(6, 1); + ImGui.TableHeadersRow(); + + foreach (var articleType in Enum.GetValues(articleTypeEnumType)) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TableHeader(articleType.ToString()); + + for (var currentCase = 0; currentCase < numCases; currentCase++) + { + ImGui.TableNextColumn(); + + try + { + var nounParams = new NounParams() + { + SheetName = sheetType.Name, + RowId = (uint)this.rowId, + Language = language, + Quantity = this.amount, + ArticleType = (int)articleType, + GrammaticalCase = currentCase, + }; + ImGui.TextUnformatted(nounProcessor.ProcessNoun(nounParams).ExtractText()); + } + catch (Exception ex) + { + ImGui.TextUnformatted(ex.ToString()); + } + } + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs new file mode 100644 index 000000000..2a56cb6c7 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs @@ -0,0 +1,1276 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; + +using Dalamud.Configuration.Internal; +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.Text.Evaluator; +using Dalamud.Game.Text.Noun.Enums; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Memory; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Component.Text; + +using ImGuiNET; + +using Lumina.Data; +using Lumina.Data.Files.Excel; +using Lumina.Data.Structs.Excel; +using Lumina.Excel; +using Lumina.Excel.Sheets; +using Lumina.Text.Expressions; +using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget to create SeStrings. +/// +internal class SeStringCreatorWidget : IDataWindowWidget +{ + private const LinkMacroPayloadType DalamudLinkType = (LinkMacroPayloadType)Payload.EmbeddedInfoType.DalamudLink - 1; + + private readonly Dictionary expressionNames = new() + { + { MacroCode.SetResetTime, ["Hour", "WeekDay"] }, + { MacroCode.SetTime, ["Time"] }, + { MacroCode.If, ["Condition", "StatementTrue", "StatementFalse"] }, + { MacroCode.Switch, ["Condition"] }, + { MacroCode.PcName, ["EntityId"] }, + { MacroCode.IfPcGender, ["EntityId", "CaseMale", "CaseFemale"] }, + { MacroCode.IfPcName, ["EntityId", "CaseTrue", "CaseFalse"] }, + // { MacroCode.Josa, [] }, + // { MacroCode.Josaro, [] }, + { MacroCode.IfSelf, ["EntityId", "CaseTrue", "CaseFalse"] }, + // { MacroCode.NewLine, [] }, + { MacroCode.Wait, ["Seconds"] }, + { MacroCode.Icon, ["IconId"] }, + { MacroCode.Color, ["Color"] }, + { MacroCode.EdgeColor, ["Color"] }, + { MacroCode.ShadowColor, ["Color"] }, + // { MacroCode.SoftHyphen, [] }, + // { MacroCode.Key, [] }, + // { MacroCode.Scale, [] }, + { MacroCode.Bold, ["Enabled"] }, + { MacroCode.Italic, ["Enabled"] }, + // { MacroCode.Edge, [] }, + // { MacroCode.Shadow, [] }, + // { MacroCode.NonBreakingSpace, [] }, + { MacroCode.Icon2, ["IconId"] }, + // { MacroCode.Hyphen, [] }, + { MacroCode.Num, ["Value"] }, + { MacroCode.Hex, ["Value"] }, + { MacroCode.Kilo, ["Value", "Separator"] }, + { MacroCode.Byte, ["Value"] }, + { MacroCode.Sec, ["Time"] }, + { MacroCode.Time, ["Value"] }, + { MacroCode.Float, ["Value", "Radix", "Separator"] }, + { MacroCode.Link, ["Type"] }, + { MacroCode.Sheet, ["SheetName", "RowId", "ColumnIndex", "ColumnParam"] }, + { MacroCode.String, ["String"] }, + { MacroCode.Caps, ["String"] }, + { MacroCode.Head, ["String"] }, + { MacroCode.Split, ["String", "Separator"] }, + { MacroCode.HeadAll, ["String"] }, + // { MacroCode.Fixed, [] }, + { MacroCode.Lower, ["String"] }, + { MacroCode.JaNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.EnNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.DeNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.FrNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.ChNoun, ["SheetName", "ArticleType", "RowId", "Amount", "Case", "UnkInt5"] }, + { MacroCode.LowerHead, ["String"] }, + { MacroCode.ColorType, ["ColorType"] }, + { MacroCode.EdgeColorType, ["ColorType"] }, + { MacroCode.Digit, ["Value", "TargetLength"] }, + { MacroCode.Ordinal, ["Value"] }, + { MacroCode.Sound, ["IsJingle", "SoundId"] }, + { MacroCode.LevelPos, ["LevelId"] }, + }; + + private readonly Dictionary linkExpressionNames = new() + { + { LinkMacroPayloadType.Character, ["Flags", "WorldId"] }, + { LinkMacroPayloadType.Item, ["ItemId", "Rarity"] }, + { LinkMacroPayloadType.MapPosition, ["TerritoryType/MapId", "RawX", "RawY"] }, + { LinkMacroPayloadType.Quest, ["QuestId"] }, + { LinkMacroPayloadType.Achievement, ["AchievementId"] }, + { LinkMacroPayloadType.HowTo, ["HowToId"] }, + // PartyFinderNotification + { LinkMacroPayloadType.Status, ["StatusId"] }, + { LinkMacroPayloadType.PartyFinder, ["ListingId", string.Empty, "WorldId"] }, + { LinkMacroPayloadType.AkatsukiNote, ["AkatsukiNoteId"] }, + { DalamudLinkType, ["CommandId", "Extra1", "Extra2", "ExtraString"] }, + }; + + private readonly Dictionary fixedExpressionNames = new() + { + { 1, ["Type0", "Type1", "WorldId"] }, + { 2, ["Type0", "Type1", "ClassJobId", "Level"] }, + { 3, ["Type0", "Type1", "TerritoryTypeId", "Instance & MapId", "RawX", "RawY", "RawZ", "PlaceNameIdOverride"] }, + { 4, ["Type0", "Type1", "ItemId", "Rarity", string.Empty, string.Empty, "Item Name"] }, + { 5, ["Type0", "Type1", "Sound Effect Id"] }, + { 6, ["Type0", "Type1", "ObjStrId"] }, + { 7, ["Type0", "Type1", "Text"] }, + { 8, ["Type0", "Type1", "Seconds"] }, + { 9, ["Type0", "Type1", string.Empty] }, + { 10, ["Type0", "Type1", "StatusId", "HasOverride", "NameOverride", "DescriptionOverride"] }, + { 11, ["Type0", "Type1", "ListingId", string.Empty, "WorldId", "CrossWorldFlag"] }, + { 12, ["Type0", "Type1", "QuestId", string.Empty, string.Empty, string.Empty, "QuestName"] }, + }; + + private readonly List entries = [ + new TextEntry(TextEntryType.String, "Welcome to "), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.String, "Dalamud"), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.Macro, ""), + new TextEntry(TextEntryType.Macro, " "), + ]; + + private SeStringParameter[]? localParameters = [Util.GetScmVersion()]; + private ReadOnlySeString input; + private ClientLanguage? language; + private int importSelectedSheetName; + private int importRowId; + private string[]? validImportSheetNames; + private float inputsWidth; + private float lastContentWidth; + + private enum TextEntryType + { + String, + Macro, + Fixed, + } + + /// + public string[]? CommandShortcuts { get; init; } = []; + + /// + public string DisplayName { get; init; } = "SeString Creator"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.language = Service.Get().EffectiveLanguage.ToClientLanguage(); + this.UpdateInputString(false); + this.Ready = true; + } + + /// + public void Draw() + { + var contentWidth = ImGui.GetContentRegionAvail().X; + + // split panels in the middle by default + if (this.inputsWidth == 0) + { + this.inputsWidth = contentWidth / 2f; + } + + // resize panels relative to the window size + if (contentWidth != this.lastContentWidth) + { + var originalWidth = this.lastContentWidth != 0 ? this.lastContentWidth : contentWidth; + this.inputsWidth = this.inputsWidth / originalWidth * contentWidth; + this.lastContentWidth = contentWidth; + } + + using var tabBar = ImRaii.TabBar("SeStringCreatorWidgetTabBar"); + if (!tabBar) return; + + this.DrawCreatorTab(contentWidth); + this.DrawGlobalParametersTab(); + } + + private void DrawCreatorTab(float contentWidth) + { + using var tab = ImRaii.TabItem("Creator"); + if (!tab) return; + + this.DrawControls(); + ImGui.Spacing(); + this.DrawInputs(); + + this.localParameters ??= this.GetLocalParameters(this.input.AsSpan(), []); + + var evaluated = Service.Get().Evaluate( + this.input.AsSpan(), + this.localParameters, + this.language); + + ImGui.SameLine(0, 0); + + ImGui.Button("###InputPanelResizer", new Vector2(4, -1)); + if (ImGui.IsItemActive()) + { + this.inputsWidth += ImGui.GetIO().MouseDelta.X; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeEW); + + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) + { + this.inputsWidth = contentWidth / 2f; + } + } + + ImGui.SameLine(); + + using var child = ImRaii.Child("Preview", new Vector2(ImGui.GetContentRegionAvail().X, -1)); + if (!child) return; + + if (this.localParameters!.Length != 0) + { + ImGui.Spacing(); + this.DrawParameters(); + } + + this.DrawPreview(evaluated); + + ImGui.Spacing(); + this.DrawPayloads(evaluated); + } + + private unsafe void DrawGlobalParametersTab() + { + using var tab = ImRaii.TabItem("Global Parameters"); + if (!tab) return; + + using var table = ImRaii.Table("GlobalParametersTable", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed, 40); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("ValuePtr", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(5, 1); + ImGui.TableHeadersRow(); + + var deque = RaptureTextModule.Instance()->GlobalParameters; + for (var i = 0u; i < deque.MySize; i++) + { + var item = deque[i]; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); // Id + ImGui.TextUnformatted(i.ToString()); + + ImGui.TableNextColumn(); // Type + ImGui.TextUnformatted(item.Type.ToString()); + + ImGui.TableNextColumn(); // ValuePtr + WidgetUtil.DrawCopyableText($"0x{(nint)item.ValuePtr:X}"); + + ImGui.TableNextColumn(); // Value + switch (item.Type) + { + case TextParameterType.Integer: + WidgetUtil.DrawCopyableText($"0x{item.IntValue:X}"); + ImGui.SameLine(); + WidgetUtil.DrawCopyableText(item.IntValue.ToString()); + break; + + case TextParameterType.ReferencedUtf8String: + if (item.ReferencedUtf8StringValue != null) + WidgetUtil.DrawCopyableText(new ReadOnlySeStringSpan(item.ReferencedUtf8StringValue->Utf8String).ToString()); + else + ImGui.TextUnformatted("null"); + + break; + + case TextParameterType.String: + if (item.StringValue != null) + WidgetUtil.DrawCopyableText(MemoryHelper.ReadStringNullTerminated((nint)item.StringValue)); + else + ImGui.TextUnformatted("null"); + break; + } + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(i switch + { + 0 => "Player Name", + 1 => "Temp Player 1 Name", + 2 => "Temp Player 2 Name", + 3 => "Player Sex", + 4 => "Temp Player 1 Sex", + 5 => "Temp Player 2 Sex", + 6 => "Temp Player 1 Unk 1", + 7 => "Temp Player 2 Unk 1", + 10 => "Eorzea Time Hours", + 11 => "Eorzea Time Minutes", + 12 => "ColorSay", + 13 => "ColorShout", + 14 => "ColorTell", + 15 => "ColorParty", + 16 => "ColorAlliance", + 17 => "ColorLS1", + 18 => "ColorLS2", + 19 => "ColorLS3", + 20 => "ColorLS4", + 21 => "ColorLS5", + 22 => "ColorLS6", + 23 => "ColorLS7", + 24 => "ColorLS8", + 25 => "ColorFCompany", + 26 => "ColorPvPGroup", + 27 => "ColorPvPGroupAnnounce", + 28 => "ColorBeginner", + 29 => "ColorEmoteUser", + 30 => "ColorEmote", + 31 => "ColorYell", + 32 => "ColorFCAnnounce", + 33 => "ColorBeginnerAnnounce", + 34 => "ColorCWLS", + 35 => "ColorAttackSuccess", + 36 => "ColorAttackFailure", + 37 => "ColorAction", + 38 => "ColorItem", + 39 => "ColorCureGive", + 40 => "ColorBuffGive", + 41 => "ColorDebuffGive", + 42 => "ColorEcho", + 43 => "ColorSysMsg", + 51 => "Player Grand Company Rank (Maelstrom)", + 52 => "Player Grand Company Rank (Twin Adders)", + 53 => "Player Grand Company Rank (Immortal Flames)", + 54 => "Companion Name", + 55 => "Content Name", + 56 => "ColorSysBattle", + 57 => "ColorSysGathering", + 58 => "ColorSysErr", + 59 => "ColorNpcSay", + 60 => "ColorItemNotice", + 61 => "ColorGrowup", + 62 => "ColorLoot", + 63 => "ColorCraft", + 64 => "ColorGathering", + 65 => "Temp Player 1 Unk 2", + 66 => "Temp Player 2 Unk 2", + 67 => "Player ClassJobId", + 68 => "Player Level", + 70 => "Player Race", + 71 => "Player Synced Level", + 77 => "Client/Plattform?", + 78 => "Player BirthMonth", + 82 => "Datacenter Region", + 83 => "ColorCWLS2", + 84 => "ColorCWLS3", + 85 => "ColorCWLS4", + 86 => "ColorCWLS5", + 87 => "ColorCWLS6", + 88 => "ColorCWLS7", + 89 => "ColorCWLS8", + 91 => "Player Grand Company", + 92 => "TerritoryType Id", + 93 => "Is Soft Keyboard Enabled", + 94 => "LogSetRoleColor 1: LogColorRoleTank", + 95 => "LogSetRoleColor 2: LogColorRoleTank", + 96 => "LogSetRoleColor 1: LogColorRoleHealer", + 97 => "LogSetRoleColor 2: LogColorRoleHealer", + 98 => "LogSetRoleColor 1: LogColorRoleDPS", + 99 => "LogSetRoleColor 2: LogColorRoleDPS", + 100 => "LogSetRoleColor 1: LogColorOtherClass", + 101 => "LogSetRoleColor 2: LogColorOtherClass", + 102 => "Has Login Security Token", + _ => string.Empty, + }); + } + } + + private unsafe void DrawControls() + { + if (ImGui.Button("Add entry")) + { + this.entries.Add(new(TextEntryType.String, string.Empty)); + } + + ImGui.SameLine(); + + if (ImGui.Button("Add from Sheet")) + { + ImGui.OpenPopup("AddFromSheetPopup"); + } + + this.DrawAddFromSheetPopup(); + + ImGui.SameLine(); + + if (ImGui.Button("Print")) + { + var output = Utf8String.CreateEmpty(); + var temp = Utf8String.CreateEmpty(); + var temp2 = Utf8String.CreateEmpty(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + output->ConcatCStr(entry.Message); + break; + + case TextEntryType.Macro: + temp->Clear(); + RaptureTextModule.Instance()->MacroEncoder.EncodeString(temp, entry.Message); + output->Append(temp); + break; + + case TextEntryType.Fixed: + temp->SetString(entry.Message); + temp2->Clear(); + + RaptureTextModule.Instance()->TextModule.ProcessMacroCode(temp2, temp->StringPtr); + var out1 = PronounModule.Instance()->ProcessString(temp2, true); + var out2 = PronounModule.Instance()->ProcessString(out1, false); + + output->Append(out2); + break; + } + } + + RaptureLogModule.Instance()->PrintString(output->StringPtr); + temp2->Dtor(true); + temp->Dtor(true); + output->Dtor(true); + } + + ImGui.SameLine(); + + if (ImGui.Button("Print Evaluated")) + { + var sb = new LSeStringBuilder(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + sb.Append(entry.Message); + break; + + case TextEntryType.Macro: + case TextEntryType.Fixed: + sb.AppendMacroString(entry.Message); + break; + } + } + + RaptureLogModule.Instance()->PrintString(Service.Get().Evaluate(sb.ToReadOnlySeString())); + } + + if (this.entries.Count != 0) + { + ImGui.SameLine(); + + if (ImGui.Button("Copy MacroString")) + { + var sb = new LSeStringBuilder(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + sb.Append(entry.Message); + break; + + case TextEntryType.Macro: + case TextEntryType.Fixed: + sb.AppendMacroString(entry.Message); + break; + } + } + + ImGui.SetClipboardText(sb.ToReadOnlySeString().ToString()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Clear entries")) + { + this.entries.Clear(); + this.UpdateInputString(); + } + } + + var raptureTextModule = RaptureTextModule.Instance(); + if (!raptureTextModule->MacroEncoder.EncoderError.IsEmpty) + { + ImGui.SameLine(); + ImGui.TextUnformatted(raptureTextModule->MacroEncoder.EncoderError.ToString()); // TODO: EncoderError doesn't clear + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(90 * ImGuiHelpers.GlobalScale); + using (var dropdown = ImRaii.Combo("##Language", this.language.ToString() ?? "Language...")) + { + if (dropdown) + { + var values = Enum.GetValues().OrderBy((ClientLanguage lang) => lang.ToString()); + foreach (var value in values) + { + if (ImGui.Selectable(Enum.GetName(value), value == this.language)) + { + this.language = value; + this.UpdateInputString(); + } + } + } + } + } + + private void DrawAddFromSheetPopup() + { + using var popup = ImRaii.Popup("AddFromSheetPopup"); + if (!popup) return; + + var dataManager = Service.Get(); + + this.validImportSheetNames ??= dataManager.Excel.SheetNames.Where(sheetName => + { + try + { + var headerFile = dataManager.GameData.GetFile($"exd/{sheetName}.exh"); + if (headerFile.Header.Variant != ExcelVariant.Default) + return false; + + var sheet = dataManager.Excel.GetSheet(Language.English, sheetName); + return sheet.Columns.Any(col => col.Type == ExcelColumnDataType.String); + } + catch + { + return false; + } + }).OrderBy(sheetName => sheetName, StringComparer.InvariantCulture).ToArray(); + + var sheetChanged = ImGui.Combo("Sheet Name", ref this.importSelectedSheetName, this.validImportSheetNames, this.validImportSheetNames.Length); + + try + { + var sheet = dataManager.Excel.GetSheet(this.language?.ToLumina() ?? Language.English, this.validImportSheetNames[this.importSelectedSheetName]); + var minRowId = (int)sheet.FirstOrDefault().RowId; + var maxRowId = (int)sheet.LastOrDefault().RowId; + + var rowIdChanged = ImGui.InputInt("RowId", ref this.importRowId, 1, 10); + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.TextUnformatted($"(Range: {minRowId} - {maxRowId})"); + + if (sheetChanged || rowIdChanged) + { + if (sheetChanged || this.importRowId < minRowId) + this.importRowId = minRowId; + + if (this.importRowId > maxRowId) + this.importRowId = maxRowId; + } + + if (!sheet.TryGetRow((uint)this.importRowId, out var row)) + { + ImGui.TextColored(new Vector4(1, 0, 0, 1), "Row not found"); + return; + } + + ImGui.TextUnformatted("Select string to add:"); + + using var table = ImRaii.Table("StringSelectionTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Column", ImGuiTableColumnFlags.WidthFixed, 50); + ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableHeadersRow(); + + for (var i = 0; i < sheet.Columns.Count; i++) + { + var column = sheet.Columns[i]; + if (column.Type != ExcelColumnDataType.String) + continue; + + var value = row.ReadStringColumn(i); + if (value.IsEmpty) + continue; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(i.ToString()); + + ImGui.TableNextColumn(); + if (ImGui.Selectable($"{value.ToString().Truncate(100)}###Column{i}")) + { + foreach (var payload in value) + { + switch (payload.Type) + { + case ReadOnlySePayloadType.Text: + this.entries.Add(new(TextEntryType.String, Encoding.UTF8.GetString(payload.Body.Span))); + break; + + case ReadOnlySePayloadType.Macro: + this.entries.Add(new(TextEntryType.Macro, payload.ToString())); + break; + } + } + + this.UpdateInputString(); + ImGui.CloseCurrentPopup(); + } + } + } + catch (Exception e) + { + ImGui.TextUnformatted(e.Message); + return; + } + } + + private unsafe void DrawInputs() + { + using var child = ImRaii.Child("Inputs", new Vector2(this.inputsWidth, -1)); + if (!child) return; + + using var table = ImRaii.Table("StringMakerTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoSavedSettings); + if (!table) return; + + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Text", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 80); + ImGui.TableSetupScrollFreeze(3, 1); + ImGui.TableHeadersRow(); + + var arrowUpButtonSize = this.GetIconButtonSize(FontAwesomeIcon.ArrowUp); + var arrowDownButtonSize = this.GetIconButtonSize(FontAwesomeIcon.ArrowDown); + var trashButtonSize = this.GetIconButtonSize(FontAwesomeIcon.Trash); + var terminalButtonSize = this.GetIconButtonSize(FontAwesomeIcon.Terminal); + + var entryToRemove = -1; + var entryToMoveUp = -1; + var entryToMoveDown = -1; + var updateString = false; + + for (var i = 0; i < this.entries.Count; i++) + { + var key = $"##Entry{i}"; + var entry = this.entries[i]; + + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); // Type + var type = (int)entry.Type; + ImGui.SetNextItemWidth(-1); + if (ImGui.Combo($"##Type{i}", ref type, ["String", "Macro", "Fixed"], 3)) + { + entry.Type = (TextEntryType)type; + updateString |= true; + } + + ImGui.TableNextColumn(); // Text + var message = entry.Message; + ImGui.SetNextItemWidth(-1); + if (ImGui.InputText($"##{i}_Message", ref message, 255)) + { + entry.Message = message; + updateString |= true; + } + + ImGui.TableNextColumn(); // Actions + + if (i > 0) + { + if (this.IconButton(key + "_Up", FontAwesomeIcon.ArrowUp, "Move up")) + { + entryToMoveUp = i; + } + } + else + { + ImGui.Dummy(arrowUpButtonSize); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + if (i < this.entries.Count - 1) + { + if (this.IconButton(key + "_Down", FontAwesomeIcon.ArrowDown, "Move down")) + { + entryToMoveDown = i; + } + } + else + { + ImGui.Dummy(arrowDownButtonSize); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + + if (ImGui.IsKeyDown(ImGuiKey.LeftShift) || ImGui.IsKeyDown(ImGuiKey.RightShift)) + { + if (this.IconButton(key + "_Delete", FontAwesomeIcon.Trash, "Delete")) + { + entryToRemove = i; + } + } + else + { + this.IconButton( + key + "_Delete", + FontAwesomeIcon.Trash, + "Delete with shift", + disabled: true); + } + } + + table.Dispose(); + + if (entryToMoveUp != -1) + { + var removedItem = this.entries[entryToMoveUp]; + this.entries.RemoveAt(entryToMoveUp); + this.entries.Insert(entryToMoveUp - 1, removedItem); + updateString |= true; + } + + if (entryToMoveDown != -1) + { + var removedItem = this.entries[entryToMoveDown]; + this.entries.RemoveAt(entryToMoveDown); + this.entries.Insert(entryToMoveDown + 1, removedItem); + updateString |= true; + } + + if (entryToRemove != -1) + { + this.entries.RemoveAt(entryToRemove); + updateString |= true; + } + + if (updateString) + { + this.UpdateInputString(); + } + } + + private unsafe void UpdateInputString(bool resetLocalParameters = true) + { + var sb = new LSeStringBuilder(); + + foreach (var entry in this.entries) + { + switch (entry.Type) + { + case TextEntryType.String: + sb.Append(entry.Message); + break; + + case TextEntryType.Macro: + case TextEntryType.Fixed: + sb.AppendMacroString(entry.Message); + break; + } + } + + this.input = sb.ToReadOnlySeString(); + + if (resetLocalParameters) + this.localParameters = null; + } + + private void DrawPreview(ReadOnlySeString str) + { + using var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00); + using var node = ImRaii.TreeNode("Preview", ImGuiTreeNodeFlags.DefaultOpen); + nodeColor.Pop(); + if (!node) return; + + ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeight())); + ImGui.SameLine(0, 0); + ImGuiHelpers.SeStringWrapped(str); + } + + private void DrawParameters() + { + using var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00); + using var node = ImRaii.TreeNode("Parameters", ImGuiTreeNodeFlags.DefaultOpen); + nodeColor.Pop(); + if (!node) return; + + for (var i = 0; i < this.localParameters!.Length; i++) + { + if (this.localParameters[i].IsString) + { + var str = this.localParameters[i].StringValue.ExtractText(); + if (ImGui.InputText($"lstr({i + 1})", ref str, 255)) + { + this.localParameters[i] = new(str); + } + } + else + { + var num = (int)this.localParameters[i].UIntValue; + if (ImGui.InputInt($"lnum({i + 1})", ref num)) + { + this.localParameters[i] = new((uint)num); + } + } + } + } + + private void DrawPayloads(ReadOnlySeString evaluated) + { + using (var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00)) + using (var node = ImRaii.TreeNode("Payloads", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth)) + { + nodeColor.Pop(); + if (node) this.DrawSeString("payloads", this.input.AsSpan(), treeNodeFlags: ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth); + } + + if (this.input.Equals(evaluated)) + return; + + using (var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FF00)) + using (var node = ImRaii.TreeNode("Payloads (Evaluated)", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth)) + { + nodeColor.Pop(); + if (node) this.DrawSeString("payloads-evaluated", evaluated.AsSpan(), treeNodeFlags: ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth); + } + } + + private void DrawSeString(string id, ReadOnlySeStringSpan rosss, bool asTreeNode = false, bool renderSeString = false, int depth = 0, ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags.None) + { + using var seStringId = ImRaii.PushId(id); + + if (rosss.PayloadCount == 0) + { + ImGui.Dummy(Vector2.Zero); + return; + } + + using var node = asTreeNode ? this.SeStringTreeNode(id, rosss) : null; + if (asTreeNode && !node!) return; + + if (!asTreeNode && renderSeString) + { + ImGuiHelpers.SeStringWrapped(rosss, new() + { + ForceEdgeColor = true, + }); + } + + var payloadIdx = -1; + foreach (var payload in rosss) + { + payloadIdx++; + using var payloadId = ImRaii.PushId(payloadIdx); + + var preview = payload.Type.ToString(); + if (payload.Type == ReadOnlySePayloadType.Macro) + preview += $": {payload.MacroCode}"; + + using var nodeColor = ImRaii.PushColor(ImGuiCol.Text, 0xFF00FFFF); + using var payloadNode = ImRaii.TreeNode($"[{payloadIdx}] {preview}", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth); + nodeColor.Pop(); + if (!payloadNode) continue; + + using var table = ImRaii.Table($"##Payload{payloadIdx}Table", 2); + if (!table) return; + + ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed, 120); + ImGui.TableSetupColumn("Tree", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(payload.Type == ReadOnlySePayloadType.Text ? "Text" : "ToString()"); + ImGui.TableNextColumn(); + var text = payload.ToString(); + WidgetUtil.DrawCopyableText($"\"{text}\"", text); + + if (payload.Type != ReadOnlySePayloadType.Macro) + continue; + + if (payload.ExpressionCount > 0) + { + var exprIdx = 0; + uint? subType = null; + uint? fixedType = null; + + if (payload.MacroCode == MacroCode.Link && payload.TryGetExpression(out var linkExpr1) && linkExpr1.TryGetUInt(out var linkExpr1Val)) + { + subType = linkExpr1Val; + } + else if (payload.MacroCode == MacroCode.Fixed && payload.TryGetExpression(out var fixedTypeExpr, out var linkExpr2) && fixedTypeExpr.TryGetUInt(out var fixedTypeVal) && linkExpr2.TryGetUInt(out var linkExpr2Val)) + { + subType = linkExpr2Val; + fixedType = fixedTypeVal; + } + + foreach (var expr in payload) + { + using var exprId = ImRaii.PushId(exprIdx); + + this.DrawExpression(payload.MacroCode, subType, fixedType, exprIdx++, expr); + } + } + } + } + + private unsafe void DrawExpression(MacroCode macroCode, uint? subType, uint? fixedType, int exprIdx, ReadOnlySeExpressionSpan expr) + { + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + var expressionName = this.GetExpressionName(macroCode, subType, exprIdx, expr); + ImGui.TextUnformatted($"[{exprIdx}] " + (string.IsNullOrEmpty(expressionName) ? $"Expr {exprIdx}" : expressionName)); + + ImGui.TableNextColumn(); + + if (expr.Body.IsEmpty) + { + ImGui.TextUnformatted("(?)"); + return; + } + + if (expr.TryGetUInt(out var u32)) + { + if (macroCode is MacroCode.Icon or MacroCode.Icon2 && exprIdx == 0) + { + var iconId = u32; + + if (macroCode == MacroCode.Icon2) + { + var iconMapping = RaptureAtkModule.Instance()->AtkFontManager.Icon2RemapTable; + for (var i = 0; i < 30; i++) + { + if (iconMapping[i].IconId == iconId) + { + iconId = iconMapping[i].RemappedIconId; + break; + } + } + } + + var builder = LSeStringBuilder.SharedPool.Get(); + builder.AppendIcon(iconId); + ImGuiHelpers.SeStringWrapped(builder.ToArray()); + LSeStringBuilder.SharedPool.Return(builder); + + ImGui.SameLine(); + } + + WidgetUtil.DrawCopyableText(u32.ToString()); + ImGui.SameLine(); + WidgetUtil.DrawCopyableText($"0x{u32:X}"); + + if (macroCode == MacroCode.Link && exprIdx == 0) + { + var name = subType != null && (LinkMacroPayloadType)subType == DalamudLinkType + ? "Dalamud" + : Enum.GetName((LinkMacroPayloadType)u32); + + if (!string.IsNullOrEmpty(name)) + { + ImGui.SameLine(); + ImGui.TextUnformatted(name); + } + } + + if (macroCode is MacroCode.JaNoun or MacroCode.EnNoun or MacroCode.DeNoun or MacroCode.FrNoun && exprIdx == 1) + { + var language = macroCode switch + { + MacroCode.JaNoun => ClientLanguage.Japanese, + MacroCode.DeNoun => ClientLanguage.German, + MacroCode.FrNoun => ClientLanguage.French, + _ => ClientLanguage.English, + }; + var articleTypeEnumType = language switch + { + ClientLanguage.Japanese => typeof(JapaneseArticleType), + ClientLanguage.German => typeof(GermanArticleType), + ClientLanguage.French => typeof(FrenchArticleType), + _ => typeof(EnglishArticleType), + }; + ImGui.SameLine(); + ImGui.TextUnformatted(Enum.GetName(articleTypeEnumType, u32)); + } + + if (macroCode is MacroCode.DeNoun && exprIdx == 4 && u32 is >= 0 and <= 3) + { + ImGui.SameLine(); + ImGui.TextUnformatted(NounProcessorWidget.GermanCases[u32]); + } + + if (macroCode is MacroCode.Fixed && subType != null && fixedType != null && fixedType is 100 or 200 && subType == 5 && exprIdx == 2) + { + ImGui.SameLine(); + if (ImGui.SmallButton("Play")) + { + UIGlobals.PlayChatSoundEffect(u32 + 1); + } + } + + if (macroCode is MacroCode.Link && subType != null && exprIdx == 1) + { + var dataManager = Service.Get(); + + switch ((LinkMacroPayloadType)subType) + { + case LinkMacroPayloadType.Item when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var itemRow): + ImGui.SameLine(); + ImGui.TextUnformatted(itemRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.Quest when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var questRow): + ImGui.SameLine(); + ImGui.TextUnformatted(questRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.Achievement when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var achievementRow): + ImGui.SameLine(); + ImGui.TextUnformatted(achievementRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.HowTo when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var howToRow): + ImGui.SameLine(); + ImGui.TextUnformatted(howToRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.Status when dataManager.GetExcelSheet(this.language).TryGetRow(u32, out var statusRow): + ImGui.SameLine(); + ImGui.TextUnformatted(statusRow.Name.ExtractText()); + break; + + case LinkMacroPayloadType.AkatsukiNote when + dataManager.GetSubrowExcelSheet(this.language).TryGetRow(u32, out var akatsukiNoteRow) && + dataManager.GetExcelSheet(this.language).TryGetRow((uint)akatsukiNoteRow[0].Unknown2, out var akatsukiNoteStringRow): + ImGui.SameLine(); + ImGui.TextUnformatted(akatsukiNoteStringRow.Unknown0.ExtractText()); + break; + } + } + + return; + } + + if (expr.TryGetString(out var s)) + { + this.DrawSeString("Preview", s, treeNodeFlags: ImGuiTreeNodeFlags.DefaultOpen); + return; + } + + if (expr.TryGetPlaceholderExpression(out var exprType)) + { + if (((ExpressionType)exprType).GetNativeName() is { } nativeName) + { + ImGui.TextUnformatted(nativeName); + return; + } + + ImGui.TextUnformatted($"?x{exprType:X02}"); + return; + } + + if (expr.TryGetParameterExpression(out exprType, out var e1)) + { + if (((ExpressionType)exprType).GetNativeName() is { } nativeName) + { + ImGui.TextUnformatted($"{nativeName}({e1.ToString()})"); + return; + } + + throw new InvalidOperationException("All native names must be defined for unary expressions."); + } + + if (expr.TryGetBinaryExpression(out exprType, out e1, out var e2)) + { + if (((ExpressionType)exprType).GetNativeName() is { } nativeName) + { + ImGui.TextUnformatted($"{e1.ToString()} {nativeName} {e2.ToString()}"); + return; + } + + throw new InvalidOperationException("All native names must be defined for binary expressions."); + } + + var sb = new StringBuilder(); + sb.EnsureCapacity(1 + 3 * expr.Body.Length); + sb.Append($"({expr.Body[0]:X02}"); + for (var i = 1; i < expr.Body.Length; i++) + sb.Append($" {expr.Body[i]:X02}"); + sb.Append(')'); + ImGui.TextUnformatted(sb.ToString()); + } + + private string GetExpressionName(MacroCode macroCode, uint? subType, int idx, ReadOnlySeExpressionSpan expr) + { + if (this.expressionNames.TryGetValue(macroCode, out var names) && idx < names.Length) + return names[idx]; + + if (macroCode == MacroCode.Switch) + return $"Case {idx - 1}"; + + if (macroCode == MacroCode.Link && subType != null && this.linkExpressionNames.TryGetValue((LinkMacroPayloadType)subType, out var linkNames) && idx - 1 < linkNames.Length) + return linkNames[idx - 1]; + + if (macroCode == MacroCode.Fixed && subType != null && this.fixedExpressionNames.TryGetValue((uint)subType, out var fixedNames) && idx < fixedNames.Length) + return fixedNames[idx]; + + if (macroCode == MacroCode.Link && idx == 4) + return "Copy String"; + + return string.Empty; + } + + private SeStringParameter[] GetLocalParameters(ReadOnlySeStringSpan rosss, Dictionary? parameters) + { + parameters ??= []; + + void ProcessString(ReadOnlySeStringSpan rosss) + { + foreach (var payload in rosss) + { + foreach (var expression in payload) + { + ProcessExpression(expression); + } + } + } + + void ProcessExpression(ReadOnlySeExpressionSpan expression) + { + if (expression.TryGetString(out var exprString)) + { + ProcessString(exprString); + return; + } + + if (expression.TryGetBinaryExpression(out var expressionType, out var operand1, out var operand2)) + { + ProcessExpression(operand1); + ProcessExpression(operand2); + return; + } + + if (expression.TryGetParameterExpression(out expressionType, out var operand)) + { + if (!operand.TryGetUInt(out var index)) + return; + + if (parameters.ContainsKey(index)) + return; + + if (expressionType == (int)ExpressionType.LocalNumber) + { + parameters[index] = new SeStringParameter(0); + return; + } + else if (expressionType == (int)ExpressionType.LocalString) + { + parameters[index] = new SeStringParameter(string.Empty); + return; + } + } + } + + ProcessString(rosss); + + if (parameters.Count > 0) + { + var last = parameters.OrderBy(x => x.Key).Last(); + + if (parameters.Count != last.Key) + { + // fill missing local parameter slots, so we can go off the array index in SeStringContext + + for (var i = 1u; i <= last.Key; i++) + { + if (!parameters.ContainsKey(i)) + parameters[i] = new SeStringParameter(0); + } + } + } + + return parameters.OrderBy(x => x.Key).Select(x => x.Value).ToArray(); + } + + private ImRaii.IEndObject SeStringTreeNode(string id, ReadOnlySeStringSpan previewText, uint color = 0xFF00FFFF, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.None) + { + using var titleColor = ImRaii.PushColor(ImGuiCol.Text, color); + var node = ImRaii.TreeNode("##" + id, flags); + ImGui.SameLine(); + ImGuiHelpers.SeStringWrapped(previewText, new() + { + ForceEdgeColor = true, + WrapWidth = 9999, + }); + return node; + } + + private bool IconButton(string key, FontAwesomeIcon icon, string tooltip, Vector2 size = default, bool disabled = false, bool active = false) + { + using var iconFont = ImRaii.PushFont(UiBuilder.IconFont); + if (!key.StartsWith("##")) key = "##" + key; + + var disposables = new List(); + + if (disabled) + { + disposables.Add(ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled])); + disposables.Add(ImRaii.PushColor(ImGuiCol.ButtonActive, ImGui.GetStyle().Colors[(int)ImGuiCol.Button])); + disposables.Add(ImRaii.PushColor(ImGuiCol.ButtonHovered, ImGui.GetStyle().Colors[(int)ImGuiCol.Button])); + } + else if (active) + { + disposables.Add(ImRaii.PushColor(ImGuiCol.Button, ImGui.GetStyle().Colors[(int)ImGuiCol.ButtonActive])); + } + + var pressed = ImGui.Button(icon.ToIconString() + key, size); + + foreach (var disposable in disposables) + disposable.Dispose(); + + iconFont?.Dispose(); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltip); + ImGui.EndTooltip(); + } + + return pressed; + } + + private Vector2 GetIconButtonSize(FontAwesomeIcon icon) + { + using var iconFont = ImRaii.PushFont(UiBuilder.IconFont); + return ImGui.CalcTextSize(icon.ToIconString()) + ImGui.GetStyle().FramePadding * 2; + } + + private class TextEntry(TextEntryType type, string text) + { + public string Message { get; set; } = text; + + public TextEntryType Type { get; set; } = type; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs index ccee570c7..323d82bbc 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs @@ -1,6 +1,11 @@ -using Dalamud.Game.ClientState.GamePad; +using System.Linq; -using ImGuiNET; +using Dalamud.Game.ClientState.GamePad; +using Dalamud.Interface.Utility; + +using Lumina.Text.Payloads; + +using LSeStringBuilder = Lumina.Text.SeStringBuilder; namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; @@ -17,11 +22,34 @@ internal class GamepadStateAgingStep : IAgingStep { var gamepadState = Service.Get(); - ImGui.Text("Hold down North, East, L1"); + var buttons = new (GamepadButtons Button, uint IconId)[] + { + (GamepadButtons.North, 11), + (GamepadButtons.East, 8), + (GamepadButtons.L1, 12), + }; - if (gamepadState.Raw(GamepadButtons.North) == 1 - && gamepadState.Raw(GamepadButtons.East) == 1 - && gamepadState.Raw(GamepadButtons.L1) == 1) + var builder = LSeStringBuilder.SharedPool.Get(); + + builder.Append("Hold down "); + + for (var i = 0; i < buttons.Length; i++) + { + var (button, iconId) = buttons[i]; + + builder.BeginMacro(MacroCode.Icon).AppendUIntExpression(iconId).EndMacro(); + builder.PushColorRgba(gamepadState.Raw(button) == 1 ? 0x0000FF00u : 0x000000FF); + builder.Append(button.ToString()); + builder.PopColor(); + + builder.Append(i < buttons.Length - 1 ? ", " : "."); + } + + ImGuiHelpers.SeStringWrapped(builder.ToReadOnlySeString()); + + LSeStringBuilder.SharedPool.Return(builder); + + if (buttons.All(tuple => gamepadState.Raw(tuple.Button) == 1)) { return SelfTestStepResult.Pass; } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs new file mode 100644 index 000000000..4073616b2 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs @@ -0,0 +1,259 @@ +using Dalamud.Game; +using Dalamud.Game.Text.Noun; +using Dalamud.Game.Text.Noun.Enums; + +using ImGuiNET; + +using LSheets = Lumina.Excel.Sheets; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for NounProcessor. +/// +internal class NounProcessorAgingStep : IAgingStep +{ + private NounTestEntry[] tests = + [ + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その蜂蜜酒の運び人"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "蜂蜜酒の運び人"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.NearListener, 1, "それらの蜂蜜酒の運び人"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.Distant, 1, "あれらの蜂蜜酒の運び人"), + + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "a mead-porting Midlander"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the mead-porting Midlander"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 2, (int)EnglishArticleType.Indefinite, 1, "mead-porting Midlanders"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.English, 2, (int)EnglishArticleType.Definite, 1, "mead-porting Midlanders"), + + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "ein Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "eines Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "einem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "einen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "der Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "des Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "dem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "den Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "dein Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deines Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deinen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "kein Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keines Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keinen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Met schleppendem Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "dieser Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieses Met schleppenden Wiesländers"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesem Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diesen Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "2 Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "2 Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "2 Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "2 Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "die Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "der Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "den Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "die Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "deine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deiner Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinen Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "keine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keiner Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinen Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keine Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Met schleppender Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Met schleppende Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "diese Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieser Met schleppenden Wiesländer"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesen Met schleppenden Wiesländern"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diese Met schleppenden Wiesländer"), + + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "un livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "le livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mon livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ton livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "son livreur d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.Indefinite, 1, "des livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.Definite, 1, "les livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes livreurs d'hydromel"), + new(nameof(LSheets.BNpcName), 1330, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses livreurs d'hydromel"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その酔いどれのネル"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "酔いどれのネル"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "Nell Half-full"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "Nell Half-full"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "Nell die Beschwipste"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "Nell der Beschwipsten"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "Nell die Beschwipste"), + + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "ma Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ta Nell la Boit-sans-soif"), + new(nameof(LSheets.ENpcResident), 1031947, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "sa Nell la Boit-sans-soif"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.NearListener, 1, "その希少トームストーン:幻想"), + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 1, (int)JapaneseArticleType.Distant, 1, "希少トームストーン:幻想"), + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.NearListener, 1, "それらの希少トームストーン:幻想"), + new(nameof(LSheets.Item), 44348, ClientLanguage.Japanese, 2, (int)JapaneseArticleType.Distant, 1, "あれらの希少トームストーン:幻想"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 1, (int)EnglishArticleType.Indefinite, 1, "an irregular tomestone of phantasmagoria"), + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 1, (int)EnglishArticleType.Definite, 1, "the irregular tomestone of phantasmagoria"), + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 2, (int)EnglishArticleType.Indefinite, 1, "irregular tomestones of phantasmagoria"), + new(nameof(LSheets.Item), 44348, ClientLanguage.English, 2, (int)EnglishArticleType.Definite, 1, "irregular tomestones of phantasmagoria"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "ein ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "eines ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "einem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "einen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "der ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "des ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "dem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "den ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "dein ungewöhnliche Allagische Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deines ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deinen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "kein ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keines ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keinen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "ungewöhnlicher Allagischer Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "ungewöhnlichem Allagischem Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "dieser ungewöhnliche Allagische Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieses ungewöhnlichen Allagischen Steins der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesem ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 1, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diesen ungewöhnlichen Allagischen Stein der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Nominative, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Genitive, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Dative, "2 ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Indefinite, (int)GermanCases.Accusative, "2 ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Nominative, "die ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Genitive, "der ungewöhnlicher Allagischer Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Dative, "den ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Definite, (int)GermanCases.Accusative, "die ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Nominative, "deine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Genitive, "deiner ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Dative, "deinen ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Possessive, (int)GermanCases.Accusative, "deine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Nominative, "keine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Genitive, "keiner ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Dative, "keinen ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Negative, (int)GermanCases.Accusative, "keine ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Nominative, "ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Genitive, "ungewöhnlicher Allagischer Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Dative, "ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.ZeroArticle, (int)GermanCases.Accusative, "ungewöhnliche Allagische Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Nominative, "diese ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Genitive, "dieser ungewöhnlichen Allagischen Steine der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Dative, "diesen ungewöhnlichen Allagischen Steinen der Phantasmagorie"), + new(nameof(LSheets.Item), 44348, ClientLanguage.German, 2, (int)GermanArticleType.Demonstrative, (int)GermanCases.Accusative, "diese ungewöhnlichen Allagischen Steine der Phantasmagorie"), + + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.Indefinite, 1, "un mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.Definite, 1, "le mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mon mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveSecondPerson, 1, "ton mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 1, (int)FrenchArticleType.PossessiveThirdPerson, 1, "son mémoquartz inhabituel fantasmagorique"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.Indefinite, 1, "des mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.Definite, 1, "les mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes mémoquartz inhabituels fantasmagoriques"), + new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses mémoquartz inhabituels fantasmagoriques"), + + new(nameof(LSheets.Action), 45, ClientLanguage.German, 1, (int)FrenchArticleType.Indefinite, 1, "Blumenflüsterer IV"), + ]; + + private enum GermanCases + { + Nominative, + Genitive, + Dative, + Accusative, + } + + /// + public string Name => "Test NounProcessor"; + + /// + public unsafe SelfTestStepResult RunStep() + { + var nounProcessor = Service.Get(); + + for (var i = 0; i < this.tests.Length; i++) + { + var e = this.tests[i]; + + var nounParams = new NounParams() + { + SheetName = e.SheetName, + RowId = e.RowId, + Language = e.Language, + Quantity = e.Quantity, + ArticleType = e.ArticleType, + GrammaticalCase = e.GrammaticalCase, + }; + var output = nounProcessor.ProcessNoun(nounParams); + + if (e.ExpectedResult != output) + { + ImGui.TextUnformatted($"Mismatch detected (Test #{i}):"); + ImGui.TextUnformatted($"Got: {output}"); + ImGui.TextUnformatted($"Expected: {e.ExpectedResult}"); + + if (ImGui.Button("Continue")) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + } + + return SelfTestStepResult.Pass; + } + + /// + public void CleanUp() + { + // ignored + } + + private record struct NounTestEntry( + string SheetName, + uint RowId, + ClientLanguage Language, + int Quantity, + int ArticleType, + int GrammaticalCase, + string ExpectedResult); +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs new file mode 100644 index 000000000..3a0a7d546 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs @@ -0,0 +1,92 @@ +using Dalamud.Game.ClientState; +using Dalamud.Game.Text.Evaluator; + +using ImGuiNET; + +using Lumina.Text.ReadOnly; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for SeStringEvaluator. +/// +internal class SeStringEvaluatorAgingStep : IAgingStep +{ + private int step = 0; + + /// + public string Name => "Test SeStringEvaluator"; + + /// + public SelfTestStepResult RunStep() + { + var seStringEvaluator = Service.Get(); + + switch (this.step) + { + case 0: + ImGui.TextUnformatted("Is this the current time, and is it ticking?"); + + // This checks that EvaluateFromAddon fetches the correct Addon row, + // that MacroDecoder.GetMacroTime()->SetTime() has been called + // and that local and global parameters have been read correctly. + + ImGui.TextUnformatted(seStringEvaluator.EvaluateFromAddon(31, [(uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds()]).ExtractText()); + + if (ImGui.Button("Yes")) + this.step++; + + ImGui.SameLine(); + + if (ImGui.Button("No")) + return SelfTestStepResult.Fail; + + break; + + case 1: + ImGui.TextUnformatted("Checking pcname macro using the local player name..."); + + // This makes sure that NameCache.Instance()->TryGetCharacterInfoByEntityId() has been called, + // that it returned the local players name by using its EntityId, + // and that it didn't include the world name by checking the HomeWorldId against AgentLobby.Instance()->LobbyData.HomeWorldId. + + var clientState = Service.Get(); + var localPlayer = clientState.LocalPlayer; + if (localPlayer is null) + { + ImGui.TextUnformatted("You need to be logged in for this step."); + + if (ImGui.Button("Skip")) + return SelfTestStepResult.NotRan; + + return SelfTestStepResult.Waiting; + } + + var evaluatedPlayerName = seStringEvaluator.Evaluate(ReadOnlySeString.FromMacroString(""), [localPlayer.EntityId]).ExtractText(); + var localPlayerName = localPlayer.Name.TextValue; + + if (evaluatedPlayerName != localPlayerName) + { + ImGui.TextUnformatted("The player name doesn't match:"); + ImGui.TextUnformatted($"Evaluated Player Name (got): {evaluatedPlayerName}"); + ImGui.TextUnformatted($"Local Player Name (expected): {localPlayerName}"); + + if (ImGui.Button("Continue")) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored + this.step = 0; + } +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs new file mode 100644 index 000000000..0c9dc763f --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs @@ -0,0 +1,130 @@ +using System.Runtime.InteropServices; + +using Dalamud.Game; +using Dalamud.Game.Text.Evaluator.Internal; + +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for SheetRedirectResolver. +/// +internal class SheetRedirectResolverAgingStep : IAgingStep +{ + private RedirectEntry[] redirects = + [ + new("Item", 10, SheetRedirectFlags.Item), + new("ItemHQ", 10, SheetRedirectFlags.Item | SheetRedirectFlags.HighQuality), + new("ItemMP", 10, SheetRedirectFlags.Item | SheetRedirectFlags.Collectible), + new("Item", 35588, SheetRedirectFlags.Item), + new("Item", 1035588, SheetRedirectFlags.Item | SheetRedirectFlags.HighQuality), + new("Item", 2000217, SheetRedirectFlags.Item | SheetRedirectFlags.EventItem), + new("ActStr", 10, SheetRedirectFlags.Action), // Trait + new("ActStr", 1000010, SheetRedirectFlags.Action | SheetRedirectFlags.ActionSheet), // Action + new("ActStr", 2000010, SheetRedirectFlags.Action), // Item + new("ActStr", 3000010, SheetRedirectFlags.Action), // EventItem + new("ActStr", 4000010, SheetRedirectFlags.Action), // EventAction + new("ActStr", 5000010, SheetRedirectFlags.Action), // GeneralAction + new("ActStr", 6000010, SheetRedirectFlags.Action), // BuddyAction + new("ActStr", 7000010, SheetRedirectFlags.Action), // MainCommand + new("ActStr", 8000010, SheetRedirectFlags.Action), // Companion + new("ActStr", 9000010, SheetRedirectFlags.Action), // CraftAction + new("ActStr", 10000010, SheetRedirectFlags.Action | SheetRedirectFlags.ActionSheet), // Action + new("ActStr", 11000010, SheetRedirectFlags.Action), // PetAction + new("ActStr", 12000010, SheetRedirectFlags.Action), // CompanyAction + new("ActStr", 13000010, SheetRedirectFlags.Action), // Mount + // new("ActStr", 14000010, RedirectFlags.Action), + // new("ActStr", 15000010, RedirectFlags.Action), + // new("ActStr", 16000010, RedirectFlags.Action), + // new("ActStr", 17000010, RedirectFlags.Action), + // new("ActStr", 18000010, RedirectFlags.Action), + new("ActStr", 19000010, SheetRedirectFlags.Action), // BgcArmyAction + new("ActStr", 20000010, SheetRedirectFlags.Action), // Ornament + new("ObjStr", 10), // BNpcName + new("ObjStr", 1000010), // ENpcResident + new("ObjStr", 2000010), // Treasure + new("ObjStr", 3000010), // Aetheryte + new("ObjStr", 4000010), // GatheringPointName + new("ObjStr", 5000010), // EObjName + new("ObjStr", 6000010), // Mount + new("ObjStr", 7000010), // Companion + // new("ObjStr", 8000010), + // new("ObjStr", 9000010), + new("ObjStr", 10000010), // Item + new("EObj", 2003479), // EObj => EObjName + new("Treasure", 1473), // Treasure (without name, falls back to rowId 0) + new("Treasure", 1474), // Treasure (with name) + new("WeatherPlaceName", 0), + new("WeatherPlaceName", 28), + new("WeatherPlaceName", 40), + new("WeatherPlaceName", 52), + new("WeatherPlaceName", 2300), + ]; + + private unsafe delegate SheetRedirectFlags ResolveSheetRedirect(RaptureTextModule* thisPtr, Utf8String* sheetName, uint* rowId, uint* flags); + + /// + public string Name => "Test SheetRedirectResolver"; + + /// + public unsafe SelfTestStepResult RunStep() + { + // Client::UI::Misc::RaptureTextModule_ResolveSheetRedirect + if (!Service.Get().TryScanText("E8 ?? ?? ?? ?? 44 8B E8 A8 10", out var addr)) + return SelfTestStepResult.Fail; + + var sheetRedirectResolver = Service.Get(); + var resolveSheetRedirect = Marshal.GetDelegateForFunctionPointer(addr); + var utf8SheetName = Utf8String.CreateEmpty(); + + try + { + for (var i = 0; i < this.redirects.Length; i++) + { + var redirect = this.redirects[i]; + + utf8SheetName->SetString(redirect.SheetName); + + var rowId1 = redirect.RowId; + uint colIndex1 = ushort.MaxValue; + var flags1 = resolveSheetRedirect(RaptureTextModule.Instance(), utf8SheetName, &rowId1, &colIndex1); + + var sheetName2 = redirect.SheetName; + var rowId2 = redirect.RowId; + uint colIndex2 = ushort.MaxValue; + var flags2 = sheetRedirectResolver.Resolve(ref sheetName2, ref rowId2, ref colIndex2); + + if (utf8SheetName->ToString() != sheetName2 || rowId1 != rowId2 || colIndex1 != colIndex2 || flags1 != flags2) + { + ImGui.TextUnformatted($"Mismatch detected (Test #{i}):"); + ImGui.TextUnformatted($"Input: {redirect.SheetName}#{redirect.RowId}"); + ImGui.TextUnformatted($"Game: {utf8SheetName->ToString()}#{rowId1}-{colIndex1} ({flags1})"); + ImGui.TextUnformatted($"Evaluated: {sheetName2}#{rowId2}-{colIndex2} ({flags2})"); + + if (ImGui.Button("Continue")) + return SelfTestStepResult.Fail; + + return SelfTestStepResult.Waiting; + } + } + + return SelfTestStepResult.Pass; + } + finally + { + utf8SheetName->Dtor(true); + } + } + + /// + public void CleanUp() + { + // ignored + } + + private record struct RedirectEntry(string SheetName, uint RowId, SheetRedirectFlags Flags = SheetRedirectFlags.None); +} diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 3b3670228..1be6f31a3 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -50,6 +50,9 @@ internal class SelfTestWindow : Window new DutyStateAgingStep(), new GameConfigAgingStep(), new MarketBoardAgingStep(), + new SheetRedirectResolverAgingStep(), + new NounProcessorAgingStep(), + new SeStringEvaluatorAgingStep(), new LogoutEventAgingStep(), }; diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index 2093d9bcb..d9056fec4 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -210,8 +210,6 @@ public static class ImGuiHelpers /// ImGui ID, if link functionality is desired. /// Button flags to use on link interaction. /// Interaction result of the rendered text. - /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. - /// The function definition is stable; only in the next API version a function may be removed. public static SeStringDrawResult SeStringWrapped( ReadOnlySpan sss, scoped in SeStringDrawParams style = default, @@ -226,8 +224,6 @@ public static class ImGuiHelpers /// ImGui ID, if link functionality is desired. /// Button flags to use on link interaction. /// Interaction result of the rendered text. - /// This function is experimental. Report any issues to GitHub issues or to Discord #dalamud-dev channel. - /// The function definition is stable; only in the next API version a function may be removed. public static SeStringDrawResult CompileSeStringWrapped( string text, scoped in SeStringDrawParams style = default, diff --git a/Dalamud/Plugin/Services/ISeStringEvaluator.cs b/Dalamud/Plugin/Services/ISeStringEvaluator.cs new file mode 100644 index 000000000..2bd423b7c --- /dev/null +++ b/Dalamud/Plugin/Services/ISeStringEvaluator.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.CodeAnalysis; + +using Dalamud.Game; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.Text.Evaluator; + +using Lumina.Text.ReadOnly; + +namespace Dalamud.Plugin.Services; + +/// +/// Defines a service for retrieving localized text for various in-game entities. +/// +[Experimental("SeStringEvaluator")] +public interface ISeStringEvaluator +{ + /// + /// Evaluates macros in a . + /// + /// The string containing macros. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString Evaluate(ReadOnlySeString str, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in a . + /// + /// The string containing macros. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString Evaluate(ReadOnlySeStringSpan str, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in text from the Addon sheet. + /// + /// The row id of the Addon sheet. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString EvaluateFromAddon(uint addonId, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in text from the Lobby sheet. + /// + /// The row id of the Lobby sheet. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString EvaluateFromLobby(uint lobbyId, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates macros in text from the LogMessage sheet. + /// + /// The row id of the LogMessage sheet. + /// An optional list of local parameters. + /// An optional language override. + /// An evaluated . + ReadOnlySeString EvaluateFromLogMessage(uint logMessageId, Span localParameters = default, ClientLanguage? language = null); + + /// + /// Evaluates ActStr from the given ActionKind and id. + /// + /// The ActionKind. + /// The action id. + /// An optional language override. + /// The name of the action. + string EvaluateActStr(ActionKind actionKind, uint id, ClientLanguage? language = null); + + /// + /// Evaluates ObjStr from the given ObjectKind and id. + /// + /// The ObjectKind. + /// The object id. + /// An optional language override. + /// The singular name of the object. + string EvaluateObjStr(ObjectKind objectKind, uint id, ClientLanguage? language = null); +} diff --git a/Dalamud/Utility/ActionKindExtensions.cs b/Dalamud/Utility/ActionKindExtensions.cs new file mode 100644 index 000000000..21026bc31 --- /dev/null +++ b/Dalamud/Utility/ActionKindExtensions.cs @@ -0,0 +1,26 @@ +using Dalamud.Game; + +namespace Dalamud.Utility; + +/// +/// Extension methods for the enum. +/// +public static class ActionKindExtensions +{ + /// + /// Converts the id of an ActionKind to the id used in the ActStr sheet redirect. + /// + /// The ActionKind this id is for. + /// The id. + /// An id that can be used in the ActStr sheet redirect. + public static uint GetActStrId(this ActionKind actionKind, uint id) + { + // See "83 F9 0D 76 0B" + var idx = (uint)actionKind; + + if (idx is <= 13 or 19 or 20) + return id + (1000000 * idx); + + return 0; + } +} diff --git a/Dalamud/Utility/ClientLanguageExtensions.cs b/Dalamud/Utility/ClientLanguageExtensions.cs index 69c39c9b8..47f0a2082 100644 --- a/Dalamud/Utility/ClientLanguageExtensions.cs +++ b/Dalamud/Utility/ClientLanguageExtensions.cs @@ -23,4 +23,40 @@ public static class ClientLanguageExtensions _ => throw new ArgumentOutOfRangeException(nameof(language)), }; } + + /// + /// Gets the language code from a ClientLanguage. + /// + /// The ClientLanguage to convert. + /// The language code (ja, en, de, fr). + /// An exception that is thrown when no valid ClientLanguage was given. + public static string ToCode(this ClientLanguage value) + { + return value switch + { + ClientLanguage.Japanese => "ja", + ClientLanguage.English => "en", + ClientLanguage.German => "de", + ClientLanguage.French => "fr", + _ => throw new ArgumentOutOfRangeException(nameof(value)), + }; + } + + /// + /// Gets the ClientLanguage from a language code. + /// + /// The language code to convert (ja, en, de, fr). + /// The ClientLanguage. + /// An exception that is thrown when no valid language code was given. + public static ClientLanguage ToClientLanguage(this string value) + { + return value switch + { + "ja" => ClientLanguage.Japanese, + "en" => ClientLanguage.English, + "de" => ClientLanguage.German, + "fr" => ClientLanguage.French, + _ => throw new ArgumentOutOfRangeException(nameof(value)), + }; + } } diff --git a/Dalamud/Utility/ItemUtil.cs b/Dalamud/Utility/ItemUtil.cs new file mode 100644 index 000000000..de1e5a721 --- /dev/null +++ b/Dalamud/Utility/ItemUtil.cs @@ -0,0 +1,159 @@ +using System.Runtime.CompilerServices; + +using Dalamud.Data; +using Dalamud.Game; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Game.Text; + +using Lumina.Excel.Sheets; +using Lumina.Text.ReadOnly; + +using static Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload; +using Lumina.Text; + +namespace Dalamud.Utility; + +/// +/// Utilities related to Items. +/// +internal static class ItemUtil +{ + private static int? eventItemRowCount; + + /// Converts raw item ID to item ID with its classification. + /// Raw item ID. + /// Item ID and its classification. + internal static (uint ItemId, ItemKind Kind) GetBaseId(uint rawItemId) + { + if (IsEventItem(rawItemId)) return (rawItemId, ItemKind.EventItem); // EventItem IDs are NOT adjusted + if (IsHighQuality(rawItemId)) return (rawItemId - 1_000_000, ItemKind.Hq); + if (IsCollectible(rawItemId)) return (rawItemId - 500_000, ItemKind.Collectible); + return (rawItemId, ItemKind.Normal); + } + + /// Converts item ID with its classification to raw item ID. + /// Item ID. + /// Item classification. + /// Raw Item ID. + internal static uint GetRawId(uint itemId, ItemKind kind) + { + return kind switch + { + ItemKind.Collectible when itemId < 500_000 => itemId + 500_000, + ItemKind.Hq when itemId < 1_000_000 => itemId + 1_000_000, + ItemKind.EventItem => itemId, // EventItem IDs are not adjusted + _ => itemId, + }; + } + + /// + /// Checks if the item id belongs to a normal item. + /// + /// The item id to check. + /// true when the item id belongs to a normal item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsNormalItem(uint itemId) + { + return itemId < 500_000; + } + + /// + /// Checks if the item id belongs to a collectible item. + /// + /// The item id to check. + /// true when the item id belongs to a collectible item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsCollectible(uint itemId) + { + return itemId is >= 500_000 and < 1_000_000; + } + + /// + /// Checks if the item id belongs to a high quality item. + /// + /// The item id to check. + /// true when the item id belongs to a high quality item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsHighQuality(uint itemId) + { + return itemId is >= 1_000_000 and < 2_000_000; + } + + /// + /// Checks if the item id belongs to an event item. + /// + /// The item id to check. + /// true when the item id belongs to an event item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsEventItem(uint itemId) + { + return itemId >= 2_000_000 && itemId - 2_000_000 < (eventItemRowCount ??= Service.Get().GetExcelSheet().Count); + } + + /// + /// Gets the name of an item. + /// + /// The raw item id. + /// Whether to include the High Quality or Collectible icon. + /// An optional client language override. + /// The item name. + internal static ReadOnlySeString GetItemName(uint itemId, bool includeIcon = true, ClientLanguage? language = null) + { + var dataManager = Service.Get(); + + if (IsEventItem(itemId)) + { + return dataManager + .GetExcelSheet(language) + .TryGetRow(itemId, out var eventItem) + ? eventItem.Name + : default; + } + + var (baseId, kind) = GetBaseId(itemId); + + if (!dataManager + .GetExcelSheet(language) + .TryGetRow(baseId, out var item)) + { + return default; + } + + if (!includeIcon || kind is not (ItemKind.Hq or ItemKind.Collectible)) + return item.Name; + + var builder = SeStringBuilder.SharedPool.Get(); + + builder.Append(item.Name); + + switch (kind) + { + case ItemPayload.ItemKind.Hq: + builder.Append($" {(char)SeIconChar.HighQuality}"); + break; + case ItemPayload.ItemKind.Collectible: + builder.Append($" {(char)SeIconChar.Collectible}"); + break; + } + + var itemName = builder.ToReadOnlySeString(); + SeStringBuilder.SharedPool.Return(builder); + return itemName; + } + + /// + /// Gets the color row id for an item name. + /// + /// The raw item Id. + /// Wheather this color is used as edge color. + /// The Color row id. + internal static uint GetItemRarityColorType(uint itemId, bool isEdgeColor = false) + { + var rarity = 1u; + + if (!IsEventItem(itemId) && Service.Get().GetExcelSheet().TryGetRow(GetBaseId(itemId).ItemId, out var item)) + rarity = item.Rarity; + + return (isEdgeColor ? 548u : 547u) + (rarity * 2u); + } +} diff --git a/Dalamud/Utility/ObjectKindExtensions.cs b/Dalamud/Utility/ObjectKindExtensions.cs new file mode 100644 index 000000000..5d42dc760 --- /dev/null +++ b/Dalamud/Utility/ObjectKindExtensions.cs @@ -0,0 +1,33 @@ +using Dalamud.Game.ClientState.Objects.Enums; + +namespace Dalamud.Utility; + +/// +/// Extension methods for the enum. +/// +public static class ObjectKindExtensions +{ + /// + /// Converts the id of an ObjectKind to the id used in the ObjStr sheet redirect. + /// + /// The ObjectKind this id is for. + /// The id. + /// An id that can be used in the ObjStr sheet redirect. + public static uint GetObjStrId(this ObjectKind objectKind, uint id) + { + // See "8D 41 FE 83 F8 0C 77 4D" + return objectKind switch + { + ObjectKind.BattleNpc => id < 1000000 ? id : id - 900000, + ObjectKind.EventNpc => id, + ObjectKind.Treasure or + ObjectKind.Aetheryte or + ObjectKind.GatheringPoint or + ObjectKind.Companion or + ObjectKind.Housing => id + (1000000 * (uint)objectKind) - 2000000, + ObjectKind.EventObj => id + (1000000 * (uint)objectKind) - 4000000, + ObjectKind.CardStand => id + 3000000, + _ => 0, + }; + } +} diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index f50f19d8e..b21b9b743 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,3 +1,5 @@ +using System.Linq; + using Lumina.Text.Parse; using Lumina.Text.ReadOnly; @@ -74,4 +76,154 @@ public static class SeStringExtensions /// character name to validate. /// indicator if character is name is valid. public static bool IsValidCharacterName(this DSeString value) => value.ToString().IsValidCharacterName(); + + /// + /// Determines whether the contains only text payloads. + /// + /// The to check. + /// true if the string contains only text payloads; otherwise, false. + public static bool IsTextOnly(this ReadOnlySeString ross) + { + return ross.AsSpan().IsTextOnly(); + } + + /// + /// Determines whether the contains only text payloads. + /// + /// The to check. + /// true if the span contains only text payloads; otherwise, false. + public static bool IsTextOnly(this ReadOnlySeStringSpan rosss) + { + foreach (var payload in rosss) + { + if (payload.Type != ReadOnlySePayloadType.Text) + return false; + } + + return true; + } + + /// + /// Determines whether the contains the specified text. + /// + /// The to search. + /// The text to find. + /// true if the text is found; otherwise, false. + public static bool ContainsText(this ReadOnlySeString ross, ReadOnlySpan needle) + { + return ross.AsSpan().ContainsText(needle); + } + + /// + /// Determines whether the contains the specified text. + /// + /// The to search. + /// The text to find. + /// true if the text is found; otherwise, false. + public static bool ContainsText(this ReadOnlySeStringSpan rosss, ReadOnlySpan needle) + { + foreach (var payload in rosss) + { + if (payload.Type != ReadOnlySePayloadType.Text) + continue; + + if (payload.Body.IndexOf(needle) != -1) + return true; + } + + return false; + } + + /// + /// Determines whether the contains the specified text. + /// + /// The builder to search. + /// The text to find. + /// true if the text is found; otherwise, false. + public static bool ContainsText(this LSeStringBuilder builder, ReadOnlySpan needle) + { + return builder.ToReadOnlySeString().ContainsText(needle); + } + + /// + /// Replaces occurrences of a specified text in a with another text. + /// + /// The original string. + /// The text to find. + /// The replacement text. + /// A new with the replacements made. + public static ReadOnlySeString ReplaceText( + this ReadOnlySeString ross, + ReadOnlySpan toFind, + ReadOnlySpan replacement) + { + if (ross.IsEmpty) + return ross; + + var sb = LSeStringBuilder.SharedPool.Get(); + + foreach (var payload in ross) + { + if (payload.Type == ReadOnlySePayloadType.Invalid) + continue; + + if (payload.Type != ReadOnlySePayloadType.Text) + { + sb.Append(payload); + continue; + } + + var index = payload.Body.Span.IndexOf(toFind); + if (index == -1) + { + sb.Append(payload); + continue; + } + + var lastIndex = 0; + while (index != -1) + { + sb.Append(payload.Body.Span[lastIndex..index]); + + if (!replacement.IsEmpty) + { + sb.Append(replacement); + } + + lastIndex = index + toFind.Length; + index = payload.Body.Span[lastIndex..].IndexOf(toFind); + + if (index != -1) + index += lastIndex; + } + + sb.Append(payload.Body.Span[lastIndex..]); + } + + var output = sb.ToReadOnlySeString(); + LSeStringBuilder.SharedPool.Return(sb); + return output; + } + + /// + /// Replaces occurrences of a specified text in an with another text. + /// + /// The builder to modify. + /// The text to find. + /// The replacement text. + public static void ReplaceText( + this LSeStringBuilder builder, + ReadOnlySpan toFind, + ReadOnlySpan replacement) + { + if (toFind.IsEmpty) + return; + + var str = builder.ToReadOnlySeString(); + if (str.IsEmpty) + return; + + var replaced = ReplaceText(new ReadOnlySeString(builder.GetViewAsMemory()), toFind, replacement); + builder.Clear().Append(replaced); + } } diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index 24aa48446..50973e338 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; using FFXIVClientStructs.FFXIV.Client.UI; @@ -43,4 +44,48 @@ public static class StringExtensions if (!UIGlobals.IsValidPlayerCharacterName(value)) return false; return includeLegacy || value.Length <= 21; } + + /// + /// Converts the first character of the string to uppercase while leaving the rest of the string unchanged. + /// + /// The input string. + /// + /// A new string with the first character converted to uppercase. + [return: NotNullIfNotNull("input")] + public static string? FirstCharToUpper(this string? input, CultureInfo? culture = null) => + string.IsNullOrWhiteSpace(input) + ? input + : $"{char.ToUpper(input[0], culture ?? CultureInfo.CurrentCulture)}{input.AsSpan(1)}"; + + /// + /// Converts the first character of the string to lowercase while leaving the rest of the string unchanged. + /// + /// The input string. + /// + /// A new string with the first character converted to lowercase. + [return: NotNullIfNotNull("input")] + public static string? FirstCharToLower(this string? input, CultureInfo? culture = null) => + string.IsNullOrWhiteSpace(input) + ? input + : $"{char.ToLower(input[0], culture ?? CultureInfo.CurrentCulture)}{input.AsSpan(1)}"; + + /// + /// Removes soft hyphen characters (U+00AD) from the input string. + /// + /// The input string to remove soft hyphen characters from. + /// A string with all soft hyphens removed. + public static string StripSoftHyphen(this string input) => input.Replace("\u00AD", string.Empty); + + /// + /// Truncates the given string to the specified maximum number of characters, + /// appending an ellipsis if truncation occurs. + /// + /// The string to truncate. + /// The maximum allowed length of the string. + /// The string to append if truncation occurs (defaults to "..."). + /// The truncated string, or the original string if no truncation is needed. + public static string? Truncate(this string input, int maxChars, string ellipses = "...") + { + return string.IsNullOrEmpty(input) || input.Length <= maxChars ? input : input[..maxChars] + ellipses; + } } From 30cadef34b18a86f5d9d9e8693073bf1f4983ea7 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 09:12:13 -0700 Subject: [PATCH 301/375] fix: support XL_PLATFORM as well (hopefully) --- Dalamud.Common/Util/EnvironmentUtils.cs | 18 ++++++++++++++++++ Dalamud.Injector/EntryPoint.cs | 6 ++++++ 2 files changed, 24 insertions(+) create mode 100644 Dalamud.Common/Util/EnvironmentUtils.cs diff --git a/Dalamud.Common/Util/EnvironmentUtils.cs b/Dalamud.Common/Util/EnvironmentUtils.cs new file mode 100644 index 000000000..d6cf65e3d --- /dev/null +++ b/Dalamud.Common/Util/EnvironmentUtils.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Dalamud.Common.Util; + +public static class EnvironmentUtils +{ + /// + /// Attempts to get an environment variable using the Try pattern. + /// + /// The env var to get. + /// An output containing the env var, if present. + /// A boolean indicating whether the var was present. + public static bool TryGetEnvironmentVariable(string variableName, [NotNullWhen(true)] out string? value) + { + value = Environment.GetEnvironmentVariable(variableName); + return value != null; + } +} diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 0ee2e5507..4d26313a9 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -11,6 +11,8 @@ using System.Text.RegularExpressions; using Dalamud.Common; using Dalamud.Common.Game; +using Dalamud.Common.Util; + using Newtonsoft.Json; using Reloaded.Memory.Buffers; using Serilog; @@ -311,6 +313,10 @@ namespace Dalamud.Injector var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant(); var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; + // env vars are brought in prior to launch args, since args can override them. + if (EnvironmentUtils.TryGetEnvironmentVariable("XL_PLATFORM", out var xlPlatformEnv)) + platformStr = xlPlatformEnv.ToLowerInvariant(); + for (var i = 2; i < args.Count; i++) { if (args[i].StartsWith(key = "--dalamud-working-directory=")) From daeb923f6db51c4e1dbeeddecd02124580f791d1 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 19:16:52 +0100 Subject: [PATCH 302/375] Add missing declaration for utils::is_running_on_wine() --- Dalamud.Boot/utils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index eef405b26..2cdaf60a7 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -267,6 +267,8 @@ namespace utils { return get_env_list(unicode::convert(pcszName).c_str()); } + bool is_running_on_wine(); + std::filesystem::path get_module_path(HMODULE hModule); /// @brief Find the game main window. From a1305159dc393beff3c0ccd598b4bb3b17e23392 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 19:17:43 +0100 Subject: [PATCH 303/375] Remove unused enumerator object pool from ObjectTable --- .../Game/ClientState/Objects/ObjectTable.cs | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 6b7f5f193..f97385fce 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -7,8 +7,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; -using Dalamud.Logging.Internal; -using Dalamud.Plugin.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -31,20 +29,13 @@ namespace Dalamud.Game.ClientState.Objects; #pragma warning restore SA1015 internal sealed partial class ObjectTable : IServiceType, IObjectTable { - private static readonly ModuleLog Log = new("ObjectTable"); - private static int objectTableLength; private readonly ClientState clientState; private readonly CachedEntry[] cachedObjectTable; - private readonly ObjectPool multiThreadedEnumerators = - new DefaultObjectPoolProvider().Create(); - private readonly Enumerator?[] frameworkThreadEnumerators = new Enumerator?[4]; - private long nextMultithreadedUsageWarnTime; - [ServiceManager.ServiceConstructor] private unsafe ObjectTable(ClientState clientState) { @@ -228,21 +219,12 @@ internal sealed partial class ObjectTable /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - private sealed class Enumerator : IEnumerator, IResettable + private sealed class Enumerator(ObjectTable owner, int slotId) : IEnumerator, IResettable { - private readonly int slotId; - private ObjectTable? owner; + private ObjectTable? owner = owner; private int index = -1; - public Enumerator() => this.slotId = -1; - - public Enumerator(ObjectTable owner, int slotId) - { - this.owner = owner; - this.slotId = slotId; - } - public IGameObject Current { get; private set; } = null!; object IEnumerator.Current => this.Current; @@ -265,8 +247,6 @@ internal sealed partial class ObjectTable return false; } - public void InitializeForPooledObjects(ObjectTable ot) => this.owner = ot; - public void Reset() => this.index = -1; public void Dispose() @@ -274,10 +254,8 @@ internal sealed partial class ObjectTable if (this.owner is not { } o) return; - if (this.slotId == -1) - o.multiThreadedEnumerators.Return(this); - else - o.frameworkThreadEnumerators[this.slotId] = this; + if (slotId != -1) + o.frameworkThreadEnumerators[slotId] = this; } public bool TryReset() From 61a4f3bcef100a4376e3976054af01771c69a40a Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 19:17:59 +0100 Subject: [PATCH 304/375] Re-remove obsolete ServicePointManager TLS version override --- Dalamud/EntryPoint.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index ebfc975ff..9f9b98cd4 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -181,9 +181,6 @@ public sealed class EntryPoint // Apply common fixes for culture issues CultureFixes.Apply(); - - // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; // Currently VEH is not fully functional on WINE if (!Util.IsWine()) From fc562778da39c2655961d8b7529dd3dd7984e8e1 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 19:19:44 +0100 Subject: [PATCH 305/375] Rename SMN enums to follow code standards --- .../Game/ClientState/JobGauge/Enums/DeliriumStep.cs | 9 ++++++--- .../ClientState/JobGauge/Enums/SummonAttunement.cs | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs index cb35a83d2..d2a41b93c 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DeliriumStep.cs @@ -1,19 +1,22 @@ namespace Dalamud.Game.ClientState.JobGauge.Enums; +/// +/// Enum representing the current step of Delirium. +/// public enum DeliriumStep { /// /// Scarlet Delirium can be used. /// - SCARLET_DELIRIUM = 0, + ScarletDelirium = 0, /// /// Comeuppance can be used. /// - COMEUPPANCE = 1, + Comeuppance = 1, /// /// Torcleaver can be used. /// - TORCLEAVER = 2, + Torcleaver = 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs index e0b65966c..a07c0d04f 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonAttunement.cs @@ -1,27 +1,30 @@ namespace Dalamud.Game.ClientState.JobGauge.Enums; +/// +/// Enum representing the current attunement of a summoner. +/// public enum SummonAttunement { /// /// No attunement. /// - NONE = 0, + None = 0, /// /// Attuned to the summon Ifrit. /// Same as . /// - IFRIT = 1, + Ifrit = 1, /// /// Attuned to the summon Titan. /// Same as . /// - TITAN = 2, + Titan = 2, /// /// Attuned to the summon Garuda. /// Same as . /// - GARUDA = 3, + Garuda = 3, } From c0c61c66bece36e3df02af5e788d3bda88a3296b Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 19:21:04 +0100 Subject: [PATCH 306/375] Fix warnings --- Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs | 9 ++++++--- .../Text/Evaluator/Internal/SheetRedirectResolver.cs | 3 ++- Dalamud/Utility/ItemUtil.cs | 8 +++----- Dalamud/Utility/Util.cs | 2 -- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index 4c27ae83a..899ea78eb 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -22,6 +22,9 @@ public unsafe class SMNGauge : JobGaugeBase /// public ushort SummonTimerRemaining => this.Struct->SummonTimer; + /// + /// Gets the time remaining for the current attunement. + /// [Obsolete("Typo fixed. Use AttunementTimerRemaining instead.", true)] public ushort AttunmentTimerRemaining => this.AttunementTimerRemaining; @@ -102,19 +105,19 @@ public unsafe class SMNGauge : JobGaugeBase /// Gets a value indicating whether if Ifrit is currently attuned. /// /// true or false. - public bool IsIfritAttuned => this.AttunementType == SummonAttunement.IFRIT; + public bool IsIfritAttuned => this.AttunementType == SummonAttunement.Ifrit; /// /// Gets a value indicating whether if Titan is currently attuned. /// /// true or false. - public bool IsTitanAttuned => this.AttunementType == SummonAttunement.TITAN; + public bool IsTitanAttuned => this.AttunementType == SummonAttunement.Titan; /// /// Gets a value indicating whether if Garuda is currently attuned. /// /// true or false. - public bool IsGarudaAttuned => this.AttunementType == SummonAttunement.GARUDA; + public bool IsGarudaAttuned => this.AttunementType == SummonAttunement.Garuda; /// /// Gets a value indicating whether there are any Aetherflow stacks available. diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs index 57a58c80d..49af895d9 100644 --- a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs @@ -85,8 +85,9 @@ internal class SheetRedirectResolver : IServiceType { flags |= SheetRedirectFlags.HighQuality; } - else if (kind == ItemKind.Collectible || sheetName == "ItemMP") // MP for Masterpiece?! + else if (kind == ItemKind.Collectible || sheetName == "ItemMP") { + // MP for Masterpiece?! flags |= SheetRedirectFlags.Collectible; } diff --git a/Dalamud/Utility/ItemUtil.cs b/Dalamud/Utility/ItemUtil.cs index de1e5a721..32160aa15 100644 --- a/Dalamud/Utility/ItemUtil.cs +++ b/Dalamud/Utility/ItemUtil.cs @@ -2,14 +2,12 @@ using System.Runtime.CompilerServices; using Dalamud.Data; using Dalamud.Game; -using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.Text; - using Lumina.Excel.Sheets; +using Lumina.Text; using Lumina.Text.ReadOnly; using static Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload; -using Lumina.Text; namespace Dalamud.Utility; @@ -128,10 +126,10 @@ internal static class ItemUtil switch (kind) { - case ItemPayload.ItemKind.Hq: + case ItemKind.Hq: builder.Append($" {(char)SeIconChar.HighQuality}"); break; - case ItemPayload.ItemKind.Collectible: + case ItemKind.Collectible: builder.Append($" {(char)SeIconChar.Collectible}"); break; } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 2fcc4806a..87cb86e1c 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -12,14 +12,12 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; -using Dalamud.Logging.Internal; using Dalamud.Interface.Utility.Raii; using Dalamud.Support; using ImGuiNET; From 84f5dad0a3010b634bdafed99f9ecf7d90378737 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 11:31:19 -0700 Subject: [PATCH 307/375] fix: JobGauge enum casing --- .../ClientState/JobGauge/Enums/BeastChakra.cs | 8 +++---- .../ClientState/JobGauge/Enums/CardType.cs | 18 +++++++------- .../JobGauge/Enums/DismissedFairy.cs | 4 ++-- .../ClientState/JobGauge/Enums/DrawType.cs | 4 ++-- .../Game/ClientState/JobGauge/Enums/Kaeshi.cs | 10 ++++---- .../Game/ClientState/JobGauge/Enums/Mudras.cs | 6 ++--- .../Game/ClientState/JobGauge/Enums/Nadi.cs | 6 ++--- .../ClientState/JobGauge/Enums/PetGlam.cs | 16 ++++++------- .../Game/ClientState/JobGauge/Enums/Sen.cs | 8 +++---- .../JobGauge/Enums/SerpentCombo.cs | 14 +++++------ .../Game/ClientState/JobGauge/Enums/Song.cs | 8 +++---- .../ClientState/JobGauge/Enums/SummonPet.cs | 4 ++-- .../ClientState/JobGauge/Types/BRDGauge.cs | 24 +++++++++---------- .../ClientState/JobGauge/Types/MNKGauge.cs | 2 +- .../ClientState/JobGauge/Types/SAMGauge.cs | 6 ++--- 15 files changed, 69 insertions(+), 69 deletions(-) diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs index bbe4ad70d..9191ca020 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/BeastChakra.cs @@ -8,20 +8,20 @@ public enum BeastChakra : byte /// /// No chakra. /// - NONE = 0, + None = 0, /// /// The Opo-Opo chakra. /// - OPOOPO = 1, + OpoOpo = 1, /// /// The Raptor chakra. /// - RAPTOR = 2, + Raptor = 2, /// /// The Coeurl chakra. /// - COEURL = 3, + Coeurl = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs index 24ffc2b19..89fceed96 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs @@ -8,45 +8,45 @@ public enum CardType : byte /// /// No card. /// - NONE = 0, + None = 0, /// /// The Balance card. /// - BALANCE = 1, + Balance = 1, /// /// The Bole card. /// - BOLE = 2, + Bole = 2, /// /// The Arrow card. /// - ARROW = 3, + Arrow = 3, /// /// The Spear card. /// - SPEAR = 4, + Spear = 4, /// /// The Ewer card. /// - EWER = 5, + Ewer = 5, /// /// The Spire card. /// - SPIRE = 6, + Spire = 6, /// /// The Lord of Crowns card. /// - LORD = 7, + Lord = 7, /// /// The Lady of Crowns card. /// - LADY = 8, + Lady = 8, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs index b674d11b8..446489bd1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs @@ -8,10 +8,10 @@ public enum DismissedFairy : byte /// /// Dismissed fairy is Eos. /// - EOS = 6, + Eos = 6, /// /// Dismissed fairy is Selene. /// - SELENE = 7, + Selene = 7, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs index f8833d6d0..619059daa 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DrawType.cs @@ -8,10 +8,10 @@ public enum DrawType : byte /// /// Astral Draw active. /// - ASTRAL = 0, + Astral = 0, /// /// Umbral Draw active. /// - UMBRAL = 1, + Umbral = 1, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs index e35dcc7f9..86e58771b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Kaeshi.cs @@ -8,25 +8,25 @@ public enum Kaeshi : byte /// /// No Kaeshi is active. /// - NONE = 0, + None = 0, /// /// Kaeshi: Higanbana type. /// - HIGANBANA = 1, + Higanbana = 1, /// /// Kaeshi: Goken type. /// - GOKEN = 2, + Goken = 2, /// /// Kaeshi: Setsugekka type. /// - SETSUGEKKA = 3, + Setsugekka = 3, /// /// Kaeshi: Namikiri type. /// - NAMIKIRI = 4, + Namikiri = 4, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs index 31ee6dac1..8955c0735 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs @@ -8,15 +8,15 @@ public enum Mudras : byte /// /// Ten mudra. /// - TEN = 1, + Ten = 1, /// /// Chi mudra. /// - CHI = 2, + Chi = 2, /// /// Jin mudra. /// - JIN = 3, + Jin = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs index 8be5c739e..f61e54104 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Nadi.cs @@ -9,15 +9,15 @@ public enum Nadi : byte /// /// No nadi. /// - NONE = 0, + None = 0, /// /// The Lunar nadi. /// - LUNAR = 1, + Lunar = 1, /// /// The Solar nadi. /// - SOLAR = 2, + Solar = 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs index 248caa396..c10c369d3 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs @@ -8,40 +8,40 @@ public enum PetGlam : byte /// /// No pet glam. /// - NONE = 0, + None = 0, /// /// Emerald carbuncle pet glam. /// - EMERALD = 1, + Emerald = 1, /// /// Topaz carbuncle pet glam. /// - TOPAZ = 2, + Topaz = 2, /// /// Ruby carbuncle pet glam. /// - RUBY = 3, + Ruby = 3, /// /// Normal carbuncle pet glam. /// - CARBUNCLE = 4, + Carbuncle = 4, /// /// Ifrit Egi pet glam. /// - IFRIT = 5, + Ifrit = 5, /// /// Titan Egi pet glam. /// - TITAN = 6, + Titan = 6, /// /// Garuda Egi pet glam. /// - GARUDA = 7, + Garuda = 7, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs index a1a6035c6..0eb86b1b1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs @@ -9,20 +9,20 @@ public enum Sen : byte /// /// No Sen. /// - NONE = 0, + None = 0, /// /// Setsu Sen type. /// - SETSU = 1 << 0, + Setsu = 1 << 0, /// /// Getsu Sen type. /// - GETSU = 1 << 1, + Getsu = 1 << 1, /// /// Ka Sen type. /// - KA = 1 << 2, + Ka = 1 << 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs index 0fc50d87a..f4850cf8c 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SerpentCombo.cs @@ -8,35 +8,35 @@ public enum SerpentCombo : byte /// /// No Serpent combo is active. /// - NONE = 0, + None = 0, /// /// Death Rattle action. /// - DEATHRATTLE = 1, + DeathRattle = 1, /// /// Last Lash action. /// - LASTLASH = 2, + LastLash = 2, /// /// First Legacy action. /// - FIRSTLEGACY = 3, + FirstLegacy = 3, /// /// Second Legacy action. /// - SECONDLEGACY = 4, + SecondLegacy = 4, /// /// Third Legacy action. /// - THIRDLEGACY = 5, + ThirdLegacy = 5, /// /// Fourth Legacy action. /// - FOURTHLEGACY = 6, + FourthLegacy = 6, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs index c23fbbbff..7ca6b6c07 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs @@ -8,20 +8,20 @@ public enum Song : byte /// /// No song is active type. /// - NONE = 0, + None = 0, /// /// Mage's Ballad type. /// - MAGE = 1, + Mage = 1, /// /// Army's Paeon type. /// - ARMY = 2, + Army = 2, /// /// The Wanderer's Minuet type. /// - WANDERER = 3, + Wanderer = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs index 30cc0fff0..37569f4bf 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs @@ -8,10 +8,10 @@ public enum SummonPet : byte /// /// No pet. /// - NONE = 0, + None = 0, /// /// The summoned pet Carbuncle. /// - CARBUNCLE = 23, + Carbuncle = 23, } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs index bfcf3cc38..8880c3555 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs @@ -40,15 +40,15 @@ public unsafe class BRDGauge : JobGaugeBaseSongFlags.HasFlag(SongFlags.WanderersMinuet)) - return Song.WANDERER; + return Song.Wanderer; if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeon)) - return Song.ARMY; + return Song.Army; if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBallad)) - return Song.MAGE; + return Song.Mage; - return Song.NONE; + return Song.None; } } @@ -60,15 +60,15 @@ public unsafe class BRDGauge : JobGaugeBaseSongFlags.HasFlag(SongFlags.WanderersMinuetLastPlayed)) - return Song.WANDERER; + return Song.Wanderer; if (this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonLastPlayed)) - return Song.ARMY; + return Song.Army; if (this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladLastPlayed)) - return Song.MAGE; + return Song.Mage; - return Song.NONE; + return Song.None; } } @@ -76,7 +76,7 @@ public unsafe class BRDGauge : JobGaugeBase /// - /// This will always return an array of size 3, inactive Coda are represented by . + /// This will always return an array of size 3, inactive Coda are represented by . /// public Song[] Coda { @@ -84,9 +84,9 @@ public unsafe class BRDGauge : JobGaugeBaseSongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.MAGE : Song.NONE, - this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.ARMY : Song.NONE, - this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.WANDERER : Song.NONE, + this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.Mage : Song.None, + this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.Army : Song.None, + this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.Wanderer : Song.None, }; } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs index 803740f33..31a5ceb9b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs @@ -27,7 +27,7 @@ public unsafe class MNKGauge : JobGaugeBase /// - /// This will always return an array of size 3, inactive Beast Chakra are represented by . + /// This will always return an array of size 3, inactive Beast Chakra are represented by . /// public BeastChakra[] BeastChakra => this.Struct->BeastChakra.Select(c => (BeastChakra)c).ToArray(); diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs index f3417f002..52dc0e51a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs @@ -40,17 +40,17 @@ public unsafe class SAMGauge : JobGaugeBase /// true or false. - public bool HasSetsu => (this.Sen & Sen.SETSU) != 0; + public bool HasSetsu => (this.Sen & Sen.Setsu) != 0; /// /// Gets a value indicating whether the Getsu Sen is active. /// /// true or false. - public bool HasGetsu => (this.Sen & Sen.GETSU) != 0; + public bool HasGetsu => (this.Sen & Sen.Getsu) != 0; /// /// Gets a value indicating whether the Ka Sen is active. /// /// true or false. - public bool HasKa => (this.Sen & Sen.KA) != 0; + public bool HasKa => (this.Sen & Sen.Ka) != 0; } From f209ac087af3dee6d9e5b50efadfb3c9e0813ba5 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 20:23:56 +0100 Subject: [PATCH 308/375] SeStringEvaluator: Throw if not on main thread --- Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs | 6 +----- Dalamud/Utility/ThreadSafety.cs | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 723dbcb41..eb6dee290 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1682,11 +1682,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator if (rtm is null) return false; - if (!ThreadSafety.IsMainThread) - { - Log.Error("Global parameters may only be used from the main thread."); - return false; - } + ThreadSafety.AssertMainThread("Global parameters may only be used from the main thread."); ref var gp = ref rtm->TextModule.MacroDecoder.GlobalParameters; if (parameterIndex >= gp.MySize) diff --git a/Dalamud/Utility/ThreadSafety.cs b/Dalamud/Utility/ThreadSafety.cs index ea8238d44..c31cc0005 100644 --- a/Dalamud/Utility/ThreadSafety.cs +++ b/Dalamud/Utility/ThreadSafety.cs @@ -18,13 +18,14 @@ public static class ThreadSafety /// /// Throws an exception when the current thread is not the main thread. /// + /// The message to be passed into the exception, if one is to be thrown. /// Thrown when the current thread is not the main thread. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AssertMainThread() + public static void AssertMainThread(string? message = null) { if (!threadStaticIsMainThread) { - throw new InvalidOperationException("Not on main thread!"); + throw new InvalidOperationException(message ?? "Not on main thread!"); } } From d1e6e34f4036f9f8aff93d17f3047c6cc7b0364f Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 20:44:57 +0100 Subject: [PATCH 309/375] Bump to version 12 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index c26573fbd..51fc46ebe 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 11.0.8.0 + 12.0.0.0 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 9e3c03d0e8e7ced5c53fae68f35969f30668a5f9 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 20:45:16 +0100 Subject: [PATCH 310/375] Add deprecation warning to targets file --- targets/Dalamud.Plugin.targets | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/targets/Dalamud.Plugin.targets b/targets/Dalamud.Plugin.targets index da897c252..08d19735e 100644 --- a/targets/Dalamud.Plugin.targets +++ b/targets/Dalamud.Plugin.targets @@ -29,9 +29,7 @@ - From 2176b32219a68b744df9d488ae1ff8b5723f5dd3 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 13:25:13 -0700 Subject: [PATCH 311/375] chore: Bump ClientStructs and make it build again --- Dalamud/Dalamud.csproj | 1 + .../Game/Addon/Events/AddonEventListener.cs | 14 ++--- Dalamud/Game/ClientState/Statuses/Status.cs | 4 +- Dalamud/Game/Config/GameConfigSection.cs | 12 ++-- Dalamud/Game/Gui/Dtr/DtrBar.cs | 18 +++--- Dalamud/Game/Inventory/GameInventoryItem.cs | 10 ++- .../Game/Network/Internal/NetworkHandlers.cs | 8 +-- .../Game/Text/Evaluator/SeStringEvaluator.cs | 2 +- Dalamud/Interface/Internal/UiDebug.cs | 14 ++--- .../UiDebug2/Browsing/AddonTree.AtkValues.cs | 5 +- .../UiDebug2/Browsing/NodeTree.Text.cs | 2 +- .../Data/Widgets/SeStringCreatorWidget.cs | 6 +- .../Internal/Windows/TitleScreenMenuWindow.cs | 4 +- .../TextureManager.FromExistingTexture.cs | 63 +++++++++---------- Dalamud/Utility/SeStringExtensions.cs | 7 +++ lib/FFXIVClientStructs | 2 +- 16 files changed, 92 insertions(+), 80 deletions(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 51fc46ebe..9f3a9bb4a 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -90,6 +90,7 @@ + diff --git a/Dalamud/Game/Addon/Events/AddonEventListener.cs b/Dalamud/Game/Addon/Events/AddonEventListener.cs index cc6416fe8..515785d72 100644 --- a/Dalamud/Game/Addon/Events/AddonEventListener.cs +++ b/Dalamud/Game/Addon/Events/AddonEventListener.cs @@ -11,9 +11,9 @@ namespace Dalamud.Game.Addon.Events; internal unsafe class AddonEventListener : IDisposable { private ReceiveEventDelegate? receiveEventDelegate; - + private AtkEventListener* eventListener; - + /// /// Initializes a new instance of the class. /// @@ -24,7 +24,7 @@ internal unsafe class AddonEventListener : IDisposable this.eventListener = (AtkEventListener*)Marshal.AllocHGlobal(sizeof(AtkEventListener)); this.eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)Marshal.AllocHGlobal(sizeof(void*) * 3); - this.eventListener->VirtualTable->Dtor = (delegate* unmanaged)(delegate* unmanaged)&NullSub; + this.eventListener->VirtualTable->Dtor = (delegate* unmanaged)(delegate* unmanaged)&NullSub; this.eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged)(delegate* unmanaged)&NullSub; this.eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.receiveEventDelegate); } @@ -38,17 +38,17 @@ internal unsafe class AddonEventListener : IDisposable /// Pointer to the AtkEvent. /// Pointer to the AtkEventData. public delegate void ReceiveEventDelegate(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr); - + /// /// Gets the address of this listener. /// public nint Address => (nint)this.eventListener; - + /// public void Dispose() { if (this.eventListener is null) return; - + Marshal.FreeHGlobal((nint)this.eventListener->VirtualTable); Marshal.FreeHGlobal((nint)this.eventListener); @@ -88,7 +88,7 @@ internal unsafe class AddonEventListener : IDisposable node->RemoveEvent(eventType, param, this.eventListener, false); }); } - + [UnmanagedCallersOnly] private static void NullSub() { diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index f09d13fb3..c3493ce55 100644 --- a/Dalamud/Game/ClientState/Statuses/Status.cs +++ b/Dalamud/Game/ClientState/Statuses/Status.cs @@ -42,8 +42,10 @@ public unsafe class Status /// /// Gets the stack count of this status. + /// Only valid if this is a non-food status. /// - public byte StackCount => this.Struct->StackCount; + [Obsolete($"Replaced with {nameof(Param)}", true)] + public byte StackCount => (byte)this.Struct->Param; /// /// Gets the time remaining of this status. diff --git a/Dalamud/Game/Config/GameConfigSection.cs b/Dalamud/Game/Config/GameConfigSection.cs index 9cd239d84..8ebab8a60 100644 --- a/Dalamud/Game/Config/GameConfigSection.cs +++ b/Dalamud/Game/Config/GameConfigSection.cs @@ -52,7 +52,7 @@ public class GameConfigSection /// /// Event which is fired when a game config option is changed within the section. /// - internal event EventHandler? Changed; + internal event EventHandler? Changed; /// /// Gets the number of config entries contained within the section. @@ -526,8 +526,8 @@ public class GameConfigSection { if (!this.enumMap.TryGetValue(entry->Index, out var enumObject)) { - if (entry->Name == null) return null; - var name = MemoryHelper.ReadStringNullTerminated(new IntPtr(entry->Name)); + if (entry->Name.Value == null) return null; + var name = entry->Name.ToString(); if (Enum.TryParse(typeof(TEnum), name, out enumObject)) { this.enumMap.TryAdd(entry->Index, enumObject); @@ -544,7 +544,7 @@ public class GameConfigSection this.Changed?.InvokeSafely(this, eventArgs); return eventArgs; } - + private unsafe bool TryGetIndex(string name, out uint index) { if (this.indexMap.TryGetValue(name, out index)) @@ -556,12 +556,12 @@ public class GameConfigSection var e = configBase->ConfigEntry; for (var i = 0U; i < configBase->ConfigCount; i++, e++) { - if (e->Name == null) + if (e->Name.Value == null) { continue; } - var eName = MemoryHelper.ReadStringNullTerminated(new IntPtr(e->Name)); + var eName = e->Name.ToString(); if (eName.Equals(name)) { this.indexMap.TryAdd(name, i); diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index f37b3addc..c6208fb2f 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -30,7 +30,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private const uint BaseNodeId = 1000; private static readonly ModuleLog Log = new("DtrBar"); - + [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); @@ -58,7 +58,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar private ImmutableList? entriesReadOnlyCopy; private Utf8String* emptyString; - + private uint runningNodeIds = BaseNodeId; private float entryStartPos = float.NaN; @@ -72,7 +72,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.addonLifecycle.RegisterListener(this.dtrPostDrawListener); this.addonLifecycle.RegisterListener(this.dtrPostRequestedUpdateListener); this.addonLifecycle.RegisterListener(this.dtrPreFinalizeListener); - + this.framework.Update += this.Update; this.configuration.DtrOrder ??= []; @@ -522,7 +522,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler), this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler), }); - + var lastChild = dtr->RootNode->ChildNode; while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode; Log.Debug($"Found last sibling: {(ulong)lastChild:X}"); @@ -590,7 +590,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar if (this.emptyString == null) this.emptyString = Utf8String.FromString(" "); - + newTextNode->SetText(this.emptyString->StringPtr); newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 }; @@ -609,7 +609,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar return newTextNode; } - + private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode) { var addon = (AtkUnitBase*)atkUnitBase; @@ -632,7 +632,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar case AddonEventType.MouseOver: AtkStage.Instance()->TooltipManager.ShowTooltip(addon->Id, node, dtrBarEntry.Tooltip.Encode()); break; - + case AddonEventType.MouseOut: AtkStage.Instance()->TooltipManager.HideTooltip(addon->Id); break; @@ -646,11 +646,11 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar case AddonEventType.MouseOver: this.uiEventManager.SetCursor(AddonCursorType.Clickable); break; - + case AddonEventType.MouseOut: this.uiEventManager.ResetCursor(); break; - + case AddonEventType.MouseClick: dtrBarEntry.OnClick.Invoke(); break; diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index a9b178411..32eb9911b 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -17,7 +17,7 @@ public unsafe struct GameInventoryItem : IEquatable /// [FieldOffset(0)] internal readonly InventoryItem InternalItem; - + /// /// The view of the backing data, in . /// @@ -55,10 +55,16 @@ public unsafe struct GameInventoryItem : IEquatable /// public int Quantity => this.InternalItem.Quantity; + /// + /// Gets the spiritbond or collectability of this item. + /// + public uint SpiritbondOrCollectability => this.InternalItem.SpiritbondOrCollectability; + /// /// Gets the spiritbond of this item. /// - public uint Spiritbond => this.InternalItem.Spiritbond; + [Obsolete($"Renamed to {nameof(SpiritbondOrCollectability)}", true)] + public uint Spiritbond => this.SpiritbondOrCollectability; /// /// Gets the repair condition of this item. diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 2ba7f2587..c0929fa84 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -565,7 +565,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService return this.configuration.IsMbCollect; } - private void MarketPurchasePacketDetour(PacketDispatcher* a1, nint packetData) + private void MarketPurchasePacketDetour(uint targetId, nint packetData) { try { @@ -576,7 +576,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketPurchasePacketHandler threw an exception"); } - this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData); + this.mbPurchaseHook.OriginalDisposeSafe(targetId, packetData); } private void MarketHistoryPacketDetour(InfoProxyItemSearch* a1, nint packetData) @@ -609,7 +609,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount); } - private void MarketItemRequestStartDetour(PacketDispatcher* a1, nint packetRef) + private void MarketItemRequestStartDetour(uint targetId, nint packetRef) { try { @@ -620,7 +620,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService Log.Error(ex, "MarketItemRequestStartDetour threw an exception"); } - this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef); + this.mbItemRequestStartHook.OriginalDisposeSafe(targetId, packetRef); } private void MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetRef) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index eb6dee290..47c4b5899 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1744,7 +1744,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator return false; case TextParameterType.String: - this.EvaluateAndAppendTo(builder, new(p.StringValue), null, language); + this.EvaluateAndAppendTo(builder, p.StringValue.AsReadOnlySeStringSpan(), null, language); return false; case TextParameterType.Uninitialized: diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 2e8f4416b..9dfff75ec 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -225,7 +225,7 @@ internal unsafe class UiDebug ImGui.SameLine(); if (ImGui.Button($"Decode##{(ulong)textNode:X}")) - textNode->NodeText.SetString(new ReadOnlySeStringSpan(textNode->NodeText.StringPtr).ToString()); + textNode->NodeText.SetString(textNode->NodeText.StringPtr.AsReadOnlySeStringSpan().ToString()); ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); int b = textNode->AlignmentFontType; @@ -418,27 +418,27 @@ internal unsafe class UiDebug ImGui.Text("InputBase Text1: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText1); - + ImGui.Text("InputBase Text2: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->AtkComponentInputBase.UnkText2); - + ImGui.Text("Text1: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->UnkText01); - + ImGui.Text("Text2: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->UnkText02); - + ImGui.Text("Text3: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->UnkText03); - + ImGui.Text("Text4: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->UnkText04); - + ImGui.Text("Text5: "); ImGui.SameLine(); Service.Get().Draw(textInputComponent->UnkText05); diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs index c3930821b..c3f6133dd 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.AtkValues.cs @@ -76,14 +76,13 @@ public unsafe partial class AddonTree case ValueType.String8: case ValueType.String: { - if (atkValue->String == null) + if (atkValue->String.Value == null) { ImGui.TextDisabled("null"); } else { - var str = MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue->String)); - Util.ShowStruct(str, (ulong)atkValue); + Util.ShowStruct(atkValue->String.ToString(), (ulong)atkValue); } break; diff --git a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs index 61e0e79b8..02bd5feca 100644 --- a/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs +++ b/Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Text.cs @@ -89,7 +89,7 @@ internal unsafe partial class TextNodeTree : ResNodeTree var seStringBytes = new byte[utf8String.BufUsed]; for (var i = 0L; i < utf8String.BufUsed; i++) { - seStringBytes[i] = utf8String.StringPtr[i]; + seStringBytes[i] = utf8String.StringPtr.Value[i]; } var seString = SeString.Parse(seStringBytes); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs index 2a56cb6c7..92e57ddac 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringCreatorWidget.cs @@ -187,7 +187,7 @@ internal class SeStringCreatorWidget : IDataWindowWidget // resize panels relative to the window size if (contentWidth != this.lastContentWidth) { - var originalWidth = this.lastContentWidth != 0 ? this.lastContentWidth : contentWidth; + var originalWidth = this.lastContentWidth != 0 ? this.lastContentWidth : contentWidth; this.inputsWidth = this.inputsWidth / originalWidth * contentWidth; this.lastContentWidth = contentWidth; } @@ -299,8 +299,8 @@ internal class SeStringCreatorWidget : IDataWindowWidget break; case TextParameterType.String: - if (item.StringValue != null) - WidgetUtil.DrawCopyableText(MemoryHelper.ReadStringNullTerminated((nint)item.StringValue)); + if (item.StringValue.Value != null) + WidgetUtil.DrawCopyableText(item.StringValue.ToString()); else ImGui.TextUnformatted("null"); break; diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 3b6140b8c..891751c1e 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -442,7 +442,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable textNode->TextFlags |= (byte)TextFlags.MultiLine; textNode->AlignmentType = AlignmentType.TopLeft; - var containsDalamudVersionString = textNode->OriginalTextPointer == textNode->NodeText.StringPtr; + var containsDalamudVersionString = textNode->OriginalTextPointer.Value == textNode->NodeText.StringPtr.Value; if (!this.configuration.ShowTsm || !this.showTsm.Value) { if (containsDalamudVersionString) @@ -460,7 +460,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable this.lastLoadedPluginCount = count; var lssb = LSeStringBuilder.SharedPool.Get(); - lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String)).Append("\n\n"); + lssb.Append(new ReadOnlySeStringSpan(addon->AtkValues[1].String.Value)).Append("\n\n"); lssb.PushEdgeColorType(701).PushColorType(539) .Append(SeIconChar.BoxedLetterD.ToIconChar()) .PopColorType().PopEdgeColorType(); diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs index 2ce96e59d..829b8d0c5 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.FromExistingTexture.cs @@ -9,7 +9,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.TerraFxCom; -using Lumina.Data.Files; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -24,32 +24,30 @@ internal sealed partial class TextureManager (nint)this.ConvertToKernelTexture(wrap, leaveWrapOpen); /// - public unsafe FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture* ConvertToKernelTexture( - IDalamudTextureWrap wrap, - bool leaveWrapOpen = false) + public unsafe Texture* ConvertToKernelTexture(IDalamudTextureWrap wrap, bool leaveWrapOpen = false) { using var wrapAux = new WrapAux(wrap, leaveWrapOpen); - var flags = TexFile.Attribute.TextureType2D; + var flags = TextureFlags.TextureType2D; if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_IMMUTABLE) - flags |= TexFile.Attribute.Immutable; + flags |= TextureFlags.Immutable; if (wrapAux.Desc.Usage == D3D11_USAGE.D3D11_USAGE_DYNAMIC) - flags |= TexFile.Attribute.ReadWrite; + flags |= TextureFlags.ReadWrite; if ((wrapAux.Desc.CPUAccessFlags & (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ) != 0) - flags |= TexFile.Attribute.CpuRead; + flags |= TextureFlags.CpuRead; if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET) != 0) - flags |= TexFile.Attribute.TextureRenderTarget; + flags |= TextureFlags.TextureRenderTarget; if ((wrapAux.Desc.BindFlags & (uint)D3D11_BIND_FLAG.D3D11_BIND_DEPTH_STENCIL) != 0) - flags |= TexFile.Attribute.TextureDepthStencil; + flags |= TextureFlags.TextureDepthStencil; if (wrapAux.Desc.ArraySize != 1) throw new NotSupportedException("TextureArray2D is currently not supported."); - var gtex = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture.CreateTexture2D( + var gtex = Texture.CreateTexture2D( (int)wrapAux.Desc.Width, (int)wrapAux.Desc.Height, (byte)wrapAux.Desc.MipLevels, - (uint)TexFile.TextureFormat.Null, // instructs the game to skip preprocessing it seems - (uint)flags, + 0, // instructs the game to skip preprocessing it seems + flags, 0); // Kernel::Texture owns these resources. We're passing the ownership to them. @@ -57,28 +55,27 @@ internal sealed partial class TextureManager wrapAux.SrvPtr->AddRef(); // Not sure this is needed - var ltf = wrapAux.Desc.Format switch + gtex->TextureFormat = wrapAux.Desc.Format switch { - DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TexFile.TextureFormat.R32G32B32A32F, - DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TexFile.TextureFormat.R16G16B16A16F, - DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TexFile.TextureFormat.R32G32F, - DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TexFile.TextureFormat.R16G16F, - DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TexFile.TextureFormat.R32F, - DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TexFile.TextureFormat.D24S8, - DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TexFile.TextureFormat.D16, - DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TexFile.TextureFormat.A8, - DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TexFile.TextureFormat.BC1, - DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TexFile.TextureFormat.BC2, - DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TexFile.TextureFormat.BC3, - DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TexFile.TextureFormat.BC5, - DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TexFile.TextureFormat.B4G4R4A4, - DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TexFile.TextureFormat.B5G5R5A1, - DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TexFile.TextureFormat.B8G8R8A8, - DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TexFile.TextureFormat.B8G8R8X8, - DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TexFile.TextureFormat.BC7, - _ => TexFile.TextureFormat.Null, + DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT => TextureFormat.R32G32_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R16G16_FLOAT => TextureFormat.R16G16_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT => TextureFormat.R32_FLOAT, + DXGI_FORMAT.DXGI_FORMAT_R24G8_TYPELESS => TextureFormat.D24_UNORM_S8_UINT, + DXGI_FORMAT.DXGI_FORMAT_R16_TYPELESS => TextureFormat.D16_UNORM, + DXGI_FORMAT.DXGI_FORMAT_A8_UNORM => TextureFormat.A8_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM => TextureFormat.BC1_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM => TextureFormat.BC2_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM => TextureFormat.BC3_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM => TextureFormat.BC5_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM => TextureFormat.B4G4R4A4_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM => TextureFormat.B5G5R5A1_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNORM, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM => TextureFormat.B8G8R8X8_UNORM, + DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM => TextureFormat.BC7_UNORM, + _ => 0, }; - gtex->TextureFormat = (FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.TextureFormat)ltf; gtex->D3D11Texture2D = wrapAux.TexPtr; gtex->D3D11ShaderResourceView = wrapAux.SrvPtr; diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index b21b9b743..7dbffc696 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,5 +1,7 @@ using System.Linq; +using InteropGenerator.Runtime; + using Lumina.Text.Parse; using Lumina.Text.ReadOnly; @@ -226,4 +228,9 @@ public static class SeStringExtensions var replaced = ReplaceText(new ReadOnlySeString(builder.GetViewAsMemory()), toFind, replacement); builder.Clear().Append(replaced); } + + public static unsafe ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) + { + return new ReadOnlySeStringSpan(ptr.Value); + } } diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 2c3e84640..3c99b4f8f 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 2c3e84640af5220b78b944a06fdca79c52144075 +Subproject commit 3c99b4f8f7f56ee4defd3ee75809c73312359f9e From 9a25946ec1cf413bf12823c6977ca8f9e01c344c Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 21:28:41 +0100 Subject: [PATCH 312/375] Associate Dalamud.Common and LocExporter with x64 configs --- Dalamud.sln | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Dalamud.sln b/Dalamud.sln index 5b6f56c6e..92e28f8b7 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -74,8 +74,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64 {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64 {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64 @@ -124,14 +122,14 @@ Global {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64 - {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|Any CPU - {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|Any CPU + {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.ActiveCfg = Debug|x64 + {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|x64 + {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|x64 + {F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|x64 + {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|x64 + {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|x64 + {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|x64 + {A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|x64 {3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3620414C-7DFC-423E-929F-310E19F5D930}.Debug|Any CPU.Build.0 = Debug|Any CPU {3620414C-7DFC-423E-929F-310E19F5D930}.Release|Any CPU.ActiveCfg = Release|Any CPU From 5dcd1cc52fbfa31beaae77afa165e491df9cab68 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 13:35:42 -0700 Subject: [PATCH 313/375] chore: fix angry compiler --- .../Internal/Windows/Data/Widgets/InventoryWidget.cs | 2 +- .../Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs | 4 ++-- Dalamud/Utility/SeStringExtensions.cs | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs index 5cefc0853..efa5fa0df 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -219,7 +219,7 @@ internal class InventoryWidget : IDataWindowWidget if (!this.IsEventItem(item.ItemId)) { - AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.Spiritbond.ToString()); + AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.SpiritbondOrCollectability.ToString()); if (item.CrafterContentId != 0) AddKeyValueRow("CrafterContentId", item.CrafterContentId.ToString()); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs index f08eccd96..24faf562f 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs @@ -110,7 +110,7 @@ internal class ContextMenuAgingStep : IAgingStep return SelfTestStepResult.Waiting; } - + /// public void CleanUp() { @@ -244,7 +244,7 @@ internal class ContextMenuAgingStep : IAgingStep b.AppendLine($"Container: {item.ContainerType}"); b.AppendLine($"Slot: {item.InventorySlot}"); b.AppendLine($"Quantity: {item.Quantity}"); - b.AppendLine($"{(item.IsCollectable ? "Collectability" : "Spiritbond")}: {item.Spiritbond}"); + b.AppendLine($"{(item.IsCollectable ? "Collectability" : "Spiritbond")}: {item.SpiritbondOrCollectability}"); b.AppendLine($"Condition: {item.Condition / 300f:0.00}% ({item.Condition})"); b.AppendLine($"Is HQ: {item.IsHq}"); b.AppendLine($"Is Company Crest Applied: {item.IsCompanyCrestApplied}"); diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 7dbffc696..f2c28bd7d 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -229,6 +229,11 @@ public static class SeStringExtensions builder.Clear().Append(replaced); } + /// + /// Convert a CStringPointer to a ReadOnySeStringSpan. + /// + /// The pointer to convert. + /// A span. public static unsafe ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) { return new ReadOnlySeStringSpan(ptr.Value); From 24358f5af6774d8e19c4539167dc76597393a9ba Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 21:41:55 +0100 Subject: [PATCH 314/375] Fix warning in injector --- Dalamud.Injector/EntryPoint.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 4d26313a9..71c919179 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -419,7 +419,9 @@ namespace Dalamud.Injector } OSPlatform platform; - if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len]) // covers both win32 and Windows + + // covers both win32 and Windows + if (platformStr[0..(len = Math.Min(platformStr.Length, (key = "win").Length))] == key[0..len]) { platform = OSPlatform.Windows; } From ddda4d477d92efacfe92609a532a694355c0736e Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 24 Mar 2025 21:54:57 +0100 Subject: [PATCH 315/375] Make VS happy about SLN changes --- Dalamud.sln | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dalamud.sln b/Dalamud.sln index 92e28f8b7..5b1eb9d30 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -74,6 +74,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|x64 + {94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|x64 + {94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|x64 + {94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|x64 {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64 {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64 {B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64 From 314c046ec965c3ce6fa15bdb73a7825427324d27 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 17:43:49 -0700 Subject: [PATCH 316/375] feat: Add CStringPointer#ExtractText - Move CString extensions to their own class. - Add some words to our dictionary. --- Dalamud.sln.DotSettings | 2 ++ Dalamud/Utility/CStringExtensions.cs | 32 +++++++++++++++++++++++++++ Dalamud/Utility/SeStringExtensions.cs | 10 --------- 3 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 Dalamud/Utility/CStringExtensions.cs diff --git a/Dalamud.sln.DotSettings b/Dalamud.sln.DotSettings index b0f66b736..6d0e1fdcd 100644 --- a/Dalamud.sln.DotSettings +++ b/Dalamud.sln.DotSettings @@ -54,6 +54,7 @@ True True True + True True True True @@ -66,6 +67,7 @@ True True True + True True True True diff --git a/Dalamud/Utility/CStringExtensions.cs b/Dalamud/Utility/CStringExtensions.cs new file mode 100644 index 000000000..a1848c512 --- /dev/null +++ b/Dalamud/Utility/CStringExtensions.cs @@ -0,0 +1,32 @@ +using InteropGenerator.Runtime; + +using Lumina.Text.ReadOnly; + +namespace Dalamud.Utility; + +/// +/// A set of helpful utilities for working with s from ClientStructs. +/// +public static class CStringExtensions +{ + /// + /// Convert a CStringPointer to a ReadOnlySeStringSpan. + /// + /// The pointer to convert. + /// A span. + public static unsafe ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) + { + return ptr.AsSpan(); + } + + /// + /// Extract text from this CStringPointer following 's rules. Only + /// useful for SeStrings. + /// + /// The CStringPointer to process. + /// Extracted text. + public static string ExtractText(this CStringPointer ptr) + { + return ptr.AsReadOnlySeStringSpan().ExtractText(); + } +} diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index f2c28bd7d..904375250 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -228,14 +228,4 @@ public static class SeStringExtensions var replaced = ReplaceText(new ReadOnlySeString(builder.GetViewAsMemory()), toFind, replacement); builder.Clear().Append(replaced); } - - /// - /// Convert a CStringPointer to a ReadOnySeStringSpan. - /// - /// The pointer to convert. - /// A span. - public static unsafe ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) - { - return new ReadOnlySeStringSpan(ptr.Value); - } } From a7509ef77dd0d50cbf53f7334759559190012ebe Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 18:16:39 -0700 Subject: [PATCH 317/375] feat: More utilities! - Also document that CStringExtensions only works with bundled CS. --- .../Game/Text/Evaluator/SeStringEvaluator.cs | 2 +- Dalamud/Utility/CStringExtensions.cs | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 47c4b5899..6c768cfa1 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1744,7 +1744,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator return false; case TextParameterType.String: - this.EvaluateAndAppendTo(builder, p.StringValue.AsReadOnlySeStringSpan(), null, language); + this.EvaluateAndAppendTo(builder, p.StringValue.AsSpan(), null, language); return false; case TextParameterType.Uninitialized: diff --git a/Dalamud/Utility/CStringExtensions.cs b/Dalamud/Utility/CStringExtensions.cs index a1848c512..f025ee85c 100644 --- a/Dalamud/Utility/CStringExtensions.cs +++ b/Dalamud/Utility/CStringExtensions.cs @@ -1,4 +1,6 @@ -using InteropGenerator.Runtime; +using Dalamud.Game.Text.SeStringHandling; + +using InteropGenerator.Runtime; using Lumina.Text.ReadOnly; @@ -7,6 +9,9 @@ namespace Dalamud.Utility; /// /// A set of helpful utilities for working with s from ClientStructs. /// +/// +/// WARNING: Will break if a custom ClientStructs is used. These are here for CONVENIENCE ONLY! +/// public static class CStringExtensions { /// @@ -14,11 +19,34 @@ public static class CStringExtensions /// /// The pointer to convert. /// A span. - public static unsafe ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) + public static ReadOnlySeStringSpan AsReadOnlySeStringSpan(this CStringPointer ptr) { return ptr.AsSpan(); } + /// + /// Convert a CStringPointer to a Dalamud SeString. + /// + /// The pointer to convert. + /// A Dalamud-flavored SeString. + public static SeString AsDalamudSeString(this CStringPointer ptr) + { + return ptr.AsReadOnlySeStringSpan().ToDalamudString(); + } + + /// + /// Get a new SeString that's a copy of the text in this CStringPointer. + /// + /// The pointer to copy. + /// A new Lumina SeString. + public static Lumina.Text.SeString AsLuminaSeString(this CStringPointer ptr) + { + var ssb = new Lumina.Text.SeStringBuilder(); + ssb.Append(ptr.AsSpan()); + + return ssb.ToSeString(); + } + /// /// Extract text from this CStringPointer following 's rules. Only /// useful for SeStrings. From 27dc6595610d461b2578271bf8ce532b40ff4fda Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 18:32:44 -0700 Subject: [PATCH 318/375] chore: Convert AsLuminaSeString to AsReadOnlySeString - Somewhat more useful, or so one would hope. --- Dalamud/Utility/CStringExtensions.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Dalamud/Utility/CStringExtensions.cs b/Dalamud/Utility/CStringExtensions.cs index f025ee85c..acc70e469 100644 --- a/Dalamud/Utility/CStringExtensions.cs +++ b/Dalamud/Utility/CStringExtensions.cs @@ -35,16 +35,17 @@ public static class CStringExtensions } /// - /// Get a new SeString that's a copy of the text in this CStringPointer. + /// Get a new ReadOnlySeString that's a copy of the text in this CStringPointer. /// + /// + /// This should be functionally identical to , but exists + /// for convenience in places that already expect ReadOnlySeString as a type (and where a copy is desired). + /// /// The pointer to copy. - /// A new Lumina SeString. - public static Lumina.Text.SeString AsLuminaSeString(this CStringPointer ptr) + /// A new Lumina ReadOnlySeString. + public static ReadOnlySeString AsReadOnlySeString(this CStringPointer ptr) { - var ssb = new Lumina.Text.SeStringBuilder(); - ssb.Append(ptr.AsSpan()); - - return ssb.ToSeString(); + return new ReadOnlySeString(ptr.AsSpan().ToArray()); } /// From 445fe09181eb190198a47c83b36ffd5222028832 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 18:50:39 -0700 Subject: [PATCH 319/375] fix: dalamud boot deadlock over wine --- Dalamud/EntryPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 9f9b98cd4..f6ba990e6 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -183,7 +183,7 @@ public sealed class EntryPoint CultureFixes.Apply(); // Currently VEH is not fully functional on WINE - if (!Util.IsWine()) + if (info.Platform != OSPlatform.Windows) InitSymbolHandler(info); var dalamud = new Dalamud(info, fs, configuration, mainThreadContinueEvent); From 919ca87bbe1ece20a184341395ddc2eb1e0e4062 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Mon, 24 Mar 2025 21:58:23 -0700 Subject: [PATCH 320/375] update clientstructs - fix broken gamepad sig by delegating to CS --- .../ClientState/ClientStateAddressResolver.cs | 8 -- .../Game/ClientState/GamePad/GamepadInput.cs | 75 ----------------- .../Game/ClientState/GamePad/GamepadState.cs | 83 +++++++++---------- .../Windows/Data/Widgets/GamepadWidget.cs | 14 ++-- lib/FFXIVClientStructs | 2 +- 5 files changed, 48 insertions(+), 134 deletions(-) delete mode 100644 Dalamud/Game/ClientState/GamePad/GamepadInput.cs diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index d44275ef8..97bc5dae1 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -24,12 +24,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// public IntPtr ProcessPacketPlayerSetup { get; private set; } - /// - /// Gets the address of the method which polls the gamepads for data. - /// Called every frame, even when `Enable Gamepad` is off in the settings. - /// - public IntPtr GamepadPoll { get; private set; } - /// /// Scan for and setup any configured address pointers. /// @@ -43,7 +37,5 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; - - this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); // unnamed in cs } } diff --git a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs deleted file mode 100644 index d9dcea60f..000000000 --- a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Dalamud.Game.ClientState.GamePad; - -/// -/// Struct which gets populated by polling the gamepads. -/// -/// Has an array of gamepads, among many other things (here not mapped). -/// All we really care about is the final data which the game uses to determine input. -/// -/// The size is definitely bigger than only the following fields but I do not know how big. -/// -[StructLayout(LayoutKind.Explicit)] -public struct GamepadInput -{ - /// - /// Left analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x78)] - public int LeftStickX; - - /// - /// Left analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x7C)] - public int LeftStickY; - - /// - /// Right analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x80)] - public int RightStickX; - - /// - /// Right analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x84)] - public int RightStickY; - - /// - /// Raw input, set the whole time while a button is held. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x88)] - public ushort ButtonsRaw; - - /// - /// Button pressed, set once when the button is pressed. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x8C)] - public ushort ButtonsPressed; - - /// - /// Button released input, set once right after the button is not hold anymore. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x90)] - public ushort ButtonsReleased; - - /// - /// Repeatedly emits the held button input in fixed intervals. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x94)] - public ushort ButtonsRepeat; -} diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index 05d691823..5237c6f0c 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -4,7 +4,8 @@ using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; -using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.System.Input; using ImGuiNET; using Serilog; @@ -23,7 +24,7 @@ namespace Dalamud.Game.ClientState.GamePad; #pragma warning restore SA1015 internal unsafe class GamepadState : IInternalDisposableService, IGamepadState { - private readonly Hook? gamepadPoll; + private readonly Hook? gamepadPoll; private bool isDisposed; @@ -35,25 +36,21 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState [ServiceManager.ServiceConstructor] private GamepadState(ClientState clientState) { - var resolver = clientState.AddressResolver; - Log.Verbose($"GamepadPoll address {Util.DescribeAddress(resolver.GamepadPoll)}"); - this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour); + this.gamepadPoll = Hook.FromAddress((nint)PadDevice.StaticVirtualTablePointer->Poll, this.GamepadPollDetour); this.gamepadPoll?.Enable(); } - private delegate int ControllerPoll(IntPtr controllerInput); - /// /// Gets the pointer to the current instance of the GamepadInput struct. /// public IntPtr GamepadInputAddress { get; private set; } /// - public Vector2 LeftStick => + public Vector2 LeftStick => new(this.leftStickX, this.leftStickY); - + /// - public Vector2 RightStick => + public Vector2 RightStick => new(this.rightStickX, this.rightStickY); /// @@ -61,28 +58,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsPressed { get; private set; } + internal GamepadButtons ButtonsPressed { get; private set; } /// /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsRaw { get; private set; } + internal GamepadButtons ButtonsRaw { get; private set; } /// /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsReleased { get; private set; } + internal GamepadButtons ButtonsReleased { get; private set; } /// /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. /// /// Exposed internally for Debug Data window. /// - internal ushort ButtonsRepeat { get; private set; } + internal GamepadButtons ButtonsRepeat { get; private set; } /// /// Gets or sets a value indicating whether detour should block gamepad input for game. @@ -95,16 +92,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState internal bool NavEnableGamepad { get; set; } /// - public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; + public float Pressed(GamepadButtons button) => (this.ButtonsPressed & button) > 0 ? 1 : 0; /// - public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; + public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & button) > 0 ? 1 : 0; /// - public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; + public float Released(GamepadButtons button) => (this.ButtonsReleased & button) > 0 ? 1 : 0; /// - public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; + public float Raw(GamepadButtons button) => (this.ButtonsRaw & button) > 0 ? 1 : 0; /// /// Disposes this instance, alongside its hooks. @@ -115,28 +112,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState GC.SuppressFinalize(this); } - private int GamepadPollDetour(IntPtr gamepadInput) + private nint GamepadPollDetour(PadDevice* gamepadInput) { var original = this.gamepadPoll!.Original(gamepadInput); try { - this.GamepadInputAddress = gamepadInput; - var input = (GamepadInput*)gamepadInput; - this.leftStickX = input->LeftStickX; - this.leftStickY = input->LeftStickY; - this.rightStickX = input->RightStickX; - this.rightStickY = input->RightStickY; - this.ButtonsRaw = input->ButtonsRaw; - this.ButtonsPressed = input->ButtonsPressed; - this.ButtonsReleased = input->ButtonsReleased; - this.ButtonsRepeat = input->ButtonsRepeat; + this.GamepadInputAddress = (nint)gamepadInput; + + this.leftStickX = gamepadInput->GamepadInputData.LeftStickX; + this.leftStickY = gamepadInput->GamepadInputData.LeftStickY; + this.rightStickX = gamepadInput->GamepadInputData.RightStickX; + this.rightStickY = gamepadInput->GamepadInputData.RightStickY; + this.ButtonsRaw = (GamepadButtons)gamepadInput->GamepadInputData.Buttons; + this.ButtonsPressed = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsPressed; + this.ButtonsReleased = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsReleased; + this.ButtonsRepeat = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsRepeat; if (this.NavEnableGamepad) { - input->LeftStickX = 0; - input->LeftStickY = 0; - input->RightStickX = 0; - input->RightStickY = 0; + gamepadInput->GamepadInputData.LeftStickX = 0; + gamepadInput->GamepadInputData.LeftStickY = 0; + gamepadInput->GamepadInputData.RightStickX = 0; + gamepadInput->GamepadInputData.RightStickY = 0; // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). @@ -153,16 +150,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. // This is debatable. // ImGui itself does not care either way as it uses the Raw values and does its own state handling. - const ushort deletionMask = (ushort)(~GamepadButtons.L2 - & ~GamepadButtons.R2 - & ~GamepadButtons.DpadDown - & ~GamepadButtons.DpadLeft - & ~GamepadButtons.DpadUp - & ~GamepadButtons.DpadRight); - input->ButtonsRaw &= deletionMask; - input->ButtonsPressed = 0; - input->ButtonsReleased = 0; - input->ButtonsRepeat = 0; + const GamepadButtonsFlags deletionMask = ~GamepadButtonsFlags.L2 + & ~GamepadButtonsFlags.R2 + & ~GamepadButtonsFlags.DPadDown + & ~GamepadButtonsFlags.DPadLeft + & ~GamepadButtonsFlags.DPadUp + & ~GamepadButtonsFlags.DPadRight; + gamepadInput->GamepadInputData.Buttons &= deletionMask; + gamepadInput->GamepadInputData.ButtonsPressed = 0; + gamepadInput->GamepadInputData.ButtonsReleased = 0; + gamepadInput->GamepadInputData.ButtonsRepeat = 0; return 0; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs index 5121d82e6..610fa90cc 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs @@ -12,9 +12,9 @@ internal class GamepadWidget : IDataWindowWidget { /// public string[]? CommandShortcuts { get; init; } = { "gamepad", "controller" }; - + /// - public string DisplayName { get; init; } = "Gamepad"; + public string DisplayName { get; init; } = "Gamepad"; /// public bool Ready { get; set; } @@ -42,24 +42,24 @@ internal class GamepadWidget : IDataWindowWidget this.DrawHelper( "Buttons Raw", - gamepadState.ButtonsRaw, + (uint)gamepadState.ButtonsRaw, gamepadState.Raw); this.DrawHelper( "Buttons Pressed", - gamepadState.ButtonsPressed, + (uint)gamepadState.ButtonsPressed, gamepadState.Pressed); this.DrawHelper( "Buttons Repeat", - gamepadState.ButtonsRepeat, + (uint)gamepadState.ButtonsRepeat, gamepadState.Repeat); this.DrawHelper( "Buttons Released", - gamepadState.ButtonsReleased, + (uint)gamepadState.ButtonsReleased, gamepadState.Released); ImGui.Text($"LeftStick {gamepadState.LeftStick}"); ImGui.Text($"RightStick {gamepadState.RightStick}"); } - + private void DrawHelper(string text, uint mask, Func resolve) { ImGui.Text($"{text} {mask:X4}"); diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 3c99b4f8f..48076a4ce 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 3c99b4f8f7f56ee4defd3ee75809c73312359f9e +Subproject commit 48076a4ce750c8f008f3bedd04d0bced03147f56 From 12ce09870fb7f8f9ec9eead5d6d4fccde40cc7b6 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 25 Mar 2025 09:10:00 -0700 Subject: [PATCH 321/375] deps: bump deps - Lumina.Excel to 7.2.0 - ClientStructs to latest Co-authored-by: Asriel Camora --- Directory.Build.props | 2 +- lib/FFXIVClientStructs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6fd42faa1..2905b80b1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ 5.6.1 - 7.1.3 + 7.2.0 13.0.3 diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 48076a4ce..794671e6c 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 48076a4ce750c8f008f3bedd04d0bced03147f56 +Subproject commit 794671e6c66d12c975dc67cf95691c52bc5f6feb From 67561af32f6e28a97a075a7d4c5c7182a0bb94d4 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 25 Mar 2025 09:28:57 -0700 Subject: [PATCH 322/375] fix: Lumina updates --- Dalamud/Game/Internal/DalamudAtkTweaks.cs | 6 ++--- .../Internal/SheetRedirectResolver.cs | 2 +- .../Game/Text/Evaluator/SeStringEvaluator.cs | 4 ++-- Dalamud/Game/Text/SeStringHandling/Payload.cs | 4 ++-- .../Payloads/UIForegroundPayload.cs | 4 ++-- .../Payloads/UIGlowPayload.cs | 4 ++-- .../Internal/SeStringColorStackSet.cs | 8 +++---- .../Windows/Data/Widgets/InventoryWidget.cs | 2 +- .../Windows/Data/Widgets/UIColorWidget.cs | 24 +++++++++---------- Dalamud/Utility/CStringExtensions.cs | 2 +- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 7834ab58f..83a2f3525 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -53,7 +53,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); // this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; - + this.hookAgentHudOpenSystemMenu.Enable(); this.hookUiModuleExecuteMainCommand.Enable(); this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); @@ -180,7 +180,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService // about hooking the exd reader, thank god var firstStringEntry = &atkValueArgs[5 + 18]; firstStringEntry->ChangeType(ValueType.String); - + var secondStringEntry = &atkValueArgs[6 + 18]; secondStringEntry->ChangeType(ValueType.String); @@ -193,7 +193,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService .Append($"{SeIconChar.BoxedLetterD.ToIconString()} ") .Append(new UIForegroundPayload(0)) .Append(this.locDalamudSettings).Encode(); - + firstStringEntry->SetManagedString(strPlugins); secondStringEntry->SetManagedString(strSettings); diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs index 49af895d9..1be0f8866 100644 --- a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs @@ -193,7 +193,7 @@ internal class SheetRedirectResolver : IServiceType colIndex = 43; if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) - rowId = row.Order; + rowId = row.SortKey; break; } diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index 6c768cfa1..d9b2b2e01 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1595,7 +1595,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator if (eColorTypeVal == 0) context.Builder.PopColor(); else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) - context.Builder.PushColorBgra((row.UIForeground >> 8) | (row.UIForeground << 24)); + context.Builder.PushColorBgra((row.Light >> 8) | (row.Light << 24)); return true; } @@ -1609,7 +1609,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator if (eColorTypeVal == 0) context.Builder.PopEdgeColor(); else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) - context.Builder.PushEdgeColorBgra((row.UIForeground >> 8) | (row.UIForeground << 24)); + context.Builder.PushEdgeColorBgra((row.Light >> 8) | (row.Light << 24)); return true; } diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index a38a8271d..7131a88a7 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -201,7 +201,7 @@ public abstract partial class Payload case SeStringChunkType.Icon: payload = new IconPayload(); break; - + default: // Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); break; @@ -306,7 +306,7 @@ public abstract partial class Payload /// See the . /// NewLine = 0x10, - + /// /// See the class. /// diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index 995c20211..4e8de8f8f 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -74,13 +74,13 @@ public class UIForegroundPayload : Payload /// Gets the Red/Green/Blue/Alpha values for this foreground color, encoded as a typical hex color. /// [JsonIgnore] - public uint RGBA => this.UIColor.Value.UIForeground; + public uint RGBA => this.UIColor.Value.Dark; /// /// Gets the ABGR value for this foreground color, as ImGui requires it in PushColor. /// [JsonIgnore] - public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIForeground); + public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.Dark); /// public override string ToString() diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index 3049ccac3..840d6c85c 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -71,13 +71,13 @@ public class UIGlowPayload : Payload /// Gets the Red/Green/Blue/Alpha values for this glow color, encoded as a typical hex color. /// [JsonIgnore] - public uint RGBA => this.UIColor.Value.UIGlow; + public uint RGBA => this.UIColor.Value.Light; /// /// Gets the ABGR value for this glow color, as ImGui requires it in PushColor. /// [JsonIgnore] - public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIGlow); + public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.Light); /// /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs index ddff55923..ad60d405e 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -43,10 +43,10 @@ internal sealed class SeStringColorStackSet foreach (var row in uiColor) { // Contains ABGR. - this.colorTypes[row.RowId, 0] = row.UIForeground; - this.colorTypes[row.RowId, 1] = row.UIGlow; - this.colorTypes[row.RowId, 2] = row.Unknown0; - this.colorTypes[row.RowId, 3] = row.Unknown1; + this.colorTypes[row.RowId, 0] = row.Dark; + this.colorTypes[row.RowId, 1] = row.Light; + this.colorTypes[row.RowId, 2] = row.ClassicFF; + this.colorTypes[row.RowId, 3] = row.ClearBlue; } if (BitConverter.IsLittleEndian) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs index efa5fa0df..f455f2e66 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -380,7 +380,7 @@ internal class InventoryWidget : IDataWindowWidget var rowId = this.GetItemRarityColorType(item, isEdgeColor); return this.dataManager.Excel.GetSheet().TryGetRow(rowId, out var color) - ? BinaryPrimitives.ReverseEndianness(color.UIForeground) | 0xFF000000 + ? BinaryPrimitives.ReverseEndianness(color.Light) | 0xFF000000 : 0xFFFFFFFF; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs index d43ae50a3..45f1ad715 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/UIColorWidget.cs @@ -93,34 +93,34 @@ internal class UiColorWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.PushID($"row{id}_col1"); - if (this.DrawColorColumn(row.UIForeground) && + ImGui.PushID($"row{id}_dark"); + if (this.DrawColorColumn(row.Dark) && adjacentRow.HasValue) - DrawEdgePreview(id, row.UIForeground, adjacentRow.Value.UIForeground); + DrawEdgePreview(id, row.Dark, adjacentRow.Value.Dark); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.PushID($"row{id}_col2"); - if (this.DrawColorColumn(row.UIGlow) && + ImGui.PushID($"row{id}_light"); + if (this.DrawColorColumn(row.Light) && adjacentRow.HasValue) - DrawEdgePreview(id, row.UIGlow, adjacentRow.Value.UIGlow); + DrawEdgePreview(id, row.Light, adjacentRow.Value.Light); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.PushID($"row{id}_col3"); - if (this.DrawColorColumn(row.Unknown0) && + ImGui.PushID($"row{id}_classic"); + if (this.DrawColorColumn(row.ClassicFF) && adjacentRow.HasValue) - DrawEdgePreview(id, row.Unknown0, adjacentRow.Value.Unknown0); + DrawEdgePreview(id, row.ClassicFF, adjacentRow.Value.ClassicFF); ImGui.PopID(); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.PushID($"row{id}_col4"); - if (this.DrawColorColumn(row.Unknown1) && + ImGui.PushID($"row{id}_blue"); + if (this.DrawColorColumn(row.ClearBlue) && adjacentRow.HasValue) - DrawEdgePreview(id, row.Unknown1, adjacentRow.Value.Unknown1); + DrawEdgePreview(id, row.ClearBlue, adjacentRow.Value.ClearBlue); ImGui.PopID(); } } diff --git a/Dalamud/Utility/CStringExtensions.cs b/Dalamud/Utility/CStringExtensions.cs index acc70e469..83ebb186f 100644 --- a/Dalamud/Utility/CStringExtensions.cs +++ b/Dalamud/Utility/CStringExtensions.cs @@ -10,7 +10,7 @@ namespace Dalamud.Utility; /// A set of helpful utilities for working with s from ClientStructs. /// /// -/// WARNING: Will break if a custom ClientStructs is used. These are here for CONVENIENCE ONLY! +/// WARNING: Will break if a custom ClientStructs is used. These are here for CONVENIENCE ONLY!. /// public static class CStringExtensions { From c5af536032c291c16071685ddf466b57bf77f86e Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 25 Mar 2025 09:35:21 -0700 Subject: [PATCH 323/375] docs: Explain what the UI*Payloads actually are. --- .../Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs | 3 ++- Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index 4e8de8f8f..8443e06ce 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -10,7 +10,8 @@ using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// -/// An SeString Payload representing a UI foreground color applied to following text payloads. +/// An SeString Payload that allows text to have a specific color. The color selected will be determined by the +/// theme's coloring, regardless of the active theme. /// public class UIForegroundPayload : Payload { diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index 840d6c85c..d22318378 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -10,7 +10,8 @@ using Newtonsoft.Json; namespace Dalamud.Game.Text.SeStringHandling.Payloads; /// -/// An SeString Payload representing a UI glow color applied to following text payloads. +/// An SeString Payload that allows text to have a specific edge glow. The color selected will be determined by the +/// theme's coloring, regardless of the active theme. /// public class UIGlowPayload : Payload { From 0377e847658d3f65b4b7acf6f610ab20b9a8bbad Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 25 Mar 2025 17:37:18 +0100 Subject: [PATCH 324/375] Config Options for 7.2 (#2205) --- Dalamud/Game/Config/SystemConfigOption.cs | 84 +++++++++++----------- Dalamud/Game/Config/UiConfigOption.cs | 85 +++++++++++++---------- 2 files changed, 91 insertions(+), 78 deletions(-) diff --git a/Dalamud/Game/Config/SystemConfigOption.cs b/Dalamud/Game/Config/SystemConfigOption.cs index f7e3bd3f8..154992637 100644 --- a/Dalamud/Game/Config/SystemConfigOption.cs +++ b/Dalamud/Game/Config/SystemConfigOption.cs @@ -597,6 +597,20 @@ public enum SystemConfigOption [GameConfigOption("EnablePsFunction", ConfigType.UInt)] EnablePsFunction, + /// + /// System option with the internal name ActiveInstanceGuid. + /// This option is a String. + /// + [GameConfigOption("ActiveInstanceGuid", ConfigType.String)] + ActiveInstanceGuid, + + /// + /// System option with the internal name ActiveProductGuid. + /// This option is a String. + /// + [GameConfigOption("ActiveProductGuid", ConfigType.String)] + ActiveProductGuid, + /// /// System option with the internal name WaterWet. /// This option is a UInt. @@ -996,6 +1010,27 @@ public enum SystemConfigOption [GameConfigOption("AutoChangeCameraMode", ConfigType.UInt)] AutoChangeCameraMode, + /// + /// System option with the internal name MsqProgress. + /// This option is a UInt. + /// + [GameConfigOption("MsqProgress", ConfigType.UInt)] + MsqProgress, + + /// + /// System option with the internal name PromptConfigUpdate. + /// This option is a UInt. + /// + [GameConfigOption("PromptConfigUpdate", ConfigType.UInt)] + PromptConfigUpdate, + + /// + /// System option with the internal name TitleScreenType. + /// This option is a UInt. + /// + [GameConfigOption("TitleScreenType", ConfigType.UInt)] + TitleScreenType, + /// /// System option with the internal name AccessibilitySoundVisualEnable. /// This option is a UInt. @@ -1059,6 +1094,13 @@ public enum SystemConfigOption [GameConfigOption("IdlingCameraAFK", ConfigType.UInt)] IdlingCameraAFK, + /// + /// System option with the internal name FirstConfigBackup. + /// This option is a UInt. + /// + [GameConfigOption("FirstConfigBackup", ConfigType.UInt)] + FirstConfigBackup, + /// /// System option with the internal name MouseSpeed. /// This option is a Float. @@ -1436,46 +1478,4 @@ public enum SystemConfigOption /// [GameConfigOption("PadButton_R3", ConfigType.String)] PadButton_R3, - - /// - /// System option with the internal name ActiveInstanceGuid. - /// This option is a String. - /// - [GameConfigOption("ActiveInstanceGuid", ConfigType.String)] - ActiveInstanceGuid, - - /// - /// System option with the internal name ActiveProductGuid. - /// This option is a String. - /// - [GameConfigOption("ActiveProductGuid", ConfigType.String)] - ActiveProductGuid, - - /// - /// System option with the internal name MsqProgress. - /// This option is a UInt. - /// - [GameConfigOption("MsqProgress", ConfigType.UInt)] - MsqProgress, - - /// - /// System option with the internal name PromptConfigUpdate. - /// This option is a UInt. - /// - [GameConfigOption("PromptConfigUpdate", ConfigType.UInt)] - PromptConfigUpdate, - - /// - /// System option with the internal name TitleScreenType. - /// This option is a UInt. - /// - [GameConfigOption("TitleScreenType", ConfigType.UInt)] - TitleScreenType, - - /// - /// System option with the internal name FirstConfigBackup. - /// This option is a UInt. - /// - [GameConfigOption("FirstConfigBackup", ConfigType.UInt)] - FirstConfigBackup, } diff --git a/Dalamud/Game/Config/UiConfigOption.cs b/Dalamud/Game/Config/UiConfigOption.cs index 53e64c89f..1a59b8945 100644 --- a/Dalamud/Game/Config/UiConfigOption.cs +++ b/Dalamud/Game/Config/UiConfigOption.cs @@ -37,6 +37,13 @@ public enum UiConfigOption [GameConfigOption("BattleEffectPvPEnemyPc", ConfigType.UInt)] BattleEffectPvPEnemyPc, + /// + /// UiConfig option with the internal name PadMode. + /// This option is a UInt. + /// + [GameConfigOption("PadMode", ConfigType.UInt)] + PadMode, + /// /// UiConfig option with the internal name WeaponAutoPutAway. /// This option is a UInt. @@ -114,14 +121,6 @@ public enum UiConfigOption [GameConfigOption("LockonDefaultZoom", ConfigType.Float)] LockonDefaultZoom, - /// - /// UiConfig option with the internal name LockonDefaultZoom_179. - /// This option is a Float. - /// - [Obsolete("This option won't work. Use LockonDefaultZoom.", true)] - [GameConfigOption("LockonDefaultZoom_179", ConfigType.Float)] - LockonDefaultZoom_179, - /// /// UiConfig option with the internal name CameraProductionOfAction. /// This option is a UInt. @@ -311,6 +310,27 @@ public enum UiConfigOption [GameConfigOption("RightClickExclusionMinion", ConfigType.UInt)] RightClickExclusionMinion, + /// + /// UiConfig option with the internal name EnableMoveTiltCharacter. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)] + EnableMoveTiltCharacter, + + /// + /// UiConfig option with the internal name EnableMoveTiltMountGround. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)] + EnableMoveTiltMountGround, + + /// + /// UiConfig option with the internal name EnableMoveTiltMountFly. + /// This option is a UInt. + /// + [GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)] + EnableMoveTiltMountFly, + /// /// UiConfig option with the internal name TurnSpeed. /// This option is a UInt. @@ -1130,6 +1150,27 @@ public enum UiConfigOption [GameConfigOption("HotbarXHBEditEnable", ConfigType.UInt)] HotbarXHBEditEnable, + /// + /// UiConfig option with the internal name HotbarContentsAction2ReverseOperation. + /// This option is a UInt. + /// + [GameConfigOption("HotbarContentsAction2ReverseOperation", ConfigType.UInt)] + HotbarContentsAction2ReverseOperation, + + /// + /// UiConfig option with the internal name HotbarContentsAction2ReturnInitialSlot. + /// This option is a UInt. + /// + [GameConfigOption("HotbarContentsAction2ReturnInitialSlot", ConfigType.UInt)] + HotbarContentsAction2ReturnInitialSlot, + + /// + /// UiConfig option with the internal name HotbarContentsAction2ReverseRotate. + /// This option is a UInt. + /// + [GameConfigOption("HotbarContentsAction2ReverseRotate", ConfigType.UInt)] + HotbarContentsAction2ReverseRotate, + /// /// UiConfig option with the internal name PlateType. /// This option is a UInt. @@ -3572,32 +3613,4 @@ public enum UiConfigOption /// [GameConfigOption("PvPFrontlinesGCFree", ConfigType.UInt)] PvPFrontlinesGCFree, - - /// - /// UiConfig option with the internal name PadMode. - /// This option is a UInt. - /// - [GameConfigOption("PadMode", ConfigType.UInt)] - PadMode, - - /// - /// UiConfig option with the internal name EnableMoveTiltCharacter. - /// This option is a UInt. - /// - [GameConfigOption("EnableMoveTiltCharacter", ConfigType.UInt)] - EnableMoveTiltCharacter, - - /// - /// UiConfig option with the internal name EnableMoveTiltMountGround. - /// This option is a UInt. - /// - [GameConfigOption("EnableMoveTiltMountGround", ConfigType.UInt)] - EnableMoveTiltMountGround, - - /// - /// UiConfig option with the internal name EnableMoveTiltMountFly. - /// This option is a UInt. - /// - [GameConfigOption("EnableMoveTiltMountFly", ConfigType.UInt)] - EnableMoveTiltMountFly, } From fe5ce40a973677a914d6ccb86c9b85d8975438a5 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 25 Mar 2025 17:38:21 +0100 Subject: [PATCH 325/375] Fix ISeStringEvaluator service not resolving (#2204) --- Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index d9b2b2e01..d0b182f04 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -11,6 +11,8 @@ using Dalamud.Game.Config; using Dalamud.Game.Text.Evaluator.Internal; using Dalamud.Game.Text.Noun; using Dalamud.Game.Text.Noun.Enums; +using Dalamud.IoC; +using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -41,7 +43,9 @@ namespace Dalamud.Game.Text.Evaluator; /// /// Evaluator for SeStrings. /// +[PluginInterface] [ServiceManager.EarlyLoadedService] +[ResolveVia] internal class SeStringEvaluator : IServiceType, ISeStringEvaluator { private static readonly ModuleLog Log = new("SeStringEvaluator"); From abb5cf5dd1034a8f140d19fa1d2aab5e1fa29bbe Mon Sep 17 00:00:00 2001 From: marzent Date: Tue, 25 Mar 2025 18:43:18 +0100 Subject: [PATCH 326/375] fix wine_get_host_version calling convention (#2206) --- Dalamud.Injector/EntryPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 71c919179..f927cb164 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -277,7 +277,7 @@ namespace Dalamud.Injector { if (wineGetHostVersionPtr == nint.Zero) return null; - var methodDelegate = (delegate* unmanaged[Fastcall])wineGetHostVersionPtr; + var methodDelegate = (delegate* unmanaged[Cdecl])wineGetHostVersionPtr; methodDelegate(out var platformPtr, out var _); if (platformPtr == null) return null; From dc30651fb67335cb030f2d7879fc5de02d8f5d18 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 25 Mar 2025 13:02:20 -0700 Subject: [PATCH 327/375] deps: bump cs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 794671e6c..937d4fd01 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 794671e6c66d12c975dc67cf95691c52bc5f6feb +Subproject commit 937d4fd012ad378132f26b09690e685b1096dfc4 From cba1a7d18cf68edd6be62a08c7802f18da459edb Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Tue, 25 Mar 2025 16:17:21 -0700 Subject: [PATCH 328/375] fix: AddonLifecycle sig, cs bump --- .../Addon/Lifecycle/AddonLifecycleAddressResolver.cs | 10 +++++----- lib/FFXIVClientStructs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs index baf8bb86c..854d666fd 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs @@ -13,19 +13,19 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver /// This is called for a majority of all addon OnSetup's. /// public nint AddonSetup { get; private set; } - + /// /// Gets the address of the other addon setup hook invoked by the AtkUnitManager. /// There are two callsites for this vFunc, we need to hook both of them to catch both normal UI and special UI cases like dialogue. /// This seems to be called rarely for specific addons. /// public nint AddonSetup2 { get; private set; } - + /// /// Gets the address of the addon finalize hook invoked by the AtkUnitManager. /// public nint AddonFinalize { get; private set; } - + /// /// Gets the address of the addon draw hook invoked by virtual function call. /// @@ -35,7 +35,7 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver /// Gets the address of the addon update hook invoked by virtual function call. /// public nint AddonUpdate { get; private set; } - + /// /// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call. /// @@ -51,6 +51,6 @@ internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver this.AddonFinalize = sig.ScanText("E8 ?? ?? ?? ?? 48 83 EF 01 75 D5"); this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C4 48 81 EF ?? ?? ?? ?? 48 83 ED 01"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF ?? ?? ?? ?? 45 33 D2"); - this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20"); + this.AddonOnRequestedUpdate = sig.ScanText("FF 90 A0 01 00 00 48 8B 5C 24 30"); } } diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 937d4fd01..c3a15633b 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 937d4fd012ad378132f26b09690e685b1096dfc4 +Subproject commit c3a15633bbfa7e03831129d1bf0ef4cbf956bf58 From 2f029567e4beaee24240a5fadcb5d933eefb7ea7 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 26 Mar 2025 01:32:24 +0100 Subject: [PATCH 329/375] Fixing colors and NounResolver (#2208) * Fixing colors and NounResolver * Remove failing special case NounProcessor selftest --- Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs | 2 +- Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs | 4 ++-- .../Internal/Windows/Data/Widgets/InventoryWidget.cs | 2 +- .../Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs | 2 -- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs index 1be0f8866..f851e7686 100644 --- a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs +++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs @@ -193,7 +193,7 @@ internal class SheetRedirectResolver : IServiceType colIndex = 43; if (this.dataManager.GetExcelSheet().TryGetRow(rowId, out var row)) - rowId = row.SortKey; + rowId = row.ContentFinderCondition.RowId; break; } diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs index d0b182f04..83f8e241a 100644 --- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs +++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs @@ -1599,7 +1599,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator if (eColorTypeVal == 0) context.Builder.PopColor(); else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) - context.Builder.PushColorBgra((row.Light >> 8) | (row.Light << 24)); + context.Builder.PushColorBgra((row.Dark >> 8) | (row.Dark << 24)); return true; } @@ -1613,7 +1613,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator if (eColorTypeVal == 0) context.Builder.PopEdgeColor(); else if (this.dataManager.GetExcelSheet().TryGetRow(eColorTypeVal, out var row)) - context.Builder.PushEdgeColorBgra((row.Light >> 8) | (row.Light << 24)); + context.Builder.PushEdgeColorBgra((row.Dark >> 8) | (row.Dark << 24)); return true; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs index f455f2e66..d532c2cdc 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -380,7 +380,7 @@ internal class InventoryWidget : IDataWindowWidget var rowId = this.GetItemRarityColorType(item, isEdgeColor); return this.dataManager.Excel.GetSheet().TryGetRow(rowId, out var color) - ? BinaryPrimitives.ReverseEndianness(color.Light) | 0xFF000000 + ? BinaryPrimitives.ReverseEndianness(color.Dark) | 0xFF000000 : 0xFFFFFFFF; } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs index 4073616b2..4bea503d9 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs @@ -191,8 +191,6 @@ internal class NounProcessorAgingStep : IAgingStep new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveFirstPerson, 1, "mes mémoquartz inhabituels fantasmagoriques"), new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveSecondPerson, 1, "tes mémoquartz inhabituels fantasmagoriques"), new(nameof(LSheets.Item), 44348, ClientLanguage.French, 2, (int)FrenchArticleType.PossessiveThirdPerson, 1, "ses mémoquartz inhabituels fantasmagoriques"), - - new(nameof(LSheets.Action), 45, ClientLanguage.German, 1, (int)FrenchArticleType.Indefinite, 1, "Blumenflüsterer IV"), ]; private enum GermanCases From 43e380493f70fb60da08f6fb63a0d1d9ac5b635e Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Wed, 26 Mar 2025 04:25:10 +0100 Subject: [PATCH 330/375] Fix FlyTextKind enum (#2207) * Fix FlyTextKind enum * Remove the experimental tags --- Dalamud/Game/Gui/FlyText/FlyTextKind.cs | 96 +++++++++++++++---------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs index 3727fd0f8..0edbd09ee 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs @@ -92,214 +92,232 @@ public enum FlyTextKind : int /// IslandExp = 15, + /// + /// Val1 in serif font next to all caps condensed font Text1 with Text2 in sans-serif as subtitle. + /// Added in 7.2, usage currently unknown. + /// + Unknown16 = 16, + + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Added in 7.2, usage currently unknown. + /// + Unknown17 = 17, + + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Added in 7.2, usage currently unknown. + /// + Unknown18 = 18, + /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. /// - MpDrain = 16, + MpDrain = 19, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. /// - NamedTp = 17, + NamedTp = 20, /// /// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1. /// - Healing = 18, + Healing = 21, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. /// - MpRegen = 19, + MpRegen = 22, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. /// - NamedTp2 = 20, + NamedTp2 = 23, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. /// - EpRegen = 21, + EpRegen = 24, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle. /// - CpRegen = 22, + CpRegen = 25, /// /// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle. /// - GpRegen = 23, + GpRegen = 26, /// /// Displays nothing. /// - None = 24, + None = 27, /// /// All caps serif INVULNERABLE. /// - Invulnerable = 25, + Invulnerable = 28, /// /// All caps sans-serif condensed font INTERRUPTED! /// Does a large bounce effect on appearance. /// Does not scroll up or down the screen. /// - Interrupted = 26, + Interrupted = 29, /// /// Val1 in serif font. /// - CraftingProgress = 27, + CraftingProgress = 30, /// /// Val1 in serif font. /// - CraftingQuality = 28, + CraftingQuality = 31, /// /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance. /// - CraftingQualityCrit = 29, + CraftingQualityCrit = 32, /// /// Currently not used by the game. /// Val1 in serif font. /// - AutoAttackNoText3 = 30, + AutoAttackNoText3 = 33, /// /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). /// - HealingCrit = 31, + HealingCrit = 34, /// /// Currently not used by the game. /// Same as DamageCrit with a MP in condensed font to the right of Val1. /// Does a jiggle effect to the right on appearance. /// - NamedCriticalHitWithMp = 32, + NamedCriticalHitWithMp = 35, /// /// Currently not used by the game. /// Same as DamageCrit with a TP in condensed font to the right of Val1. /// Does a jiggle effect to the right on appearance. /// - NamedCriticalHitWithTp = 33, + NamedCriticalHitWithTp = 36, /// /// Icon next to sans-serif Text1 with sans-serif "has no effect!" to the right. /// - DebuffNoEffect = 34, + DebuffNoEffect = 37, /// /// Icon next to sans-serif slightly faded Text1. /// - BuffFading = 35, + BuffFading = 38, /// /// Icon next to sans-serif slightly faded Text1. /// - DebuffFading = 36, + DebuffFading = 39, /// /// Text1 in sans-serif font. /// - Named = 37, + Named = 40, /// /// Icon next to sans-serif Text1 with sans-serif "(fully resisted)" to the right. /// - DebuffResisted = 38, + DebuffResisted = 41, /// /// All caps serif 'INCAPACITATED!'. /// - Incapacitated = 39, + Incapacitated = 42, /// /// Text1 with sans-serif "(fully resisted)" to the right. /// - FullyResisted = 40, + FullyResisted = 43, /// /// Text1 with sans-serif "has no effect!" to the right. /// - HasNoEffect = 41, + HasNoEffect = 44, /// /// Val1 in serif font, Text2 in sans-serif as subtitle with sans-serif Text1 to the left of the Val1. /// - HpDrain = 42, + HpDrain = 45, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. /// - NamedMp3 = 43, + NamedMp3 = 46, /// /// Currently not used by the game. /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. /// - NamedTp3 = 44, + NamedTp3 = 47, /// /// Icon next to sans-serif Text1 with serif "INVULNERABLE!" beneath the Text1. /// - DebuffInvulnerable = 45, + DebuffInvulnerable = 48, /// /// All caps serif RESIST. /// - Resist = 46, + Resist = 49, /// /// Icon with an item icon outline next to sans-serif Text1. /// - LootedItem = 47, + LootedItem = 50, /// /// Val1 in serif font. /// - Collectability = 48, + Collectability = 51, /// /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. /// Does a bigger bounce effect on appearance. /// - CollectabilityCrit = 49, + CollectabilityCrit = 52, /// /// All caps serif REFLECT. /// - Reflect = 50, + Reflect = 53, /// /// All caps serif REFLECTED. /// - Reflected = 51, + Reflected = 54, /// /// Val1 in serif font, Text2 in sans-serif as subtitle. /// Does a bounce effect on appearance. /// - CraftingQualityDh = 52, + CraftingQualityDh = 55, /// /// Currently not used by the game. /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. /// Does a bigger bounce effect on appearance. /// - CriticalHit4 = 53, + CriticalHit4 = 56, /// /// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle. /// Does a large bounce effect on appearance. Does not scroll up or down the screen. /// - CraftingQualityCritDh = 54, + CraftingQualityCritDh = 57, } From 494291511ca7d226fc7c76a9e5a5984a390bcb30 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Wed, 26 Mar 2025 09:52:07 -0700 Subject: [PATCH 331/375] *CS bumping noises* --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index c3a15633b..5279df085 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit c3a15633bbfa7e03831129d1bf0ef4cbf956bf58 +Subproject commit 5279df085ea13df1743aa99684ee3eb67583500c From 81bc0012ed3d0b438c3a28e34b578a6f9a3d77c7 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Wed, 26 Mar 2025 12:12:36 -0700 Subject: [PATCH 332/375] more cs bumpies uwu - boing - boing - boing --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 5279df085..dd25bdcd5 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 5279df085ea13df1743aa99684ee3eb67583500c +Subproject commit dd25bdcd57d0eccf5d0615e9c052192a788eca75 From 19ba6a961fed05453dfa3eca4f25372d118d5811 Mon Sep 17 00:00:00 2001 From: Blair Date: Thu, 27 Mar 2025 05:36:18 +1000 Subject: [PATCH 333/375] Switch INotificationManager and ITitleScreenMenu to use ISharedImmediateTexture (#1879) * Switch INotificationManager and ITitleScreenMenu to use ISharedImmediateTexture #1879 * Remove SetIconTexture Remove some remarks that no longer apply * Cleanup StyleCop warnings --- .../ImGuiNotification/IActiveNotification.cs | 61 --------------- .../ImGuiNotification/INotification.cs | 35 +-------- .../Internal/ActiveNotification.ImGui.cs | 4 +- .../Internal/ActiveNotification.cs | 78 ++----------------- .../ImGuiNotification/Notification.cs | 13 +--- .../NotificationUtilities.cs | 24 +++--- .../Interface/Internal/DalamudInterface.cs | 9 ++- .../Windows/Data/Widgets/ImGuiWidget.cs | 61 +++++++-------- .../Internal/Windows/TitleScreenMenuWindow.cs | 44 ++++++++++- .../ForwardingSharedImmediateTexture.cs | 50 ++++++++++++ .../TitleScreenMenu/TitleScreenMenu.cs | 49 ++++-------- .../TitleScreenMenu/TitleScreenMenuEntry.cs | 11 ++- Dalamud/Plugin/Services/ITitleScreenMenu.cs | 11 ++- 13 files changed, 176 insertions(+), 274 deletions(-) create mode 100644 Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs diff --git a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs index 332516315..d39fe6bee 100644 --- a/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/IActiveNotification.cs @@ -1,9 +1,6 @@ using System.Threading; -using System.Threading.Tasks; using Dalamud.Interface.ImGuiNotification.EventArgs; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; namespace Dalamud.Interface.ImGuiNotification; @@ -52,64 +49,6 @@ public interface IActiveNotification : INotification /// This does not override . void ExtendBy(TimeSpan extension); - /// Sets the icon from , overriding the icon. - /// The new texture wrap to use, or null to clear and revert back to the icon specified - /// from . - /// - /// The texture passed will be disposed when the notification is dismissed or a new different texture is set - /// via another call to this function or overwriting the property. You do not have to dispose it yourself. - /// If is not null, then calling this function will simply dispose the - /// passed without actually updating the icon. - /// - void SetIconTexture(IDalamudTextureWrap? textureWrap); - - /// Sets the icon from , overriding the icon, once the given task - /// completes. - /// The task that will result in a new texture wrap to use, or null to clear and - /// revert back to the icon specified from . - /// - /// The texture resulted from the passed will be disposed when the notification - /// is dismissed or a new different texture is set via another call to this function over overwriting the property. - /// You do not have to dispose the resulted instance of yourself. - /// If the task fails for any reason, the exception will be silently ignored and the icon specified from - /// will be used instead. - /// If is not null, then calling this function will simply dispose the - /// result of the passed without actually updating the icon. - /// - void SetIconTexture(Task? textureWrapTask); - - /// Sets the icon from , overriding the icon. - /// The new texture wrap to use, or null to clear and revert back to the icon specified - /// from . - /// Whether to keep the passed not disposed. - /// - /// If is false, the texture passed will be disposed when the - /// notification is dismissed or a new different texture is set via another call to this function. You do not have - /// to dispose it yourself. - /// If is not null and is false, then - /// calling this function will simply dispose the passed without actually updating - /// the icon. - /// - void SetIconTexture(IDalamudTextureWrap? textureWrap, bool leaveOpen); - - /// Sets the icon from , overriding the icon, once the given task - /// completes. - /// The task that will result in a new texture wrap to use, or null to clear and - /// revert back to the icon specified from . - /// Whether to keep the result from the passed not - /// disposed. - /// - /// If is false, the texture resulted from the passed - /// will be disposed when the notification is dismissed or a new different texture is - /// set via another call to this function. You do not have to dispose the resulted instance of - /// yourself. - /// If the task fails for any reason, the exception will be silently ignored and the icon specified from - /// will be used instead. - /// If is not null, then calling this function will simply dispose the - /// result of the passed without actually updating the icon. - /// - void SetIconTexture(Task? textureWrapTask, bool leaveOpen); - /// Generates a new value to use for . /// The new value. internal static long CreateNewId() => Interlocked.Increment(ref idCounter); diff --git a/Dalamud/Interface/ImGuiNotification/INotification.cs b/Dalamud/Interface/ImGuiNotification/INotification.cs index eab0fd131..0b15d2398 100644 --- a/Dalamud/Interface/ImGuiNotification/INotification.cs +++ b/Dalamud/Interface/ImGuiNotification/INotification.cs @@ -1,8 +1,4 @@ -using System.Threading.Tasks; - -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; -using Dalamud.Plugin.Services; +using Dalamud.Interface.Textures; namespace Dalamud.Interface.ImGuiNotification; @@ -22,35 +18,12 @@ public interface INotification /// Gets or sets the type of the notification. NotificationType Type { get; set; } - /// Gets or sets the icon source, in case is not set or the task has faulted. + /// Gets or sets the icon source, in case is not set. /// INotificationIcon? Icon { get; set; } - /// Gets or sets a texture wrap that will be used in place of if set. - /// - /// A texture wrap set via this property will NOT be disposed when the notification is dismissed. - /// Use or - /// to use a texture, after calling - /// . Call either of those functions with null to revert - /// the effective icon back to this property. - /// This property and are bound together. If the task is not null but - /// is false (because the task is still in progress or faulted,) - /// the property will return null. Setting this property will set to a new - /// completed with the new value as its result. - /// - public IDalamudTextureWrap? IconTexture { get; set; } - - /// Gets or sets a task that results in a texture wrap that will be used in place of if - /// available. - /// - /// A texture wrap set via this property will NOT be disposed when the notification is dismissed. - /// Use or - /// to use a texture, after calling - /// . Call either of those functions with null to revert - /// the effective icon back to this property. - /// This property and are bound together. - /// - Task? IconTextureTask { get; set; } + /// Gets or sets a texture that will be used in place of if set. + public ISharedImmediateTexture? IconTexture { get; set; } /// Gets or sets the hard expiry. /// diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs index 16d58bea5..e8d6c5cc0 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.ImGui.cs @@ -404,7 +404,7 @@ internal sealed partial class ActiveNotification var maxCoord = minCoord + size; var iconColor = this.Type.ToColor(); - if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.IconTextureTask)) + if (NotificationUtilities.DrawIconFrom(minCoord, maxCoord, this.IconTexture)) return; if (this.Icon?.DrawIcon(minCoord, maxCoord, iconColor) is true) @@ -499,7 +499,7 @@ internal sealed partial class ActiveNotification if (fillStartCw == 0 && fillEndCw == 0) return; - + var radius = Math.Min(size.X, size.Y) / 3f; var ifrom = fillStartCw * MathF.PI * 2; var ito = fillEndCw * MathF.PI * 2; diff --git a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs index 607c7c49d..6587b6c32 100644 --- a/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs +++ b/Dalamud/Interface/ImGuiNotification/Internal/ActiveNotification.cs @@ -1,11 +1,9 @@ using System.Runtime.Loader; -using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Interface.Animation; using Dalamud.Interface.Animation.EasingFunctions; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; @@ -23,9 +21,6 @@ internal sealed partial class ActiveNotification : IActiveNotification private readonly Easing progressEasing; private readonly Easing expandoEasing; - /// Whether to call on . - private bool hasIconTextureOwnership; - /// Gets the time of starting to count the timer for the expiration. private DateTime lastInterestTime; @@ -119,31 +114,10 @@ internal sealed partial class ActiveNotification : IActiveNotification } /// - public IDalamudTextureWrap? IconTexture + public ISharedImmediateTexture? IconTexture { get => this.underlyingNotification.IconTexture; - set => this.IconTextureTask = value is null ? null : Task.FromResult(value); - } - - /// - public Task? IconTextureTask - { - get => this.underlyingNotification.IconTextureTask; - set - { - // Do nothing if the value did not change. - if (this.underlyingNotification.IconTextureTask == value) - return; - - if (this.hasIconTextureOwnership) - { - _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true); - this.underlyingNotification.IconTextureTask = null; - this.hasIconTextureOwnership = false; - } - - this.underlyingNotification.IconTextureTask = value; - } + set => this.underlyingNotification.IconTexture = value; } /// @@ -265,39 +239,6 @@ internal sealed partial class ActiveNotification : IActiveNotification this.extendedExpiry = newExpiry; } - /// - public void SetIconTexture(IDalamudTextureWrap? textureWrap) => - this.SetIconTexture(textureWrap, false); - - /// - public void SetIconTexture(IDalamudTextureWrap? textureWrap, bool leaveOpen) => - this.SetIconTexture(textureWrap is null ? null : Task.FromResult(textureWrap), leaveOpen); - - /// - public void SetIconTexture(Task? textureWrapTask) => - this.SetIconTexture(textureWrapTask, false); - - /// - public void SetIconTexture(Task? textureWrapTask, bool leaveOpen) - { - // If we're requested to replace the texture with the same texture, do nothing. - if (this.underlyingNotification.IconTextureTask == textureWrapTask) - return; - - if (this.DismissReason is not null) - { - if (!leaveOpen) - textureWrapTask?.ToContentDisposedTask(true); - return; - } - - if (this.hasIconTextureOwnership) - _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true); - - this.hasIconTextureOwnership = !leaveOpen; - this.underlyingNotification.IconTextureTask = textureWrapTask; - } - /// Removes non-Dalamud invocation targets from events. /// /// This is done to prevent references of plugins being unloaded from outliving the plugin itself. @@ -317,10 +258,8 @@ internal sealed partial class ActiveNotification : IActiveNotification if (this.Icon is { } previousIcon && !IsOwnedByDalamud(previousIcon.GetType())) this.Icon = null; - // Clear the texture if we don't have the ownership. - // The texture probably was owned by the plugin being unloaded in such case. - if (!this.hasIconTextureOwnership) - this.IconTextureTask = null; + if (this.IconTexture is { } previousTexture && !IsOwnedByDalamud(previousTexture.GetType())) + this.IconTexture = null; this.isInitiatorUnloaded = true; this.UserDismissable = true; @@ -400,13 +339,6 @@ internal sealed partial class ActiveNotification : IActiveNotification /// Clears the resources associated with this instance of . internal void DisposeInternal() { - if (this.hasIconTextureOwnership) - { - _ = this.underlyingNotification.IconTextureTask?.ToContentDisposedTask(true); - this.underlyingNotification.IconTextureTask = null; - this.hasIconTextureOwnership = false; - } - this.Dismiss = null; this.Click = null; this.DrawActions = null; diff --git a/Dalamud/Interface/ImGuiNotification/Notification.cs b/Dalamud/Interface/ImGuiNotification/Notification.cs index 927dd5ba9..4dcb10c17 100644 --- a/Dalamud/Interface/ImGuiNotification/Notification.cs +++ b/Dalamud/Interface/ImGuiNotification/Notification.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using Dalamud.Interface.ImGuiNotification.Internal; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; +using Serilog; namespace Dalamud.Interface.ImGuiNotification; - /// Represents a blueprint for a notification. public sealed record Notification : INotification { @@ -30,14 +30,7 @@ public sealed record Notification : INotification public INotificationIcon? Icon { get; set; } /// - public IDalamudTextureWrap? IconTexture - { - get => this.IconTextureTask?.IsCompletedSuccessfully is true ? this.IconTextureTask.Result : null; - set => this.IconTextureTask = value is null ? null : Task.FromResult(value); - } - - /// - public Task? IconTextureTask { get; set; } + public ISharedImmediateTexture? IconTexture { get; set; } /// public DateTime HardExpiry { get; set; } = DateTime.MaxValue; diff --git a/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs b/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs index 2172663e8..b31321d8b 100644 --- a/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs +++ b/Dalamud/Interface/ImGuiNotification/NotificationUtilities.cs @@ -7,6 +7,7 @@ using Dalamud.Game.Text; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.ManagedFontAtlas; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Plugin.Internal.Types; @@ -78,6 +79,19 @@ public static class NotificationUtilities return true; } + /// Draws an icon from an instance of . + /// The coordinates of the top left of the icon area. + /// The coordinates of the bottom right of the icon area. + /// The texture. + /// true if anything has been drawn. + internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, ISharedImmediateTexture? texture) + { + if (texture is null) + return false; + + return DrawIconFrom(minCoord, maxCoord, texture.GetWrapOrEmpty()); + } + /// Draws an icon from an instance of . /// The coordinates of the top left of the icon area. /// The coordinates of the bottom right of the icon area. @@ -105,16 +119,6 @@ public static class NotificationUtilities } } - /// Draws an icon from an instance of that results in an - /// . - /// The coordinates of the top left of the icon area. - /// The coordinates of the bottom right of the icon area. - /// The task that results in a texture. - /// true if anything has been drawn. - /// Exceptions from the task will be treated as if no texture is provided. - internal static bool DrawIconFrom(Vector2 minCoord, Vector2 maxCoord, Task? textureTask) => - textureTask?.IsCompletedSuccessfully is true && DrawIconFrom(minCoord, maxCoord, textureTask.Result); - /// Draws an icon from an instance of . /// The coordinates of the top left of the icon area. /// The coordinates of the bottom right of the icon area. diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index f3f2564af..7d798c541 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -26,6 +26,7 @@ using Dalamud.Interface.Internal.Windows.Settings; using Dalamud.Interface.Internal.Windows.StyleEditor; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Style; +using Dalamud.Interface.Textures; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; @@ -169,16 +170,16 @@ internal class DalamudInterface : IInternalDisposableService { titleScreenMenu.AddEntryCore( Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), this.OpenPluginInstaller); titleScreenMenu.AddEntryCore( Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), this.OpenSettings); titleScreenMenu.AddEntryCore( "Toggle Dev Menu", - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), () => Service.GetNullable()?.ToggleDevMenu(), VirtualKey.SHIFT); @@ -186,7 +187,7 @@ internal class DalamudInterface : IInternalDisposableService { titleScreenMenu.AddEntryCore( Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), - dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall), + new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)), () => this.isImGuiDrawDevMenu = true); } }); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs index 1476ce2e6..08d29398b 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ImGuiWidget.cs @@ -5,11 +5,11 @@ using System.Threading.Tasks; using Dalamud.Game.Text; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Windowing; using Dalamud.Storage.Assets; -using Dalamud.Utility; using ImGuiNET; @@ -144,8 +144,6 @@ internal class ImGuiWidget : IDataWindowWidget "Action Bar (always on if not user dismissable for the example)", ref this.notificationTemplate.ActionBar); - ImGui.Checkbox("Leave Textures Open", ref this.notificationTemplate.LeaveTexturesOpen); - if (ImGui.Button("Add notification")) { var text = @@ -212,35 +210,34 @@ internal class ImGuiWidget : IDataWindowWidget switch (this.notificationTemplate.IconInt) { case 5: - n.SetIconTexture( - DisposeLoggingTextureWrap.Wrap( - dam.GetDalamudTextureWrap( - Enum.Parse( - NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt]))), - this.notificationTemplate.LeaveTexturesOpen); - break; - case 6: - n.SetIconTexture( - dam.GetDalamudTextureWrapAsync( - Enum.Parse( - NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt])) - .ContinueWith( - r => r.IsCompletedSuccessfully - ? Task.FromResult(DisposeLoggingTextureWrap.Wrap(r.Result)) - : r).Unwrap(), - this.notificationTemplate.LeaveTexturesOpen); + var textureWrap = DisposeLoggingTextureWrap.Wrap( + dam.GetDalamudTextureWrap( + Enum.Parse( + NotificationTemplate.AssetSources[this.notificationTemplate.IconAssetInt]))); + + if (textureWrap != null) + { + n.IconTexture = new ForwardingSharedImmediateTexture(textureWrap); + } + break; case 7: - n.SetIconTexture( - DisposeLoggingTextureWrap.Wrap( - tm.Shared.GetFromGame(this.notificationTemplate.IconText).GetWrapOrDefault()), - this.notificationTemplate.LeaveTexturesOpen); + var textureWrap2 = DisposeLoggingTextureWrap.Wrap( + tm.Shared.GetFromGame(this.notificationTemplate.IconText).GetWrapOrDefault()); + if (textureWrap2 != null) + { + n.IconTexture = new ForwardingSharedImmediateTexture(textureWrap2); + } + break; case 8: - n.SetIconTexture( - DisposeLoggingTextureWrap.Wrap( - tm.Shared.GetFromFile(this.notificationTemplate.IconText).GetWrapOrDefault()), - this.notificationTemplate.LeaveTexturesOpen); + var textureWrap3 = DisposeLoggingTextureWrap.Wrap( + tm.Shared.GetFromFile(this.notificationTemplate.IconText).GetWrapOrDefault()); + if (textureWrap3 != null) + { + n.IconTexture = new ForwardingSharedImmediateTexture(textureWrap3); + } + break; } @@ -303,15 +300,15 @@ internal class ImGuiWidget : IDataWindowWidget }; } } - + ImGui.SameLine(); if (ImGui.Button("Replace images using setter")) { foreach (var n in this.notifications) { var i = (uint)Random.Shared.NextInt64(0, 200000); - n.IconTexture = DisposeLoggingTextureWrap.Wrap( - Service.Get().Shared.GetFromGameIcon(new(i)).GetWrapOrDefault()); + + n.IconTexture = Service.Get().Shared.GetFromGameIcon(new(i, false, false)); } } } @@ -428,7 +425,6 @@ internal class ImGuiWidget : IDataWindowWidget public bool Minimized; public bool UserDismissable; public bool ActionBar; - public bool LeaveTexturesOpen; public int ProgressMode; public void Reset() @@ -450,7 +446,6 @@ internal class ImGuiWidget : IDataWindowWidget this.Minimized = true; this.UserDismissable = true; this.ActionBar = true; - this.LeaveTexturesOpen = true; this.ProgressMode = 0; this.RespectUiHidden = true; } diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index ce8c192a4..c9ca65e0c 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -28,6 +28,8 @@ using ImGuiNET; using Lumina.Text.ReadOnly; +using Serilog; + using LSeStringBuilder = Lumina.Text.SeStringBuilder; namespace Dalamud.Interface.Internal.Windows; @@ -185,6 +187,23 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (!entry.IsShowConditionSatisfied()) continue; + if (entry.Texture.TryGetWrap(out var textureWrap, out var exception)) + { + if (textureWrap.Width != 64 && textureWrap.Height != 64) + { + Log.Error("Texture provided for ITitleScreenMenuEntry must be 64x64. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + } + + if (exception != null) + { + Log.Error(exception, "An exception occurred while attempting to get the texture wrap for a ITitleScreenMenuEntry. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing)) { moveEasing = new InOutQuint(TimeSpan.FromMilliseconds(400)); @@ -259,6 +278,23 @@ internal class TitleScreenMenuWindow : Window, IDisposable if (!entry.IsShowConditionSatisfied()) continue; + if (entry.Texture.TryGetWrap(out var textureWrap, out var exception)) + { + if (textureWrap.Width != 64 && textureWrap.Height != 64) + { + Log.Error($"Texture provided for ITitleScreenMenuEntry {entry.Name} must be 64x64. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + } + + if (exception != null) + { + Log.Error(exception, $"An exception occurred while attempting to get the texture wrap for ITitleScreenMenuEntry {entry.Name}. Entry will be removed."); + this.titleScreenMenu.RemoveEntry(entry); + continue; + } + var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale; this.DrawEntry(entry, i != 0, true, i == 0, false, false); @@ -374,7 +410,9 @@ internal class TitleScreenMenuWindow : Window, IDisposable ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f); } - ImGui.Image(entry.Texture.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale)); + // Wrap should always be valid at this point due to us checking the validity of the image each frame + var dalamudTextureWrap = entry.Texture.GetWrapOrEmpty(); + ImGui.Image(dalamudTextureWrap.ImGuiHandle, new Vector2(TitleScreenMenu.TextureSize * scale)); if (overrideAlpha || isFirst) { ImGui.PopStyleVar(); @@ -388,7 +426,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable var textHeight = ImGui.GetTextLineHeightWithSpacing(); var cursor = ImGui.GetCursorPos(); - cursor.Y += (entry.Texture.Height * scale / 2) - (textHeight / 2); + cursor.Y += (dalamudTextureWrap.Height * scale / 2) - (textHeight / 2); if (overrideAlpha) { @@ -411,7 +449,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable ImGui.PopStyleVar(); } - initialCursor.Y += entry.Texture.Height * scale; + initialCursor.Y += dalamudTextureWrap.Height * scale; ImGui.SetCursorPos(initialCursor); return isHover; diff --git a/Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs b/Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs new file mode 100644 index 000000000..12e312b3e --- /dev/null +++ b/Dalamud/Interface/Textures/ForwardingSharedImmediateTexture.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Threading.Tasks; + +using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Storage.Assets; + +namespace Dalamud.Interface.Textures; + +/// +/// Wraps a dalamud texture allowing interoperability with certain services. Only use this if you need to provide a texture that has been created or rented as a ISharedImmediateTexture. +/// +public class ForwardingSharedImmediateTexture : ISharedImmediateTexture +{ + private readonly IDalamudTextureWrap textureWrap; + + /// + /// Initializes a new instance of the class. + /// + /// A textureWrap that has been created or provided by RentAsync. + public ForwardingSharedImmediateTexture(IDalamudTextureWrap textureWrap) + { + this.textureWrap = textureWrap; + } + + /// + public IDalamudTextureWrap GetWrapOrEmpty() + { + return this.textureWrap; + } + + /// + public IDalamudTextureWrap? GetWrapOrDefault(IDalamudTextureWrap? defaultWrap = null) + { + return this.textureWrap; + } + + /// + public bool TryGetWrap(out IDalamudTextureWrap? texture, out Exception? exception) + { + texture = this.textureWrap; + exception = null; + return true; + } + + /// + public Task RentAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(this.textureWrap); + } +} diff --git a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs index 6f98b9757..586d65559 100644 --- a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs +++ b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs @@ -3,8 +3,7 @@ using System.Linq; using System.Reflection; using Dalamud.Game.ClientState.Keys; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; @@ -67,7 +66,7 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu } } } - + /// /// Adds a new entry to the title screen menu. /// @@ -76,13 +75,8 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// The action to execute when the option is selected. /// A object that can be used to manage the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public ITitleScreenMenuEntry AddPluginEntry(string text, IDalamudTextureWrap texture, Action onTriggered) + public ITitleScreenMenuEntry AddPluginEntry(string text, ISharedImmediateTexture texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -103,13 +97,13 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu } /// - public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(string text, ISharedImmediateTexture texture, Action onTriggered) { return this.AddPluginEntry(text, texture, onTriggered); } /// - public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { return this.AddPluginEntry(priority, text, texture, onTriggered); } @@ -123,13 +117,8 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// The action to execute when the option is selected. /// A object that can be used to manage the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public ITitleScreenMenuEntry AddPluginEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + public ITitleScreenMenuEntry AddPluginEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -166,13 +155,8 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// The action to execute when the option is selected. /// A object that can be used to manage the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -199,15 +183,10 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu /// Thrown when the texture provided does not match the required resolution(64x64). internal TitleScreenMenuEntry AddEntryCore( string text, - IDalamudTextureWrap texture, + ISharedImmediateTexture texture, Action onTriggered, params VirtualKey[] showConditionKeys) { - if (texture.Height != TextureSize || texture.Width != TextureSize) - { - throw new ArgumentException("Texture must be 64x64"); - } - TitleScreenMenuEntry entry; lock (this.entries) { @@ -240,7 +219,7 @@ internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleS { [ServiceManager.ServiceDependency] private readonly TitleScreenMenu titleScreenMenuService = Service.Get(); - + private readonly List pluginEntries = new(); /// @@ -254,25 +233,25 @@ internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleS this.titleScreenMenuService.RemoveEntry(entry); } } - + /// - public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(string text, ISharedImmediateTexture texture, Action onTriggered) { var entry = this.titleScreenMenuService.AddPluginEntry(text, texture, onTriggered); this.pluginEntries.Add(entry); return entry; } - + /// - public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered) + public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered) { var entry = this.titleScreenMenuService.AddPluginEntry(priority, text, texture, onTriggered); this.pluginEntries.Add(entry); return entry; } - + /// public void RemoveEntry(IReadOnlyTitleScreenMenuEntry entry) { diff --git a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs index 2acb275ad..a98d32770 100644 --- a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs +++ b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenuEntry.cs @@ -4,8 +4,7 @@ using System.Linq; using System.Reflection; using Dalamud.Game.ClientState.Keys; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; namespace Dalamud.Interface; @@ -64,7 +63,7 @@ public interface IReadOnlyTitleScreenMenuEntry /// /// Gets the texture of this entry. /// - IDalamudTextureWrap Texture { get; } + ISharedImmediateTexture Texture { get; } } /// @@ -87,7 +86,7 @@ public class TitleScreenMenuEntry : ITitleScreenMenuEntry Assembly? callingAssembly, ulong priority, string text, - IDalamudTextureWrap texture, + ISharedImmediateTexture texture, Action onTriggered, IEnumerable? showConditionKeys = null) { @@ -106,8 +105,8 @@ public class TitleScreenMenuEntry : ITitleScreenMenuEntry public string Name { get; set; } /// - public IDalamudTextureWrap Texture { get; set; } - + public ISharedImmediateTexture Texture { get; set; } + /// public bool IsInternal { get; set; } diff --git a/Dalamud/Plugin/Services/ITitleScreenMenu.cs b/Dalamud/Plugin/Services/ITitleScreenMenu.cs index 5ebd80017..9f7b17ea3 100644 --- a/Dalamud/Plugin/Services/ITitleScreenMenu.cs +++ b/Dalamud/Plugin/Services/ITitleScreenMenu.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; using Dalamud.Interface; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures.TextureWraps; +using Dalamud.Interface.Textures; namespace Dalamud.Plugin.Services; @@ -20,22 +19,22 @@ public interface ITitleScreenMenu /// Adds a new entry to the title screen menu. /// /// The text to show. - /// The texture to show. + /// The texture to show. The texture must be 64x64 or the entry will be removed and an error will be logged. /// The action to execute when the option is selected. /// A object that can be reference the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public IReadOnlyTitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered); + public IReadOnlyTitleScreenMenuEntry AddEntry(string text, ISharedImmediateTexture texture, Action onTriggered); /// /// Adds a new entry to the title screen menu. /// /// Priority of the entry. /// The text to show. - /// The texture to show. + /// The texture to show. The texture must be 64x64 or the entry will be removed and an error will be logged. /// The action to execute when the option is selected. /// A object that can be used to reference the entry. /// Thrown when the texture provided does not match the required resolution(64x64). - public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered); + public IReadOnlyTitleScreenMenuEntry AddEntry(ulong priority, string text, ISharedImmediateTexture texture, Action onTriggered); /// /// Remove an entry from the title screen menu. From 8320a824cefb0ac18964d61d3b603f72c8a95227 Mon Sep 17 00:00:00 2001 From: marzent Date: Wed, 26 Mar 2025 20:38:27 +0100 Subject: [PATCH 334/375] return an error HRESULT when dotnet runtime can't be found (#2209) --- lib/CoreCLR/boot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index f5e626c3f..84d3d15cf 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -126,7 +126,7 @@ HRESULT InitializeClrAndGetEntryPoint( if (!dotnet_path || !std::filesystem::exists(dotnet_path)) { logging::E("Error: Unable to find .NET runtime path"); - return 1; + return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND); } // =========================================================================== // From 202da067c193b6625ef963d65e1d1e42ef7909c5 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Wed, 26 Mar 2025 20:43:09 +0100 Subject: [PATCH 335/375] ci: disable rollup --- .github/workflows/rollup.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rollup.yml b/.github/workflows/rollup.yml index 49b3d8c1d..8bc9a3c51 100644 --- a/.github/workflows/rollup.yml +++ b/.github/workflows/rollup.yml @@ -1,8 +1,8 @@ name: Rollup changes to next version on: - push: - branches: - - master +# push: +# branches: +# - master workflow_dispatch: jobs: From 46ed621761e9b65194f572a988ed731daccb8318 Mon Sep 17 00:00:00 2001 From: Caraxi Date: Thu, 27 Mar 2025 09:50:37 +1030 Subject: [PATCH 336/375] Add BitmapFontIcons from 7.2 (#2212) --- .../Text/SeStringHandling/BitmapFontIcon.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index 7db528f6a..427e7902a 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -739,4 +739,49 @@ public enum BitmapFontIcon : uint /// The Venture Delivery Moogle icon. /// VentureDeliveryMoogle = 172, + + /// + /// The Watching Cutscene icon. + /// + WatchingCutscene = 173, + + /// + /// The Away from Keyboard icon. + /// + Away = 174, + + /// + /// The Camera Mode icon. + /// + CameraMode = 175, + + /// + /// The Looking For Party icon. + /// + LookingForParty = 176, + + /// + /// The Group Finder icon. + /// + GroupFinder = 177, + + /// + /// The Party Leader icon. + /// + PartyLeader = 178, + + /// + /// The Party Member icon. + /// + PartyMember = 179, + + /// + /// The Cross-World Party Leader icon. + /// + CrossWorldPartyLeader = 180, + + /// + /// The Cross-World Party Member icon. + /// + CrossWorldPartyMember = 181, } From 0a06a9cbbdbcfd32711597812ad275e1b112261d Mon Sep 17 00:00:00 2001 From: AllunEve <42642303+Alluneve@users.noreply.github.com> Date: Thu, 27 Mar 2025 03:48:32 +0100 Subject: [PATCH 337/375] Astral fire and Umbral ice are Gone. (#2214) Co-authored-by: Alluneve --- Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs index ebf70abe7..c4058132a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs @@ -19,11 +19,6 @@ public unsafe class BLMGauge : JobGaugeBase public short EnochianTimer => this.Struct->EnochianTimer; - /// - /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds. - /// - public short ElementTimeRemaining => this.Struct->ElementTimeRemaining; - /// /// Gets the number of Polyglot stacks remaining. /// From 6dddfa1597001e9a9727dc931a13340347ec5088 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Thu, 27 Mar 2025 03:57:35 +0100 Subject: [PATCH 338/375] Update ClientStructs (#2213) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index dd25bdcd5..40565ddff 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit dd25bdcd57d0eccf5d0615e9c052192a788eca75 +Subproject commit 40565ddff870f5f267e0973d21008a7dd718f8ee From 9c80b72e5ebf4989cce76af8080f0df387fa03ef Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Thu, 27 Mar 2025 16:48:12 -0700 Subject: [PATCH 339/375] deps: bump clientstructs (#2217) - and fix the errors --- Dalamud/Game/ClientState/Fates/Fate.cs | 2 +- Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs | 5 +++-- Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs | 3 ++- lib/FFXIVClientStructs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 03714a52b..f48e66ad6 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -249,5 +249,5 @@ internal unsafe partial class Fate : IFate /// /// Gets the territory this is located in. /// - public RowRef TerritoryType => LuminaUtils.CreateRef(this.Struct->TerritoryId); + public RowRef TerritoryType => LuminaUtils.CreateRef(this.Struct->MapMarkers[0].TerritoryId); } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index fef3f9a86..0a6fe801f 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Objects; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Arrays; using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Gui.NamePlate; @@ -124,7 +125,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext /// /// Gets a pointer to the NamePlate addon's number array entries as a struct. /// - internal AddonNamePlate.AddonNamePlateNumberArray* NumberStruct { get; private set; } + internal NamePlateNumberArray* NumberStruct { get; private set; } /// /// Gets or sets a value indicating whether any handler in the current context has instantiated a part builder. @@ -141,7 +142,7 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext { this.Addon = (AddonNamePlate*)addon; this.NumberData = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate); - this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray; + this.NumberStruct = (NamePlateNumberArray*)this.NumberData->IntArray; this.StringData = AtkStage.Instance()->GetStringArrayData(StringArrayType.NamePlate); this.HasParts = false; diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs index 4f16ab660..185be9d24 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateHandler.cs @@ -7,6 +7,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Text.SeStringHandling; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Arrays; using FFXIVClientStructs.Interop; namespace Dalamud.Game.Gui.NamePlate; @@ -503,7 +504,7 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler private AddonNamePlate.NamePlateObject* NamePlateObject => &this.context.Addon->NamePlateObjectArray[this.NamePlateIndex]; - private AddonNamePlate.AddonNamePlateNumberArray.NamePlateObjectIntArrayData* ObjectData => + private NamePlateNumberArray.NamePlateObjectIntArrayData* ObjectData => this.context.NumberStruct->ObjectData.GetPointer(this.ArrayIndex); /// diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 40565ddff..0d2db9a66 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 40565ddff870f5f267e0973d21008a7dd718f8ee +Subproject commit 0d2db9a66b676792fd0a44280d40f5b58cc3522e From fb61d72faec096155d7582fb1bb8097cbab9cc1c Mon Sep 17 00:00:00 2001 From: goaaats Date: Fri, 28 Mar 2025 00:51:59 +0100 Subject: [PATCH 340/375] Remove legacy corrupted state exceptions entirely --- Dalamud.Boot/DalamudStartInfo.cpp | 2 -- Dalamud.Boot/DalamudStartInfo.h | 1 - Dalamud.Boot/dllmain.cpp | 1 - Dalamud.Common/DalamudStartInfo.cs | 5 ----- Dalamud.Injector.Boot/main.cpp | 1 - Dalamud.Injector/EntryPoint.cs | 1 - lib/CoreCLR/boot.cpp | 9 +-------- lib/CoreCLR/boot.h | 1 - 8 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 4aa7d46dd..35ad42212 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -124,7 +124,6 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { config.BootVehEnabled = json.value("BootVehEnabled", config.BootVehEnabled); config.BootVehFull = json.value("BootVehFull", config.BootVehFull); config.BootEnableEtw = json.value("BootEnableEtw", config.BootEnableEtw); - config.BootDisableLegacyCorruptedStateExceptions = json.value("BootDisableLegacyCorruptedStateExceptions", config.BootDisableLegacyCorruptedStateExceptions); config.BootDotnetOpenProcessHookMode = json.value("BootDotnetOpenProcessHookMode", config.BootDotnetOpenProcessHookMode); if (const auto it = json.find("BootEnabledGameFixes"); it != json.end() && it->is_array()) { config.BootEnabledGameFixes.clear(); @@ -150,7 +149,6 @@ void DalamudStartInfo::from_envvars() { BootVehEnabled = utils::get_env(L"DALAMUD_IS_VEH"); BootVehFull = utils::get_env(L"DALAMUD_IS_VEH_FULL"); BootEnableEtw = utils::get_env(L"DALAMUD_ENABLE_ETW"); - BootDisableLegacyCorruptedStateExceptions = utils::get_env(L"DALAMUD_DISABLE_LEGACY_CORRUPTED_STATE_EXCEPTIONS"); BootDotnetOpenProcessHookMode = static_cast(utils::get_env(L"DALAMUD_DOTNET_OPENPROCESS_HOOKMODE")); for (const auto& item : utils::get_env_list(L"DALAMUD_GAMEFIX_LIST")) BootEnabledGameFixes.insert(unicode::convert(item, &unicode::lower)); diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index 64450e290..05ff7b23e 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -62,7 +62,6 @@ struct DalamudStartInfo { bool BootVehEnabled = false; bool BootVehFull = false; bool BootEnableEtw = false; - bool BootDisableLegacyCorruptedStateExceptions = false; DotNetOpenProcessHookMode BootDotnetOpenProcessHookMode = DotNetOpenProcessHookMode::ImportHooks; std::set BootEnabledGameFixes{}; std::set BootUnhookDlls{}; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 68f04b84b..b72063855 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -117,7 +117,6 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { const auto result = InitializeClrAndGetEntryPoint( g_hModule, g_startInfo.BootEnableEtw, - false, // !g_startInfo.BootDisableLegacyCorruptedStateExceptions, runtimeconfig_path, module_path, L"Dalamud.EntryPoint, Dalamud", diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index eb2410cfd..680dce217 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -131,11 +131,6 @@ public record DalamudStartInfo /// public bool BootEnableEtw { get; set; } - /// - /// Gets or sets a value indicating whether to enable legacy corrupted state exceptions. - /// - public bool BootDisableLegacyCorruptedStateExceptions { get; set; } - /// /// Gets or sets a value choosing the OpenProcess hookmode. /// diff --git a/Dalamud.Injector.Boot/main.cpp b/Dalamud.Injector.Boot/main.cpp index 50555a09a..df4120009 100644 --- a/Dalamud.Injector.Boot/main.cpp +++ b/Dalamud.Injector.Boot/main.cpp @@ -27,7 +27,6 @@ int wmain(int argc, wchar_t** argv) const auto result = InitializeClrAndGetEntryPoint( GetModuleHandleW(nullptr), false, - false, runtimeconfig_path, module_path, L"Dalamud.Injector.EntryPoint, Dalamud.Injector", diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index f927cb164..b526beb1c 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -466,7 +466,6 @@ namespace Dalamud.Injector // Set boot defaults startInfo.BootShowConsole = args.Contains("--console"); startInfo.BootEnableEtw = args.Contains("--etw"); - startInfo.BootDisableLegacyCorruptedStateExceptions = args.Contains("--no-legacy-corrupted-state-exceptions"); startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName); startInfo.BootEnabledGameFixes = new() { diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index 84d3d15cf..50ddecb89 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -46,7 +46,7 @@ static wchar_t* GetRuntimePath() result = GetEnvironmentVariableW(L"DALAMUD_RUNTIME", &buffer[0], result); return _wcsdup(buffer.c_str()); } - + // Detect Windows first result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &_appdata); @@ -89,7 +89,6 @@ static wchar_t* GetRuntimePath() HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, - bool enable_legacy_corrupted_state_exception_policy, std::wstring runtimeconfig_path, std::wstring module_path, std::wstring entrypoint_assembly_name, @@ -102,12 +101,6 @@ HRESULT InitializeClrAndGetEntryPoint( int result; SetEnvironmentVariable(L"DOTNET_MULTILEVEL_LOOKUP", L"0"); - if (enable_legacy_corrupted_state_exception_policy) - { - SetEnvironmentVariable(L"COMPlus_legacyCorruptedStateExceptionsPolicy", L"1"); - SetEnvironmentVariable(L"DOTNET_legacyCorruptedStateExceptionsPolicy", L"1"); - } - SetEnvironmentVariable(L"COMPLUS_ForceENC", L"1"); SetEnvironmentVariable(L"DOTNET_ForceENC", L"1"); diff --git a/lib/CoreCLR/boot.h b/lib/CoreCLR/boot.h index b227a3438..33bc58bbf 100644 --- a/lib/CoreCLR/boot.h +++ b/lib/CoreCLR/boot.h @@ -4,7 +4,6 @@ void ConsoleTeardown(); HRESULT InitializeClrAndGetEntryPoint( void* calling_module, bool enable_etw, - bool enable_legacy_corrupted_state_exception_policy, std::wstring runtimeconfig_path, std::wstring module_path, std::wstring entrypoint_assembly_name, From f5b3d850666c215d3203507f61b3a90fb534ae5d Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 28 Mar 2025 16:57:39 +0100 Subject: [PATCH 341/375] Bump Lumina.Excel to 7.2.1 (#2220) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2905b80b1..ef07620a4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ 5.6.1 - 7.2.0 + 7.2.1 13.0.3 From 6160252418658972e35ba978ca28fa10bb490ffc Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 28 Mar 2025 17:00:14 +0100 Subject: [PATCH 342/375] Update GameInventoryItem (#2219) * Update GameInventoryItem - Resolve symbolic InventoryItem, used in HandIn - Harden Materia/MateriaGrade/Stains results - Make sure GameInventoryItem is constructed correctly * Remove some duplicate code from InventoryWidget * Fix null check --- Dalamud/Game/Inventory/GameInventory.cs | 4 +- Dalamud/Game/Inventory/GameInventoryItem.cs | 145 ++++++++++++++++-- .../Windows/Data/Widgets/InventoryWidget.cs | 85 ++-------- 3 files changed, 145 insertions(+), 89 deletions(-) diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index 02412c551..27f14bf79 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -121,6 +121,8 @@ internal class GameInventory : IInternalDisposableService // Assumption: newItems is sorted by slots, and the last item has the highest slot number. var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1]; + if (this.inventoryItems[i] == null) + oldItems.Initialize(); foreach (ref readonly var newItem in newItems) { diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 32eb9911b..5a0ec0c74 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -1,9 +1,13 @@ -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Dalamud.Data; +using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Client.Game; +using Lumina.Excel.Sheets; + namespace Dalamud.Game.Inventory; /// @@ -24,6 +28,14 @@ public unsafe struct GameInventoryItem : IEquatable [FieldOffset(0)] private fixed ulong dataUInt64[InventoryItem.StructSize / 0x8]; + /// + /// Initializes a new instance of the struct. + /// + public GameInventoryItem() + { + this.InternalItem.Ctor(); + } + /// /// Initializes a new instance of the struct. /// @@ -33,32 +45,37 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets a value indicating whether the this is empty. /// - public bool IsEmpty => this.InternalItem.ItemId == 0; + public bool IsEmpty => this.InternalItem.IsEmpty(); /// /// Gets the container inventory type. /// - public GameInventoryType ContainerType => (GameInventoryType)this.InternalItem.Container; + public GameInventoryType ContainerType => (GameInventoryType)this.InternalItem.GetInventoryType(); /// /// Gets the inventory slot index this item is in. /// - public uint InventorySlot => (uint)this.InternalItem.Slot; + public uint InventorySlot => this.InternalItem.GetSlot(); /// /// Gets the item id. /// - public uint ItemId => this.InternalItem.ItemId; + public uint ItemId => this.InternalItem.GetItemId(); + + /// + /// Gets the base item id (without HQ or Collectible offset applied). + /// + public uint BaseItemId => ItemUtil.GetBaseId(this.ItemId).ItemId; /// /// Gets the quantity of items in this item stack. /// - public int Quantity => this.InternalItem.Quantity; + public int Quantity => (int)this.InternalItem.GetQuantity(); /// /// Gets the spiritbond or collectability of this item. /// - public uint SpiritbondOrCollectability => this.InternalItem.SpiritbondOrCollectability; + public uint SpiritbondOrCollectability => this.InternalItem.GetSpiritbondOrCollectability(); /// /// Gets the spiritbond of this item. @@ -69,37 +86,89 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets the repair condition of this item. /// - public uint Condition => this.InternalItem.Condition; + public uint Condition => this.InternalItem.GetCondition(); // Note: This will be the Breeding Capacity of Race Chocobos /// /// Gets a value indicating whether the item is High Quality. /// - public bool IsHq => (this.InternalItem.Flags & InventoryItem.ItemFlags.HighQuality) != 0; + public bool IsHq => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.HighQuality); /// /// Gets a value indicating whether the item has a company crest applied. /// - public bool IsCompanyCrestApplied => (this.InternalItem.Flags & InventoryItem.ItemFlags.CompanyCrestApplied) != 0; + public bool IsCompanyCrestApplied => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.CompanyCrestApplied); /// /// Gets a value indicating whether the item is a relic. /// - public bool IsRelic => (this.InternalItem.Flags & InventoryItem.ItemFlags.Relic) != 0; + public bool IsRelic => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.Relic); /// /// Gets a value indicating whether the is a collectable. /// - public bool IsCollectable => (this.InternalItem.Flags & InventoryItem.ItemFlags.Collectable) != 0; + public bool IsCollectable => this.InternalItem.GetFlags().HasFlag(InventoryItem.ItemFlags.Collectable); /// /// Gets the array of materia types. /// - public ReadOnlySpan Materia => new(Unsafe.AsPointer(ref this.InternalItem.Materia[0]), 5); + public ReadOnlySpan Materia + { + get + { + var baseItemId = this.BaseItemId; + + if (ItemUtil.IsEventItem(baseItemId) || this.IsMateriaUsedForDate) + return []; + + var dataManager = Service.Get(); + + if (!dataManager.GetExcelSheet().TryGetRow(baseItemId, out var item) || item.MateriaSlotCount == 0) + return []; + + Span materiaIds = new ushort[item.MateriaSlotCount]; + var materiaRowCount = dataManager.GetExcelSheet().Count; + + for (byte i = 0; i < item.MateriaSlotCount; i++) + { + var materiaId = this.InternalItem.GetMateriaId(i); + if (materiaId < materiaRowCount) + materiaIds[i] = materiaId; + } + + return materiaIds; + } + } /// /// Gets the array of materia grades. /// - public ReadOnlySpan MateriaGrade => new(Unsafe.AsPointer(ref this.InternalItem.MateriaGrades[0]), 5); + public ReadOnlySpan MateriaGrade + { + get + { + var baseItemId = this.BaseItemId; + + if (ItemUtil.IsEventItem(baseItemId) || this.IsMateriaUsedForDate) + return []; + + var dataManager = Service.Get(); + + if (!dataManager.GetExcelSheet().TryGetRow(baseItemId, out var item) || item.MateriaSlotCount == 0) + return []; + + Span materiaGrades = new byte[item.MateriaSlotCount]; + var materiaGradeRowCount = dataManager.GetExcelSheet().Count; + + for (byte i = 0; i < item.MateriaSlotCount; i++) + { + var materiaGrade = this.InternalItem.GetMateriaGrade(i); + if (materiaGrade < materiaGradeRowCount) + materiaGrades[i] = materiaGrade; + } + + return materiaGrades; + } + } /// /// Gets the address of native inventory item in the game.
@@ -128,18 +197,60 @@ public unsafe struct GameInventoryItem : IEquatable /// /// Gets the color used for this item. /// - public ReadOnlySpan Stains => new(Unsafe.AsPointer(ref this.InternalItem.Stains[0]), 2); + public ReadOnlySpan Stains + { + get + { + var baseItemId = this.BaseItemId; + + if (ItemUtil.IsEventItem(baseItemId)) + return []; + + var dataManager = Service.Get(); + + if (!dataManager.GetExcelSheet().TryGetRow(baseItemId, out var item) || item.DyeCount == 0) + return []; + + Span stainIds = new byte[item.DyeCount]; + var stainRowCount = dataManager.GetExcelSheet().Count; + + for (byte i = 0; i < item.DyeCount; i++) + { + var stainId = this.InternalItem.GetStain(i); + if (stainId < stainRowCount) + stainIds[i] = stainId; + } + + return stainIds; + } + } /// /// Gets the glamour id for this item. /// - public uint GlamourId => this.InternalItem.GlamourId; + public uint GlamourId => this.InternalItem.GetGlamourId(); /// /// Gets the items crafter's content id. /// NOTE: I'm not sure if this is a good idea to include or not in the dalamud api. Marked internal for now. /// - internal ulong CrafterContentId => this.InternalItem.CrafterContentId; + internal ulong CrafterContentId => this.InternalItem.GetCrafterContentId(); + + /// + /// Gets a value indicating whether the Materia fields are used to store a date. + /// + private bool IsMateriaUsedForDate => this.BaseItemId + // Race Chocobo related items + is 9560 // Proof of Covering + + // Wedding related items + or 8575 // Eternity Ring + or 8693 // Promise of Innocence + or 8694 // Promise of Passion + or 8695 // Promise of Devotion + or 8696 // (Unknown/unused) + or 8698 // Blank Invitation + or 8699; // Ceremony Invitation public static bool operator ==(in GameInventoryItem l, in GameInventoryItem r) => l.Equals(r); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs index d532c2cdc..f8aa4e500 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/InventoryWidget.cs @@ -9,6 +9,7 @@ using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; @@ -145,12 +146,9 @@ internal class InventoryWidget : IDataWindowWidget ImGui.TableNextColumn(); // Item if (item.ItemId != 0 && item.Quantity != 0) { - var itemName = this.GetItemName(item.ItemId); + var itemName = ItemUtil.GetItemName(item.ItemId).ExtractText(); var iconId = this.GetItemIconId(item.ItemId); - if (item.IsHq) - itemName += " " + SeIconChar.HighQuality.ToIconString(); - if (this.textureManager.Shared.TryGetFromGameIcon(new GameIconLookup(iconId, item.IsHq), out var tex) && tex.TryGetWrap(out var texture, out _)) { ImGui.Image(texture.ImGuiHandle, new Vector2(ImGui.GetTextLineHeight())); @@ -217,7 +215,7 @@ internal class InventoryWidget : IDataWindowWidget AddKeyValueRow("Quantity", item.Quantity.ToString()); AddKeyValueRow("GlamourId", item.GlamourId.ToString()); - if (!this.IsEventItem(item.ItemId)) + if (!ItemUtil.IsEventItem(item.ItemId)) { AddKeyValueRow(item.IsCollectable ? "Collectability" : "Spiritbond", item.SpiritbondOrCollectability.ToString()); @@ -261,9 +259,9 @@ internal class InventoryWidget : IDataWindowWidget AddKeyValueRow("Flags", flagsBuilder.ToString()); - if (this.IsNormalItem(item.ItemId) && this.dataManager.Excel.GetSheet().TryGetRow(item.ItemId, out var itemRow)) + if (ItemUtil.IsNormalItem(item.ItemId) && this.dataManager.Excel.GetSheet().TryGetRow(item.ItemId, out var itemRow)) { - if (itemRow.DyeCount > 0) + if (itemRow.DyeCount > 0 && item.Stains.Length > 0) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -279,11 +277,12 @@ internal class InventoryWidget : IDataWindowWidget for (var i = 0; i < itemRow.DyeCount; i++) { - AddValueValueRow(item.Stains[i].ToString(), this.GetStainName(item.Stains[i])); + var stainId = item.Stains[i]; + AddValueValueRow(stainId.ToString(), this.GetStainName(stainId)); } } - if (itemRow.MateriaSlotCount > 0) + if (itemRow.MateriaSlotCount > 0 && item.Materia.Length > 0) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -307,45 +306,6 @@ internal class InventoryWidget : IDataWindowWidget } } - private bool IsEventItem(uint itemId) => itemId is > 2_000_000; - - private bool IsHighQuality(uint itemId) => itemId is > 1_000_000 and < 2_000_000; - - private bool IsCollectible(uint itemId) => itemId is > 500_000 and < 1_000_000; - - private bool IsNormalItem(uint itemId) => itemId is < 500_000; - - private uint GetBaseItemId(uint itemId) - { - if (this.IsEventItem(itemId)) return itemId; // uses EventItem sheet - if (this.IsHighQuality(itemId)) return itemId - 1_000_000; - if (this.IsCollectible(itemId)) return itemId - 500_000; - return itemId; - } - - private string GetItemName(uint itemId) - { - // EventItem - if (this.IsEventItem(itemId)) - { - return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var eventItemRow) - ? StripSoftHypen(eventItemRow.Name.ExtractText()) - : $"EventItem#{itemId}"; - } - - // HighQuality - if (this.IsHighQuality(itemId)) - itemId -= 1_000_000; - - // Collectible - if (this.IsCollectible(itemId)) - itemId -= 500_000; - - return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var itemRow) - ? StripSoftHypen(itemRow.Name.ExtractText()) - : $"Item#{itemId}"; - } - private string GetStainName(uint stainId) { return this.dataManager.Excel.GetSheet().TryGetRow(stainId, out var stainRow) @@ -353,32 +313,15 @@ internal class InventoryWidget : IDataWindowWidget : $"Stain#{stainId}"; } - private uint GetItemRarityColorType(Item item, bool isEdgeColor = false) - { - return (isEdgeColor ? 548u : 547u) + item.Rarity * 2u; - } - - private uint GetItemRarityColorType(uint itemId, bool isEdgeColor = false) - { - // EventItem - if (this.IsEventItem(itemId)) - return this.GetItemRarityColorType(1, isEdgeColor); - - if (!this.dataManager.Excel.GetSheet().TryGetRow(this.GetBaseItemId(itemId), out var item)) - return this.GetItemRarityColorType(1, isEdgeColor); - - return this.GetItemRarityColorType(item, isEdgeColor); - } - private uint GetItemRarityColor(uint itemId, bool isEdgeColor = false) { - if (this.IsEventItem(itemId)) + if (ItemUtil.IsEventItem(itemId)) return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; - if (!this.dataManager.Excel.GetSheet().TryGetRow(this.GetBaseItemId(itemId), out var item)) + if (!this.dataManager.Excel.GetSheet().TryGetRow(ItemUtil.GetBaseId(itemId).ItemId, out var item)) return isEdgeColor ? 0xFF000000 : 0xFFFFFFFF; - var rowId = this.GetItemRarityColorType(item, isEdgeColor); + var rowId = ItemUtil.GetItemRarityColorType(item.RowId, isEdgeColor); return this.dataManager.Excel.GetSheet().TryGetRow(rowId, out var color) ? BinaryPrimitives.ReverseEndianness(color.Dark) | 0xFF000000 : 0xFFFFFFFF; @@ -387,15 +330,15 @@ internal class InventoryWidget : IDataWindowWidget private uint GetItemIconId(uint itemId) { // EventItem - if (this.IsEventItem(itemId)) + if (ItemUtil.IsEventItem(itemId)) return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var eventItem) ? eventItem.Icon : 0u; // HighQuality - if (this.IsHighQuality(itemId)) + if (ItemUtil.IsHighQuality(itemId)) itemId -= 1_000_000; // Collectible - if (this.IsCollectible(itemId)) + if (ItemUtil.IsCollectible(itemId)) itemId -= 500_000; return this.dataManager.Excel.GetSheet().TryGetRow(itemId, out var item) ? item.Icon : 0u; From e5e3b2780c754e96532b263904071fcc9c724bf1 Mon Sep 17 00:00:00 2001 From: Bloodsoul Date: Fri, 28 Mar 2025 17:07:35 +0100 Subject: [PATCH 343/375] Update FateState.cs (#2218) Updating to 7.2 according to related changes in CS https://github.com/aers/FFXIVClientStructs/commit/01c4dfe8ca442fd43cf50d8bb446a45aafd7abc2 --- Dalamud/Game/ClientState/Fates/FateState.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dalamud/Game/ClientState/Fates/FateState.cs b/Dalamud/Game/ClientState/Fates/FateState.cs index 8f2ef85cc..ad605a99c 100644 --- a/Dalamud/Game/ClientState/Fates/FateState.cs +++ b/Dalamud/Game/ClientState/Fates/FateState.cs @@ -8,25 +8,25 @@ public enum FateState : byte /// /// The Fate is active. /// - Running = 0x02, + Running = 0x04, /// /// The Fate has ended. /// - Ended = 0x04, + Ended = 0x07, /// /// The player failed the Fate. /// - Failed = 0x05, + Failed = 0x08, /// /// The Fate is preparing to run. /// - Preparation = 0x07, + Preparation = 0x03, /// /// The Fate is preparing to end. /// - WaitingForEnd = 0x08, + WaitingForEnd = 0x05, } From c1557df585aa4fb8668d18a0fc7327cd4fb82ad5 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:07:55 +0100 Subject: [PATCH 344/375] Update ClientStructs (#2216) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 0d2db9a66..d5d1aa119 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 0d2db9a66b676792fd0a44280d40f5b58cc3522e +Subproject commit d5d1aa119d371f8ad228824f72db59af615fe979 From b28fab2d087dfae7784fe13256d22aaf86f15b00 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 28 Mar 2025 17:19:25 +0100 Subject: [PATCH 345/375] More InvokeSafely calls (#2215) * Safely invoke DutyState event handlers * Safely invoke GameGui event handlers --- Dalamud/Game/DutyState/DutyState.cs | 13 +++++++------ Dalamud/Game/Gui/GameGui.cs | 20 ++------------------ 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/Dalamud/Game/DutyState/DutyState.cs b/Dalamud/Game/DutyState/DutyState.cs index 3a3afbab0..4d719685b 100644 --- a/Dalamud/Game/DutyState/DutyState.cs +++ b/Dalamud/Game/DutyState/DutyState.cs @@ -1,10 +1,11 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Conditions; using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; namespace Dalamud.Game.DutyState; @@ -81,33 +82,33 @@ internal unsafe class DutyState : IInternalDisposableService, IDutyState // Duty Commenced case 0x4000_0001: this.IsDutyStarted = true; - this.DutyStarted?.Invoke(this, this.clientState.TerritoryType); + this.DutyStarted?.InvokeSafely(this, this.clientState.TerritoryType); break; // Party Wipe case 0x4000_0005: this.IsDutyStarted = false; - this.DutyWiped?.Invoke(this, this.clientState.TerritoryType); + this.DutyWiped?.InvokeSafely(this, this.clientState.TerritoryType); break; // Duty Recommence case 0x4000_0006: this.IsDutyStarted = true; - this.DutyRecommenced?.Invoke(this, this.clientState.TerritoryType); + this.DutyRecommenced?.InvokeSafely(this, this.clientState.TerritoryType); break; // Duty Completed Flytext Shown case 0x4000_0002 when !this.CompletedThisTerritory: this.IsDutyStarted = false; this.CompletedThisTerritory = true; - this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType); + this.DutyCompleted?.InvokeSafely(this, this.clientState.TerritoryType); break; // Duty Completed case 0x4000_0003 when !this.CompletedThisTerritory: this.IsDutyStarted = false; this.CompletedThisTerritory = true; - this.DutyCompleted?.Invoke(this, this.clientState.TerritoryType); + this.DutyCompleted?.InvokeSafely(this, this.clientState.TerritoryType); break; } } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 1041464a7..d3fe444ea 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -307,15 +307,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui if (values != null && valueCount == 1 && values->Int == -1) { this.HoveredItem = 0; - - try - { - this.HoveredItemChanged?.Invoke(this, 0); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } + this.HoveredItemChanged?.InvokeSafely(this, 0ul); Log.Verbose("HoveredItem changed: 0"); } @@ -347,15 +339,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.HoveredAction.ActionKind = HoverActionKind.None; this.HoveredAction.BaseActionID = 0; this.HoveredAction.ActionID = 0; - - try - { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredActionChanged event."); - } + this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); Log.Verbose("HoverActionId: 0"); } From baf796f5e1d69f9f8916f889678cd1910939fee7 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Fri, 28 Mar 2025 18:51:54 +0100 Subject: [PATCH 346/375] Update ClientStructs (#2221) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index d5d1aa119..f151c29c1 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit d5d1aa119d371f8ad228824f72db59af615fe979 +Subproject commit f151c29c1533c54c88b8669a320dc8da5b7e517c From 0d9eca18773dd26a346c11c2ef92bf28e29fa364 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 28 Mar 2025 19:32:23 +0100 Subject: [PATCH 347/375] Fix GameInventoryItem array initialization (#2222) --- Dalamud/Game/Inventory/GameInventory.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index 27f14bf79..5c8ed27b0 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -120,9 +120,7 @@ internal class GameInventory : IInternalDisposableService continue; // Assumption: newItems is sorted by slots, and the last item has the highest slot number. - var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1]; - if (this.inventoryItems[i] == null) - oldItems.Initialize(); + var oldItems = this.inventoryItems[i] ??= this.CreateItemsArray(newItems[^1].InternalItem.Slot + 1); foreach (ref readonly var newItem in newItems) { @@ -314,6 +312,13 @@ internal class GameInventory : IInternalDisposableService this.mergedEvents.Clear(); } + private GameInventoryItem[] CreateItemsArray(int length) + { + var items = new GameInventoryItem[length]; + items.Initialize(); + return items; + } + private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1) { this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0; From 541c073c32c6be3e4bc82c0f55be0ba37ff63f8f Mon Sep 17 00:00:00 2001 From: goaaats Date: Fri, 28 Mar 2025 22:41:00 +0100 Subject: [PATCH 348/375] build: 12.0.0.1 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 9f3a9bb4a..9fee96101 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.0 + 12.0.0.1 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 0e87856bd28845b81ae158d2b6f8c5f844dec079 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 28 Mar 2025 23:16:17 +0100 Subject: [PATCH 349/375] Remove MateriaSlotCount check (#2223) --- Dalamud/Game/Inventory/GameInventoryItem.cs | 22 ++++++--------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs index 5a0ec0c74..085200742 100644 --- a/Dalamud/Game/Inventory/GameInventoryItem.cs +++ b/Dalamud/Game/Inventory/GameInventoryItem.cs @@ -120,15 +120,10 @@ public unsafe struct GameInventoryItem : IEquatable if (ItemUtil.IsEventItem(baseItemId) || this.IsMateriaUsedForDate) return []; - var dataManager = Service.Get(); + Span materiaIds = new ushort[this.InternalItem.Materia.Length]; + var materiaRowCount = Service.Get().GetExcelSheet().Count; - if (!dataManager.GetExcelSheet().TryGetRow(baseItemId, out var item) || item.MateriaSlotCount == 0) - return []; - - Span materiaIds = new ushort[item.MateriaSlotCount]; - var materiaRowCount = dataManager.GetExcelSheet().Count; - - for (byte i = 0; i < item.MateriaSlotCount; i++) + for (byte i = 0; i < this.InternalItem.Materia.Length; i++) { var materiaId = this.InternalItem.GetMateriaId(i); if (materiaId < materiaRowCount) @@ -151,15 +146,10 @@ public unsafe struct GameInventoryItem : IEquatable if (ItemUtil.IsEventItem(baseItemId) || this.IsMateriaUsedForDate) return []; - var dataManager = Service.Get(); + Span materiaGrades = new byte[this.InternalItem.MateriaGrades.Length]; + var materiaGradeRowCount = Service.Get().GetExcelSheet().Count; - if (!dataManager.GetExcelSheet().TryGetRow(baseItemId, out var item) || item.MateriaSlotCount == 0) - return []; - - Span materiaGrades = new byte[item.MateriaSlotCount]; - var materiaGradeRowCount = dataManager.GetExcelSheet().Count; - - for (byte i = 0; i < item.MateriaSlotCount; i++) + for (byte i = 0; i < this.InternalItem.MateriaGrades.Length; i++) { var materiaGrade = this.InternalItem.GetMateriaGrade(i); if (materiaGrade < materiaGradeRowCount) From 106b59dc45ba8ee8379531ec80259bb496131063 Mon Sep 17 00:00:00 2001 From: goaaats Date: Fri, 28 Mar 2025 23:25:38 +0100 Subject: [PATCH 350/375] build: 12.0.0.2 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 9fee96101..93201df33 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.1 + 12.0.0.2 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From a9fd92ad624a36c82dba2a3dc3b14804589b5442 Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 29 Mar 2025 06:53:25 +0100 Subject: [PATCH 351/375] Update ClientStructs (#2224) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index f151c29c1..0a40380ec 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit f151c29c1533c54c88b8669a320dc8da5b7e517c +Subproject commit 0a40380ecf8bae0cb882b410f50ac7a6057ff9c7 From 37ca457d975c376839a604f9985d7e6eddd6ab2f Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sat, 29 Mar 2025 18:54:10 +0100 Subject: [PATCH 352/375] Update ClientStructs (#2226) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 0a40380ec..826c9de4d 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 0a40380ecf8bae0cb882b410f50ac7a6057ff9c7 +Subproject commit 826c9de4d409b0539a5f600193d7f8ff5c834210 From 813bb1d3607c1539e022c1e5fb94622ffa19dd95 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 29 Mar 2025 19:06:48 +0100 Subject: [PATCH 353/375] build: 12.0.0.3 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 93201df33..f5fe61047 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.2 + 12.0.0.3 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From dac14b9168eb2959497bfdaf6a68dd6fc5915e2c Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 29 Mar 2025 20:04:41 +0100 Subject: [PATCH 354/375] Add test for fastfail exception --- Dalamud/Interface/Internal/DalamudInterface.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 7d798c541..b54f16860 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Numerics; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CheapLoc; @@ -37,6 +38,8 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + using ImGuiNET; using ImPlotNET; @@ -830,6 +833,18 @@ internal class DalamudInterface : IInternalDisposableService } } + if (ImGui.MenuItem("Cause CLR fastfail")) + { + unsafe void CauseFastFail() + { + // ReSharper disable once NotAccessedVariable + var texture = Unsafe.AsRef((void*)0x12345678); + texture.TextureType = TextureType.Crest; + } + + Service.Get().RunOnFrameworkThread(CauseFastFail); + } + if (ImGui.MenuItem("Cause ImGui assert")) { ImGui.PopStyleVar(); From 7cb0d61fe47b12dd98be23fefd7cf8a2893bbec7 Mon Sep 17 00:00:00 2001 From: goaaats Date: Sat, 29 Mar 2025 20:09:26 +0100 Subject: [PATCH 355/375] Use RunOnTick instead --- Dalamud/Interface/Internal/DalamudInterface.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index b54f16860..5d21e954a 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -842,7 +842,7 @@ internal class DalamudInterface : IInternalDisposableService texture.TextureType = TextureType.Crest; } - Service.Get().RunOnFrameworkThread(CauseFastFail); + Service.Get().RunOnTick(CauseFastFail); } if (ImGui.MenuItem("Cause ImGui assert")) From d86232dbe8995033ccf8cc680d22bd5c95730cda Mon Sep 17 00:00:00 2001 From: bleatbot <106497096+bleatbot@users.noreply.github.com> Date: Sun, 30 Mar 2025 22:51:42 +0200 Subject: [PATCH 356/375] Update ClientStructs (#2228) Co-authored-by: github-actions[bot] --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 826c9de4d..a625ce4d2 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 826c9de4d409b0539a5f600193d7f8ff5c834210 +Subproject commit a625ce4d2c54e5ff8d70978a3de98d4a8475d15d From 126e96689bfaf4b2256f81ab943eb0b8a26f54d1 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Mon, 31 Mar 2025 09:25:47 -0700 Subject: [PATCH 357/375] fix: Repair broken contextmenu test (#2231) - Bump clientstructs --- .../Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs | 5 ----- lib/FFXIVClientStructs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs index 24faf562f..992448913 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs @@ -283,11 +283,6 @@ internal class ContextMenuAgingStep : IAgingStep } } - if (item.Stains[0] != 0) - b.AppendLine($"{this.stainSheet.GetRowOrDefault(item.Stains[0])?.Name.ExtractText() ?? "Unknown"} ({item.Stains[0]})"); - else - b.AppendLine("None"); - b.Append("Glamoured Item: "); if (item.GlamourId != 0) b.AppendLine($"{this.itemSheet.GetRowOrDefault(item.GlamourId)?.Name.ExtractText() ?? "Unknown"} ({item.GlamourId})"); diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index a625ce4d2..9e7f03ed6 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit a625ce4d2c54e5ff8d70978a3de98d4a8475d15d +Subproject commit 9e7f03ed6d3d5cb9e6952f00c4779ac64427bc81 From 3d04d758113d132d2d88b6986ae110779c422cc5 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 31 Mar 2025 18:31:00 +0200 Subject: [PATCH 358/375] build: 12.0.0.4 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index f5fe61047..91d314b00 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.3 + 12.0.0.4 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 104504f6a6c46762ee86bc10a2b363725ed92a3f Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 31 Mar 2025 22:37:41 +0200 Subject: [PATCH 359/375] Add 2025 april fools --- .../PluginInstaller/PluginInstallerWindow.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 66f338586..4e684b0e1 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -140,6 +140,14 @@ internal class PluginInstallerWindow : Window, IDisposable private string? staleDalamudNewVersion = null; + private int? aprilFoolsLineIdx = null; + private string[] aprilFoolsLines = + [ + "We sincerely apologize for this year's April Fool's joke.\nWe hope you enjoyed it, and we promise to do better next year.", + "Congratulations! You are Dalamud's 1,000,000th user!\nClick here to claim your prize!", + "Please do not feed the ducks.\nThey are not ducks.", + ]; + /// /// Initializes a new instance of the class. /// @@ -1571,6 +1579,14 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.ScaledDummy(10); } + if (DateTime.Now is { Day: 1, Month: 4 }) + { + ImGuiHelpers.ScaledDummy(10); + this.aprilFoolsLineIdx ??= new Random().Next(0, this.aprilFoolsLines.Length); + DrawLinesCentered(this.aprilFoolsLines[this.aprilFoolsLineIdx.Value]); + ImGuiHelpers.ScaledDummy(10); + } + if (this.staleDalamudNewVersion != null) { DrawWarningIcon(); From 46c0a86375b64ea744bb8e32a4d75c16bb0db9b1 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 31 Mar 2025 22:51:28 +0200 Subject: [PATCH 360/375] build: 12.0.0.5 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 91d314b00..4b75043ea 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.4 + 12.0.0.5 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From c67ed345b72f21e7e2cda29377bc1417f6d775bb Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 01:27:44 +0200 Subject: [PATCH 361/375] Add prizes --- .../PluginInstaller/PluginInstallerWindow.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 4e684b0e1..6c27bfcd2 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -144,7 +144,7 @@ internal class PluginInstallerWindow : Window, IDisposable private string[] aprilFoolsLines = [ "We sincerely apologize for this year's April Fool's joke.\nWe hope you enjoyed it, and we promise to do better next year.", - "Congratulations! You are Dalamud's 1,000,000th user!\nClick here to claim your prize!", + "Congratulations! You are Dalamud's 1,000,000th user!\nClick here to claim your prize, a set of 4 high quality wallpapers!", "Please do not feed the ducks.\nThey are not ducks.", ]; @@ -1561,13 +1561,17 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PopStyleColor(); } - void DrawLinesCentered(string text) + bool DrawLinesCentered(string text) { + var clicked = false; var lines = text.Split('\n'); foreach (var line in lines) { ImGuiHelpers.CenteredText(line); + clicked |= ImGui.IsItemClicked(); } + + return clicked; } var pm = Service.Get(); @@ -1583,7 +1587,13 @@ internal class PluginInstallerWindow : Window, IDisposable { ImGuiHelpers.ScaledDummy(10); this.aprilFoolsLineIdx ??= new Random().Next(0, this.aprilFoolsLines.Length); - DrawLinesCentered(this.aprilFoolsLines[this.aprilFoolsLineIdx.Value]); + + var clicked = DrawLinesCentered(this.aprilFoolsLines[this.aprilFoolsLineIdx.Value]); + if (clicked && this.aprilFoolsLineIdx == 1) + { + Util.OpenLink("https://goatcorp.github.io/high_quality_wallpapers.zip"); + } + ImGuiHelpers.ScaledDummy(10); } From 4a1f7824ce95ab09647d7a36050f438c05e9c8b6 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 01:28:05 +0200 Subject: [PATCH 362/375] build: 12.0.0.6 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 4b75043ea..7a5eb5517 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.5 + 12.0.0.6 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 2a049c40d687d908445a7114a2e7a0c3099d2467 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 01:53:14 +0200 Subject: [PATCH 363/375] Make it lame again --- .../PluginInstaller/PluginInstallerWindow.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 6c27bfcd2..44516fc7e 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -144,7 +144,6 @@ internal class PluginInstallerWindow : Window, IDisposable private string[] aprilFoolsLines = [ "We sincerely apologize for this year's April Fool's joke.\nWe hope you enjoyed it, and we promise to do better next year.", - "Congratulations! You are Dalamud's 1,000,000th user!\nClick here to claim your prize, a set of 4 high quality wallpapers!", "Please do not feed the ducks.\nThey are not ducks.", ]; @@ -1561,17 +1560,13 @@ internal class PluginInstallerWindow : Window, IDisposable ImGui.PopStyleColor(); } - bool DrawLinesCentered(string text) + void DrawLinesCentered(string text) { - var clicked = false; var lines = text.Split('\n'); foreach (var line in lines) { ImGuiHelpers.CenteredText(line); - clicked |= ImGui.IsItemClicked(); } - - return clicked; } var pm = Service.Get(); @@ -1587,13 +1582,7 @@ internal class PluginInstallerWindow : Window, IDisposable { ImGuiHelpers.ScaledDummy(10); this.aprilFoolsLineIdx ??= new Random().Next(0, this.aprilFoolsLines.Length); - - var clicked = DrawLinesCentered(this.aprilFoolsLines[this.aprilFoolsLineIdx.Value]); - if (clicked && this.aprilFoolsLineIdx == 1) - { - Util.OpenLink("https://goatcorp.github.io/high_quality_wallpapers.zip"); - } - + DrawLinesCentered(this.aprilFoolsLines[this.aprilFoolsLineIdx.Value]); ImGuiHelpers.ScaledDummy(10); } From f2c89bfc009a70812ba6d9ca52bb19c9b32c7fac Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 01:54:01 +0200 Subject: [PATCH 364/375] build: 12.0.0.7 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 7a5eb5517..55847cf46 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 12.0.0.6 + 12.0.0.7 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From 34679d085be4d4b1e65657a21427f4260ee17298 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 18:55:27 +0200 Subject: [PATCH 365/375] Extract plugin to temp directory before copying to final location, some minor cleanup --- .../Textures/Internal/TextureManager.Wic.cs | 2 +- Dalamud/Plugin/Internal/PluginManager.cs | 174 ++++++++++-------- .../Internal/Profiles/ProfileManager.cs | 12 +- Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 4 + .../Types/Manifest/LocalPluginManifest.cs | 10 +- Dalamud/Storage/ReliableFileStorage.cs | 44 ++--- Dalamud/Utility/FilesystemUtil.cs | 116 ++++++++++++ Dalamud/Utility/Util.cs | 54 +----- 8 files changed, 263 insertions(+), 153 deletions(-) create mode 100644 Dalamud/Utility/FilesystemUtil.cs diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs index 245a2a9ac..df84f9545 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.Wic.cs @@ -91,7 +91,7 @@ internal sealed partial class TextureManager throw new NullReferenceException($"{nameof(path)} cannot be null."); using var wrapAux = new WrapAux(wrap, true); - var pathTemp = Util.GetTempFileNameForFileReplacement(path); + var pathTemp = Util.GetReplaceableFileName(path); var trashfire = new List(); try { diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 5cab004ed..ccae6dd06 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -48,6 +48,8 @@ internal class PluginManager : IInternalDisposableService ///
public const int PluginWaitBeforeFreeDefault = 1000; // upped from 500ms, seems more stable + private const string BrokenMarkerFileName = ".broken"; + private static readonly ModuleLog Log = ModuleLog.Create(); private readonly object pluginListLock = new(); @@ -874,7 +876,7 @@ internal class PluginManager : IInternalDisposableService } /// - /// Cleanup disabled plugins. Does not target devPlugins. + /// Cleanup disabled and broken plugins. Does not target devPlugins. /// public void CleanupPlugins() { @@ -882,6 +884,13 @@ internal class PluginManager : IInternalDisposableService { try { + if (File.Exists(Path.Combine(pluginDir.FullName, BrokenMarkerFileName))) + { + Log.Warning("Cleaning up broken plugin {Name}", pluginDir.Name); + pluginDir.Delete(true); + continue; + } + var versionDirs = pluginDir.GetDirectories(); versionDirs = versionDirs @@ -1423,7 +1432,8 @@ internal class PluginManager : IInternalDisposableService else { // If we are doing anything other than a fresh install, not having a workingPluginId is an error that must be fixed - Debug.Assert(inheritedWorkingPluginId != null, "inheritedWorkingPluginId != null"); + if (inheritedWorkingPluginId != null) + throw new InvalidOperationException("Inherited WorkingPluginId must not be null"); } // Ensure that we have a testing opt-in for this plugin if we are installing a testing version @@ -1434,31 +1444,28 @@ internal class PluginManager : IInternalDisposableService this.configuration.QueueSave(); } - var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version?.ToString() ?? string.Empty)); + var pluginVersionsDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName)); + var tempOutputDir = new DirectoryInfo(FilesystemUtil.GetTempFileName()); + var outputDir = new DirectoryInfo(Path.Combine(pluginVersionsDir.FullName, version?.ToString() ?? string.Empty)); + + FilesystemUtil.DeleteAndRecreateDirectory(tempOutputDir); + FilesystemUtil.DeleteAndRecreateDirectory(outputDir); + + Log.Debug("Extracting plugin to {TempOutputDir}", tempOutputDir); try { - if (outputDir.Exists) - outputDir.Delete(true); + using var archive = new ZipArchive(zipStream); - outputDir.Create(); - } - catch - { - // ignored, since the plugin may be loaded already - } - - Log.Debug("Extracting to {OutputDir}", outputDir); - - using (var archive = new ZipArchive(zipStream)) - { foreach (var zipFile in archive.Entries) { - var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName))); + var outputFile = new FileInfo( + Path.GetFullPath(Path.Combine(tempOutputDir.FullName, zipFile.FullName))); - if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase)) + if (!outputFile.FullName.StartsWith(tempOutputDir.FullName, StringComparison.OrdinalIgnoreCase)) { - throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); + throw new IOException( + "Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); } if (outputFile.Directory == null) @@ -1469,70 +1476,88 @@ internal class PluginManager : IInternalDisposableService if (zipFile.Name.IsNullOrEmpty()) { // Assuming Empty for Directory - Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}"); + Log.Verbose( + "ZipFile name is null or empty, treating as a directory: {Path}", outputFile.Directory.FullName); Directory.CreateDirectory(outputFile.Directory.FullName); continue; } // Ensure directory is created Directory.CreateDirectory(outputFile.Directory.FullName); - - try - { - zipFile.ExtractToFile(outputFile.FullName, true); - } - catch (Exception ex) - { - if (outputFile.Extension.EndsWith("dll")) - { - throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}"); - } - - Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}"); - } + zipFile.ExtractToFile(outputFile.FullName, true); } + + var tempDllFile = LocalPluginManifest.GetPluginFile(tempOutputDir, repoManifest); + var tempManifestFile = LocalPluginManifest.GetManifestFile(tempDllFile); + + // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. + FilesystemUtil.WriteAllTextSafe( + tempManifestFile.FullName, + JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); + + // Reload as a local manifest, add some attributes, and save again. + var tempManifest = LocalPluginManifest.Load(tempManifestFile); + + if (tempManifest == null) + throw new Exception("Plugin had no valid manifest"); + + if (tempManifest.InternalName != repoManifest.InternalName) + { + throw new Exception( + $"Distributed internal name does not match repo internal name: {tempManifest.InternalName} - {repoManifest.InternalName}"); + } + + if (tempManifest.WorkingPluginId != Guid.Empty) + throw new Exception("Plugin shall not specify a WorkingPluginId"); + + tempManifest.WorkingPluginId = inheritedWorkingPluginId ?? Guid.NewGuid(); + + if (useTesting) + { + tempManifest.Testing = true; + } + + // Document the url the plugin was installed from + tempManifest.InstalledFromUrl = repoManifest.SourceRepo.IsThirdParty + ? repoManifest.SourceRepo.PluginMasterUrl + : SpecialPluginSource.MainRepo; + + tempManifest.Save(tempManifestFile, "installation"); + + // Copy the directory to the final location + Log.Debug("Copying plugin from {TempOutputDir} to {OutputDir}", tempOutputDir, outputDir); + FilesystemUtil.CopyFilesRecursively(tempOutputDir, outputDir); + + var finalDllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); + var finalManifestFile = LocalPluginManifest.GetManifestFile(finalDllFile); + var finalManifest = LocalPluginManifest.Load(finalManifestFile) ?? + throw new Exception("Plugin had no valid manifest after copy"); + + Log.Information("Installed plugin {InternalName} (testing={UseTesting})", tempManifest.Name, useTesting); + var plugin = await this.LoadPluginAsync(finalDllFile, finalManifest, reason); + + this.NotifyinstalledPluginsListChanged(); + return plugin; } - - var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - - // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. - Util.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); - - // Reload as a local manifest, add some attributes, and save again. - var manifest = LocalPluginManifest.Load(manifestFile); - - if (manifest == null) - throw new Exception("Plugin had no valid manifest"); - - if (manifest.InternalName != repoManifest.InternalName) + catch { - Directory.Delete(outputDir.FullName, true); - throw new Exception( - $"Distributed internal name does not match repo internal name: {manifest.InternalName} - {repoManifest.InternalName}"); + // Attempt to clean up if we can + try + { + outputDir.Delete(true); + } + catch + { + // Write marker file if we can't, we'll try to do it at the next start + File.WriteAllText(Path.Combine(pluginVersionsDir.FullName, BrokenMarkerFileName), string.Empty); + } + + throw; } - - if (manifest.WorkingPluginId != Guid.Empty) - throw new Exception("Plugin shall not specify a WorkingPluginId"); - - manifest.WorkingPluginId = inheritedWorkingPluginId ?? Guid.NewGuid(); - - if (useTesting) + finally { - manifest.Testing = true; + tempOutputDir.Delete(true); } - - // Document the url the plugin was installed from - manifest.InstalledFromUrl = repoManifest.SourceRepo.IsThirdParty ? repoManifest.SourceRepo.PluginMasterUrl : SpecialPluginSource.MainRepo; - - manifest.Save(manifestFile, "installation"); - - Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})"); - - var plugin = await this.LoadPluginAsync(dllFile, manifest, reason); - - this.NotifyinstalledPluginsListChanged(); - return plugin; } /// @@ -1547,7 +1572,6 @@ internal class PluginManager : IInternalDisposableService /// The loaded plugin. private async Task LoadPluginAsync(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) { - var name = manifest?.Name ?? dllFile.Name; var loadPlugin = !doNotLoad; LocalPlugin? plugin; @@ -1560,7 +1584,7 @@ internal class PluginManager : IInternalDisposableService if (isDev) { - Log.Information($"Loading dev plugin {name}"); + Log.Information("Loading dev plugin {Name}", manifest.InternalName); plugin = new LocalDevPlugin(dllFile, manifest); // This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet @@ -1569,7 +1593,7 @@ internal class PluginManager : IInternalDisposableService } else { - Log.Information($"Loading plugin {name}"); + Log.Information("Loading plugin {Name}", manifest.InternalName); plugin = new LocalPlugin(dllFile, manifest); } @@ -1663,7 +1687,7 @@ internal class PluginManager : IInternalDisposableService } else { - Log.Verbose($"{name} not loaded, wantToLoad:{wantedByAnyProfile} orphaned:{plugin.IsOrphaned}"); + Log.Verbose("{Name} not loaded, wantToLoad:{WantedByAnyProfile} orphaned:{IsOrphaned}", manifest.InternalName, wantedByAnyProfile, plugin.IsOrphaned); } } catch (InvalidPluginException) diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs index e36e9908b..8a1711b0d 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs @@ -57,7 +57,7 @@ internal class ProfileManager : IServiceType /// Gets a value indicating whether or not the profile manager is busy enabling/disabling plugins. /// public bool IsBusy => this.isBusy; - + /// /// Get a disposable that will lock the profile list while it is not disposed. /// You must NEVER use this in async code. @@ -77,7 +77,7 @@ internal class ProfileManager : IServiceType { var want = false; var wasInAnyProfile = false; - + lock (this.profiles) { foreach (var profile in this.profiles) @@ -93,7 +93,7 @@ internal class ProfileManager : IServiceType if (!wasInAnyProfile && addIfNotDeclared) { - Log.Warning("'{Guid}'('{InternalName}') was not in any profile, adding to default with {Default}", workingPluginId, internalName, defaultState); + Log.Warning("{Guid}({InternalName}) was not in any profile, adding to default with {Default}", workingPluginId, internalName, defaultState); await this.DefaultProfile.AddOrUpdateAsync(workingPluginId, internalName, defaultState, false); return defaultState; @@ -175,7 +175,7 @@ internal class ProfileManager : IServiceType { // Disable it modelV1.IsEnabled = false; - + // Try to find matching plugins for all plugins in the profile var pm = Service.Get(); foreach (var plugin in modelV1.Plugins) @@ -313,7 +313,7 @@ internal class ProfileManager : IServiceType profile.MigrateProfilesToGuidsForPlugin(internalName, newGuid); } } - + /// /// Validate profiles for errors. /// @@ -328,7 +328,7 @@ internal class ProfileManager : IServiceType { if (seenIds.Contains(pluginEntry.WorkingPluginId)) throw new Exception($"Plugin '{pluginEntry.WorkingPluginId}'('{pluginEntry.InternalName}') is twice in profile '{profile.Guid}'('{profile.Name}')"); - + seenIds.Add(pluginEntry.WorkingPluginId); } } diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index a86a39905..1b9025538 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -605,6 +605,10 @@ internal class LocalPlugin : IAsyncDisposable if (this.loader != null) return; + this.DllFile.Refresh(); + if (!this.DllFile.Exists) + throw new Exception($"Plugin DLL file at '{this.DllFile.FullName}' did not exist, cannot load."); + try { this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig); diff --git a/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs index dc05409b0..3aededa18 100644 --- a/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/Manifest/LocalPluginManifest.cs @@ -57,15 +57,15 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest /// The reason the manifest was saved. public void Save(FileInfo manifestFile, string reason) { - Log.Verbose("Saving manifest for '{PluginName}' because '{Reason}'", this.InternalName, reason); + Log.Verbose("Saving manifest for {PluginName} because {Reason}", this.InternalName, reason); try { - Util.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); + FilesystemUtil.WriteAllTextSafe(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); } catch { - Log.Error("Could not write out manifest for '{PluginName}' because '{Reason}'", this.InternalName, reason); + Log.Error("Could not write out manifest for {PluginName} because {Reason}", this.InternalName, reason); throw; } } @@ -78,7 +78,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest public static LocalPluginManifest? Load(FileInfo manifestFile) => JsonConvert.DeserializeObject(File.ReadAllText(manifestFile.FullName)); /// - /// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist. + /// A standardized way to get the plugin DLL name that should accompany a manifest file. /// /// Manifest directory. /// The manifest. @@ -86,7 +86,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll")); /// - /// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist. + /// A standardized way to get the manifest file that should accompany a plugin DLL. /// /// The plugin DLL. /// The file. diff --git a/Dalamud/Storage/ReliableFileStorage.cs b/Dalamud/Storage/ReliableFileStorage.cs index b78d16cc7..9b87a71a0 100644 --- a/Dalamud/Storage/ReliableFileStorage.cs +++ b/Dalamud/Storage/ReliableFileStorage.cs @@ -29,7 +29,7 @@ internal class ReliableFileStorage : IInternalDisposableService private readonly object syncRoot = new(); private SQLiteConnection? db; - + /// /// Initializes a new instance of the class. /// @@ -37,7 +37,7 @@ internal class ReliableFileStorage : IInternalDisposableService public ReliableFileStorage(string vfsDbPath) { var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db"); - + Log.Verbose("Initializing VFS database at {Path}", databasePath); try @@ -52,7 +52,7 @@ internal class ReliableFileStorage : IInternalDisposableService { if (File.Exists(databasePath)) File.Delete(databasePath); - + this.SetupDb(databasePath); } catch (Exception) @@ -79,13 +79,13 @@ internal class ReliableFileStorage : IInternalDisposableService if (this.db == null) return false; - + // If the file doesn't actually exist on the FS, but it does in the DB, we can say YES and read operations will read from the DB instead var normalizedPath = NormalizePath(path); var file = this.db.Table().FirstOrDefault(f => f.Path == normalizedPath && f.ContainerId == containerId); return file != null; } - + /// /// Write all text to a file. /// @@ -94,7 +94,7 @@ internal class ReliableFileStorage : IInternalDisposableService /// Container to write to. public void WriteAllText(string path, string? contents, Guid containerId = default) => this.WriteAllText(path, contents, Encoding.UTF8, containerId); - + /// /// Write all text to a file. /// @@ -107,7 +107,7 @@ internal class ReliableFileStorage : IInternalDisposableService var bytes = encoding.GetBytes(contents ?? string.Empty); this.WriteAllBytes(path, bytes, containerId); } - + /// /// Write all bytes to a file. /// @@ -122,10 +122,10 @@ internal class ReliableFileStorage : IInternalDisposableService { if (this.db == null) { - Util.WriteAllBytesSafe(path, bytes); + FilesystemUtil.WriteAllBytesSafe(path, bytes); return; } - + this.db.RunInTransaction(() => { var normalizedPath = NormalizePath(path); @@ -145,8 +145,8 @@ internal class ReliableFileStorage : IInternalDisposableService file.Data = bytes; this.db.Update(file); } - - Util.WriteAllBytesSafe(path, bytes); + + FilesystemUtil.WriteAllBytesSafe(path, bytes); }); } } @@ -180,7 +180,7 @@ internal class ReliableFileStorage : IInternalDisposableService var bytes = this.ReadAllBytes(path, forceBackup, containerId); return encoding.GetString(bytes); } - + /// /// Read all text from a file, and automatically try again with the backup if the file does not exist or /// the function throws an exception. If the backup read also throws an exception, @@ -208,11 +208,11 @@ internal class ReliableFileStorage : IInternalDisposableService public void ReadAllText(string path, Encoding encoding, Action reader, Guid containerId = default) { ArgumentException.ThrowIfNullOrEmpty(path); - + // TODO: We are technically reading one time too many here, if the file does not exist on the FS, ReadAllText // fails over to the backup, and then the backup fails to read in the lambda. We should do something about that, // but it's not a big deal. Would be nice if ReadAllText could indicate if it did fail over. - + // 1.) Try without using the backup try { @@ -229,7 +229,7 @@ internal class ReliableFileStorage : IInternalDisposableService { Log.Verbose(ex, "First chance read from {Path} failed, trying backup", path); } - + // 2.) Try using the backup try { @@ -256,13 +256,13 @@ internal class ReliableFileStorage : IInternalDisposableService public byte[] ReadAllBytes(string path, bool forceBackup = false, Guid containerId = default) { ArgumentException.ThrowIfNullOrEmpty(path); - + if (forceBackup) { // If the db failed to load, act as if the file does not exist if (this.db == null) throw new FileNotFoundException("Backup database was not available"); - + var normalizedPath = NormalizePath(path); var file = this.db.Table().FirstOrDefault(f => f.Path == normalizedPath && f.ContainerId == containerId); if (file == null) @@ -274,7 +274,7 @@ internal class ReliableFileStorage : IInternalDisposableService // If the file doesn't exist, immediately check the backup db if (!File.Exists(path)) return this.ReadAllBytes(path, true, containerId); - + try { return File.ReadAllBytes(path); @@ -302,10 +302,10 @@ internal class ReliableFileStorage : IInternalDisposableService // Replace users folder var usersFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); path = path.Replace(usersFolder, "%USERPROFILE%"); - + return path; } - + private void SetupDb(string path) { this.db = new SQLiteConnection(path, @@ -320,9 +320,9 @@ internal class ReliableFileStorage : IInternalDisposableService [PrimaryKey] [AutoIncrement] public int Id { get; set; } - + public Guid ContainerId { get; set; } - + public string Path { get; set; } = null!; public byte[] Data { get; set; } = null!; diff --git a/Dalamud/Utility/FilesystemUtil.cs b/Dalamud/Utility/FilesystemUtil.cs new file mode 100644 index 000000000..3b4298b37 --- /dev/null +++ b/Dalamud/Utility/FilesystemUtil.cs @@ -0,0 +1,116 @@ +using System.ComponentModel; +using System.IO; +using System.Text; + +using Windows.Win32.Storage.FileSystem; + +namespace Dalamud.Utility; + +/// +/// Helper functions for filesystem operations. +/// +public static class FilesystemUtil +{ + /// + /// Overwrite text in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The text to write. + public static void WriteAllTextSafe(string path, string text) + { + WriteAllTextSafe(path, text, Encoding.UTF8); + } + + /// + /// Overwrite text in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The text to write. + /// Encoding to use. + public static void WriteAllTextSafe(string path, string text, Encoding encoding) + { + WriteAllBytesSafe(path, encoding.GetBytes(text)); + } + + /// + /// Overwrite data in a file by first writing it to a temporary file, and then + /// moving that file to the path specified. + /// + /// The path of the file to write to. + /// The data to write. + public static unsafe void WriteAllBytesSafe(string path, byte[] bytes) + { + ArgumentException.ThrowIfNullOrEmpty(path); + + // Open the temp file + var tempPath = path + ".tmp"; + + using var tempFile = Windows.Win32.PInvoke.CreateFile( + tempPath, + (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), + FILE_SHARE_MODE.FILE_SHARE_NONE, + null, + FILE_CREATION_DISPOSITION.CREATE_ALWAYS, + FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, + null); + + if (tempFile.IsInvalid) + throw new Win32Exception(); + + // Write the data + uint bytesWritten = 0; + if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan(bytes), &bytesWritten, null)) + throw new Win32Exception(); + + if (bytesWritten != bytes.Length) + throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); + + if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile)) + throw new Win32Exception(); + + tempFile.Close(); + + if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) + throw new Win32Exception(); + } + + /// + /// Generates a temporary file name. + /// + /// A temporary file name that is almost guaranteed to be unique. + internal static string GetTempFileName() + { + // https://stackoverflow.com/a/50413126 + return Path.Combine(Path.GetTempPath(), "dalamud_" + Guid.NewGuid()); + } + + /// + /// Copy files recursively from one directory to another. + /// + /// The source directory. + /// The target directory. + internal static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) + { + foreach (var dir in source.GetDirectories()) + CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name)); + + foreach (var file in source.GetFiles()) + file.CopyTo(Path.Combine(target.FullName, file.Name)); + } + + /// + /// Delete and recreate a directory. + /// + /// The directory to delete and recreate. + internal static void DeleteAndRecreateDirectory(DirectoryInfo dir) + { + if (dir.Exists) + { + dir.Delete(true); + } + + dir.Create(); + } +} diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 87cb86e1c..966fa1e11 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -604,10 +604,9 @@ public static class Util /// /// The path of the file to write to. /// The text to write. - public static void WriteAllTextSafe(string path, string text) - { - WriteAllTextSafe(path, text, Encoding.UTF8); - } + [Api13ToDo("Remove.")] + [Obsolete("Replaced with FilesystemUtil.WriteAllTextSafe()")] + public static void WriteAllTextSafe(string path, string text) => FilesystemUtil.WriteAllTextSafe(path, text); /// /// Overwrite text in a file by first writing it to a temporary file, and then @@ -616,10 +615,9 @@ public static class Util /// The path of the file to write to. /// The text to write. /// Encoding to use. - public static void WriteAllTextSafe(string path, string text, Encoding encoding) - { - WriteAllBytesSafe(path, encoding.GetBytes(text)); - } + [Api13ToDo("Remove.")] + [Obsolete("Replaced with FilesystemUtil.WriteAllTextSafe()")] + public static void WriteAllTextSafe(string path, string text, Encoding encoding) => FilesystemUtil.WriteAllTextSafe(path, text, encoding); /// /// Overwrite data in a file by first writing it to a temporary file, and then @@ -627,41 +625,9 @@ public static class Util /// /// The path of the file to write to. /// The data to write. - public static unsafe void WriteAllBytesSafe(string path, byte[] bytes) - { - ArgumentException.ThrowIfNullOrEmpty(path); - - // Open the temp file - var tempPath = path + ".tmp"; - - using var tempFile = Windows.Win32.PInvoke.CreateFile( - tempPath, - (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), - FILE_SHARE_MODE.FILE_SHARE_NONE, - null, - FILE_CREATION_DISPOSITION.CREATE_ALWAYS, - FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, - null); - - if (tempFile.IsInvalid) - throw new Win32Exception(); - - // Write the data - uint bytesWritten = 0; - if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan(bytes), &bytesWritten, null)) - throw new Win32Exception(); - - if (bytesWritten != bytes.Length) - throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); - - if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile)) - throw new Win32Exception(); - - tempFile.Close(); - - if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) - throw new Win32Exception(); - } + [Api13ToDo("Remove.")] + [Obsolete("Replaced with FilesystemUtil.WriteAllBytesSafe()")] + public static void WriteAllBytesSafe(string path, byte[] bytes) => FilesystemUtil.WriteAllBytesSafe(path, bytes); /// Gets a temporary file name, for use as the sourceFileName in /// . @@ -669,7 +635,7 @@ public static class Util /// A temporary file name that should be usable with . /// /// No write operation is done on the filesystem. - public static string GetTempFileNameForFileReplacement(string targetFile) + public static string GetReplaceableFileName(string targetFile) { Span buf = stackalloc byte[9]; Random.Shared.NextBytes(buf); From 34ca8b8f9974a342c9729cf9c425d277512e40d6 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 19:58:19 +0200 Subject: [PATCH 366/375] Fix updates failing due to flipped WorkingPluginId check --- Dalamud/Plugin/Internal/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index ccae6dd06..a20f87241 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -1432,8 +1432,8 @@ internal class PluginManager : IInternalDisposableService else { // If we are doing anything other than a fresh install, not having a workingPluginId is an error that must be fixed - if (inheritedWorkingPluginId != null) - throw new InvalidOperationException("Inherited WorkingPluginId must not be null"); + if (inheritedWorkingPluginId == null) + throw new ArgumentNullException(nameof(inheritedWorkingPluginId), "Inherited WorkingPluginId must not be null when updating"); } // Ensure that we have a testing opt-in for this plugin if we are installing a testing version From 5eee67889938827d14ce6e7204dc3df7f6b6b882 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 23:15:31 +0200 Subject: [PATCH 367/375] Unify body notice drawing logic, clear search when switching groups --- .../PluginInstaller/PluginInstallerWindow.cs | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 44516fc7e..1085df4cf 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1262,7 +1262,6 @@ internal class PluginInstallerWindow : Window, IDisposable if (filteredAvailableManifests.Count == 0) { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); return proxies; } @@ -1314,6 +1313,23 @@ internal class PluginInstallerWindow : Window, IDisposable } #pragma warning restore SA1201 +#pragma warning disable SA1204 + private static void DrawMutedBodyText(string text, float paddingBefore, float paddingAfter) +#pragma warning restore SA1204 + { + ImGuiHelpers.ScaledDummy(paddingBefore); + + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) + { + foreach (var line in text.Split('\n')) + { + ImGuiHelpers.CenteredText(line); + } + } + + ImGuiHelpers.ScaledDummy(paddingAfter); + } + private void DrawAvailablePluginList() { var i = 0; @@ -1344,6 +1360,24 @@ internal class PluginInstallerWindow : Window, IDisposable { this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.All; } + + using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)) + { + var hasSearch = !this.searchText.IsNullOrEmpty(); + + if (i == 0 && !hasSearch) + { + DrawMutedBodyText(Locs.TabBody_NoPluginsAvailable, 60, 20); + } + else if (i == 0 && hasSearch) + { + DrawMutedBodyText(Locs.TabBody_SearchNoMatching, 60, 20); + } + else if (hasSearch) + { + DrawMutedBodyText(Locs.TabBody_NoMoreResultsFor(this.searchText), 20, 20); + } + } } private void DrawInstalledPluginList(InstalledPluginListFilter filter) @@ -1353,7 +1387,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (pluginList.Count == 0) { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + DrawMutedBodyText(Locs.TabBody_SearchNoInstalled, 60, 20); return; } @@ -1363,7 +1397,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (filteredList.Count == 0) { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + DrawMutedBodyText(Locs.TabBody_SearchNoMatching, 60, 20); return; } @@ -1419,6 +1453,11 @@ internal class PluginInstallerWindow : Window, IDisposable } } } + else if (!this.searchText.IsNullOrEmpty()) + { + DrawMutedBodyText(Locs.TabBody_NoMoreResultsFor(this.searchText), 20, 20); + ImGuiHelpers.ScaledDummy(20); + } } private void DrawPluginCategories() @@ -1493,6 +1532,9 @@ internal class PluginInstallerWindow : Window, IDisposable if (!isCurrent) { this.categoryManager.CurrentGroupKind = groupInfo.GroupKind; + + // Reset search text when switching groups + this.searchText = string.Empty; } ImGui.Indent(); @@ -3978,6 +4020,8 @@ internal class PluginInstallerWindow : Window, IDisposable public static string TabBody_NoPluginsInstalled => string.Format(Loc.Localize("InstallerNoPluginsInstalled", "You don't have any plugins installed yet!\nYou can install them from the \"{0}\" tab."), PluginCategoryManager.Locs.Category_All); + public static string TabBody_NoPluginsAvailable => Loc.Localize("InstallerNoPluginsAvailable", "No plugins are available at the moment."); + public static string TabBody_NoPluginsUpdateable => Loc.Localize("InstallerNoPluginsUpdate", "No plugins have updates available at the moment."); public static string TabBody_NoPluginsDev => Loc.Localize("InstallerNoPluginsDev", "You don't have any dev plugins. Add them from the settings."); @@ -3992,6 +4036,8 @@ internal class PluginInstallerWindow : Window, IDisposable public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the \"All Plugins\" tab."); + public static string TabBody_NoMoreResultsFor(string query) => Loc.Localize("InstallerNoMoreResultsForQuery", "No more search results for \"{0}\".").Format(query); + public static string TabBody_ChangelogNone => Loc.Localize("InstallerNoChangelog", "None of your installed plugins have a changelog."); public static string TabBody_ChangelogError => Loc.Localize("InstallerChangelogError", "Could not download changelogs."); From 95bac801b28724a91b764c01e0952aac2c5c3fc4 Mon Sep 17 00:00:00 2001 From: goaaats Date: Tue, 1 Apr 2025 23:16:28 +0200 Subject: [PATCH 368/375] Remove 2025 april fools --- .../PluginInstaller/PluginInstallerWindow.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 1085df4cf..dfd37431c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -140,13 +140,6 @@ internal class PluginInstallerWindow : Window, IDisposable private string? staleDalamudNewVersion = null; - private int? aprilFoolsLineIdx = null; - private string[] aprilFoolsLines = - [ - "We sincerely apologize for this year's April Fool's joke.\nWe hope you enjoyed it, and we promise to do better next year.", - "Please do not feed the ducks.\nThey are not ducks.", - ]; - /// /// Initializes a new instance of the class. /// @@ -1620,14 +1613,6 @@ internal class PluginInstallerWindow : Window, IDisposable ImGuiHelpers.ScaledDummy(10); } - if (DateTime.Now is { Day: 1, Month: 4 }) - { - ImGuiHelpers.ScaledDummy(10); - this.aprilFoolsLineIdx ??= new Random().Next(0, this.aprilFoolsLines.Length); - DrawLinesCentered(this.aprilFoolsLines[this.aprilFoolsLineIdx.Value]); - ImGuiHelpers.ScaledDummy(10); - } - if (this.staleDalamudNewVersion != null) { DrawWarningIcon(); From d2cae32bc2e69ce31ff2fdc5dc9bc75f6ab76507 Mon Sep 17 00:00:00 2001 From: goaaats Date: Wed, 2 Apr 2025 22:38:28 +0200 Subject: [PATCH 369/375] AgingSteps => SelfTestSteps --- .../Windows/SelfTest/SelfTestWindow.cs | 66 +++++++++---------- .../ActorTableSelfTestStep.cs} | 4 +- .../AddonLifecycleSelfTestStep.cs} | 34 +++++----- .../AetheryteListSelfTestStep.cs} | 4 +- .../ChatSelfTestStep.cs} | 4 +- .../ConditionSelfTestStep.cs} | 4 +- .../ContextMenuSelfTestStep.cs} | 4 +- .../DutyStateSelfTestStep.cs} | 4 +- .../EnterTerritorySelfTestStep.cs} | 8 +-- .../FateTableSelfTestStep.cs} | 4 +- .../GameConfigSelfTestStep.cs} | 4 +- .../GamepadStateSelfTestStep.cs} | 4 +- .../HandledExceptionSelfTestStep.cs} | 4 +- .../HoverSelfTestStep.cs} | 4 +- .../IAgingStep.cs => Steps/ISelfTestStep.cs} | 4 +- .../ItemPayloadSelfTestStep.cs} | 4 +- .../KeyStateSelfTestStep.cs} | 4 +- .../LoginEventSelfTestStep.cs} | 4 +- .../LogoutEventSelfTestStep.cs} | 4 +- .../LuminaSelfTestStep.cs} | 4 +- .../MarketBoardSelfTestStep.cs} | 4 +- .../NamePlateSelfTestStep.cs} | 4 +- .../NounProcessorSelfTestStep.cs} | 4 +- .../PartyFinderSelfTestStep.cs} | 4 +- .../SeStringEvaluatorSelfTestStep.cs} | 4 +- .../SheetRedirectResolverSelfTestStep.cs} | 4 +- .../TargetSelfTestStep.cs} | 4 +- .../ToastSelfTestStep.cs} | 4 +- .../WaitFramesSelfTestStep.cs} | 8 +-- 29 files changed, 108 insertions(+), 108 deletions(-) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/ActorTableAgingStep.cs => Steps/ActorTableSelfTestStep.cs} (88%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/AddonLifecycleAgingStep.cs => Steps/AddonLifecycleSelfTestStep.cs} (93%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/AetheryteListAgingStep.cs => Steps/AetheryteListSelfTestStep.cs} (89%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/ChatAgingStep.cs => Steps/ChatSelfTestStep.cs} (93%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/ConditionAgingStep.cs => Steps/ConditionSelfTestStep.cs} (86%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/ContextMenuAgingStep.cs => Steps/ContextMenuSelfTestStep.cs} (99%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/DutyStateAgingStep.cs => Steps/DutyStateSelfTestStep.cs} (90%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/EnterTerritoryAgingStep.cs => Steps/EnterTerritorySelfTestStep.cs} (88%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/FateTableAgingStep.cs => Steps/FateTableSelfTestStep.cs} (89%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/GameConfigAgingStep.cs => Steps/GameConfigSelfTestStep.cs} (95%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/GamepadStateAgingStep.cs => Steps/GamepadStateSelfTestStep.cs} (93%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/HandledExceptionAgingStep.cs => Steps/HandledExceptionSelfTestStep.cs} (82%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/HoverAgingStep.cs => Steps/HoverSelfTestStep.cs} (91%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/IAgingStep.cs => Steps/ISelfTestStep.cs} (85%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/ItemPayloadAgingStep.cs => Steps/ItemPayloadSelfTestStep.cs} (97%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/KeyStateAgingStep.cs => Steps/KeyStateSelfTestStep.cs} (86%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/LoginEventAgingStep.cs => Steps/LoginEventSelfTestStep.cs} (90%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/LogoutEventAgingStep.cs => Steps/LogoutEventSelfTestStep.cs} (91%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/LuminaAgingStep.cs => Steps/LuminaSelfTestStep.cs} (90%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/MarketBoardAgingStep.cs => Steps/MarketBoardSelfTestStep.cs} (98%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/NamePlateAgingStep.cs => Steps/NamePlateSelfTestStep.cs} (97%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/NounProcessorAgingStep.cs => Steps/NounProcessorSelfTestStep.cs} (99%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/PartyFinderAgingStep.cs => Steps/PartyFinderSelfTestStep.cs} (92%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/SeStringEvaluatorAgingStep.cs => Steps/SeStringEvaluatorSelfTestStep.cs} (96%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/SheetRedirectResolverAgingStep.cs => Steps/SheetRedirectResolverSelfTestStep.cs} (97%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/TargetAgingStep.cs => Steps/TargetSelfTestStep.cs} (93%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/ToastAgingStep.cs => Steps/ToastSelfTestStep.cs} (81%) rename Dalamud/Interface/Internal/Windows/SelfTest/{AgingSteps/WaitFramesAgingStep.cs => Steps/WaitFramesSelfTestStep.cs} (72%) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 1be6f31a3..77d30b08e 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -4,7 +4,7 @@ using System.Numerics; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; -using Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +using Dalamud.Interface.Internal.Windows.SelfTest.Steps; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Logging.Internal; @@ -20,40 +20,40 @@ internal class SelfTestWindow : Window { private static readonly ModuleLog Log = new("AGING"); - private readonly List steps = + private readonly List steps = new() { - new LoginEventAgingStep(), - new WaitFramesAgingStep(1000), - new EnterTerritoryAgingStep(148, "Central Shroud"), - new ItemPayloadAgingStep(), - new ContextMenuAgingStep(), - new NamePlateAgingStep(), - new ActorTableAgingStep(), - new FateTableAgingStep(), - new AetheryteListAgingStep(), - new ConditionAgingStep(), - new ToastAgingStep(), - new TargetAgingStep(), - new KeyStateAgingStep(), - new GamepadStateAgingStep(), - new ChatAgingStep(), - new HoverAgingStep(), - new LuminaAgingStep(true), - new LuminaAgingStep(true), - new LuminaAgingStep(true), - new LuminaAgingStep(true), - new LuminaAgingStep(false), - new AddonLifecycleAgingStep(), - new PartyFinderAgingStep(), - new HandledExceptionAgingStep(), - new DutyStateAgingStep(), - new GameConfigAgingStep(), - new MarketBoardAgingStep(), - new SheetRedirectResolverAgingStep(), - new NounProcessorAgingStep(), - new SeStringEvaluatorAgingStep(), - new LogoutEventAgingStep(), + new LoginEventSelfTestStep(), + new WaitFramesSelfTestStep(1000), + new EnterTerritorySelfTestStep(148, "Central Shroud"), + new ItemPayloadSelfTestStep(), + new ContextMenuSelfTestStep(), + new NamePlateSelfTestStep(), + new ActorTableSelfTestStep(), + new FateTableSelfTestStep(), + new AetheryteListSelfTestStep(), + new ConditionSelfTestStep(), + new ToastSelfTestStep(), + new TargetSelfTestStep(), + new KeyStateSelfTestStep(), + new GamepadStateSelfTestStep(), + new ChatSelfTestStep(), + new HoverSelfTestStep(), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(false), + new AddonLifecycleSelfTestStep(), + new PartyFinderSelfTestStep(), + new HandledExceptionSelfTestStep(), + new DutyStateSelfTestStep(), + new GameConfigSelfTestStep(), + new MarketBoardSelfTestStep(), + new SheetRedirectResolverSelfTestStep(), + new NounProcessorSelfTestStep(), + new SeStringEvaluatorSelfTestStep(), + new LogoutEventSelfTestStep(), }; private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ActorTableSelfTestStep.cs similarity index 88% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ActorTableSelfTestStep.cs index 242e93a49..f246cb940 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ActorTableSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Actor Table. /// -internal class ActorTableAgingStep : IAgingStep +internal class ActorTableSelfTestStep : ISelfTestStep { private int index = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AddonLifecycleSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/AddonLifecycleSelfTestStep.cs index 28edab88a..458edfaff 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AddonLifecycleAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AddonLifecycleSelfTestStep.cs @@ -5,23 +5,23 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup AddonLifecycle Service. /// -internal class AddonLifecycleAgingStep : IAgingStep +internal class AddonLifecycleSelfTestStep : ISelfTestStep { private readonly List listeners; - + private AddonLifecycle? service; private TestStep currentStep = TestStep.CharacterRefresh; private bool listenersRegistered; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public AddonLifecycleAgingStep() + public AddonLifecycleSelfTestStep() { this.listeners = new List { @@ -33,7 +33,7 @@ internal class AddonLifecycleAgingStep : IAgingStep new(AddonEvent.PreFinalize, "Character", this.PreFinalize), }; } - + private enum TestStep { CharacterRefresh, @@ -44,10 +44,10 @@ internal class AddonLifecycleAgingStep : IAgingStep CharacterFinalize, Complete, } - + /// public string Name => "Test AddonLifecycle"; - + /// public SelfTestStepResult RunStep() { @@ -60,7 +60,7 @@ internal class AddonLifecycleAgingStep : IAgingStep { this.service.RegisterListener(listener); } - + this.listenersRegistered = true; } @@ -89,7 +89,7 @@ internal class AddonLifecycleAgingStep : IAgingStep // Nothing to report to tester. break; } - + return this.currentStep is TestStep.Complete ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; } @@ -101,32 +101,32 @@ internal class AddonLifecycleAgingStep : IAgingStep this.service?.UnregisterListener(listener); } } - + private void PostSetup(AddonEvent eventType, AddonArgs addonInfo) - { + { if (this.currentStep is TestStep.CharacterSetup) this.currentStep++; } - + private void PostUpdate(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterUpdate) this.currentStep++; } - + private void PostDraw(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterDraw) this.currentStep++; } - + private void PostRefresh(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterRefresh) this.currentStep++; } - + private void PostRequestedUpdate(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterRequestedUpdate) this.currentStep++; } - + private void PreFinalize(AddonEvent eventType, AddonArgs addonInfo) { if (this.currentStep is TestStep.CharacterFinalize) this.currentStep++; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AetheryteListSelfTestStep.cs similarity index 89% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/AetheryteListSelfTestStep.cs index 6a4519eab..207f718ff 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/AetheryteListAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/AetheryteListSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Aetherytes; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Aetheryte List. /// -internal class AetheryteListAgingStep : IAgingStep +internal class AetheryteListSelfTestStep : ISelfTestStep { private int index = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ChatSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ChatSelfTestStep.cs index 2fdd8c060..8ab1809ad 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ChatSelfTestStep.cs @@ -4,12 +4,12 @@ using Dalamud.Game.Text.SeStringHandling; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Chat. /// -internal class ChatAgingStep : IAgingStep +internal class ChatSelfTestStep : ISelfTestStep { private int step = 0; private bool subscribed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ConditionSelfTestStep.cs similarity index 86% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ConditionSelfTestStep.cs index 8ce2111c9..2d49fbdcd 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ConditionSelfTestStep.cs @@ -3,12 +3,12 @@ using Dalamud.Game.ClientState.Conditions; using ImGuiNET; using Serilog; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Condition. /// -internal class ConditionAgingStep : IAgingStep +internal class ConditionSelfTestStep : ISelfTestStep { /// public string Name => "Test Condition"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ContextMenuSelfTestStep.cs similarity index 99% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ContextMenuSelfTestStep.cs index 992448913..0e2f61aba 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ContextMenuAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ContextMenuSelfTestStep.cs @@ -12,12 +12,12 @@ using Lumina.Excel; using Lumina.Excel.Sheets; using Serilog; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Tests for context menu. /// -internal class ContextMenuAgingStep : IAgingStep +internal class ContextMenuSelfTestStep : ISelfTestStep { private SubStep currentSubStep; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/DutyStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/DutyStateSelfTestStep.cs similarity index 90% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/DutyStateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/DutyStateSelfTestStep.cs index 19e218ecb..4c33347fc 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/DutyStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/DutyStateSelfTestStep.cs @@ -2,12 +2,12 @@ using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the DutyState service class. /// -internal class DutyStateAgingStep : IAgingStep +internal class DutyStateSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/EnterTerritorySelfTestStep.cs similarity index 88% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/EnterTerritorySelfTestStep.cs index a61af7f94..04c8eda5e 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/EnterTerritorySelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Territory Change. /// -internal class EnterTerritoryAgingStep : IAgingStep +internal class EnterTerritorySelfTestStep : ISelfTestStep { private readonly ushort territory; private readonly string terriName; @@ -15,11 +15,11 @@ internal class EnterTerritoryAgingStep : IAgingStep private bool hasPassed = false; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The territory to check for. /// Name to show. - public EnterTerritoryAgingStep(ushort terri, string name) + public EnterTerritorySelfTestStep(ushort terri, string name) { this.terriName = name; this.territory = terri; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FateTableSelfTestStep.cs similarity index 89% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/FateTableSelfTestStep.cs index eef986302..982633dcb 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FateTableSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Fates; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Fate Table. /// -internal class FateTableAgingStep : IAgingStep +internal class FateTableSelfTestStep : ISelfTestStep { private byte index = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GameConfigAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GameConfigSelfTestStep.cs similarity index 95% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GameConfigAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/GameConfigSelfTestStep.cs index 97c163590..440ec9f5d 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GameConfigAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GameConfigSelfTestStep.cs @@ -2,12 +2,12 @@ using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test of GameConfig. /// -internal class GameConfigAgingStep : IAgingStep +internal class GameConfigSelfTestStep : ISelfTestStep { private bool started; private bool isStartedLegacy; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GamepadStateSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/GamepadStateSelfTestStep.cs index 323d82bbc..04684d521 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/GamepadStateSelfTestStep.cs @@ -7,12 +7,12 @@ using Lumina.Text.Payloads; using LSeStringBuilder = Lumina.Text.SeStringBuilder; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Gamepad State. /// -internal class GamepadStateAgingStep : IAgingStep +internal class GamepadStateSelfTestStep : ISelfTestStep { /// public string Name => "Test GamePadState"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HandledExceptionSelfTestStep.cs similarity index 82% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/HandledExceptionSelfTestStep.cs index 5d2173ad5..9757481e4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HandledExceptionSelfTestStep.cs @@ -1,11 +1,11 @@ using System.Runtime.InteropServices; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test dedicated to handling of Access Violations. /// -internal class HandledExceptionAgingStep : IAgingStep +internal class HandledExceptionSelfTestStep : ISelfTestStep { /// public string Name => "Test Handled Exception"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HoverSelfTestStep.cs similarity index 91% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/HoverSelfTestStep.cs index 8f509b8e7..85b5d3784 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/HoverSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.Gui; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Hover events. /// -internal class HoverAgingStep : IAgingStep +internal class HoverSelfTestStep : ISelfTestStep { private bool clearedItem = false; private bool clearedAction = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ISelfTestStep.cs similarity index 85% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ISelfTestStep.cs index 680961344..529d788ab 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ISelfTestStep.cs @@ -1,9 +1,9 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Interface for test implementations. /// -internal interface IAgingStep +internal interface ISelfTestStep { /// /// Gets the name of the test. diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs similarity index 97% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs index 1ccb5934f..0988413b0 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ItemPayloadAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ItemPayloadSelfTestStep.cs @@ -4,12 +4,12 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for item payloads. /// -internal class ItemPayloadAgingStep : IAgingStep +internal class ItemPayloadSelfTestStep : ISelfTestStep { private SubStep currentSubStep; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/KeyStateSelfTestStep.cs similarity index 86% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/KeyStateSelfTestStep.cs index 522943e87..667f3adc4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/KeyStateSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState.Keys; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the Key State. /// -internal class KeyStateAgingStep : IAgingStep +internal class KeyStateSelfTestStep : ISelfTestStep { /// public string Name => "Test KeyState"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LoginEventSelfTestStep.cs similarity index 90% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/LoginEventSelfTestStep.cs index d755e95ef..9540c636a 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LoginEventSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the login events. /// -internal class LoginEventAgingStep : IAgingStep +internal class LoginEventSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LogoutEventSelfTestStep.cs similarity index 91% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/LogoutEventSelfTestStep.cs index 723182111..13bcfcf35 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LogoutEventSelfTestStep.cs @@ -2,12 +2,12 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for the login events. /// -internal class LogoutEventAgingStep : IAgingStep +internal class LogoutEventSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LuminaSelfTestStep.cs similarity index 90% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/LuminaSelfTestStep.cs index 0f411b8f1..3fbc4361f 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/LuminaSelfTestStep.cs @@ -2,14 +2,14 @@ using Dalamud.Data; using Dalamud.Utility; using Lumina.Excel; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Lumina. /// /// ExcelRow to run test on. /// Whether or not the sheet is large. If it is large, the self test will iterate through the full sheet in one frame and benchmark the time taken. -internal class LuminaAgingStep(bool isLargeSheet) : IAgingStep +internal class LuminaSelfTestStep(bool isLargeSheet) : ISelfTestStep where T : struct, IExcelRow { private int step = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs similarity index 98% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs index 513141fa9..4a6dd185f 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/MarketBoardAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/MarketBoardSelfTestStep.cs @@ -5,12 +5,12 @@ using Dalamud.Game.MarketBoard; using Dalamud.Game.Network.Structures; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Tests the various market board events. /// -internal class MarketBoardAgingStep : IAgingStep +internal class MarketBoardSelfTestStep : ISelfTestStep { private SubStep currentSubStep; private bool eventsSubscribed; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NamePlateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NamePlateSelfTestStep.cs similarity index 97% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NamePlateAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/NamePlateSelfTestStep.cs index 5a03a6dc2..e7ce8e42a 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NamePlateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NamePlateSelfTestStep.cs @@ -6,12 +6,12 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Tests for nameplates. /// -internal class NamePlateAgingStep : IAgingStep +internal class NamePlateSelfTestStep : ISelfTestStep { private SubStep currentSubStep; private Dictionary? updateCount; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NounProcessorSelfTestStep.cs similarity index 99% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/NounProcessorSelfTestStep.cs index 4bea503d9..7319eec7b 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/NounProcessorAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/NounProcessorSelfTestStep.cs @@ -6,12 +6,12 @@ using ImGuiNET; using LSheets = Lumina.Excel.Sheets; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for NounProcessor. /// -internal class NounProcessorAgingStep : IAgingStep +internal class NounProcessorSelfTestStep : ISelfTestStep { private NounTestEntry[] tests = [ diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/PartyFinderSelfTestStep.cs similarity index 92% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/PartyFinderSelfTestStep.cs index d6f38092b..475412e70 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/PartyFinderSelfTestStep.cs @@ -3,12 +3,12 @@ using Dalamud.Game.Gui.PartyFinder.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for Party Finder events. /// -internal class PartyFinderAgingStep : IAgingStep +internal class PartyFinderSelfTestStep : ISelfTestStep { private bool subscribed = false; private bool hasPassed = false; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs similarity index 96% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs index 3a0a7d546..423a56172 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SeStringEvaluatorAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SeStringEvaluatorSelfTestStep.cs @@ -5,12 +5,12 @@ using ImGuiNET; using Lumina.Text.ReadOnly; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for SeStringEvaluator. /// -internal class SeStringEvaluatorAgingStep : IAgingStep +internal class SeStringEvaluatorSelfTestStep : ISelfTestStep { private int step = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs similarity index 97% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs index 0c9dc763f..6ab08cd91 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/SheetRedirectResolverAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/SheetRedirectResolverSelfTestStep.cs @@ -8,12 +8,12 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for SheetRedirectResolver. /// -internal class SheetRedirectResolverAgingStep : IAgingStep +internal class SheetRedirectResolverSelfTestStep : ISelfTestStep { private RedirectEntry[] redirects = [ diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/TargetSelfTestStep.cs similarity index 93% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/TargetSelfTestStep.cs index f8767ad53..b56b08ed5 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/TargetSelfTestStep.cs @@ -4,12 +4,12 @@ using Dalamud.Game.ClientState.Objects.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for targets. /// -internal class TargetAgingStep : IAgingStep +internal class TargetSelfTestStep : ISelfTestStep { private int step = 0; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs similarity index 81% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs index f02eafc99..1c82e274c 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs @@ -1,11 +1,11 @@ using Dalamud.Game.Gui.Toast; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test setup for toasts. /// -internal class ToastAgingStep : IAgingStep +internal class ToastSelfTestStep : ISelfTestStep { /// public string Name => "Test Toasts"; diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/WaitFramesSelfTestStep.cs similarity index 72% rename from Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs rename to Dalamud/Interface/Internal/Windows/SelfTest/Steps/WaitFramesSelfTestStep.cs index 54aeee145..35c64376d 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/WaitFramesSelfTestStep.cs @@ -1,18 +1,18 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// /// Test that waits N frames. /// -internal class WaitFramesAgingStep : IAgingStep +internal class WaitFramesSelfTestStep : ISelfTestStep { private readonly int frames; private int cFrames; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Amount of frames to wait. - public WaitFramesAgingStep(int frames) + public WaitFramesSelfTestStep(int frames) { this.frames = frames; this.cFrames = frames; From 7286d9714c4d8ff427d4eadabf9c1a9519f78234 Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 3 Apr 2025 00:03:16 +0200 Subject: [PATCH 370/375] Add question to toast self test --- .../SelfTest/Steps/ToastSelfTestStep.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs index 1c82e274c..cd34fa30b 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/ToastSelfTestStep.cs @@ -1,5 +1,7 @@ using Dalamud.Game.Gui.Toast; +using ImGuiNET; + namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// @@ -7,23 +9,42 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// internal class ToastSelfTestStep : ISelfTestStep { + private bool sentToasts = false; + /// public string Name => "Test Toasts"; /// public SelfTestStepResult RunStep() { - var toastGui = Service.Get(); + if (!this.sentToasts) + { + var toastGui = Service.Get(); + toastGui.ShowNormal("Normal Toast"); + toastGui.ShowError("Error Toast"); + toastGui.ShowQuest("Quest Toast"); + this.sentToasts = true; + } - toastGui.ShowNormal("Normal Toast"); - toastGui.ShowError("Error Toast"); + ImGui.Text("Did you see a normal toast, a quest toast and an error toast?"); - return SelfTestStepResult.Pass; + if (ImGui.Button("Yes")) + { + return SelfTestStepResult.Pass; + } + + ImGui.SameLine(); + if (ImGui.Button("No")) + { + return SelfTestStepResult.Fail; + } + + return SelfTestStepResult.Waiting; } /// public void CleanUp() { - // ignored + this.sentToasts = false; } } From 810611e08603dc0492c5e7a53fa10aa0eae988da Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 3 Apr 2025 00:17:20 +0200 Subject: [PATCH 371/375] Add "jump to" button to self-test window --- .../Windows/SelfTest/SelfTestWindow.cs | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 77d30b08e..9fbb4438c 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -6,6 +6,7 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.Windows.SelfTest.Steps; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Logging.Internal; using ImGuiNET; @@ -56,7 +57,7 @@ internal class SelfTestWindow : Window new LogoutEventSelfTestStep(), }; - private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); + private readonly Dictionary testIndexToResult = new(); private bool selfTestRunning = false; private int currentStep = 0; @@ -89,7 +90,7 @@ internal class SelfTestWindow : Window if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward)) { - this.stepResults.Add((SelfTestStepResult.NotRan, null)); + this.testIndexToResult.Add(this.currentStep, (SelfTestStepResult.NotRan, null)); this.steps[this.currentStep].CleanUp(); this.currentStep++; this.lastTestStart = DateTimeOffset.Now; @@ -106,7 +107,7 @@ internal class SelfTestWindow : Window { this.selfTestRunning = true; this.currentStep = 0; - this.stepResults.Clear(); + this.testIndexToResult.Clear(); this.lastTestStart = DateTimeOffset.Now; } } @@ -128,7 +129,7 @@ internal class SelfTestWindow : Window this.StopTests(); } - if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail)) + if (this.testIndexToResult.Any(x => x.Value.Result == SelfTestStepResult.Fail)) { ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!"); } @@ -168,8 +169,8 @@ internal class SelfTestWindow : Window if (result != SelfTestStepResult.Waiting) { var duration = DateTimeOffset.Now - this.lastTestStart; + this.testIndexToResult.Add(this.currentStep, (result, duration)); this.currentStep++; - this.stepResults.Add((result, duration)); this.lastTestStart = DateTimeOffset.Now; } @@ -177,12 +178,13 @@ internal class SelfTestWindow : Window private void DrawResultTable() { - if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders)) + if (ImGui.BeginTable("agingResultTable", 5, ImGuiTableFlags.Borders)) { - ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f); + ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("Name"); - ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f); - ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f); + ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, 30f * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); @@ -197,11 +199,10 @@ internal class SelfTestWindow : Window ImGui.TableSetColumnIndex(1); ImGui.Text(step.Name); - ImGui.TableSetColumnIndex(2); - ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont); - if (this.stepResults.Count > i) + if (this.testIndexToResult.TryGetValue(i, out var result)) { - var result = this.stepResults[i]; + ImGui.TableSetColumnIndex(2); + ImGui.PushFont(InterfaceManager.MonoFont); switch (result.Result) { @@ -215,9 +216,18 @@ internal class SelfTestWindow : Window ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); break; } + + ImGui.PopFont(); + + ImGui.TableSetColumnIndex(3); + if (result.Duration.HasValue) + { + ImGui.TextUnformatted(result.Duration.Value.ToString("g")); + } } else { + ImGui.TableSetColumnIndex(2); if (this.selfTestRunning && this.currentStep == i) { ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT"); @@ -226,27 +236,29 @@ internal class SelfTestWindow : Window { ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); } - } - ImGui.PopFont(); - - ImGui.TableSetColumnIndex(3); - if (this.stepResults.Count > i) - { - var (_, duration) = this.stepResults[i]; - - if (duration.HasValue) - { - ImGui.TextUnformatted(duration.Value.ToString("g")); - } - } - else - { + ImGui.TableSetColumnIndex(3); if (this.selfTestRunning && this.currentStep == i) { ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g")); } } + + ImGui.TableSetColumnIndex(4); + using var id = ImRaii.PushId($"selfTest{i}"); + if (ImGuiComponents.IconButton(FontAwesomeIcon.FastForward)) + { + this.StopTests(); + this.testIndexToResult.Remove(i); + this.currentStep = i; + this.selfTestRunning = true; + this.lastTestStart = DateTimeOffset.Now; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Jump to this test"); + } } ImGui.EndTable(); From ca438b6e69112fb0192df25530de177864229e11 Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 3 Apr 2025 00:32:37 +0200 Subject: [PATCH 372/375] Add self-test for framework task scheduling --- .../Windows/SelfTest/SelfTestWindow.cs | 68 +++++++++---------- .../FrameworkTaskSchedulerSelfTestStep.cs | 64 +++++++++++++++++ 2 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 9fbb4438c..b6f08edf6 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -22,40 +22,40 @@ internal class SelfTestWindow : Window private static readonly ModuleLog Log = new("AGING"); private readonly List steps = - new() - { - new LoginEventSelfTestStep(), - new WaitFramesSelfTestStep(1000), - new EnterTerritorySelfTestStep(148, "Central Shroud"), - new ItemPayloadSelfTestStep(), - new ContextMenuSelfTestStep(), - new NamePlateSelfTestStep(), - new ActorTableSelfTestStep(), - new FateTableSelfTestStep(), - new AetheryteListSelfTestStep(), - new ConditionSelfTestStep(), - new ToastSelfTestStep(), - new TargetSelfTestStep(), - new KeyStateSelfTestStep(), - new GamepadStateSelfTestStep(), - new ChatSelfTestStep(), - new HoverSelfTestStep(), - new LuminaSelfTestStep(true), - new LuminaSelfTestStep(true), - new LuminaSelfTestStep(true), - new LuminaSelfTestStep(true), - new LuminaSelfTestStep(false), - new AddonLifecycleSelfTestStep(), - new PartyFinderSelfTestStep(), - new HandledExceptionSelfTestStep(), - new DutyStateSelfTestStep(), - new GameConfigSelfTestStep(), - new MarketBoardSelfTestStep(), - new SheetRedirectResolverSelfTestStep(), - new NounProcessorSelfTestStep(), - new SeStringEvaluatorSelfTestStep(), - new LogoutEventSelfTestStep(), - }; + [ + new LoginEventSelfTestStep(), + new WaitFramesSelfTestStep(1000), + new FrameworkTaskSchedulerSelfTestStep(), + new EnterTerritorySelfTestStep(148, "Central Shroud"), + new ItemPayloadSelfTestStep(), + new ContextMenuSelfTestStep(), + new NamePlateSelfTestStep(), + new ActorTableSelfTestStep(), + new FateTableSelfTestStep(), + new AetheryteListSelfTestStep(), + new ConditionSelfTestStep(), + new ToastSelfTestStep(), + new TargetSelfTestStep(), + new KeyStateSelfTestStep(), + new GamepadStateSelfTestStep(), + new ChatSelfTestStep(), + new HoverSelfTestStep(), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(true), + new LuminaSelfTestStep(false), + new AddonLifecycleSelfTestStep(), + new PartyFinderSelfTestStep(), + new HandledExceptionSelfTestStep(), + new DutyStateSelfTestStep(), + new GameConfigSelfTestStep(), + new MarketBoardSelfTestStep(), + new SheetRedirectResolverSelfTestStep(), + new NounProcessorSelfTestStep(), + new SeStringEvaluatorSelfTestStep(), + new LogoutEventSelfTestStep() + ]; private readonly Dictionary testIndexToResult = new(); diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs new file mode 100644 index 000000000..2376de75b --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; + +using Dalamud.Game; +using Dalamud.Utility; + +namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; + +/// +/// Test setup for Framework task scheduling. +/// +internal class FrameworkTaskSchedulerSelfTestStep : ISelfTestStep +{ + private bool passed = false; + private Task? task; + + /// + public string Name => "Test Framework Task Scheduler"; + + /// + public SelfTestStepResult RunStep() + { + var framework = Service.Get(); + + this.task ??= Task.Run(async () => + { + ThreadSafety.AssertNotMainThread(); + + await framework.RunOnTick(async () => + { + ThreadSafety.AssertMainThread(); + await Task.Delay(100); + + // TODO: We used to be scheduled back to the framework thread here. Is that by design? + //ThreadSafety.AssertMainThread(); + ThreadSafety.AssertNotMainThread(); + }).ConfigureAwait(false); + + ThreadSafety.AssertNotMainThread(); + + await framework.RunOnTick(() => + { + ThreadSafety.AssertMainThread(); + }); + + ThreadSafety.AssertMainThread(); + + this.passed = true; + }); + + if (this.task is { IsFaulted: true } or { IsCanceled: true }) + { + return SelfTestStepResult.Fail; + } + + return this.passed ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + this.passed = false; + this.task = null; + } +} From c1805bd510615a12c235a543d365c95b95af2f88 Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 3 Apr 2025 00:37:20 +0200 Subject: [PATCH 373/375] Fix warning --- .../SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs index 2376de75b..86fc8e03a 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs @@ -30,8 +30,8 @@ internal class FrameworkTaskSchedulerSelfTestStep : ISelfTestStep ThreadSafety.AssertMainThread(); await Task.Delay(100); - // TODO: We used to be scheduled back to the framework thread here. Is that by design? - //ThreadSafety.AssertMainThread(); + // TODO: Should we be scheduled back to the framework thread here? + // ThreadSafety.AssertMainThread(); ThreadSafety.AssertNotMainThread(); }).ConfigureAwait(false); From f812bf8f096252bacaa0195dbcf79d1a931213ec Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 3 Apr 2025 00:53:28 +0200 Subject: [PATCH 374/375] Correctly test Framework Run vs. RunOnTick --- .../FrameworkTaskSchedulerSelfTestStep.cs | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs index 86fc8e03a..d952be419 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/Steps/FrameworkTaskSchedulerSelfTestStep.cs @@ -3,6 +3,10 @@ using System.Threading.Tasks; using Dalamud.Game; using Dalamud.Utility; +using Microsoft.VisualBasic.Logging; + +using Log = Serilog.Log; + namespace Dalamud.Interface.Internal.Windows.SelfTest.Steps; /// @@ -25,15 +29,29 @@ internal class FrameworkTaskSchedulerSelfTestStep : ISelfTestStep { ThreadSafety.AssertNotMainThread(); + await framework.Run(async () => + { + ThreadSafety.AssertMainThread(); + + await Task.Delay(100).ConfigureAwait(true); + ThreadSafety.AssertMainThread(); + + await Task.Delay(100).ConfigureAwait(false); + ThreadSafety.AssertNotMainThread(); + }).ConfigureAwait(true); + + ThreadSafety.AssertNotMainThread(); + await framework.RunOnTick(async () => { ThreadSafety.AssertMainThread(); - await Task.Delay(100); - // TODO: Should we be scheduled back to the framework thread here? - // ThreadSafety.AssertMainThread(); + await Task.Delay(100).ConfigureAwait(true); ThreadSafety.AssertNotMainThread(); - }).ConfigureAwait(false); + + await Task.Delay(100).ConfigureAwait(false); + ThreadSafety.AssertNotMainThread(); + }).ConfigureAwait(true); ThreadSafety.AssertNotMainThread(); @@ -45,7 +63,14 @@ internal class FrameworkTaskSchedulerSelfTestStep : ISelfTestStep ThreadSafety.AssertMainThread(); this.passed = true; - }); + }).ContinueWith( + t => + { + if (t.IsFaulted) + { + Log.Error(t.Exception, "Framework Task scheduler test failed"); + } + }); if (this.task is { IsFaulted: true } or { IsCanceled: true }) { From 97b01d697b9e409e19a2a2a11d4a7b251629cecc Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 3 Apr 2025 19:15:12 +0200 Subject: [PATCH 375/375] Delay IM hook setup to after the game sets up rendering This seems to (hack)fix some odd behavior around shader caching as reported by various users. --- .../Interface/Internal/InterfaceManager.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 1cd45f4f4..ed27a1043 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -30,9 +30,12 @@ using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing.Persistence; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; +using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.Timing; +using FFXIVClientStructs.FFXIV.Client.Graphics.Environment; + using ImGuiNET; using ImGuiScene; @@ -119,6 +122,7 @@ internal partial class InterfaceManager : IInternalDisposableService [ServiceManager.ServiceConstructor] private InterfaceManager() { + this.framework.Update += this.FrameworkOnUpdate; } [UnmanagedFunctionPointer(CallingConvention.StdCall)] @@ -507,13 +511,26 @@ internal partial class InterfaceManager : IInternalDisposableService { var im = Service.GetNullable(); if (im?.dalamudAtlas is not { } atlas) - throw new InvalidOperationException($"Tried to access fonts before {nameof(ContinueConstruction)} call."); + throw new InvalidOperationException($"Tried to access fonts before {nameof(SetupHooks)} call."); if (!atlas.HasBuiltAtlas) atlas.BuildTask.GetAwaiter().GetResult(); return im; } + private unsafe void FrameworkOnUpdate(IFramework framework1) + { + // We now delay hooking until Framework is set up and has fired its first update. + // Some graphics drivers seem to consider the game's shader cache as invalid if we hook too early. + // The game loads shader packages on the file thread and then compiles them. It will show the logo once it is done. + // This is a workaround, but it fixes an issue where the game would take a very long time to get to the title screen. + if (EnvManager.Instance() == null) + return; + + this.SetupHooks(Service.Get(), Service.Get()); + this.framework.Update -= this.FrameworkOnUpdate; + } + /// Checks if the provided swap chain is the target that Dalamud should draw its interface onto, /// and initializes ImGui for drawing. /// The swap chain to test and initialize ImGui with if conditions are met. @@ -748,9 +765,7 @@ internal partial class InterfaceManager : IInternalDisposableService } } - [ServiceManager.CallWhenServicesReady( - "InterfaceManager accepts event registration and stuff even when the game window is not ready.")] - private unsafe void ContinueConstruction( + private unsafe void SetupHooks( TargetSigScanner sigScanner, FontAtlasFactory fontAtlasFactory) {