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