Merge branch 'api11' into net9

This commit is contained in:
Kaz Wolfe 2024-11-14 23:29:14 -08:00
commit 23cdf3ce4a
No known key found for this signature in database
GPG key ID: 258813F53A16EBB4
41 changed files with 895 additions and 608 deletions

View file

@ -16,6 +16,6 @@ internal class AddonEventManagerAddressResolver : BaseAddressResolver
/// <param name="scanner">The signature scanner to facilitate setup.</param> /// <param name="scanner">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(ISigScanner scanner) protected override void Setup64Bit(ISigScanner scanner)
{ {
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS
} }
} }

View file

@ -1,5 +1,4 @@
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
@ -37,7 +36,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
private readonly GameLifecycle lifecycle; private readonly GameLifecycle lifecycle;
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook; private readonly Hook<EventFramework.Delegates.SetTerritoryTypeId> setupTerritoryTypeHook;
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook; private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
private readonly Hook<ProcessPacketPlayerSetupDelegate> processPacketPlayerSetupHook; private readonly Hook<ProcessPacketPlayerSetupDelegate> processPacketPlayerSetupHook;
private readonly Hook<LogoutCallbackInterface.Delegates.OnLogout> onLogoutHook; private readonly Hook<LogoutCallbackInterface.Delegates.OnLogout> onLogoutHook;
@ -56,9 +55,10 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language; this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language;
Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}"); var setTerritoryTypeAddr = EventFramework.Addresses.SetTerritoryTypeId.Value;
Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(setTerritoryTypeAddr)}");
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); this.setupTerritoryTypeHook = Hook<EventFramework.Delegates.SetTerritoryTypeId>.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour);
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour); this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
this.processPacketPlayerSetupHook = Hook<ProcessPacketPlayerSetupDelegate>.FromAddress(this.address.ProcessPacketPlayerSetup, this.ProcessPacketPlayerSetupDetour); this.processPacketPlayerSetupHook = Hook<ProcessPacketPlayerSetupDelegate>.FromAddress(this.address.ProcessPacketPlayerSetup, this.ProcessPacketPlayerSetupDetour);
this.onLogoutHook = Hook<LogoutCallbackInterface.Delegates.OnLogout>.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour); this.onLogoutHook = Hook<LogoutCallbackInterface.Delegates.OnLogout>.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour);
@ -71,9 +71,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
this.onLogoutHook.Enable(); this.onLogoutHook.Enable();
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType);
private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet); private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet);
/// <inheritdoc/> /// <inheritdoc/>
@ -123,7 +120,14 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId; public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId;
/// <inheritdoc/> /// <inheritdoc/>
public bool IsLoggedIn { get; private set; } public unsafe bool IsLoggedIn
{
get
{
var agentLobby = AgentLobby.Instance();
return agentLobby != null && agentLobby->IsLoggedIn;
}
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsPvP { get; private set; } public bool IsPvP { get; private set; }
@ -261,7 +265,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
try try
{ {
Log.Debug("Login"); Log.Debug("Login");
this.IsLoggedIn = true;
this.Login?.InvokeSafely(); this.Login?.InvokeSafely();
gameGui?.ResetUiHideState(); gameGui?.ResetUiHideState();
this.lifecycle.ResetLogout(); this.lifecycle.ResetLogout();
@ -285,8 +288,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
Log.Debug("Logout: Type {type}, Code {code}", type, code); Log.Debug("Logout: Type {type}, Code {code}", type, code);
this.IsLoggedIn = false;
if (this.Logout is { } callback) if (this.Logout is { } callback)
{ {
foreach (var action in callback.GetInvocationList().Cast<IClientState.LogoutDelegate>()) foreach (var action in callback.GetInvocationList().Cast<IClientState.LogoutDelegate>())

View file

@ -19,11 +19,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
// Functions // Functions
/// <summary>
/// Gets the address of the method which sets the territory type.
/// </summary>
public IntPtr SetupTerritoryType { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the method which sets up the player. /// Gets the address of the method which sets up the player.
/// </summary> /// </summary>
@ -41,9 +36,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
/// <param name="sig">The signature scanner to facilitate setup.</param> /// <param name="sig">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(ISigScanner sig) protected override void Setup64Bit(ISigScanner sig)
{ {
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC ?? 48 8B D9 48 89 6C 24"); this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); // not in cs struct
this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3");
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
// lea rcx, ds:1DB9F74h[rax*4] KeyboardState // lea rcx, ds:1DB9F74h[rax*4] KeyboardState
@ -51,6 +44,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); // unnamed in cs
} }
} }

View file

@ -13,6 +13,6 @@ internal sealed class GameConfigAddressResolver : BaseAddressResolver
/// <inheritdoc/> /// <inheritdoc/>
protected override void Setup64Bit(ISigScanner scanner) protected override void Setup64Bit(ISigScanner scanner)
{ {
this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); // unnamed in CS
} }
} }

View file

@ -16,6 +16,6 @@ internal class DutyStateAddressResolver : BaseAddressResolver
/// <param name="sig">The signature scanner to facilitate setup.</param> /// <param name="sig">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(ISigScanner sig) protected override void Setup64Bit(ISigScanner sig)
{ {
this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); // unnamed in cs
} }
} }

View file

@ -23,62 +23,21 @@ namespace Dalamud.Game.Gui.FlyText;
internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
{ {
/// <summary> /// <summary>
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>. /// The hook that fires when the game creates a fly text element.
/// </summary> /// </summary>
private readonly AddFlyTextDelegate addFlyTextNative; private readonly Hook<AddonFlyText.Delegates.CreateFlyText> createFlyTextHook;
/// <summary>
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
/// </summary>
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private unsafe FlyTextGui(TargetSigScanner sigScanner) private unsafe FlyTextGui(TargetSigScanner sigScanner)
{ {
this.Address = new FlyTextGuiAddressResolver(); this.createFlyTextHook = Hook<AddonFlyText.Delegates.CreateFlyText>.FromAddress(AddonFlyText.Addresses.CreateFlyText.Value, this.CreateFlyTextDetour);
this.Address.Setup(sigScanner);
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
this.createFlyTextHook.Enable(); this.createFlyTextHook.Enable();
} }
/// <summary>
/// Private delegate for the native CreateFlyText function's hook.
/// </summary>
private unsafe delegate nint CreateFlyTextDelegate(
AtkUnitBase* thisPtr,
FlyTextKind kind,
int val1,
int val2,
byte* text2,
uint color,
uint icon,
uint damageTypeIcon,
byte* text1,
float yOffset);
/// <summary>
/// Private delegate for the native AddFlyText function pointer.
/// </summary>
private unsafe delegate void AddFlyTextDelegate(
AtkUnitBase* thisPtr,
uint actorIndex,
uint messageMax,
NumberArrayData* numberArrayData,
uint offsetNum,
uint offsetNumMax,
StringArrayData* stringArrayData,
uint offsetStr,
uint offsetStrMax,
int unknown);
/// <inheritdoc/> /// <inheritdoc/>
public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated; public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated;
private FlyTextGuiAddressResolver Address { get; }
/// <summary> /// <summary>
/// Disposes of managed and unmanaged resources. /// Disposes of managed and unmanaged resources.
/// </summary> /// </summary>
@ -94,7 +53,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
var numOffset = 161u; var numOffset = 161u;
var strOffset = 28u; var strOffset = 28u;
var flytext = RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText"); var flytext = (AddonFlyText*)RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText");
if (flytext == null) if (flytext == null)
return; return;
@ -116,23 +75,13 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false); strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false);
strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false); strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false);
this.addFlyTextNative( flytext->AddFlyText(actorIndex, 1, numArray, numOffset, 10, strArray, strOffset, 2, 0);
flytext,
actorIndex,
1,
numArray,
numOffset,
10,
strArray,
strOffset,
2,
0);
} }
private unsafe nint CreateFlyTextDetour( private unsafe nint CreateFlyTextDetour(
AtkUnitBase* thisPtr, AddonFlyText* thisPtr,
FlyTextKind kind, int kind,
int val1, int val1,
int val2, int val2,
byte* text2, byte* text2,
@ -149,7 +98,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
var handled = false; var handled = false;
var tmpKind = kind; var tmpKind = (FlyTextKind)kind;
var tmpVal1 = val1; var tmpVal1 = val1;
var tmpVal2 = val2; var tmpVal2 = val2;
var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1); var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1);
@ -193,7 +142,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator(); var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator();
// Check if any values have changed // Check if any values have changed
var dirty = tmpKind != kind || var dirty = (int)tmpKind != kind ||
tmpVal1 != val1 || tmpVal1 != val1 ||
tmpVal2 != val2 || tmpVal2 != val2 ||
!maybeModifiedText1.SequenceEqual(originalText1) || !maybeModifiedText1.SequenceEqual(originalText1) ||
@ -219,7 +168,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
retVal = this.createFlyTextHook.Original( retVal = this.createFlyTextHook.Original(
thisPtr, thisPtr,
tmpKind, (int)tmpKind,
tmpVal1, tmpVal1,
tmpVal2, tmpVal2,
(byte*)pText2, (byte*)pText2,

View file

@ -1,29 +0,0 @@
namespace Dalamud.Game.Gui.FlyText;
/// <summary>
/// An address resolver for the <see cref="FlyTextGui"/> class.
/// </summary>
internal class FlyTextGuiAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the native AddFlyText method, which occurs
/// when the game adds fly text elements to the UI. Multiple fly text
/// elements can be added in a single AddFlyText call.
/// </summary>
public IntPtr AddFlyText { get; private set; }
/// <summary>
/// Gets the address of the native CreateFlyText method, which occurs
/// when the game creates a new fly text element. This method is called
/// once per fly text element, and can be called multiple times per
/// AddFlyText call.
/// </summary>
public IntPtr CreateFlyText { get; private set; }
/// <inheritdoc/>
protected override void Setup64Bit(ISigScanner sig)
{
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
this.CreateFlyText = sig.ScanText("E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B 18");
}
}

View file

@ -38,11 +38,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
private readonly GameGuiAddressResolver address; private readonly GameGuiAddressResolver address;
private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook; private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook;
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook; private readonly Hook<AgentActionDetail.Delegates.HandleActionHover> handleActionHoverHook;
private readonly Hook<HandleActionOutDelegate> handleActionOutHook; private readonly Hook<AgentActionDetail.Delegates.ReceiveEvent> handleActionOutHook;
private readonly Hook<HandleImmDelegate> handleImmHook; private readonly Hook<HandleImmDelegate> handleImmHook;
private readonly Hook<RaptureAtkModule.Delegates.SetUiVisibility> setUiVisibilityHook; private readonly Hook<RaptureAtkModule.Delegates.SetUiVisibility> setUiVisibilityHook;
private readonly Hook<Utf8StringFromSequenceDelegate> utf8StringFromSequenceHook; private readonly Hook<Utf8String.Delegates.Ctor_FromSequence> utf8StringFromSequenceHook;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private GameGui(TargetSigScanner sigScanner) private GameGui(TargetSigScanner sigScanner)
@ -57,14 +57,14 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
this.setGlobalBgmHook = Hook<SetGlobalBgmDelegate>.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); this.setGlobalBgmHook = Hook<SetGlobalBgmDelegate>.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
this.handleActionHoverHook = Hook<HandleActionHoverDelegate>.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); this.handleActionHoverHook = Hook<AgentActionDetail.Delegates.HandleActionHover>.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour);
this.handleActionOutHook = Hook<HandleActionOutDelegate>.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); this.handleActionOutHook = Hook<AgentActionDetail.Delegates.ReceiveEvent>.FromAddress((nint)AgentActionDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleActionOutDetour);
this.handleImmHook = Hook<HandleImmDelegate>.FromAddress(this.address.HandleImm, this.HandleImmDetour); this.handleImmHook = Hook<HandleImmDelegate>.FromAddress(this.address.HandleImm, this.HandleImmDetour);
this.setUiVisibilityHook = Hook<RaptureAtkModule.Delegates.SetUiVisibility>.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour); this.setUiVisibilityHook = Hook<RaptureAtkModule.Delegates.SetUiVisibility>.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour);
this.utf8StringFromSequenceHook = Hook<Utf8StringFromSequenceDelegate>.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour); this.utf8StringFromSequenceHook = Hook<Utf8String.Delegates.Ctor_FromSequence>.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour);
this.setGlobalBgmHook.Enable(); this.setGlobalBgmHook.Enable();
this.handleImmHook.Enable(); this.handleImmHook.Enable();
@ -77,22 +77,13 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
} }
// Hooked delegates // Hooked delegates
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3);
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<bool>? UiHideToggled; public event EventHandler<bool>? UiHideToggled;
@ -138,7 +129,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
inView = false; inView = false;
return false; return false;
} }
pCoords *= MathF.Abs(1.0f / pCoords.W); pCoords *= MathF.Abs(1.0f / pCoords.W);
screenPos = new Vector2(pCoords.X, pCoords.Y); screenPos = new Vector2(pCoords.X, pCoords.Y);
@ -166,7 +157,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
worldPos = default; worldPos = default;
return false; return false;
} }
var camera = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CameraManager.Instance()->CurrentCamera; var camera = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CameraManager.Instance()->CurrentCamera;
if (camera == null) if (camera == null)
{ {
@ -221,7 +212,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon);
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr FindAgentInterface(IntPtr addonPtr) public IntPtr FindAgentInterface(IntPtr addonPtr)
{ {
@ -309,24 +300,24 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
return retVal; return retVal;
} }
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) private void HandleActionHoverDetour(AgentActionDetail* hoverState, ActionKind actionKind, uint actionId, int a4, byte a5)
{ {
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
this.HoveredAction.ActionKind = actionKind; this.HoveredAction.ActionKind = (HoverActionKind)actionKind;
this.HoveredAction.BaseActionID = actionId; this.HoveredAction.BaseActionID = actionId;
this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); this.HoveredAction.ActionID = hoverState->ActionId;
this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction); this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction);
Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{hoverState.ToInt64():X}"); Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{(nint)hoverState:X}");
} }
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) private AtkValue* HandleActionOutDetour(AgentActionDetail* agentActionDetail, AtkValue* a2, AtkValue* a3, uint a4, ulong a5)
{ {
var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4, a5);
if (a3 != IntPtr.Zero && a4 == 1) if (a3 != null && a4 == 1)
{ {
var a3Val = Marshal.ReadByte(a3, 0x8); var a3Val = a3->Int;
if (a3Val == 255) if (a3Val == 255)
{ {
@ -422,13 +413,13 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
this.gameGuiService.HoveredItemChanged += this.HoveredItemForward; this.gameGuiService.HoveredItemChanged += this.HoveredItemForward;
this.gameGuiService.HoveredActionChanged += this.HoveredActionForward; this.gameGuiService.HoveredActionChanged += this.HoveredActionForward;
} }
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<bool>? UiHideToggled; public event EventHandler<bool>? UiHideToggled;
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ulong>? HoveredItemChanged; public event EventHandler<ulong>? HoveredItemChanged;
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<HoveredAction>? HoveredActionChanged; public event EventHandler<HoveredAction>? HoveredActionChanged;
@ -444,7 +435,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
/// <inheritdoc/> /// <inheritdoc/>
public HoveredAction HoveredAction => this.gameGuiService.HoveredAction; public HoveredAction HoveredAction => this.gameGuiService.HoveredAction;
/// <inheritdoc/> /// <inheritdoc/>
void IInternalDisposableService.DisposeService() void IInternalDisposableService.DisposeService()
{ {
@ -456,7 +447,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
this.HoveredItemChanged = null; this.HoveredItemChanged = null;
this.HoveredActionChanged = null; this.HoveredActionChanged = null;
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool OpenMapWithMapLink(MapLinkPayload mapLink) public bool OpenMapWithMapLink(MapLinkPayload mapLink)
=> this.gameGuiService.OpenMapWithMapLink(mapLink); => this.gameGuiService.OpenMapWithMapLink(mapLink);
@ -464,7 +455,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
/// <inheritdoc/> /// <inheritdoc/>
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
=> this.gameGuiService.WorldToScreen(worldPos, out screenPos); => this.gameGuiService.WorldToScreen(worldPos, out screenPos);
/// <inheritdoc/> /// <inheritdoc/>
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos, out bool inView) public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos, out bool inView)
=> this.gameGuiService.WorldToScreen(worldPos, out screenPos, out inView); => this.gameGuiService.WorldToScreen(worldPos, out screenPos, out inView);
@ -476,26 +467,26 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr GetUIModule() public IntPtr GetUIModule()
=> this.gameGuiService.GetUIModule(); => this.gameGuiService.GetUIModule();
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr GetAddonByName(string name, int index = 1) public IntPtr GetAddonByName(string name, int index = 1)
=> this.gameGuiService.GetAddonByName(name, index); => this.gameGuiService.GetAddonByName(name, index);
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr FindAgentInterface(string addonName) public IntPtr FindAgentInterface(string addonName)
=> this.gameGuiService.FindAgentInterface(addonName); => this.gameGuiService.FindAgentInterface(addonName);
/// <inheritdoc/> /// <inheritdoc/>
public unsafe IntPtr FindAgentInterface(void* addon) public unsafe IntPtr FindAgentInterface(void* addon)
=> this.gameGuiService.FindAgentInterface(addon); => this.gameGuiService.FindAgentInterface(addon);
/// <inheritdoc/> /// <inheritdoc/>
public IntPtr FindAgentInterface(IntPtr addonPtr) public IntPtr FindAgentInterface(IntPtr addonPtr)
=> this.gameGuiService.FindAgentInterface(addonPtr); => this.gameGuiService.FindAgentInterface(addonPtr);
private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled); private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled);
private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId); private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId);
private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction); private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction);
} }

View file

@ -15,34 +15,15 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
/// </summary> /// </summary>
public IntPtr SetGlobalBgm { get; private set; } public IntPtr SetGlobalBgm { get; private set; }
/// <summary>
/// Gets the address of the native HandleActionHover method.
/// </summary>
public IntPtr HandleActionHover { get; private set; }
/// <summary>
/// Gets the address of the native HandleActionOut method.
/// </summary>
public IntPtr HandleActionOut { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the native HandleImm method. /// Gets the address of the native HandleImm method.
/// </summary> /// </summary>
public IntPtr HandleImm { get; private set; } public IntPtr HandleImm { get; private set; }
/// <summary>
/// Gets the address of the native Utf8StringFromSequence method.
/// </summary>
public IntPtr Utf8StringFromSequence { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Setup64Bit(ISigScanner sig) protected override void Setup64Bit(ISigScanner sig)
{ {
this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); // unnamed in CS
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); // unnamed in CS
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
} }
} }

View file

@ -16,6 +16,11 @@ public enum HoverActionKind
/// </summary> /// </summary>
Action = 28, Action = 28,
/// <summary>
/// A crafting action is hovered.
/// </summary>
CraftingAction = 29,
/// <summary> /// <summary>
/// A general action is hovered. /// A general action is hovered.
/// </summary> /// </summary>
@ -24,7 +29,7 @@ public enum HoverActionKind
/// <summary> /// <summary>
/// A companion order type of action is hovered. /// A companion order type of action is hovered.
/// </summary> /// </summary>
CompanionOrder = 31, CompanionOrder = 31, // Game Term: BuddyOrder
/// <summary> /// <summary>
/// A main command type of action is hovered. /// A main command type of action is hovered.
@ -36,6 +41,11 @@ public enum HoverActionKind
/// </summary> /// </summary>
ExtraCommand = 33, ExtraCommand = 33,
/// <summary>
/// A companion action is hovered.
/// </summary>
Companion = 34,
/// <summary> /// <summary>
/// A pet order type of action is hovered. /// A pet order type of action is hovered.
/// </summary> /// </summary>
@ -45,4 +55,99 @@ public enum HoverActionKind
/// A trait is hovered. /// A trait is hovered.
/// </summary> /// </summary>
Trait = 36, Trait = 36,
/// <summary>
/// A buddy action is hovered.
/// </summary>
BuddyAction = 37,
/// <summary>
/// A company action is hovered.
/// </summary>
CompanyAction = 38,
/// <summary>
/// A mount is hovered.
/// </summary>
Mount = 39,
/// <summary>
/// A chocobo race action is hovered.
/// </summary>
ChocoboRaceAction = 40,
/// <summary>
/// A chocobo race item is hovered.
/// </summary>
ChocoboRaceItem = 41,
/// <summary>
/// A deep dungeon equipment is hovered.
/// </summary>
DeepDungeonEquipment = 42,
/// <summary>
/// A deep dungeon equipment 2 is hovered.
/// </summary>
DeepDungeonEquipment2 = 43,
/// <summary>
/// A deep dungeon item is hovered.
/// </summary>
DeepDungeonItem = 44,
/// <summary>
/// A quick chat is hovered.
/// </summary>
QuickChat = 45,
/// <summary>
/// An action combo route is hovered.
/// </summary>
ActionComboRoute = 46,
/// <summary>
/// A pvp trait is hovered.
/// </summary>
PvPSelectTrait = 47,
/// <summary>
/// A squadron action is hovered.
/// </summary>
BgcArmyAction = 48,
/// <summary>
/// A perform action is hovered.
/// </summary>
Perform = 49,
/// <summary>
/// A deep dungeon magic stone is hovered.
/// </summary>
DeepDungeonMagicStone = 50,
/// <summary>
/// A deep dungeon demiclone is hovered.
/// </summary>
DeepDungeonDemiclone = 51,
/// <summary>
/// An eureka magia action is hovered.
/// </summary>
EurekaMagiaAction = 52,
/// <summary>
/// An island sanctuary temporary item is hovered.
/// </summary>
MYCTemporaryItem = 53,
/// <summary>
/// An ornament is hovered.
/// </summary>
Ornament = 54,
/// <summary>
/// Glasses are hovered.
/// </summary>
Glasses = 55,
} }

View file

@ -1,18 +0,0 @@
namespace Dalamud.Game.Gui.PartyFinder;
/// <summary>
/// The address resolver for the <see cref="PartyFinderGui"/> class.
/// </summary>
internal class PartyFinderAddressResolver : BaseAddressResolver
{
/// <summary>
/// Gets the address of the native ReceiveListing method.
/// </summary>
public IntPtr ReceiveListing { get; private set; }
/// <inheritdoc/>
protected override void Setup64Bit(ISigScanner sig)
{
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC ?? 48 8B D9 4C 8B FA");
}
}

View file

@ -7,6 +7,8 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using Serilog; using Serilog;
namespace Dalamud.Game.Gui.PartyFinder; namespace Dalamud.Game.Gui.PartyFinder;
@ -15,12 +17,11 @@ namespace Dalamud.Game.Gui.PartyFinder;
/// This class handles interacting with the native PartyFinder window. /// This class handles interacting with the native PartyFinder window.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui internal sealed unsafe class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
{ {
private readonly PartyFinderAddressResolver address;
private readonly nint memory; private readonly nint memory;
private readonly Hook<ReceiveListingDelegate> receiveListingHook; private readonly Hook<InfoProxyCrossRealm.Delegates.ReceiveListing> receiveListingHook;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class. /// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
@ -29,18 +30,12 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private PartyFinderGui(TargetSigScanner sigScanner) private PartyFinderGui(TargetSigScanner sigScanner)
{ {
this.address = new PartyFinderAddressResolver();
this.address.Setup(sigScanner);
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour); this.receiveListingHook = Hook<InfoProxyCrossRealm.Delegates.ReceiveListing>.FromAddress(InfoProxyCrossRealm.Addresses.ReceiveListing.Value, this.HandleReceiveListingDetour);
this.receiveListingHook.Enable(); this.receiveListingHook.Enable();
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void ReceiveListingDelegate(nint managerPtr, nint data);
/// <inheritdoc/> /// <inheritdoc/>
public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing; public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing;
@ -61,18 +56,18 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG
} }
} }
private void HandleReceiveListingDetour(nint managerPtr, nint data) private void HandleReceiveListingDetour(InfoProxyCrossRealm* infoProxy, nint packet)
{ {
try try
{ {
this.HandleListingEvents(data); this.HandleListingEvents(packet);
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "Exception on ReceiveListing hook."); Log.Error(ex, "Exception on ReceiveListing hook.");
} }
this.receiveListingHook.Original(managerPtr, data); this.receiveListingHook.Original(infoProxy, packet);
} }
private void HandleListingEvents(nint data) private void HandleListingEvents(nint data)

View file

@ -24,7 +24,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
{ {
private static readonly ModuleLog Log = new("DalamudAtkTweaks"); private static readonly ModuleLog Log = new("DalamudAtkTweaks");
private readonly Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu; private readonly Hook<AgentHUD.Delegates.OpenSystemMenu> hookAgentHudOpenSystemMenu;
// TODO: Make this into events in Framework.Gui // TODO: Make this into events in Framework.Gui
private readonly Hook<UIModule.Delegates.ExecuteMainCommand> hookUiModuleExecuteMainCommand; private readonly Hook<UIModule.Delegates.ExecuteMainCommand> hookUiModuleExecuteMainCommand;
@ -45,9 +45,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private DalamudAtkTweaks(TargetSigScanner sigScanner) private DalamudAtkTweaks(TargetSigScanner sigScanner)
{ {
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CF 4C 89 B4 24 B8 08 00 00"); this.hookAgentHudOpenSystemMenu = Hook<AgentHUD.Delegates.OpenSystemMenu>.FromAddress(AgentHUD.Addresses.OpenSystemMenu.Value, this.AgentHudOpenSystemMenuDetour);
this.hookAgentHudOpenSystemMenu = Hook<AgentHudOpenSystemMenuPrototype>.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
this.hookUiModuleExecuteMainCommand = Hook<UIModule.Delegates.ExecuteMainCommand>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour); this.hookUiModuleExecuteMainCommand = Hook<UIModule.Delegates.ExecuteMainCommand>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour);
this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBase.Delegates.ReceiveGlobalEvent>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour); this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBase.Delegates.ReceiveGlobalEvent>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour);

View file

@ -9,7 +9,7 @@ namespace Dalamud.Game.Inventory;
/// <summary> /// <summary>
/// Dalamud wrapper around a ClientStructs InventoryItem. /// Dalamud wrapper around a ClientStructs InventoryItem.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)] [StructLayout(LayoutKind.Explicit, Size = InventoryItem.StructSize)]
public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem> public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
{ {
/// <summary> /// <summary>
@ -17,22 +17,12 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
/// </summary> /// </summary>
[FieldOffset(0)] [FieldOffset(0)]
internal readonly InventoryItem InternalItem; internal readonly InventoryItem InternalItem;
private const int StructSizeInBytes = 0x40;
/// <summary> /// <summary>
/// The view of the backing data, in <see cref="ulong"/>. /// The view of the backing data, in <see cref="ulong"/>.
/// </summary> /// </summary>
[FieldOffset(0)] [FieldOffset(0)]
private fixed ulong dataUInt64[StructSizeInBytes / 0x8]; private fixed ulong dataUInt64[InventoryItem.StructSize / 0x8];
static GameInventoryItem()
{
Debug.Assert(
sizeof(InventoryItem) == StructSizeInBytes,
$"Definition of {nameof(InventoryItem)} has been changed. " +
$"Update {nameof(StructSizeInBytes)} to {sizeof(InventoryItem)} to accommodate for the size change.");
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GameInventoryItem"/> struct. /// Initializes a new instance of the <see cref="GameInventoryItem"/> struct.
@ -157,7 +147,7 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
/// <returns><c>true</c> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <c>false</c>.</returns>
public readonly bool Equals(in GameInventoryItem other) public readonly bool Equals(in GameInventoryItem other)
{ {
for (var i = 0; i < StructSizeInBytes / 8; i++) for (var i = 0; i < InventoryItem.StructSize / 8; i++)
{ {
if (this.dataUInt64[i] != other.dataUInt64[i]) if (this.dataUInt64[i] != other.dataUInt64[i])
return false; return false;
@ -173,7 +163,7 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
public override int GetHashCode() public override int GetHashCode()
{ {
var k = 0x5a8447b91aff51b4UL; var k = 0x5a8447b91aff51b4UL;
for (var i = 0; i < StructSizeInBytes / 8; i++) for (var i = 0; i < InventoryItem.StructSize / 8; i++)
k ^= this.dataUInt64[i]; k ^= this.dataUInt64[i];
return unchecked((int)(k ^ (k >> 32))); return unchecked((int)(k ^ (k >> 32)));
} }

View file

@ -6,6 +6,9 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Network;
using Serilog; using Serilog;
namespace Dalamud.Game.Network; namespace Dalamud.Game.Network;
@ -14,10 +17,10 @@ namespace Dalamud.Game.Network;
/// This class handles interacting with game network events. /// This class handles interacting with game network events.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetwork
{ {
private readonly GameNetworkAddressResolver address; private readonly GameNetworkAddressResolver address;
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook; private readonly Hook<PacketDispatcher.Delegates.OnReceivePacket> processZonePacketDownHook;
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook; private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
private readonly HitchDetector hitchDetectorUp; private readonly HitchDetector hitchDetectorUp;
@ -25,11 +28,9 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get(); private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
private IntPtr baseAddress;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private GameNetwork(TargetSigScanner sigScanner) private unsafe GameNetwork(TargetSigScanner sigScanner)
{ {
this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch); this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch);
this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch); this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch);
@ -37,20 +38,19 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
this.address = new GameNetworkAddressResolver(); this.address = new GameNetworkAddressResolver();
this.address.Setup(sigScanner); this.address.Setup(sigScanner);
var onReceivePacketAddress = (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket;
Log.Verbose("===== G A M E N E T W O R K ====="); Log.Verbose("===== G A M E N E T W O R K =====");
Log.Verbose($"ProcessZonePacketDown address {Util.DescribeAddress(this.address.ProcessZonePacketDown)}"); Log.Verbose($"OnReceivePacket address {Util.DescribeAddress(onReceivePacketAddress)}");
Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}"); Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}");
this.processZonePacketDownHook = Hook<ProcessZonePacketDownDelegate>.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); this.processZonePacketDownHook = Hook<PacketDispatcher.Delegates.OnReceivePacket>.FromAddress(onReceivePacketAddress, this.ProcessZonePacketDownDetour);
this.processZonePacketUpHook = Hook<ProcessZonePacketUpDelegate>.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); this.processZonePacketUpHook = Hook<ProcessZonePacketUpDelegate>.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
this.processZonePacketDownHook.Enable(); this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable(); this.processZonePacketUpHook.Enable();
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
@ -64,10 +64,8 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
this.processZonePacketUpHook.Dispose(); this.processZonePacketUpHook.Dispose();
} }
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) private void ProcessZonePacketDownDetour(PacketDispatcher* dispatcher, uint targetId, IntPtr dataPtr)
{ {
this.baseAddress = a;
this.hitchDetectorDown.Start(); this.hitchDetectorDown.Start();
// Go back 0x10 to get back to the start of the packet header // Go back 0x10 to get back to the start of the packet header
@ -78,7 +76,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
// Call events // Call events
this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -96,7 +94,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header);
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10);
} }
this.hitchDetectorDown.Stop(); this.hitchDetectorDown.Stop();

View file

@ -5,11 +5,6 @@ namespace Dalamud.Game.Network;
/// </summary> /// </summary>
internal sealed class GameNetworkAddressResolver : BaseAddressResolver internal sealed class GameNetworkAddressResolver : BaseAddressResolver
{ {
/// <summary>
/// Gets the address of the ProcessZonePacketDown method.
/// </summary>
public IntPtr ProcessZonePacketDown { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the ProcessZonePacketUp method. /// Gets the address of the ProcessZonePacketUp method.
/// </summary> /// </summary>
@ -18,9 +13,6 @@ internal sealed class GameNetworkAddressResolver : BaseAddressResolver
/// <inheritdoc/> /// <inheritdoc/>
protected override void Setup64Bit(ISigScanner sig) protected override void Setup64Bit(ISigScanner sig)
{ {
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); // unnamed in cs
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
this.ProcessZonePacketDown = sig.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2");
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70");
} }
} }

View file

@ -48,4 +48,10 @@ internal class UniversalisTaxData
/// </summary> /// </summary>
[JsonProperty("sharlayan")] [JsonProperty("sharlayan")]
public uint Sharlayan { get; set; } public uint Sharlayan { get; set; }
/// <summary>
/// Gets or sets Tuliyollal's current tax rate.
/// </summary>
[JsonProperty("tuliyollal")]
public uint Tuliyollal { get; set; }
} }

View file

@ -131,6 +131,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
Kugane = taxRates.KuganeTax, Kugane = taxRates.KuganeTax,
Crystarium = taxRates.CrystariumTax, Crystarium = taxRates.CrystariumTax,
Sharlayan = taxRates.SharlayanTax, Sharlayan = taxRates.SharlayanTax,
Tuliyollal = taxRates.TuliyollalTax,
}, },
}; };

View file

@ -15,6 +15,9 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Networking.Http; using Dalamud.Networking.Http;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
using FFXIVClientStructs.FFXIV.Client.Network;
using FFXIVClientStructs.FFXIV.Client.UI.Info; using FFXIVClientStructs.FFXIV.Client.UI.Info;
using Lumina.Excel.Sheets; using Lumina.Excel.Sheets;
using Serilog; using Serilog;
@ -35,13 +38,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
private readonly NetworkHandlersAddressResolver addressResolver; private readonly NetworkHandlersAddressResolver addressResolver;
private readonly Hook<CfPopDelegate> cfPopHook; private readonly Hook<PublicContentDirector.Delegates.HandleEnterContentInfoPacket> cfPopHook;
private readonly Hook<MarketBoardPurchasePacketHandler> mbPurchaseHook; private readonly Hook<PacketDispatcher.Delegates.HandleMarketBoardPurchasePacket> mbPurchaseHook;
private readonly Hook<MarketBoardHistoryPacketHandler> mbHistoryHook; private readonly Hook<InfoProxyItemSearch.Delegates.ProcessItemHistory> mbHistoryHook;
private readonly Hook<CustomTalkReceiveResponse> customTalkHook; // used for marketboard taxes private readonly Hook<CustomTalkReceiveResponse> customTalkHook; // used for marketboard taxes
private readonly Hook<MarketBoardItemRequestStartPacketHandler> mbItemRequestStartHook; private readonly Hook<PacketDispatcher.Delegates.HandleMarketBoardItemRequestStartPacket> mbItemRequestStartHook;
private readonly Hook<InfoProxyItemSearchAddPage> mbOfferingsHook; private readonly Hook<InfoProxyItemSearch.Delegates.AddPage> mbOfferingsHook;
private readonly Hook<MarketBoardSendPurchaseRequestPacket> mbSendPurchaseRequestHook; private readonly Hook<InfoProxyItemSearch.Delegates.SendPurchaseRequestPacket> mbSendPurchaseRequestHook;
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get(); private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
@ -134,14 +137,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
this.handleMarketBoardPurchaseHandler = this.HandleMarketBoardPurchaseHandler(); this.handleMarketBoardPurchaseHandler = this.HandleMarketBoardPurchaseHandler();
this.mbPurchaseHook = this.mbPurchaseHook =
Hook<MarketBoardPurchasePacketHandler>.FromAddress( Hook<PacketDispatcher.Delegates.HandleMarketBoardPurchasePacket>.FromAddress(
this.addressResolver.MarketBoardPurchasePacketHandler, PacketDispatcher.Addresses.HandleMarketBoardPurchasePacket.Value,
this.MarketPurchasePacketDetour); this.MarketPurchasePacketDetour);
this.mbPurchaseHook.Enable(); this.mbPurchaseHook.Enable();
this.mbHistoryHook = this.mbHistoryHook =
Hook<MarketBoardHistoryPacketHandler>.FromAddress( Hook<InfoProxyItemSearch.Delegates.ProcessItemHistory>.FromAddress(
this.addressResolver.MarketBoardHistoryPacketHandler, InfoProxyItemSearch.Addresses.ProcessItemHistory.Value,
this.MarketHistoryPacketDetour); this.MarketHistoryPacketDetour);
this.mbHistoryHook.Enable(); this.mbHistoryHook.Enable();
@ -151,22 +154,22 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
this.CustomTalkReceiveResponseDetour); this.CustomTalkReceiveResponseDetour);
this.customTalkHook.Enable(); this.customTalkHook.Enable();
this.mbItemRequestStartHook = Hook<MarketBoardItemRequestStartPacketHandler>.FromAddress( this.mbItemRequestStartHook = Hook<PacketDispatcher.Delegates.HandleMarketBoardItemRequestStartPacket>.FromAddress(
this.addressResolver.MarketBoardItemRequestStartPacketHandler, PacketDispatcher.Addresses.HandleMarketBoardItemRequestStartPacket.Value,
this.MarketItemRequestStartDetour); this.MarketItemRequestStartDetour);
this.mbItemRequestStartHook.Enable(); this.mbItemRequestStartHook.Enable();
this.mbOfferingsHook = Hook<InfoProxyItemSearchAddPage>.FromAddress( this.mbOfferingsHook = Hook<InfoProxyItemSearch.Delegates.AddPage>.FromAddress(
this.addressResolver.InfoProxyItemSearchAddPage, (nint)InfoProxyItemSearch.StaticVirtualTablePointer->AddPage,
this.MarketBoardOfferingsDetour); this.MarketBoardOfferingsDetour);
this.mbOfferingsHook.Enable(); this.mbOfferingsHook.Enable();
this.mbSendPurchaseRequestHook = Hook<MarketBoardSendPurchaseRequestPacket>.FromAddress( this.mbSendPurchaseRequestHook = Hook<InfoProxyItemSearch.Delegates.SendPurchaseRequestPacket>.FromAddress(
this.addressResolver.BuildMarketBoardPurchaseHandlerPacket, InfoProxyItemSearch.Addresses.SendPurchaseRequestPacket.Value,
this.MarketBoardSendPurchaseRequestDetour); this.MarketBoardSendPurchaseRequestDetour);
this.mbSendPurchaseRequestHook.Enable(); this.mbSendPurchaseRequestHook.Enable();
this.cfPopHook = Hook<CfPopDelegate>.FromAddress(this.addressResolver.CfPopPacketHandler, this.CfPopDetour); this.cfPopHook = Hook<PublicContentDirector.Delegates.HandleEnterContentInfoPacket>.FromAddress(PublicContentDirector.Addresses.HandleEnterContentInfoPacket.Value, this.CfPopDetour);
this.cfPopHook.Enable(); this.cfPopHook.Enable();
} }
@ -183,8 +186,6 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
private delegate byte MarketBoardSendPurchaseRequestPacket(InfoProxyItemSearch* infoProxy); private delegate byte MarketBoardSendPurchaseRequestPacket(InfoProxyItemSearch* infoProxy);
private delegate nint CfPopDelegate(nint packetData);
/// <summary> /// <summary>
/// Event which gets fired when a duty is ready. /// Event which gets fired when a duty is ready.
/// </summary> /// </summary>
@ -263,7 +264,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
this.cfPopHook.Dispose(); this.cfPopHook.Dispose();
} }
private unsafe nint CfPopDetour(nint packetData) private unsafe nint CfPopDetour(PublicContentDirector.EnterContentInfoPacket* packetData)
{ {
var result = this.cfPopHook.OriginalDisposeSafe(packetData); var result = this.cfPopHook.OriginalDisposeSafe(packetData);
@ -529,7 +530,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
return this.configuration.IsMbCollect; return this.configuration.IsMbCollect;
} }
private nint MarketPurchasePacketDetour(nint a1, nint packetData) private void MarketPurchasePacketDetour(PacketDispatcher* a1, nint packetData)
{ {
try try
{ {
@ -540,10 +541,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
Log.Error(ex, "MarketPurchasePacketHandler threw an exception"); Log.Error(ex, "MarketPurchasePacketHandler threw an exception");
} }
return this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData); this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData);
} }
private nint MarketHistoryPacketDetour(nint a1, nint packetData, uint a3, char a4) private void MarketHistoryPacketDetour(InfoProxyItemSearch* a1, nint packetData)
{ {
try try
{ {
@ -554,7 +555,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
Log.Error(ex, "MarketHistoryPacketDetour threw an exception"); Log.Error(ex, "MarketHistoryPacketDetour threw an exception");
} }
return this.mbHistoryHook.OriginalDisposeSafe(a1, packetData, a3, a4); this.mbHistoryHook.OriginalDisposeSafe(a1, packetData);
} }
private void CustomTalkReceiveResponseDetour(nuint a1, ushort eventId, byte responseId, uint* args, byte argCount) private void CustomTalkReceiveResponseDetour(nuint a1, ushort eventId, byte responseId, uint* args, byte argCount)
@ -573,7 +574,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount); this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount);
} }
private nint MarketItemRequestStartDetour(nint a1, nint packetRef) private void MarketItemRequestStartDetour(PacketDispatcher* a1, nint packetRef)
{ {
try try
{ {
@ -584,10 +585,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
Log.Error(ex, "MarketItemRequestStartDetour threw an exception"); Log.Error(ex, "MarketItemRequestStartDetour threw an exception");
} }
return this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef); this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef);
} }
private byte MarketBoardOfferingsDetour(nint a1, nint packetRef) private void MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetRef)
{ {
try try
{ {
@ -598,10 +599,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
Log.Error(ex, "MarketBoardOfferingsDetour threw an exception"); Log.Error(ex, "MarketBoardOfferingsDetour threw an exception");
} }
return this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef); this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef);
} }
private byte MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch) private bool MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch)
{ {
try try
{ {

View file

@ -5,62 +5,17 @@
/// </summary> /// </summary>
internal class NetworkHandlersAddressResolver : BaseAddressResolver internal class NetworkHandlersAddressResolver : BaseAddressResolver
{ {
/// <summary>
/// Gets or sets the pointer to the method responsible for handling CfPop packets.
/// </summary>
public nint CfPopPacketHandler { get; set; }
/// <summary>
/// Gets or sets the pointer to the method responsible for handling market board history. In this case, we are
/// sigging the packet handler method directly.
/// </summary>
public nint MarketBoardHistoryPacketHandler { get; set; }
/// <summary>
/// Gets or sets the pointer to the method responsible for processing the market board purchase packet. In this
/// case, we are sigging the packet handler method directly.
/// </summary>
public nint MarketBoardPurchasePacketHandler { get; set; }
/// <summary> /// <summary>
/// Gets or sets the pointer to the method responsible for custom talk events. Necessary for marketboard tax data, /// Gets or sets the pointer to the method responsible for custom talk events. Necessary for marketboard tax data,
/// as this isn't really exposed anywhere else. /// as this isn't really exposed anywhere else.
/// </summary> /// </summary>
public nint CustomTalkEventResponsePacketHandler { get; set; } public nint CustomTalkEventResponsePacketHandler { get; set; }
/// <summary>
/// Gets or sets the pointer to the method responsible for the marketboard ItemRequestStart packet.
/// </summary>
public nint MarketBoardItemRequestStartPacketHandler { get; set; }
/// <summary>
/// Gets or sets the pointer to the InfoProxyItemSearch.AddPage method, used to load market data.
/// </summary>
public nint InfoProxyItemSearchAddPage { get; set; }
/// <summary>
/// Gets or sets the pointer to the method inside InfoProxyItemSearch that is responsible for building and sending
/// a purchase request packet.
/// </summary>
public nint BuildMarketBoardPurchaseHandlerPacket { get; set; }
/// <inheritdoc /> /// <inheritdoc />
protected override void Setup64Bit(ISigScanner scanner) protected override void Setup64Bit(ISigScanner scanner)
{ {
this.CfPopPacketHandler = scanner.ScanText("40 53 57 48 83 EC 78 48 8B D9 48 8D 0D");
// TODO: I know this is a CC. I want things working for now. (KW)
this.MarketBoardHistoryPacketHandler = scanner.ScanText(
"40 53 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 85 C0 74 2F 4C 8B 00 48 8B C8 41 FF 90 18 01 00 00 48 8B C8 BA 0B 00 00 00 E8 ?? ?? ?? ?? 48 85 C0 74 10 48 8B D3 48 8B C8 48 83 C4 20 5B E9 ?? ?? ?? ?? 48 83 C4 20 5B C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 40 53");
this.MarketBoardPurchasePacketHandler =
scanner.ScanText("40 55 56 41 56 48 8B EC 48 83 EC ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 ?? 48 8B 0D ?? ?? ?? ?? 4C 8B F2");
this.CustomTalkEventResponsePacketHandler = this.CustomTalkEventResponsePacketHandler =
scanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); scanner.ScanText(
this.MarketBoardItemRequestStartPacketHandler = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); // unnamed in CS
scanner.ScanText("48 89 5C 24 08 57 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B FA E8 ?? ?? ?? ?? 48 8B D8 48 85 C0 74 4A");
this.InfoProxyItemSearchAddPage =
scanner.ScanText("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54");
this.BuildMarketBoardPurchaseHandlerPacket =
scanner.ScanText("40 53 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B D9 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B D0 48 85 C0 0F 84 ?? ?? ?? ?? 8B 8B");
} }
} }

View file

@ -1,6 +1,8 @@
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET; using ImGuiNET;
namespace Dalamud.Interface.Components; namespace Dalamud.Interface.Components;
@ -41,7 +43,9 @@ public static partial class ImGuiComponents
ImGui.OpenPopup($"###ColorPickerPopup{id}"); ImGui.OpenPopup($"###ColorPickerPopup{id}");
} }
if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) using var popup = ImRaii.Popup($"###ColorPickerPopup{id}");
if (popup)
{ {
if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags))
{ {
@ -61,8 +65,6 @@ public static partial class ImGuiComponents
ImGui.SameLine(); ImGui.SameLine();
} }
} }
ImGui.EndPopup();
} }
return selectedColor; return selectedColor;

View file

@ -1,5 +1,7 @@
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET; using ImGuiNET;
namespace Dalamud.Interface.Components; namespace Dalamud.Interface.Components;
@ -21,17 +23,16 @@ public static partial class ImGuiComponents
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f)
{ {
ImGui.PushFont(UiBuilder.IconFont); using (ImRaii.PushFont(UiBuilder.IconFont))
{
var text = icon.ToIconString();
if (id.HasValue)
{
text = $"{text}##{id}";
}
var text = icon.ToIconString(); return DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult);
if (id.HasValue) }
text = $"{text}##{id}";
var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult);
ImGui.PopFont();
return button;
} }
/// <summary> /// <summary>
@ -45,31 +46,28 @@ public static partial class ImGuiComponents
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f)
{ {
using var col = new ImRaii.Color();
if (defaultColor.HasValue) if (defaultColor.HasValue)
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); {
col.Push(ImGuiCol.Button, defaultColor.Value);
}
if (activeColor.HasValue) if (activeColor.HasValue)
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); {
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
}
if (hoveredColor.HasValue) if (hoveredColor.HasValue)
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); {
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
}
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult);
var button = ImGui.Button(labelWithId); using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, style.Alpha * alphaMult))
{
ImGui.PopStyleVar(); return ImGui.Button(labelWithId);
}
if (defaultColor.HasValue)
ImGui.PopStyleColor();
if (activeColor.HasValue)
ImGui.PopStyleColor();
if (hoveredColor.HasValue)
ImGui.PopStyleColor();
return button;
} }
} }

View file

@ -1,3 +1,7 @@
using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Common.Math;
using ImGuiNET; using ImGuiNET;
namespace Dalamud.Interface.Components; namespace Dalamud.Interface.Components;
@ -18,17 +22,32 @@ public static partial class ImGuiComponents
/// </summary> /// </summary>
/// <param name="helpText">The text to display on hover.</param> /// <param name="helpText">The text to display on hover.</param>
/// <param name="icon">The icon to use.</param> /// <param name="icon">The icon to use.</param>
public static void HelpMarker(string helpText, FontAwesomeIcon icon) /// <param name="color">The color of the icon.</param>
public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null)
{ {
using var col = new ImRaii.Color();
if (color.HasValue)
{
col.Push(ImGuiCol.TextDisabled, color.Value);
}
ImGui.SameLine(); ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
ImGui.TextDisabled(icon.ToIconString()); using (ImRaii.PushFont(UiBuilder.IconFont))
ImGui.PopFont(); {
if (!ImGui.IsItemHovered()) return; ImGui.TextDisabled(icon.ToIconString());
ImGui.BeginTooltip(); }
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f);
ImGui.TextUnformatted(helpText); if (ImGui.IsItemHovered())
ImGui.PopTextWrapPos(); {
ImGui.EndTooltip(); using (ImRaii.Tooltip())
{
using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f))
{
ImGui.TextUnformatted(helpText);
}
}
}
} }
} }

View file

@ -1,6 +1,8 @@
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET; using ImGuiNET;
namespace Dalamud.Interface.Components; namespace Dalamud.Interface.Components;
@ -15,8 +17,26 @@ public static partial class ImGuiComponents
/// </summary> /// </summary>
/// <param name="icon">The icon for the button.</param> /// <param name="icon">The icon for the button.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(FontAwesomeIcon icon) public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null);
=> IconButton(icon, null, null, null);
/// <summary>
/// IconButton component to use an icon as a button.
/// </summary>
/// <param name="icon">The icon for the button.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(FontAwesomeIcon icon, Vector2 size) => IconButton(icon, null, null, null, size);
/// <summary>
/// IconButton component to use an icon as a button.
/// </summary>
/// <param name="icon">The icon for the button.</param>
/// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor, size);
/// <summary> /// <summary>
/// IconButton component to use an icon as a button. /// IconButton component to use an icon as a button.
@ -24,8 +44,28 @@ public static partial class ImGuiComponents
/// <param name="id">The ID of the button.</param> /// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param> /// <param name="icon">The icon for the button.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(int id, FontAwesomeIcon icon) public static bool IconButton(int id, FontAwesomeIcon icon) => IconButton(id, icon, null);
=> IconButton(id, icon, null, null, null);
/// <summary>
/// IconButton component to use an icon as a button.
/// </summary>
/// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(int id, FontAwesomeIcon icon, Vector2 size) => IconButton(id, icon, null, null, null, size);
/// <summary>
/// IconButton component to use an icon as a button with color options.
/// </summary>
/// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param>
/// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size);
/// <summary> /// <summary>
/// IconButton component to use an icon as a button. /// IconButton component to use an icon as a button.
@ -33,51 +73,45 @@ public static partial class ImGuiComponents
/// <param name="id">The ID of the button.</param> /// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param> /// <param name="icon">The icon for the button.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string id, FontAwesomeIcon icon) public static bool IconButton(string id, FontAwesomeIcon icon) => IconButton(id, icon, null);
=> IconButton(id, icon, null, null, null);
/// <summary>
/// IconButton component to use an icon as a button.
/// </summary>
/// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string id, FontAwesomeIcon icon, Vector2 size)
=> IconButton(id, icon, null, null, null, size);
/// <summary>
/// IconButton component to use an icon as a button with color options.
/// </summary>
/// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param>
/// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
=> IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size);
/// <summary> /// <summary>
/// IconButton component to use an icon as a button. /// IconButton component to use an icon as a button.
/// </summary> /// </summary>
/// <param name="iconText">Text already containing the icon string.</param> /// <param name="iconText">Text already containing the icon string.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string iconText) public static bool IconButton(string iconText) => IconButton(iconText, null);
=> IconButton(iconText, null, null, null);
/// <summary> /// <summary>
/// IconButton component to use an icon as a button. /// IconButton component to use an icon as a button.
/// </summary> /// </summary>
/// <param name="icon">The icon for the button.</param> /// <param name="iconText">Text already containing the icon string.</param>
/// <param name="defaultColor">The default color of the button.</param> /// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) public static bool IconButton(string iconText, Vector2 size) => IconButton(iconText, null, null, null, size);
=> IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor);
/// <summary>
/// IconButton component to use an icon as a button with color options.
/// </summary>
/// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param>
/// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
=> IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor);
/// <summary>
/// IconButton component to use an icon as a button with color options.
/// </summary>
/// <param name="id">The ID of the button.</param>
/// <param name="icon">The icon for the button.</param>
/// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
=> IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor);
/// <summary> /// <summary>
/// IconButton component to use an icon as a button with color options. /// IconButton component to use an icon as a button with color options.
@ -86,62 +120,72 @@ public static partial class ImGuiComponents
/// <param name="defaultColor">The default color of the button.</param> /// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param> /// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param> /// <param name="hoveredColor">The color of the button when hovered.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
{ {
var numColors = 0; using var col = new ImRaii.Color();
if (defaultColor.HasValue) if (defaultColor.HasValue)
{ {
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); col.Push(ImGuiCol.Button, defaultColor.Value);
numColors++;
} }
if (activeColor.HasValue) if (activeColor.HasValue)
{ {
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); col.Push(ImGuiCol.ButtonActive, activeColor.Value);
numColors++;
} }
if (hoveredColor.HasValue) if (hoveredColor.HasValue)
{ {
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
numColors++; }
if (size.HasValue)
{
size *= ImGuiHelpers.GlobalScale;
} }
var icon = iconText; var icon = iconText;
if (icon.Contains("#")) if (icon.Contains('#'))
icon = icon[..icon.IndexOf("#", StringComparison.Ordinal)]; {
icon = icon[..icon.IndexOf('#', StringComparison.Ordinal)];
}
ImGui.PushID(iconText); bool button;
ImGui.PushFont(UiBuilder.IconFont); using (ImRaii.PushFont(UiBuilder.IconFont))
var iconSize = ImGui.CalcTextSize(icon); {
ImGui.PopFont(); var iconSize = ImGui.CalcTextSize(icon);
var cursor = ImGui.GetCursorScreenPos();
var dl = ImGui.GetWindowDrawList();
var cursor = ImGui.GetCursorScreenPos();
// Draw an ImGui button with the icon and text
var buttonWidth = iconSize.X + (ImGui.GetStyle().FramePadding.X * 2);
var buttonHeight = ImGui.GetFrameHeight();
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
// Draw the icon on the window drawlist
var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y);
ImGui.PushFont(UiBuilder.IconFont);
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon);
ImGui.PopFont();
ImGui.PopID(); var width = size is { X: not 0 } ? size.Value.X : iconSize.X + (ImGui.GetStyle().FramePadding.X * 2);
var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight();
if (numColors > 0) var buttonSize = new Vector2(width, height);
ImGui.PopStyleColor(numColors);
using (ImRaii.PushId(iconText))
{
button = ImGui.Button(string.Empty, buttonSize);
}
var iconPos = cursor + ((buttonSize - iconSize) / 2f);
ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon);
}
return button; return button;
} }
/// <summary>
/// IconButton component to use an icon as a button with color options.
/// </summary>
/// <param name="icon">Icon to show.</param>
/// <param name="text">Text to show.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text.</param>
/// <returns>Indicator if button is clicked.</returns>
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector2 size) => IconButtonWithText(icon, text, null, null, null, size);
/// <summary> /// <summary>
/// IconButton component to use an icon as a button with color options. /// IconButton component to use an icon as a button with color options.
/// </summary> /// </summary>
@ -150,61 +194,72 @@ public static partial class ImGuiComponents
/// <param name="defaultColor">The default color of the button.</param> /// <param name="defaultColor">The default color of the button.</param>
/// <param name="activeColor">The color of the button when active.</param> /// <param name="activeColor">The color of the button when active.</param>
/// <param name="hoveredColor">The color of the button when hovered.</param> /// <param name="hoveredColor">The color of the button when hovered.</param>
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text.</param>
/// <returns>Indicator if button is clicked.</returns> /// <returns>Indicator if button is clicked.</returns>
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
{ {
var numColors = 0; using var col = new ImRaii.Color();
if (defaultColor.HasValue) if (defaultColor.HasValue)
{ {
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); col.Push(ImGuiCol.Button, defaultColor.Value);
numColors++;
} }
if (activeColor.HasValue) if (activeColor.HasValue)
{ {
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); col.Push(ImGuiCol.ButtonActive, activeColor.Value);
numColors++;
} }
if (hoveredColor.HasValue) if (hoveredColor.HasValue)
{ {
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
numColors++;
} }
ImGui.PushID(text); if (size.HasValue)
{
size *= ImGuiHelpers.GlobalScale;
}
bool button;
Vector2 iconSize;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
iconSize = ImGui.CalcTextSize(icon.ToIconString());
}
var textStr = text;
if (textStr.Contains('#'))
{
textStr = textStr[..textStr.IndexOf('#', StringComparison.Ordinal)];
}
var framePadding = ImGui.GetStyle().FramePadding;
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
ImGui.PushFont(UiBuilder.IconFont);
var iconSize = ImGui.CalcTextSize(icon.ToIconString());
ImGui.PopFont();
var textSize = ImGui.CalcTextSize(text);
var dl = ImGui.GetWindowDrawList();
var cursor = ImGui.GetCursorScreenPos(); var cursor = ImGui.GetCursorScreenPos();
var iconPadding = 3 * ImGuiHelpers.GlobalScale; using (ImRaii.PushId(text))
{
// Draw an ImGui button with the icon and text var textSize = ImGui.CalcTextSize(textStr);
var buttonWidth = iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding;
var buttonHeight = ImGui.GetFrameHeight();
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
// Draw the icon on the window drawlist
var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y);
ImGui.PushFont(UiBuilder.IconFont);
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
ImGui.PopFont();
// Draw the text on the window drawlist
var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + ImGui.GetStyle().FramePadding.Y);
dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), text);
ImGui.PopID(); var width = size is { X: not 0 } ? size.Value.X : iconSize.X + textSize.X + (framePadding.X * 2) + iconPadding;
var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight();
if (numColors > 0) button = ImGui.Button(string.Empty, new Vector2(width, height));
ImGui.PopStyleColor(numColors); }
var iconPos = cursor + framePadding;
var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + framePadding.Y);
var dl = ImGui.GetWindowDrawList();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
}
dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), textStr);
return button; return button;
} }
@ -217,16 +272,15 @@ public static partial class ImGuiComponents
/// <returns>Width.</returns> /// <returns>Width.</returns>
internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text) internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text)
{ {
ImGui.PushFont(UiBuilder.IconFont); using (ImRaii.PushFont(UiBuilder.IconFont))
var iconSize = ImGui.CalcTextSize(icon.ToIconString()); {
ImGui.PopFont(); var iconSize = ImGui.CalcTextSize(icon.ToIconString());
var textSize = ImGui.CalcTextSize(text);
var dl = ImGui.GetWindowDrawList();
var cursor = ImGui.GetCursorScreenPos();
var iconPadding = 3 * ImGuiHelpers.GlobalScale; var textSize = ImGui.CalcTextSize(text);
return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding; var iconPadding = 3 * ImGuiHelpers.GlobalScale;
return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding;
}
} }
} }

