mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add error handlings for UldWidget (#2011)
* Add error handlings for UldWidget * fixes
This commit is contained in:
parent
7e0c97f59e
commit
cddad72066
7 changed files with 444 additions and 213 deletions
|
|
@ -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<T>(filePath.Category, filePath) : default;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<T> GetFileAsync<T>(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<T>(filePath.Category, filePath) ?? throw new FileNotFoundException(
|
||||
"Failed to load file, most likely because the file could not be found."),
|
||||
cancellationToken)
|
||||
: Task.FromException<T>(new FileNotFoundException("The file could not be found."));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool FileExists(string path)
|
||||
=> this.GameData.FileExists(path);
|
||||
|
|
|
|||
|
|
@ -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<string, long>? textCache;
|
||||
|
||||
|
|
@ -116,8 +118,8 @@ public class SigScanner : IDisposable, ISigScanner
|
|||
/// <returns>The found offset.</returns>
|
||||
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
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritddoc/>
|
||||
public nint[] ScanAllText(string signature)
|
||||
/// <inheritdoc/>
|
||||
public nint[] ScanAllText(string signature) => this.ScanAllText(signature, default).ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<nint> ScanAllText(string signature, CancellationToken cancellationToken)
|
||||
{
|
||||
var (needle, mask, badShift) = ParseSignature(signature);
|
||||
var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase;
|
||||
var ret = new List<nint>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>Useful functions for implementing data window widgets.</summary>
|
||||
internal static class DataWindowWidgetExtensions
|
||||
{
|
||||
/// <summary>Draws a text column, and make it copiable by clicking.</summary>
|
||||
/// <param name="widget">Owner widget.</param>
|
||||
/// <param name="s">String to display.</param>
|
||||
/// <param name="alignRight">Whether to align to right.</param>
|
||||
/// <param name="framepad">Whether to offset to frame padding.</param>
|
||||
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<NotificationManager>.Get().AddNotification(
|
||||
$"Copied {ImGui.TableGetColumnName()} to clipboard.",
|
||||
widget.DisplayName,
|
||||
NotificationType.Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NotificationManager>.Get().AddNotification(
|
||||
$"Copied {ImGui.TableGetColumnName()} to clipboard.",
|
||||
this.DisplayName,
|
||||
NotificationType.Success);
|
||||
}
|
||||
}
|
||||
|
||||
private record TextureEntry(
|
||||
IDalamudTextureWrap? SharedResource = null,
|
||||
Task<IDalamudTextureWrap>? Api10 = null,
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
/// </summary>
|
||||
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<string[]>? 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<UldFile>? selectedUldFileTask;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; } = { "uld" };
|
||||
public string[]? CommandShortcuts { get; init; } = ["uld"];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "ULD";
|
||||
|
|
@ -47,63 +70,241 @@ internal class UldWidget : IDataWindowWidget
|
|||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<DataManager>.Get();
|
||||
var textureManager = Service<TextureManager>.Get();
|
||||
|
||||
var uld = dataManager.GetFile<UldFile>(uldString[this.selectedUld].Loc);
|
||||
|
||||
if (uld == null)
|
||||
UldFile uld;
|
||||
switch (this.selectedUldFileTask ??=
|
||||
dataManager.GetFileAsync<UldFile>($"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>(T smth) => smth;
|
||||
}
|
||||
|
||||
private unsafe void DrawTextureEntry(UldRoot.TextureEntry textureEntry)
|
||||
/// <summary>
|
||||
/// Gets all known ULD locations in the game based on a few signatures.
|
||||
/// </summary>
|
||||
/// <returns>Uld locations.</returns>
|
||||
private static Task<string[]> 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<string>(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<uint>(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<T>(ref Task<T>? 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<char> 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<TexFile>(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<TexFile>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the raw data for the ULD widget.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all known ULD locations in the game based on a few signatures.
|
||||
/// </summary>
|
||||
/// <returns>Uld locations.</returns>
|
||||
internal static (string Display, string Loc)[] GetUldStrings()
|
||||
{
|
||||
if (uldStrings == null)
|
||||
ParseUldStrings();
|
||||
|
||||
return uldStrings!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the ULD strings.
|
||||
/// </summary>
|
||||
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<string>(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<uint>(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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game;
|
||||
|
||||
using Lumina;
|
||||
|
|
@ -61,6 +64,16 @@ public interface IDataManager
|
|||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||
public T? GetFile<T>(string path) where T : FileResource;
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of resource.</typeparam>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> containing the <see cref="FileResource"/> of the file on success.
|
||||
/// </returns>
|
||||
public Task<T> GetFileAsync<T>(string path, CancellationToken cancellationToken) where T : FileResource;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the file with the given path exists within the game's index files.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Dalamud.Game;
|
||||
|
||||
|
|
@ -153,4 +155,12 @@ public interface ISigScanner
|
|||
/// <param name="signature">The Signature.</param>
|
||||
/// <returns>The list of real offsets of the found elements based on signature.</returns>
|
||||
public nint[] ScanAllText(string signature);
|
||||
|
||||
/// <summary>
|
||||
/// Scan for all matching byte signatures in the .text section.
|
||||
/// </summary>
|
||||
/// <param name="signature">The Signature.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Enumerable yielding the real offsets of the found elements based on signature.</returns>
|
||||
public IEnumerable<nint> ScanAllText(string signature, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue