mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 20:54:16 +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.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.IoC;
|
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;
|
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/>
|
/// <inheritdoc/>
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
=> this.GameData.FileExists(path);
|
=> this.GameData.FileExists(path);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
using Iced.Intel;
|
using Iced.Intel;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -21,8 +23,8 @@ public class SigScanner : IDisposable, ISigScanner
|
||||||
{
|
{
|
||||||
private readonly FileInfo? cacheFile;
|
private readonly FileInfo? cacheFile;
|
||||||
|
|
||||||
private IntPtr moduleCopyPtr;
|
private nint moduleCopyPtr;
|
||||||
private long moduleCopyOffset;
|
private nint moduleCopyOffset;
|
||||||
|
|
||||||
private ConcurrentDictionary<string, long>? textCache;
|
private ConcurrentDictionary<string, long>? textCache;
|
||||||
|
|
||||||
|
|
@ -116,8 +118,8 @@ public class SigScanner : IDisposable, ISigScanner
|
||||||
/// <returns>The found offset.</returns>
|
/// <returns>The found offset.</returns>
|
||||||
public static IntPtr Scan(IntPtr baseAddress, int size, string signature)
|
public static IntPtr Scan(IntPtr baseAddress, int size, string signature)
|
||||||
{
|
{
|
||||||
var (needle, mask) = ParseSignature(signature);
|
var (needle, mask, badShift) = ParseSignature(signature);
|
||||||
var index = IndexOf(baseAddress, size, needle, mask);
|
var index = IndexOf(baseAddress, size, needle, mask, badShift);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
throw new KeyNotFoundException($"Can't find a signature of {signature}");
|
throw new KeyNotFoundException($"Can't find a signature of {signature}");
|
||||||
return baseAddress + index;
|
return baseAddress + index;
|
||||||
|
|
@ -310,32 +312,29 @@ public class SigScanner : IDisposable, ISigScanner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritddoc/>
|
/// <inheritdoc/>
|
||||||
public nint[] ScanAllText(string signature)
|
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 mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase;
|
||||||
var ret = new List<nint>();
|
|
||||||
while (mBase < this.TextSectionBase + this.TextSectionSize)
|
while (mBase < this.TextSectionBase + this.TextSectionSize)
|
||||||
{
|
{
|
||||||
try
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
{
|
|
||||||
var scanRet = Scan(mBase, this.TextSectionSize, signature);
|
var index = IndexOf(mBase, this.TextSectionSize, needle, mask, badShift);
|
||||||
if (scanRet == IntPtr.Zero)
|
if (index < 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
var scanRet = mBase + index;
|
||||||
if (this.IsCopy)
|
if (this.IsCopy)
|
||||||
scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
|
scanRet -= this.moduleCopyOffset;
|
||||||
|
|
||||||
ret.Add(scanRet);
|
yield return scanRet;
|
||||||
mBase = scanRet + 1;
|
mBase = scanRet + 1;
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -384,7 +383,7 @@ public class SigScanner : IDisposable, ISigScanner
|
||||||
return IntPtr.Add(sigLocation, 5 + jumpOffset);
|
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);
|
signature = signature.Replace(" ", string.Empty);
|
||||||
if (signature.Length % 2 != 0)
|
if (signature.Length % 2 != 0)
|
||||||
|
|
@ -407,14 +406,13 @@ public class SigScanner : IDisposable, ISigScanner
|
||||||
mask[i] = false;
|
mask[i] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (needle, mask);
|
return (needle, mask, BuildBadCharTable(needle, mask));
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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;
|
if (needle.Length > bufferLength) return -1;
|
||||||
var badShift = BuildBadCharTable(needle, mask);
|
|
||||||
var last = needle.Length - 1;
|
var last = needle.Length - 1;
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var maxoffset = bufferLength - needle.Length;
|
var maxoffset = bufferLength - needle.Length;
|
||||||
|
|
@ -513,7 +511,7 @@ public class SigScanner : IDisposable, ISigScanner
|
||||||
this.Module.ModuleMemorySize,
|
this.Module.ModuleMemorySize,
|
||||||
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()
|
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.Configuration.Internal;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
|
||||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
|
||||||
using Dalamud.Interface.Textures;
|
using Dalamud.Interface.Textures;
|
||||||
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
|
@ -457,7 +455,7 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
this.TextCopiable($"0x{wrap.ResourceAddress:X}", true, true);
|
this.TextColumnCopiable($"0x{wrap.ResourceAddress:X}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
|
||||||
|
|
@ -476,24 +474,24 @@ internal class TexWidget : IDataWindowWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
this.TextCopiable(wrap.Name, false, true);
|
this.TextColumnCopiable(wrap.Name, false, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
this.TextCopiable($"{wrap.Width:n0}", true, true);
|
this.TextColumnCopiable($"{wrap.Width:n0}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
this.TextCopiable($"{wrap.Height:n0}", true, true);
|
this.TextColumnCopiable($"{wrap.Height:n0}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
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();
|
ImGui.TableNextColumn();
|
||||||
var bytes = wrap.RawSpecs.EstimatedBytes;
|
var bytes = wrap.RawSpecs.EstimatedBytes;
|
||||||
this.TextCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true);
|
this.TextColumnCopiable(bytes < 0 ? "?" : $"{bytes:n0}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
lock (wrap.OwnerPlugins)
|
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();
|
ImGui.PopID();
|
||||||
}
|
}
|
||||||
|
|
@ -570,16 +568,16 @@ internal class TexWidget : IDataWindowWidget
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
this.TextCopiable($"{texture.InstanceIdForDebug:n0}", true, true);
|
this.TextColumnCopiable($"{texture.InstanceIdForDebug:n0}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
this.TextCopiable(texture.SourcePathForDebug, false, true);
|
this.TextColumnCopiable(texture.SourcePathForDebug, false, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
this.TextCopiable($"{texture.RefCountForDebug:n0}", true, true);
|
this.TextColumnCopiable($"{texture.RefCountForDebug:n0}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
this.TextCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true);
|
this.TextColumnCopiable(remain <= 0 ? "-" : $"{remain:00.000}", true, true);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Save))
|
||||||
|
|
@ -864,47 +862,6 @@ internal class TexWidget : IDataWindowWidget
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
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(
|
private record TextureEntry(
|
||||||
IDalamudTextureWrap? SharedResource = null,
|
IDalamudTextureWrap? SharedResource = null,
|
||||||
Task<IDalamudTextureWrap>? Api10 = null,
|
Task<IDalamudTextureWrap>? Api10 = null,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,15 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.Textures.Internal;
|
using Dalamud.Interface.Textures.Internal;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -22,21 +27,39 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class UldWidget : IDataWindowWidget
|
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 selectedUld;
|
||||||
private int selectedFrameData;
|
private int selectedFrameData;
|
||||||
private int selectedTimeline;
|
private int selectedTimeline;
|
||||||
private int selectedParts;
|
private int selectedParts;
|
||||||
private int selectedUldStyle;
|
private int selectedTheme;
|
||||||
// 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 Task<UldFile>? selectedUldFileTask;
|
||||||
private (string Display, string Location)[] uldStyles = [
|
|
||||||
("Dark", "uld/"),
|
|
||||||
("Light", "uld/light/"),
|
|
||||||
("Classic FF", "uld/third/"),
|
|
||||||
("Clear Blue", "uld/fourth/")
|
|
||||||
];
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string[]? CommandShortcuts { get; init; } = { "uld" };
|
public string[]? CommandShortcuts { get; init; } = ["uld"];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string DisplayName { get; init; } = "ULD";
|
public string DisplayName { get; init; } = "ULD";
|
||||||
|
|
@ -47,63 +70,241 @@ internal class UldWidget : IDataWindowWidget
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Load()
|
public void Load()
|
||||||
{
|
{
|
||||||
UldWidgetData.ReloadStrings();
|
this.cts?.Cancel();
|
||||||
|
ClearTask(ref this.uldNamesTask);
|
||||||
|
this.uldNamesTask = null;
|
||||||
|
this.cts = new();
|
||||||
|
|
||||||
this.Ready = true;
|
this.Ready = true;
|
||||||
|
this.selectedUld = this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0;
|
||||||
|
this.selectedTheme = 0;
|
||||||
|
this.selectedUldFileTask = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
var uldString = UldWidgetData.GetUldStrings();
|
string[] uldNames;
|
||||||
if (ImGui.Combo("Select Uld", ref this.selectedUld, uldString.Select(t => t.Display).ToArray(), uldString.Length))
|
var ct = (this.cts ??= new()).Token;
|
||||||
this.selectedFrameData = this.selectedTimeline = this.selectedParts = 0; // reset selected parts when changing ULD
|
switch (this.uldNamesTask ??= ParseUldStringsAsync(ct))
|
||||||
ImGui.Combo("Uld theme", ref this.selectedUldStyle, this.uldStyles.Select(t => t.Display).ToArray(), this.uldStyles.Length);
|
{
|
||||||
|
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 dataManager = Service<DataManager>.Get();
|
||||||
var textureManager = Service<TextureManager>.Get();
|
var textureManager = Service<TextureManager>.Get();
|
||||||
|
|
||||||
var uld = dataManager.GetFile<UldFile>(uldString[this.selectedUld].Loc);
|
UldFile uld;
|
||||||
|
switch (this.selectedUldFileTask ??=
|
||||||
if (uld == null)
|
dataManager.GetFileAsync<UldFile>($"ui/uld/{uldNames[this.selectedUld]}.uld", ct))
|
||||||
{
|
{
|
||||||
ImGui.Text("Failed to load ULD file.");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Texture Entries"))
|
if (ImGui.CollapsingHeader("Texture Entries"))
|
||||||
{
|
{
|
||||||
if (!ImGui.BeginTable("##uldTextureEntries", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders))
|
if (ForceNullable(uld.AssetData) is null)
|
||||||
return;
|
{
|
||||||
ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthFixed);
|
ImGuiHelpers.SafeTextColoredWrapped(
|
||||||
ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed);
|
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();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var textureEntry in uld.AssetData)
|
foreach (var textureEntry in uld.AssetData)
|
||||||
this.DrawTextureEntry(textureEntry);
|
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);
|
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]);
|
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);
|
if (ForceNullable(uld.Parts) is null)
|
||||||
this.DrawParts(uld.Parts[this.selectedParts], uld.AssetData, dataManager, textureManager);
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawTextureEntry(UldRoot.TextureEntry textureEntry)
|
return;
|
||||||
|
static T? ForceNullable<T>(T smth) => smth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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(
|
||||||
|
() =>
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
// game contains possibly around 1500 ULD files but current sigs only find less than that due to how they are used
|
||||||
fixed (char* p = textureEntry.Path)
|
var locations = new List<string>(1000);
|
||||||
ImGui.TextUnformatted(new string(p));
|
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)
|
||||||
|
{
|
||||||
|
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.TableNextColumn();
|
||||||
ImGui.TextUnformatted(textureEntry.Id.ToString());
|
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)
|
private void DrawTimelines(UldRoot.Timeline timeline)
|
||||||
|
|
@ -127,7 +328,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
switch (frame)
|
switch (frame)
|
||||||
{
|
{
|
||||||
case BaseKeyframeData baseKeyframeData:
|
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;
|
break;
|
||||||
case Float1Keyframe float1Keyframe:
|
case Float1Keyframe float1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(float1Keyframe.Keyframe);
|
||||||
|
|
@ -142,7 +344,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Float3Keyframe float3Keyframe:
|
case Float3Keyframe float3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(float3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case SByte1Keyframe sbyte1Keyframe:
|
case SByte1Keyframe sbyte1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(sbyte1Keyframe.Keyframe);
|
||||||
|
|
@ -157,7 +360,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case SByte3Keyframe sbyte3Keyframe:
|
case SByte3Keyframe sbyte3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(sbyte3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case Byte1Keyframe byte1Keyframe:
|
case Byte1Keyframe byte1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(byte1Keyframe.Keyframe);
|
||||||
|
|
@ -172,7 +376,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Byte3Keyframe byte3Keyframe:
|
case Byte3Keyframe byte3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(byte3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case Short1Keyframe short1Keyframe:
|
case Short1Keyframe short1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(short1Keyframe.Keyframe);
|
||||||
|
|
@ -187,7 +392,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Short3Keyframe short3Keyframe:
|
case Short3Keyframe short3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(short3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case UShort1Keyframe ushort1Keyframe:
|
case UShort1Keyframe ushort1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(ushort1Keyframe.Keyframe);
|
||||||
|
|
@ -202,7 +408,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case UShort3Keyframe ushort3Keyframe:
|
case UShort3Keyframe ushort3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(ushort3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case Int1Keyframe int1Keyframe:
|
case Int1Keyframe int1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(int1Keyframe.Keyframe);
|
||||||
|
|
@ -217,7 +424,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Int3Keyframe int3Keyframe:
|
case Int3Keyframe int3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(int3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case UInt1Keyframe uint1Keyframe:
|
case UInt1Keyframe uint1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(uint1Keyframe.Keyframe);
|
||||||
|
|
@ -232,7 +440,8 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case UInt3Keyframe uint3Keyframe:
|
case UInt3Keyframe uint3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(uint3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case Bool1Keyframe bool1Keyframe:
|
case Bool1Keyframe bool1Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(bool1Keyframe.Keyframe);
|
||||||
|
|
@ -247,123 +456,98 @@ internal class UldWidget : IDataWindowWidget
|
||||||
case Bool3Keyframe bool3Keyframe:
|
case Bool3Keyframe bool3Keyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(bool3Keyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case ColorKeyframe colorKeyframe:
|
case ColorKeyframe colorKeyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(colorKeyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
break;
|
||||||
case LabelKeyframe labelKeyframe:
|
case LabelKeyframe labelKeyframe:
|
||||||
this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe);
|
this.DrawTimelineKeyGroupFrame(labelKeyframe.Keyframe);
|
||||||
ImGui.SameLine(0, 0);
|
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;
|
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++)
|
for (var index = 0; index < partsData.Parts.Length; index++)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted($"Index: {index}");
|
ImGui.TextUnformatted($"Index: {index}");
|
||||||
var partsDataPart = partsData.Parts[index];
|
var partsDataPart = partsData.Parts[index];
|
||||||
ImGui.SameLine();
|
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}");
|
ImGui.TextUnformatted($"Could not find texture for id {partsDataPart.TextureId}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var texturePathChars = textureEntries.First(t => t.Id == partsDataPart.TextureId).Path;
|
var texturePath = GetStringNullTerminated(path);
|
||||||
string texturePath;
|
if (string.IsNullOrWhiteSpace(texturePath))
|
||||||
fixed (char* p = texturePathChars)
|
{
|
||||||
texturePath = new string(p);
|
ImGui.TextUnformatted("Texture path is empty.");
|
||||||
var texFile = dataManager.GetFile<TexFile>(texturePath.Replace("uld/", this.uldStyles[this.selectedUldStyle].Location));
|
continue;
|
||||||
if (texFile == null)
|
}
|
||||||
|
|
||||||
|
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
|
// try loading from default location if not found in selected style
|
||||||
texFile = dataManager.GetFile<TexFile>(texturePath);
|
if (!textureManager.Shared.GetFromGame(texturePath).TryGetWrap(out wrap, out var e2))
|
||||||
if (texFile == null)
|
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted($"Failed to load texture file {texturePath}");
|
// 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;
|
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 partSize = new Vector2(partsDataPart.W, partsDataPart.H);
|
||||||
|
if (wrap is null)
|
||||||
|
{
|
||||||
|
ImGuiHelpers.ScaledDummy(partSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var uv0 = new Vector2(partsDataPart.U, partsDataPart.V);
|
||||||
var uv1 = uv0 + partSize;
|
var uv1 = uv0 + partSize;
|
||||||
ImGui.Image(wrap.ImGuiHandle, partSize, uv0 / texSize, uv1 / texSize);
|
ImGui.Image(wrap.ImGuiHandle, partSize * ImGuiHelpers.GlobalScale, uv0 / wrap.Size, uv1 / wrap.Size);
|
||||||
wrap.Dispose();
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemClicked())
|
||||||
|
ImGui.SetClipboardText(texturePath);
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
ImGui.TextUnformatted("Click to copy:");
|
||||||
|
ImGui.TextUnformatted(texturePath);
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Dalamud.Game;
|
||||||
|
|
||||||
using Lumina;
|
using Lumina;
|
||||||
|
|
@ -61,6 +64,16 @@ public interface IDataManager
|
||||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||||
public T? GetFile<T>(string path) where T : FileResource;
|
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>
|
/// <summary>
|
||||||
/// Check if the file with the given path exists within the game's index files.
|
/// Check if the file with the given path exists within the game's index files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game;
|
||||||
|
|
||||||
|
|
@ -153,4 +155,12 @@ public interface ISigScanner
|
||||||
/// <param name="signature">The Signature.</param>
|
/// <param name="signature">The Signature.</param>
|
||||||
/// <returns>The list of real offsets of the found elements based on signature.</returns>
|
/// <returns>The list of real offsets of the found elements based on signature.</returns>
|
||||||
public nint[] ScanAllText(string signature);
|
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