View file

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Components;
public static partial class ImGuiComponents
{
/// <summary>
/// A radio-like input that uses icon buttons.
/// </summary>
/// <typeparam name="T">The type of the value being set.</typeparam>
/// <param name="label">Text that will be used to generate individual labels for the buttons.</param>
/// <param name="val">The value to set.</param>
/// <param name="optionIcons">The icons that will be displayed on each button.</param>
/// <param name="optionValues">The options that each button will apply.</param>
/// <param name="columns">Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row).</param>
/// <param name="buttonSize">Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <param name="defaultColor">The default color of the button range.</param>
/// <param name="activeColor">The color of the actively-selected button.</param>
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
/// <returns>True if any button is clicked.</returns>
internal static bool IconButtonSelect<T>(string label, ref T val, IEnumerable<FontAwesomeIcon> optionIcons, IEnumerable<T> optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
{
var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair<FontAwesomeIcon, T>(icon, value));
return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor);
}
/// <summary>
/// A radio-like input that uses icon buttons.
/// </summary>
/// <typeparam name="T">The type of the value being set.</typeparam>
/// <param name="label">Text that will be used to generate individual labels for the buttons.</param>
/// <param name="val">The value to set.</param>
/// <param name="options">A list of all icon/option pairs.</param>
/// <param name="columns">Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row).</param>
/// <param name="buttonSize">Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
/// <param name="defaultColor">The default color of the button range.</param>
/// <param name="activeColor">The color of the actively-selected button.</param>
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
/// <returns>True if any button is clicked.</returns>
internal static unsafe bool IconButtonSelect<T>(string label, ref T val, IEnumerable<KeyValuePair<FontAwesomeIcon, T>> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
{
defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button);
activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive);
hoveredColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonHovered);
var result = false;
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing;
var y = ImGui.GetCursorPosY();
var optArr = options.ToArray();
for (var i = 0; i < optArr.Length; i++)
{
if (i > 0)
{
if (columns == 0 || i % columns != 0)
{
ImGui.SameLine(0, innerSpacing.X);
}
else
{
y += (buttonSize is { Y: not 0 } ? buttonSize.Value.Y * ImGuiHelpers.GlobalScale : ImGui.GetFrameHeight()) + innerSpacing.Y;
ImGui.SetCursorPosY(y);
}
}
optArr[i].Deconstruct(out var icon, out var option);
var selected = val is not null && val.Equals(option);
if (IconButton($"{label}{option}{i}", icon, selected ? activeColor : defaultColor, activeColor, hoveredColor, buttonSize))
{
val = option;
result = true;
}
}
return result;
}
}

View file

@ -1,3 +1,5 @@
using Dalamud.Interface.Utility.Raii;
using ImGuiNET; using ImGuiNET;
namespace Dalamud.Interface.Components; namespace Dalamud.Interface.Components;
@ -24,7 +26,13 @@ public static partial class ImGuiComponents
else else
{ {
ImGui.Text(value + "*"); ImGui.Text(value + "*");
if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.TextUnformatted(hint);
}
}
} }
} }
} }

View file

@ -36,9 +36,14 @@ public static partial class ImGuiComponents
} }
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{
drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f);
}
else else
{
drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f);
}
drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1)));
return changed; return changed;
@ -62,7 +67,7 @@ public static partial class ImGuiComponents
// TODO: animate // TODO: animate
ImGui.InvisibleButton(id, new Vector2(width, height)); ImGui.InvisibleButton(id, new Vector2(width, height));
var dimFactor = 0.5f; const float dimFactor = 0.5f;
drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f);
drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor));

View file

@ -0,0 +1,124 @@
using Dalamud.Interface.Internal.UiDebug2.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Memory;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
public unsafe partial class AddonTree
{
/// <summary>
/// Prints a table of AtkValues associated with a given addon.
/// </summary>
/// <param name="addon">The addon to look up.</param>
internal static void PrintAtkValues(AtkUnitBase* addon)
{
var atkValue = addon->AtkValues;
if (addon->AtkValuesCount > 0 && atkValue != null)
{
using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}");
if (tree)
{
using (ImRaii.Table(
"atkUnitBase_atkValueTable",
3,
ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg))
{
ImGui.TableSetupColumn("Index");
ImGui.TableSetupColumn("Type");
ImGui.TableSetupColumn("Value");
ImGui.TableHeadersRow();
try
{
for (var i = 0; i < addon->AtkValuesCount; i++)
{
ImGui.TableNextColumn();
if (atkValue->Type == 0)
{
ImGui.TextDisabled($"#{i}");
}
else
{
ImGui.Text($"#{i}");
}
ImGui.TableNextColumn();
if (atkValue->Type == 0)
{
ImGui.TextDisabled("Not Set");
}
else
{
ImGui.Text($"{atkValue->Type}");
}
ImGui.TableNextColumn();
switch (atkValue->Type)
{
case 0:
break;
case ValueType.Int:
case ValueType.UInt:
{
ImGui.TextUnformatted($"{atkValue->Int}");
break;
}
case ValueType.ManagedString:
case ValueType.String8:
case ValueType.String:
{
if (atkValue->String == null)
{
ImGui.TextDisabled("null");
}
else
{
var str = MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue->String));
Util.ShowStruct(str, (ulong)atkValue);
}
break;
}
case ValueType.Bool:
{
ImGui.TextUnformatted($"{atkValue->Byte != 0}");
break;
}
case ValueType.Pointer:
ImGui.TextUnformatted($"{(nint)atkValue->Pointer}");
break;
default:
{
ImGui.TextDisabled("Unhandled Type");
ImGui.SameLine();
Util.ShowStruct(atkValue);
break;
}
}
atkValue++;
}
}
catch (Exception ex)
{
ImGui.TextColored(new(1, 0, 0, 1), $"{ex}");
}
}
}
Gui.PaddedSeparator();
}
}
}

View file

@ -23,6 +23,11 @@ public unsafe partial class AddonTree
/// </summary> /// </summary>
internal Dictionary<nint, List<string>> FieldNames { get; set; } = []; internal Dictionary<nint, List<string>> FieldNames { get; set; } = [];
/// <summary>
/// Gets or sets the size of the addon according to its Attributes in FFXIVClientStructs.
/// </summary>
internal int AddonSize { get; set; }
private object? GetAddonObj(AtkUnitBase* addon) private object? GetAddonObj(AtkUnitBase* addon)
{ {
if (addon == null) if (addon == null)
@ -42,6 +47,13 @@ public unsafe partial class AddonTree
select t) select t)
{ {
AddonTypeDict[this.AddonName] = t; AddonTypeDict[this.AddonName] = t;
var size = t.StructLayoutAttribute?.Size;
if (size != null)
{
this.AddonSize = size.Value;
}
break; break;
} }
} }

View file

@ -3,6 +3,7 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET; using ImGuiNET;
@ -19,14 +20,12 @@ namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
/// </summary> /// </summary>
public unsafe partial class AddonTree : IDisposable public unsafe partial class AddonTree : IDisposable
{ {
private readonly nint initialPtr;
private AddonPopoutWindow? window; private AddonPopoutWindow? window;
private AddonTree(string name, nint ptr) private AddonTree(string name, nint ptr)
{ {
this.AddonName = name; this.AddonName = name;
this.initialPtr = ptr; this.InitialPtr = ptr;
this.PopulateFieldNames(ptr); this.PopulateFieldNames(ptr);
} }
@ -35,6 +34,11 @@ public unsafe partial class AddonTree : IDisposable
/// </summary> /// </summary>
internal string AddonName { get; init; } internal string AddonName { get; init; }
/// <summary>
/// Gets the addon's pointer at the time this <see cref="AddonTree"/> was created.
/// </summary>
internal nint InitialPtr { get; init; }
/// <summary> /// <summary>
/// Gets or sets a collection of trees representing nodes within this addon. /// Gets or sets a collection of trees representing nodes within this addon.
/// </summary> /// </summary>
@ -81,7 +85,7 @@ public unsafe partial class AddonTree : IDisposable
{ {
if (AddonTrees.TryGetValue(name, out var tree)) if (AddonTrees.TryGetValue(name, out var tree))
{ {
if (tree.initialPtr == ptr) if (tree.InitialPtr == ptr)
{ {
return tree; return tree;
} }
@ -143,34 +147,47 @@ public unsafe partial class AddonTree : IDisposable
ImGui.SetTooltip("Toggle Popout Window"); ImGui.SetTooltip("Toggle Popout Window");
} }
ImGui.Separator(); PaddedSeparator(1);
PrintFieldValuePair("Address", $"{(nint)addon:X}");
var uldManager = addon->UldManager; var uldManager = addon->UldManager;
PrintFieldValuePair("Address", $"{(nint)addon:X}");
PrintFieldValuePair("Agent", $"{GameGui.FindAgentInterface(addon):X}");
PrintFieldValuePairs( PrintFieldValuePairs(
("X", $"{addon->X}"), ("X", $"{addon->X}"),
("Y", $"{addon->X}"), ("Y", $"{addon->Y}"),
("Scale", $"{addon->Scale}"), ("Scale", $"{addon->Scale}"),
("Widget Count", $"{uldManager.ObjectCount}")); ("Widget Count", $"{uldManager.ObjectCount}"));
ImGui.Separator();
var addonObj = this.GetAddonObj(addon); var addonObj = this.GetAddonObj(addon);
if (addonObj != null) if (addonObj != null)
{ {
PaddedSeparator();
ShowStruct(addonObj, (ulong)addon); ShowStruct(addonObj, (ulong)addon);
} }
ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); PaddedSeparator();
ImGui.Separator();
ResNodeTree.PrintNodeList(uldManager.NodeList, uldManager.NodeListCount, this); PrintAtkValues(addon);
ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale)); if (addon->RootNode != null)
ImGui.Separator(); {
ResNodeTree.GetOrCreate(addon->RootNode, this).Print(0);
PaddedSeparator();
}
ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F)); if (uldManager.NodeList != null)
{
var count = uldManager.NodeListCount;
ResNodeTree.PrintNodeListAsTree(uldManager.NodeList, count, $"Node List [{count}]:", this, new(0, 0.85F, 1, 1));
PaddedSeparator();
}
if (addon->CollisionNodeList != null)
{
ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F));
}
if (SearchResults.Length > 0 && Countdown <= 0) if (SearchResults.Length > 0 && Countdown <= 0)
{ {
@ -218,7 +235,7 @@ public unsafe partial class AddonTree : IDisposable
private bool ValidateAddon(out AtkUnitBase* addon) private bool ValidateAddon(out AtkUnitBase* addon)
{ {
addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName); addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName);
if (addon == null || (nint)addon != this.initialPtr) if (addon == null || (nint)addon != this.InitialPtr)
{ {
this.Dispose(); this.Dispose();
return false; return false;
@ -231,7 +248,7 @@ public unsafe partial class AddonTree : IDisposable
{ {
if (this.window == null) if (this.window == null)
{ {
this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.initialPtr}"); this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.InitialPtr}");
PopoutWindows.AddWindow(this.window); PopoutWindows.AddWindow(this.window);
} }
else else

View file

@ -1,4 +1,6 @@
using Dalamud.Interface.Internal.UiDebug2.Utility; using System.Numerics;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
@ -56,9 +58,9 @@ public static class Events
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted($"{evt->State.UnkFlags1}"); ImGui.TextUnformatted($"{evt->State.UnkFlags1}");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
Gui.ClickToCopyText($"{(nint)evt->Target:X}"); ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
Gui.ClickToCopyText($"{(nint)evt->Listener:X}"); ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1));
evt = evt->NextEvent; evt = evt->NextEvent;
} }
} }

View file

@ -34,11 +34,13 @@ internal unsafe class ComponentNodeTree : ResNodeTree
private AtkUldManager* UldManager => &this.Component->UldManager; private AtkUldManager* UldManager => &this.Component->UldManager;
private int? ComponentFieldOffset { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
private protected override string GetHeaderText() private protected override string GetHeaderText()
{ {
var childCount = (int)this.UldManager->NodeListCount; var childCount = (int)this.UldManager->NodeListCount;
return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.Component:X})"; return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)}";
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -62,10 +64,10 @@ internal unsafe class ComponentNodeTree : ResNodeTree
} }
/// <inheritdoc/> /// <inheritdoc/>
private protected override void PrintFieldNames() private protected override void PrintFieldLabels()
{ {
this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1)); this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset);
this.PrintFieldName((nint)this.Component, new(0f, 0.5f, 0.8f, 1f)); this.PrintFieldLabel((nint)this.Component, new(0f, 0.5f, 0.8f, 1f), this.ComponentFieldOffset);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -108,6 +110,34 @@ internal unsafe class ComponentNodeTree : ResNodeTree
} }
} }
/// <inheritdoc/>
private protected override void GetFieldOffset()
{
var nodeFound = false;
var componentFound = false;
for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8)
{
var readPtr = Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i);
if (readPtr == (nint)this.Node)
{
this.NodeFieldOffset = i;
nodeFound = true;
}
if (readPtr == (nint)this.Component)
{
this.ComponentFieldOffset = i;
componentFound = true;
}
if (nodeFound && componentFound)
{
break;
}
}
}
private void PrintComponentObject() private void PrintComponentObject()
{ {
PrintFieldValuePair("Component", $"{(nint)this.Component:X}"); PrintFieldValuePair("Component", $"{(nint)this.Component:X}");

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.UiDebug2.Utility; using Dalamud.Interface.Internal.UiDebug2.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
@ -370,8 +371,8 @@ internal unsafe partial class TextNodeTree
var hAlign = (int)alignment % 3; var hAlign = (int)alignment % 3;
var vAlign = ((int)alignment - hAlign) / 3; var vAlign = ((int)alignment - hAlign) / 3;
var hAlignInput = IconButtonSelect($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]); var hAlignInput = ImGuiComponents.IconButtonSelect($"{label}H", ref hAlign, [AlignLeft, AlignCenter, AlignRight], [0, 1, 2], 3u, new(25, 0));
var vAlignInput = IconButtonSelect($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]); var vAlignInput = ImGuiComponents.IconButtonSelect($"{label}V", ref vAlign, [ArrowsUpToLine, GripLines, ArrowsDownToLine], [0, 1, 2], 3u, new(25, 0));
if (hAlignInput || vAlignInput) if (hAlignInput || vAlignInput)
{ {

View file

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.UiDebug2.Utility; using Dalamud.Interface.Internal.UiDebug2.Utility;
@ -60,6 +61,11 @@ internal unsafe partial class ResNodeTree : IDisposable
/// </summary> /// </summary>
private protected NodeType NodeType { get; init; } private protected NodeType NodeType { get; init; }
/// <summary>
/// Gets or sets the offset of this node within its parent Addon.
/// </summary>
private protected int? NodeFieldOffset { get; set; }
/// <summary> /// <summary>
/// Clears this NodeTree's popout window, if it has one. /// Clears this NodeTree's popout window, if it has one.
/// </summary> /// </summary>
@ -164,19 +170,26 @@ internal unsafe partial class ResNodeTree : IDisposable
internal void WriteTreeHeading() internal void WriteTreeHeading()
{ {
ImGui.TextUnformatted(this.GetHeaderText()); ImGui.TextUnformatted(this.GetHeaderText());
this.PrintFieldNames(); this.PrintFieldLabels();
} }
/// <summary> /// <summary>
/// If the given pointer has been identified as a field within the addon struct, this method prints that field's name. /// If the given pointer is referenced with the addon struct, the offset within the addon will be printed. If the given pointer has been identified as a field within the addon struct, this method also prints that field's name.
/// </summary> /// </summary>
/// <param name="ptr">The pointer to check.</param> /// <param name="ptr">The pointer to check.</param>
/// <param name="color">The text color to use.</param> /// <param name="color">The text color to use.</param>
private protected void PrintFieldName(nint ptr, Vector4 color) /// <param name="fieldOffset">The field offset of the pointer, if it was found in the addon.</param>
private protected void PrintFieldLabel(nint ptr, Vector4 color, int? fieldOffset)
{ {
if (fieldOffset != null)
{
ImGui.SameLine(0, -1);
ImGui.TextColored(color * 0.85f, $"[0x{fieldOffset:X}]");
}
if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result)) if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result))
{ {
ImGui.SameLine(); ImGui.SameLine(0, -1);
ImGui.TextColored(color, string.Join(".", result)); ImGui.TextColored(color, string.Join(".", result));
} }
} }
@ -188,7 +201,15 @@ internal unsafe partial class ResNodeTree : IDisposable
private protected virtual string GetHeaderText() private protected virtual string GetHeaderText()
{ {
var count = this.GetDirectChildCount(); var count = this.GetDirectChildCount();
return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)} ({(nint)this.Node:X})"; return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)}";
}
/// <summary>
/// Prints any field names for the node.
/// </summary>
private protected virtual void PrintFieldLabels()
{
this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset);
} }
/// <summary> /// <summary>
@ -201,11 +222,6 @@ internal unsafe partial class ResNodeTree : IDisposable
ImGui.NewLine(); ImGui.NewLine();
} }
/// <summary>
/// Prints any field names for the node.
/// </summary>
private protected virtual void PrintFieldNames() => this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1));
/// <summary> /// <summary>
/// Prints all direct children of this node. /// Prints all direct children of this node.
/// </summary> /// </summary>
@ -227,6 +243,21 @@ internal unsafe partial class ResNodeTree : IDisposable
{ {
} }
/// <summary>
/// Attempts to retrieve the field offset of the given pointer within the parent addon.
/// </summary>
private protected virtual void GetFieldOffset()
{
for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8)
{
if (Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i) == (nint)this.Node)
{
this.NodeFieldOffset = i;
break;
}
}
}
private int GetDirectChildCount() private int GetDirectChildCount()
{ {
var count = 0; var count = 0;
@ -273,6 +304,8 @@ internal unsafe partial class ResNodeTree : IDisposable
ImGui.SetNextItemOpen(true, ImGuiCond.Always); ImGui.SetNextItemOpen(true, ImGuiCond.Always);
} }
this.GetFieldOffset();
using var col = ImRaii.PushColor(Text, displayColor); using var col = ImRaii.PushColor(Text, displayColor);
using var tree = ImRaii.TreeNode(label, SpanFullWidth); using var tree = ImRaii.TreeNode(label, SpanFullWidth);
@ -281,7 +314,7 @@ internal unsafe partial class ResNodeTree : IDisposable
new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f)); new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f));
} }
ImGui.SameLine(); ImGui.SameLine(0, -1);
this.WriteTreeHeading(); this.WriteTreeHeading();
col.Pop(); col.Pop();

View file

@ -79,7 +79,7 @@ internal unsafe class ElementSelector : IDisposable
/// </summary> /// </summary>
internal void DrawInterface() internal void DrawInterface()
{ {
using (ImRaii.Child("###sidebar_elementSelector", new(250, 0), true)) using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true))
{ {
using (ImRaii.PushFont(IconFont)) using (ImRaii.PushFont(IconFont))
{ {

View file

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Components; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Client.Graphics; using FFXIVClientStructs.FFXIV.Client.Graphics;
@ -17,40 +16,6 @@ namespace Dalamud.Interface.Internal.UiDebug2.Utility;
/// </summary> /// </summary>
internal static class Gui internal static class Gui
{ {
/// <summary>
/// A radio-button-esque input that uses Fontawesome icon buttons.
/// </summary>
/// <typeparam name="T">The type of value being set.</typeparam>
/// <param name="label">The label for the inputs.</param>
/// <param name="val">The value being set.</param>
/// <param name="options">A list of all options.</param>
/// <param name="icons">A list of icons corresponding to the options.</param>
/// <returns>true if a button is clicked.</returns>
internal static unsafe bool IconButtonSelect<T>(string label, ref T val, List<T> options, List<FontAwesomeIcon> icons)
{
var ret = false;
for (var i = 0; i < options.Count; i++)
{
if (i > 0)
{
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
}
var option = options[i];
var icon = icons.Count > i ? icons[i] : FontAwesomeIcon.Question;
var color = *ImGui.GetStyleColorVec4(val is not null && val.Equals(option) ? ButtonActive : Button);
if (ImGuiComponents.IconButton($"{label}{option}{i}", icon, color))
{
val = option;
ret = true;
}
}
return ret;
}
/// <summary> /// <summary>
/// Prints field name and its value. /// Prints field name and its value.
/// </summary> /// </summary>
@ -61,13 +26,14 @@ internal static class Gui
{ {
ImGui.TextUnformatted($"{fieldName}:"); ImGui.TextUnformatted($"{fieldName}:");
ImGui.SameLine(); ImGui.SameLine();
var grey60 = new Vector4(0.6f, 0.6f, 0.6f, 1);
if (copy) if (copy)
{ {
ClickToCopyText(value); ImGuiHelpers.ClickToCopyText(value, null, grey60);
} }
else else
{ {
ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), value); ImGui.TextColored(grey60, value);
} }
} }
@ -102,7 +68,10 @@ internal static class Gui
/// <remarks>Colors the text itself either white or black, depending on the luminosity of the background color.</remarks> /// <remarks>Colors the text itself either white or black, depending on the luminosity of the background color.</remarks>
internal static void PrintColor(Vector4 color, string fmt) internal static void PrintColor(Vector4 color, string fmt)
{ {
using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)).Push(Button, color).Push(ButtonActive, color).Push(ButtonHovered, color)) using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1))
.Push(Button, color)
.Push(ButtonActive, color)
.Push(ButtonHovered, color))
{ {
ImGui.SmallButton(fmt); ImGui.SmallButton(fmt);
} }
@ -117,39 +86,6 @@ internal static class Gui
0.5f) * vector4.W; 0.5f) * vector4.W;
} }
/// <summary>
/// Print out text that can be copied when clicked.
/// </summary>
/// <param name="text">The text to show.</param>
/// <param name="textCopy">The text to copy when clicked.</param>
internal static void ClickToCopyText(string text, string? textCopy = null)
{
using (ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1)))
{
textCopy ??= text;
ImGui.TextUnformatted($"{text}");
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString());
}
ImGui.SameLine();
ImGui.TextUnformatted($"{textCopy}");
}
}
if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText($"{textCopy}");
}
}
/// <summary> /// <summary>
/// Draws a tooltip that changes based on the cursor's x-position within the hovered item. /// Draws a tooltip that changes based on the cursor's x-position within the hovered item.
/// </summary> /// </summary>
@ -176,4 +112,23 @@ internal static class Gui
return true; return true;
} }
/// <summary>
/// Draws a separator with some padding above and below.
/// </summary>
/// <param name="mask">Governs whether to pad above, below, or both.</param>
/// <param name="padding">The amount of padding.</param>
internal static void PaddedSeparator(uint mask = 0b11, float padding = 5f)
{
if ((mask & 0b10) > 0)
{
ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale));
}
ImGui.Separator();
if ((mask & 0b01) > 0)
{
ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale));
}
}
} }

View file

@ -6,7 +6,7 @@ using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET; using ImGuiNET;
using static System.Math; using static System.MathF;
using static Dalamud.Interface.ColorHelpers; using static Dalamud.Interface.ColorHelpers;
namespace Dalamud.Interface.Internal.UiDebug2.Utility; namespace Dalamud.Interface.Internal.UiDebug2.Utility;
@ -133,7 +133,7 @@ public unsafe struct NodeBounds
if (p.Y > Min(p1.Y, p2.Y) && if (p.Y > Min(p1.Y, p2.Y) &&
p.Y <= Max(p1.Y, p2.Y) && p.Y <= Max(p1.Y, p2.Y) &&
p.X <= Max(p1.X, p2.X) && p.X <= Max(p1.X, p2.X) &&
(p1.X.Equals(p2.X) || p.X <= ((p.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y)) + p1.X)) (p1.X.Equals(p2.X) || p.X <= (((p.Y - p1.Y) * (p2.X - p1.X)) / (p2.Y - p1.Y)) + p1.X))
{ {
inside = !inside; inside = !inside;
} }
@ -144,12 +144,12 @@ public unsafe struct NodeBounds
private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s) private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s)
{ {
var cosR = (float)Cos(r); var cosR = Cos(r);
var sinR = (float)Sin(r); var sinR = Sin(r);
var d = (p - o) * s; var d = (p - o) * s;
return new( return new(
o.X + (d.X * cosR) - (d.Y * sinR), (o.X + (d.X * cosR)) - (d.Y * sinR),
o.Y + (d.X * sinR) + (d.Y * cosR)); o.Y + (d.X * sinR) + (d.Y * cosR));
} }

View file

@ -9,7 +9,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// </summary> /// </summary>
internal class FateTableAgingStep : IAgingStep internal class FateTableAgingStep : IAgingStep
{ {
private int index = 0; private byte index = 0;
/// <inheritdoc/> /// <inheritdoc/>
public string Name => "Test FateTable"; public string Name => "Test FateTable";
@ -21,6 +21,12 @@ internal class FateTableAgingStep : IAgingStep
ImGui.Text("Checking fate table..."); ImGui.Text("Checking fate table...");
if (fateTable.Length == 0)
{
ImGui.Text("Go to a zone that has FATEs currently up.");
return SelfTestStepResult.Waiting;
}
if (this.index == fateTable.Length - 1) if (this.index == fateTable.Length - 1)
{ {
return SelfTestStepResult.Pass; return SelfTestStepResult.Pass;

View file

@ -169,6 +169,7 @@ internal class MarketBoardAgingStep : IAgingStep
ImGui.Text($"Kugane: {this.marketTaxRate.KuganeTax.ToString()}"); ImGui.Text($"Kugane: {this.marketTaxRate.KuganeTax.ToString()}");
ImGui.Text($"Crystarium: {this.marketTaxRate.CrystariumTax.ToString()}"); ImGui.Text($"Crystarium: {this.marketTaxRate.CrystariumTax.ToString()}");
ImGui.Text($"Sharlayan: {this.marketTaxRate.SharlayanTax.ToString()}"); ImGui.Text($"Sharlayan: {this.marketTaxRate.SharlayanTax.ToString()}");
ImGui.Text($"Tuliyollal: {this.marketTaxRate.TuliyollalTax.ToString()}");
ImGui.Separator(); ImGui.Separator();
if (ImGui.Button("Looks Correct / Skip")) if (ImGui.Button("Looks Correct / Skip"))
{ {

View file

@ -167,17 +167,41 @@ public static class ImGuiHelpers
/// </summary> /// </summary>
/// <param name="text">The text to show.</param> /// <param name="text">The text to show.</param>
/// <param name="textCopy">The text to copy when clicked.</param> /// <param name="textCopy">The text to copy when clicked.</param>
public static void ClickToCopyText(string text, string? textCopy = null) /// <param name="color">The color of the text.</param>
public static void ClickToCopyText(string text, string? textCopy = null, Vector4? color = null)
{ {
textCopy ??= text; textCopy ??= text;
ImGui.Text($"{text}");
using (var col = new ImRaii.Color())
{
if (color.HasValue)
{
col.Push(ImGuiCol.Text, color.Value);
}
ImGui.TextUnformatted($"{text}");
}
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
if (textCopy != text) ImGui.SetTooltip(textCopy);
using (ImRaii.Tooltip())
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString());
}
ImGui.SameLine();
ImGui.TextUnformatted(textCopy);
}
} }
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText(textCopy);
}
} }
/// <summary>Draws a SeString.</summary> /// <summary>Draws a SeString.</summary>

@ -1 +1 @@
Subproject commit 78c39ef1318525d766cdf31fd9a32e6f1c7cb453 Subproject commit 679b5353cd61baa03b26d95efcac059f8e29b141