refactor: update for api 9
This commit is contained in:
parent
e0fff565bd
commit
f22702f251
|
@ -2,181 +2,182 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// The class containing BattleTalk functionality
|
||||
/// </summary>
|
||||
public class BattleTalk : IDisposable {
|
||||
private bool HookEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The class containing BattleTalk functionality
|
||||
/// The delegate for BattleTalk events.
|
||||
/// </summary>
|
||||
public class BattleTalk : IDisposable {
|
||||
private bool HookEnabled { get; }
|
||||
public delegate void BattleTalkEventDelegate(ref SeString sender, ref SeString message, ref BattleTalkOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for BattleTalk events.
|
||||
/// </summary>
|
||||
public delegate void BattleTalkEventDelegate(ref SeString sender, ref SeString message, ref BattleTalkOptions options, ref bool isHandled);
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a BattleTalk window is shown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.BattleTalk"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event BattleTalkEventDelegate? OnBattleTalk;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a BattleTalk window is shown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.BattleTalk"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event BattleTalkEventDelegate? OnBattleTalk;
|
||||
private delegate byte AddBattleTalkDelegate(IntPtr uiModule, IntPtr sender, IntPtr message, float duration, byte style);
|
||||
|
||||
private delegate byte AddBattleTalkDelegate(IntPtr uiModule, IntPtr sender, IntPtr message, float duration, byte style);
|
||||
private AddBattleTalkDelegate? AddBattleTalk { get; }
|
||||
private Hook<AddBattleTalkDelegate>? AddBattleTalkHook { get; }
|
||||
|
||||
private AddBattleTalkDelegate? AddBattleTalk { get; }
|
||||
private Hook<AddBattleTalkDelegate>? AddBattleTalkHook { get; }
|
||||
internal unsafe BattleTalk(IGameInteropProvider interop, bool hook) {
|
||||
this.HookEnabled = hook;
|
||||
|
||||
internal unsafe BattleTalk(bool hook) {
|
||||
this.HookEnabled = hook;
|
||||
var addBattleTalkPtr = (IntPtr) Framework.Instance()->GetUiModule()->VTable->ShowBattleTalk;
|
||||
if (addBattleTalkPtr != IntPtr.Zero) {
|
||||
this.AddBattleTalk = Marshal.GetDelegateForFunctionPointer<AddBattleTalkDelegate>(addBattleTalkPtr);
|
||||
|
||||
var addBattleTalkPtr = (IntPtr) Framework.Instance()->GetUiModule()->VTable->ShowBattleTalk;
|
||||
if (addBattleTalkPtr != IntPtr.Zero) {
|
||||
this.AddBattleTalk = Marshal.GetDelegateForFunctionPointer<AddBattleTalkDelegate>(addBattleTalkPtr);
|
||||
|
||||
if (this.HookEnabled) {
|
||||
this.AddBattleTalkHook = Hook<AddBattleTalkDelegate>.FromAddress(addBattleTalkPtr, this.AddBattleTalkDetour);
|
||||
this.AddBattleTalkHook.Enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.AddBattleTalkHook?.Dispose();
|
||||
}
|
||||
|
||||
private byte AddBattleTalkDetour(IntPtr uiModule, IntPtr senderPtr, IntPtr messagePtr, float duration, byte style) {
|
||||
if (this.OnBattleTalk == null) {
|
||||
goto Return;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.AddBattleTalkDetourInner(uiModule, senderPtr, messagePtr, duration, style);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in BattleTalk detour");
|
||||
}
|
||||
|
||||
Return:
|
||||
return this.AddBattleTalkHook!.Original(uiModule, senderPtr, messagePtr, duration, style);
|
||||
}
|
||||
|
||||
private unsafe byte AddBattleTalkDetourInner(IntPtr uiModule, IntPtr senderPtr, IntPtr messagePtr, float duration, byte style) {
|
||||
var rawSender = Util.ReadTerminated(senderPtr);
|
||||
var rawMessage = Util.ReadTerminated(messagePtr);
|
||||
|
||||
var sender = SeString.Parse(rawSender);
|
||||
var message = SeString.Parse(rawMessage);
|
||||
|
||||
var options = new BattleTalkOptions {
|
||||
Duration = duration,
|
||||
Style = (BattleTalkStyle) style,
|
||||
};
|
||||
|
||||
var handled = false;
|
||||
try {
|
||||
this.OnBattleTalk?.Invoke(ref sender, ref message, ref options, ref handled);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in BattleTalk event");
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var finalSender = sender.Encode().Terminate();
|
||||
var finalMessage = message.Encode().Terminate();
|
||||
|
||||
fixed (byte* fSenderPtr = finalSender, fMessagePtr = finalMessage) {
|
||||
return this.AddBattleTalkHook!.Original(uiModule, (IntPtr) fSenderPtr, (IntPtr) fMessagePtr, options.Duration, (byte) options.Style);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a BattleTalk window with the given options.
|
||||
/// </summary>
|
||||
/// <param name="sender">The name to attribute to the message</param>
|
||||
/// <param name="message">The message to show in the window</param>
|
||||
/// <param name="options">Optional options for the window</param>
|
||||
/// <exception cref="ArgumentException">If sender or message are empty</exception>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public void Show(SeString sender, SeString message, BattleTalkOptions? options = null) {
|
||||
this.Show(sender.Encode(), message.Encode(), options);
|
||||
}
|
||||
|
||||
private unsafe void Show(byte[] sender, byte[] message, BattleTalkOptions? options) {
|
||||
if (sender.Length == 0) {
|
||||
throw new ArgumentException("sender cannot be empty", nameof(sender));
|
||||
}
|
||||
|
||||
if (message.Length == 0) {
|
||||
throw new ArgumentException("message cannot be empty", nameof(message));
|
||||
}
|
||||
|
||||
if (this.AddBattleTalk == null) {
|
||||
throw new InvalidOperationException("Signature for battle talk could not be found");
|
||||
}
|
||||
|
||||
options ??= new BattleTalkOptions();
|
||||
|
||||
var uiModule = (IntPtr) Framework.Instance()->GetUiModule();
|
||||
|
||||
fixed (byte* senderPtr = sender.Terminate(), messagePtr = message.Terminate()) {
|
||||
if (this.HookEnabled) {
|
||||
this.AddBattleTalkDetour(uiModule, (IntPtr) senderPtr, (IntPtr) messagePtr, options.Duration, (byte) options.Style);
|
||||
} else {
|
||||
this.AddBattleTalk(uiModule, (IntPtr) senderPtr, (IntPtr) messagePtr, options.Duration, (byte) options.Style);
|
||||
}
|
||||
if (this.HookEnabled) {
|
||||
this.AddBattleTalkHook = interop.HookFromAddress<AddBattleTalkDelegate>(addBattleTalkPtr, this.AddBattleTalkDetour);
|
||||
this.AddBattleTalkHook.Enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for displaying a BattleTalk window.
|
||||
/// </summary>
|
||||
public class BattleTalkOptions {
|
||||
/// <summary>
|
||||
/// Duration to display the window, in seconds.
|
||||
/// </summary>
|
||||
public float Duration { get; set; } = 5f;
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.AddBattleTalkHook?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The style of the window.
|
||||
/// </summary>
|
||||
public BattleTalkStyle Style { get; set; } = BattleTalkStyle.Normal;
|
||||
private byte AddBattleTalkDetour(IntPtr uiModule, IntPtr senderPtr, IntPtr messagePtr, float duration, byte style) {
|
||||
if (this.OnBattleTalk == null) {
|
||||
goto Return;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.AddBattleTalkDetourInner(uiModule, senderPtr, messagePtr, duration, style);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in BattleTalk detour");
|
||||
}
|
||||
|
||||
Return:
|
||||
return this.AddBattleTalkHook!.Original(uiModule, senderPtr, messagePtr, duration, style);
|
||||
}
|
||||
|
||||
private unsafe byte AddBattleTalkDetourInner(IntPtr uiModule, IntPtr senderPtr, IntPtr messagePtr, float duration, byte style) {
|
||||
var rawSender = Util.ReadTerminated(senderPtr);
|
||||
var rawMessage = Util.ReadTerminated(messagePtr);
|
||||
|
||||
var sender = SeString.Parse(rawSender);
|
||||
var message = SeString.Parse(rawMessage);
|
||||
|
||||
var options = new BattleTalkOptions {
|
||||
Duration = duration,
|
||||
Style = (BattleTalkStyle) style,
|
||||
};
|
||||
|
||||
var handled = false;
|
||||
try {
|
||||
this.OnBattleTalk?.Invoke(ref sender, ref message, ref options, ref handled);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in BattleTalk event");
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var finalSender = sender.Encode().Terminate();
|
||||
var finalMessage = message.Encode().Terminate();
|
||||
|
||||
fixed (byte* fSenderPtr = finalSender, fMessagePtr = finalMessage) {
|
||||
return this.AddBattleTalkHook!.Original(uiModule, (IntPtr) fSenderPtr, (IntPtr) fMessagePtr, options.Duration, (byte) options.Style);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BattleTalk window styles.
|
||||
/// Show a BattleTalk window with the given options.
|
||||
/// </summary>
|
||||
public enum BattleTalkStyle : byte {
|
||||
/// <summary>
|
||||
/// A normal battle talk window with a white background.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
/// <param name="sender">The name to attribute to the message</param>
|
||||
/// <param name="message">The message to show in the window</param>
|
||||
/// <param name="options">Optional options for the window</param>
|
||||
/// <exception cref="ArgumentException">If sender or message are empty</exception>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public void Show(SeString sender, SeString message, BattleTalkOptions? options = null) {
|
||||
this.Show(sender.Encode(), message.Encode(), options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A battle talk window with a blue background and styled edges.
|
||||
/// </summary>
|
||||
Aetherial = 6,
|
||||
private unsafe void Show(byte[] sender, byte[] message, BattleTalkOptions? options) {
|
||||
if (sender.Length == 0) {
|
||||
throw new ArgumentException("sender cannot be empty", nameof(sender));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A battle talk window styled similarly to a system text message (black background).
|
||||
/// </summary>
|
||||
System = 7,
|
||||
if (message.Length == 0) {
|
||||
throw new ArgumentException("message cannot be empty", nameof(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// A battle talk window with a blue, computer-y background.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Used by the Ultima Weapons (Ruby, Emerald, etc.).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
Blue = 9,
|
||||
if (this.AddBattleTalk == null) {
|
||||
throw new InvalidOperationException("Signature for battle talk could not be found");
|
||||
}
|
||||
|
||||
options ??= new BattleTalkOptions();
|
||||
|
||||
var uiModule = (IntPtr) Framework.Instance()->GetUiModule();
|
||||
|
||||
fixed (byte* senderPtr = sender.Terminate(), messagePtr = message.Terminate()) {
|
||||
if (this.HookEnabled) {
|
||||
this.AddBattleTalkDetour(uiModule, (IntPtr) senderPtr, (IntPtr) messagePtr, options.Duration, (byte) options.Style);
|
||||
} else {
|
||||
this.AddBattleTalk(uiModule, (IntPtr) senderPtr, (IntPtr) messagePtr, options.Duration, (byte) options.Style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for displaying a BattleTalk window.
|
||||
/// </summary>
|
||||
public class BattleTalkOptions {
|
||||
/// <summary>
|
||||
/// Duration to display the window, in seconds.
|
||||
/// </summary>
|
||||
public float Duration { get; set; } = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The style of the window.
|
||||
/// </summary>
|
||||
public BattleTalkStyle Style { get; set; } = BattleTalkStyle.Normal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BattleTalk window styles.
|
||||
/// </summary>
|
||||
public enum BattleTalkStyle : byte {
|
||||
/// <summary>
|
||||
/// A normal battle talk window with a white background.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A battle talk window with a blue background and styled edges.
|
||||
/// </summary>
|
||||
Aetherial = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A battle talk window styled similarly to a system text message (black background).
|
||||
/// </summary>
|
||||
System = 7,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// A battle talk window with a blue, computer-y background.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Used by the Ultima Weapons (Ruby, Emerald, etc.).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
Blue = 9,
|
||||
}
|
||||
|
|
|
@ -7,151 +7,151 @@ using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
|||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// A class containing chat functionality
|
||||
/// </summary>
|
||||
public class Chat {
|
||||
private static class Signatures {
|
||||
internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9";
|
||||
internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// A class containing chat functionality
|
||||
/// </summary>
|
||||
public class Chat {
|
||||
private static class Signatures {
|
||||
internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9";
|
||||
internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D";
|
||||
}
|
||||
|
||||
private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
|
||||
|
||||
private ProcessChatBoxDelegate? ProcessChatBox { get; }
|
||||
|
||||
private readonly unsafe delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString = null!;
|
||||
|
||||
internal Chat(ISigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.SendChat, out var processChatBoxPtr, "chat sending")) {
|
||||
this.ProcessChatBox = Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(processChatBoxPtr);
|
||||
}
|
||||
|
||||
private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4);
|
||||
|
||||
private ProcessChatBoxDelegate? ProcessChatBox { get; }
|
||||
|
||||
private readonly unsafe delegate* unmanaged<Utf8String*, int, IntPtr, void> _sanitiseString = null!;
|
||||
|
||||
internal Chat(SigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.SendChat, out var processChatBoxPtr, "chat sending")) {
|
||||
this.ProcessChatBox = Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(processChatBoxPtr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if (scanner.TryScanText(Signatures.SanitiseString, out var sanitisePtr, "string sanitiser")) {
|
||||
this._sanitiseString = (delegate* unmanaged<Utf8String*, int, IntPtr, void>) sanitisePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Send a given message to the chat box. <b>This can send chat to the server.</b>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>This method is unsafe.</b> This method does no checking on your input and
|
||||
/// may send content to the server that the normal client could not. You must
|
||||
/// verify what you're sending and handle content and length to properly use
|
||||
/// this.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="message">Message to send</param>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public unsafe void SendMessageUnsafe(byte[] message) {
|
||||
if (this.ProcessChatBox == null) {
|
||||
throw new InvalidOperationException("Could not find signature for chat sending");
|
||||
}
|
||||
|
||||
var uiModule = (IntPtr) Framework.Instance()->GetUiModule();
|
||||
|
||||
using var payload = new ChatPayload(message);
|
||||
var mem1 = Marshal.AllocHGlobal(400);
|
||||
Marshal.StructureToPtr(payload, mem1, false);
|
||||
|
||||
this.ProcessChatBox(uiModule, mem1, IntPtr.Zero, 0);
|
||||
|
||||
Marshal.FreeHGlobal(mem1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Send a given message to the chat box. <b>This can send chat to the server.</b>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method is slightly less unsafe than <see cref="SendMessageUnsafe"/>. It
|
||||
/// will throw exceptions for certain inputs that the client can't normally send,
|
||||
/// but it is still possible to make mistakes. Use with caution.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="message">message to send</param>
|
||||
/// <exception cref="ArgumentException">If <paramref name="message"/> is empty, longer than 500 bytes in UTF-8, or contains invalid characters.</exception>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public void SendMessage(string message) {
|
||||
var bytes = Encoding.UTF8.GetBytes(message);
|
||||
if (bytes.Length == 0) {
|
||||
throw new ArgumentException("message is empty", nameof(message));
|
||||
}
|
||||
|
||||
if (bytes.Length > 500) {
|
||||
throw new ArgumentException("message is longer than 500 bytes", nameof(message));
|
||||
}
|
||||
|
||||
if (message.Length != this.SanitiseText(message).Length) {
|
||||
throw new ArgumentException("message contained invalid characters", nameof(message));
|
||||
}
|
||||
|
||||
this.SendMessageUnsafe(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Sanitises a string by removing any invalid input.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The result of this method is safe to use with
|
||||
/// <see cref="SendMessage"/>, provided that it is not empty or too
|
||||
/// long.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="text">text to sanitise</param>
|
||||
/// <returns>sanitised text</returns>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public unsafe string SanitiseText(string text) {
|
||||
if (this._sanitiseString == null) {
|
||||
throw new InvalidOperationException("Could not find signature for chat sanitisation");
|
||||
}
|
||||
|
||||
var uText = Utf8String.FromString(text);
|
||||
|
||||
this._sanitiseString(uText, 0x27F, IntPtr.Zero);
|
||||
var sanitised = uText->ToString();
|
||||
|
||||
uText->Dtor();
|
||||
IMemorySpace.Free(uText);
|
||||
|
||||
return sanitised;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
[SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")]
|
||||
private readonly struct ChatPayload : IDisposable {
|
||||
[FieldOffset(0)]
|
||||
private readonly IntPtr textPtr;
|
||||
|
||||
[FieldOffset(16)]
|
||||
private readonly ulong textLen;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private readonly ulong unk1;
|
||||
|
||||
[FieldOffset(24)]
|
||||
private readonly ulong unk2;
|
||||
|
||||
internal ChatPayload(byte[] stringBytes) {
|
||||
this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
|
||||
Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length);
|
||||
Marshal.WriteByte(this.textPtr + stringBytes.Length, 0);
|
||||
|
||||
this.textLen = (ulong) (stringBytes.Length + 1);
|
||||
|
||||
this.unk1 = 64;
|
||||
this.unk2 = 0;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Marshal.FreeHGlobal(this.textPtr);
|
||||
unsafe {
|
||||
if (scanner.TryScanText(Signatures.SanitiseString, out var sanitisePtr, "string sanitiser")) {
|
||||
this._sanitiseString = (delegate* unmanaged<Utf8String*, int, IntPtr, void>) sanitisePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Send a given message to the chat box. <b>This can send chat to the server.</b>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>This method is unsafe.</b> This method does no checking on your input and
|
||||
/// may send content to the server that the normal client could not. You must
|
||||
/// verify what you're sending and handle content and length to properly use
|
||||
/// this.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="message">Message to send</param>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public unsafe void SendMessageUnsafe(byte[] message) {
|
||||
if (this.ProcessChatBox == null) {
|
||||
throw new InvalidOperationException("Could not find signature for chat sending");
|
||||
}
|
||||
|
||||
var uiModule = (IntPtr) Framework.Instance()->GetUiModule();
|
||||
|
||||
using var payload = new ChatPayload(message);
|
||||
var mem1 = Marshal.AllocHGlobal(400);
|
||||
Marshal.StructureToPtr(payload, mem1, false);
|
||||
|
||||
this.ProcessChatBox(uiModule, mem1, IntPtr.Zero, 0);
|
||||
|
||||
Marshal.FreeHGlobal(mem1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Send a given message to the chat box. <b>This can send chat to the server.</b>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method is slightly less unsafe than <see cref="SendMessageUnsafe"/>. It
|
||||
/// will throw exceptions for certain inputs that the client can't normally send,
|
||||
/// but it is still possible to make mistakes. Use with caution.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="message">message to send</param>
|
||||
/// <exception cref="ArgumentException">If <paramref name="message"/> is empty, longer than 500 bytes in UTF-8, or contains invalid characters.</exception>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public void SendMessage(string message) {
|
||||
var bytes = Encoding.UTF8.GetBytes(message);
|
||||
if (bytes.Length == 0) {
|
||||
throw new ArgumentException("message is empty", nameof(message));
|
||||
}
|
||||
|
||||
if (bytes.Length > 500) {
|
||||
throw new ArgumentException("message is longer than 500 bytes", nameof(message));
|
||||
}
|
||||
|
||||
if (message.Length != this.SanitiseText(message).Length) {
|
||||
throw new ArgumentException("message contained invalid characters", nameof(message));
|
||||
}
|
||||
|
||||
this.SendMessageUnsafe(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Sanitises a string by removing any invalid input.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The result of this method is safe to use with
|
||||
/// <see cref="SendMessage"/>, provided that it is not empty or too
|
||||
/// long.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="text">text to sanitise</param>
|
||||
/// <returns>sanitised text</returns>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public unsafe string SanitiseText(string text) {
|
||||
if (this._sanitiseString == null) {
|
||||
throw new InvalidOperationException("Could not find signature for chat sanitisation");
|
||||
}
|
||||
|
||||
var uText = Utf8String.FromString(text);
|
||||
|
||||
this._sanitiseString(uText, 0x27F, IntPtr.Zero);
|
||||
var sanitised = uText->ToString();
|
||||
|
||||
uText->Dtor();
|
||||
IMemorySpace.Free(uText);
|
||||
|
||||
return sanitised;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
[SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")]
|
||||
private readonly struct ChatPayload : IDisposable {
|
||||
[FieldOffset(0)]
|
||||
private readonly IntPtr textPtr;
|
||||
|
||||
[FieldOffset(16)]
|
||||
private readonly ulong textLen;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private readonly ulong unk1;
|
||||
|
||||
[FieldOffset(24)]
|
||||
private readonly ulong unk2;
|
||||
|
||||
internal ChatPayload(byte[] stringBytes) {
|
||||
this.textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
|
||||
Marshal.Copy(stringBytes, 0, this.textPtr, stringBytes.Length);
|
||||
Marshal.WriteByte(this.textPtr + stringBytes.Length, 0);
|
||||
|
||||
this.textLen = (ulong) (stringBytes.Length + 1);
|
||||
|
||||
this.unk1 = 64;
|
||||
this.unk2 = 0;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Marshal.FreeHGlobal(this.textPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,168 +1,168 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing chat bubble events and functions
|
||||
/// </summary>
|
||||
public class ChatBubbles : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string ChatBubbleOpen = "E8 ?? ?? ?? ?? C7 43 ?? ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? E8";
|
||||
internal const string ChatBubbleUpdate = "48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C";
|
||||
}
|
||||
|
||||
private IObjectTable ObjectTable { get; }
|
||||
|
||||
private delegate void OpenChatBubbleDelegate(IntPtr manager, IntPtr @object, IntPtr text, byte a4);
|
||||
|
||||
private delegate void UpdateChatBubbleDelegate(IntPtr bubblePtr, IntPtr @object);
|
||||
|
||||
private Hook<OpenChatBubbleDelegate>? OpenChatBubbleHook { get; }
|
||||
|
||||
private Hook<UpdateChatBubbleDelegate>? UpdateChatBubbleHook { get; }
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// Class containing chat bubble events and functions
|
||||
/// The delegate for chat bubble events.
|
||||
/// </summary>
|
||||
public class ChatBubbles : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string ChatBubbleOpen = "E8 ?? ?? ?? ?? C7 43 ?? ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? E8";
|
||||
internal const string ChatBubbleUpdate = "48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C";
|
||||
public delegate void OnChatBubbleDelegate(ref GameObject @object, ref SeString text);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for chat bubble update events.
|
||||
/// </summary>
|
||||
public delegate void OnUpdateChatBubbleDelegate(ref GameObject @object);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a chat bubble is shown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.ChatBubbles"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event OnChatBubbleDelegate? OnChatBubble;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a chat bubble is updated.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.ChatBubbles"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event OnUpdateChatBubbleDelegate? OnUpdateBubble;
|
||||
|
||||
internal ChatBubbles(IObjectTable objectTable, ISigScanner scanner, IGameInteropProvider interop, bool hookEnabled) {
|
||||
this.ObjectTable = objectTable;
|
||||
|
||||
if (!hookEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
private ObjectTable ObjectTable { get; }
|
||||
|
||||
private delegate void OpenChatBubbleDelegate(IntPtr manager, IntPtr @object, IntPtr text, byte a4);
|
||||
|
||||
private delegate void UpdateChatBubbleDelegate(IntPtr bubblePtr, IntPtr @object);
|
||||
|
||||
private Hook<OpenChatBubbleDelegate>? OpenChatBubbleHook { get; }
|
||||
|
||||
private Hook<UpdateChatBubbleDelegate>? UpdateChatBubbleHook { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for chat bubble events.
|
||||
/// </summary>
|
||||
public delegate void OnChatBubbleDelegate(ref GameObject @object, ref SeString text);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for chat bubble update events.
|
||||
/// </summary>
|
||||
public delegate void OnUpdateChatBubbleDelegate(ref GameObject @object);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a chat bubble is shown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.ChatBubbles"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event OnChatBubbleDelegate? OnChatBubble;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a chat bubble is updated.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.ChatBubbles"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event OnUpdateChatBubbleDelegate? OnUpdateBubble;
|
||||
|
||||
internal ChatBubbles(ObjectTable objectTable, SigScanner scanner, bool hookEnabled) {
|
||||
this.ObjectTable = objectTable;
|
||||
|
||||
if (!hookEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.ChatBubbleOpen, out var openPtr, "chat bubbles open")) {
|
||||
this.OpenChatBubbleHook = Hook<OpenChatBubbleDelegate>.FromAddress(openPtr, this.OpenChatBubbleDetour);
|
||||
this.OpenChatBubbleHook.Enable();
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.ChatBubbleUpdate, out var updatePtr, "chat bubbles update")) {
|
||||
this.UpdateChatBubbleHook = Hook<UpdateChatBubbleDelegate>.FromAddress(updatePtr + 9, this.UpdateChatBubbleDetour);
|
||||
this.UpdateChatBubbleHook.Enable();
|
||||
}
|
||||
if (scanner.TryScanText(Signatures.ChatBubbleOpen, out var openPtr, "chat bubbles open")) {
|
||||
this.OpenChatBubbleHook = interop.HookFromAddress<OpenChatBubbleDelegate>(openPtr, this.OpenChatBubbleDetour);
|
||||
this.OpenChatBubbleHook.Enable();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.OpenChatBubbleHook?.Dispose();
|
||||
this.UpdateChatBubbleHook?.Dispose();
|
||||
}
|
||||
|
||||
private void OpenChatBubbleDetour(IntPtr manager, IntPtr @object, IntPtr text, byte a4) {
|
||||
try {
|
||||
this.OpenChatBubbleDetourInner(manager, @object, text, a4);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in chat bubble detour");
|
||||
this.OpenChatBubbleHook!.Original(manager, @object, text, a4);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenChatBubbleDetourInner(IntPtr manager, IntPtr objectPtr, IntPtr textPtr, byte a4) {
|
||||
var @object = this.ObjectTable.CreateObjectReference(objectPtr);
|
||||
if (@object == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var text = Util.ReadSeString(textPtr);
|
||||
|
||||
try {
|
||||
this.OnChatBubble?.Invoke(ref @object, ref text);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in chat bubble event");
|
||||
}
|
||||
|
||||
var newText = text.Encode().Terminate();
|
||||
|
||||
unsafe {
|
||||
fixed (byte* newTextPtr = newText) {
|
||||
this.OpenChatBubbleHook!.Original(manager, @object.Address, (IntPtr) newTextPtr, a4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChatBubbleDetour(IntPtr bubblePtr, IntPtr @object) {
|
||||
try {
|
||||
this.UpdateChatBubbleDetourInner(bubblePtr, @object);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in update chat bubble detour");
|
||||
this.UpdateChatBubbleHook!.Original(bubblePtr, @object);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChatBubbleDetourInner(IntPtr bubblePtr, IntPtr objectPtr) {
|
||||
// var bubble = (ChatBubble*) bubblePtr;
|
||||
var @object = this.ObjectTable.CreateObjectReference(objectPtr);
|
||||
if (@object == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.OnUpdateBubble?.Invoke(ref @object);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in chat bubble update event");
|
||||
}
|
||||
|
||||
this.UpdateChatBubbleHook!.Original(bubblePtr, @object.Address);
|
||||
if (scanner.TryScanText(Signatures.ChatBubbleUpdate, out var updatePtr, "chat bubbles update")) {
|
||||
this.UpdateChatBubbleHook = interop.HookFromAddress<UpdateChatBubbleDelegate>(updatePtr + 9, this.UpdateChatBubbleDetour);
|
||||
this.UpdateChatBubbleHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
|
||||
internal unsafe struct ChatBubble {
|
||||
[FieldOffset(0x0)]
|
||||
internal readonly uint Id;
|
||||
|
||||
[FieldOffset(0x4)]
|
||||
internal float Timer;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
internal readonly uint Unk_8; // enum probably
|
||||
|
||||
[FieldOffset(0xC)]
|
||||
internal ChatBubbleStatus Status; // state of the bubble
|
||||
|
||||
[FieldOffset(0x10)]
|
||||
internal readonly byte* Text;
|
||||
|
||||
[FieldOffset(0x78)]
|
||||
internal readonly ulong Unk_78; // check whats in memory here
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.OpenChatBubbleHook?.Dispose();
|
||||
this.UpdateChatBubbleHook?.Dispose();
|
||||
}
|
||||
|
||||
internal enum ChatBubbleStatus : uint {
|
||||
GetData = 0,
|
||||
On = 1,
|
||||
Init = 2,
|
||||
Off = 3,
|
||||
private void OpenChatBubbleDetour(IntPtr manager, IntPtr @object, IntPtr text, byte a4) {
|
||||
try {
|
||||
this.OpenChatBubbleDetourInner(manager, @object, text, a4);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in chat bubble detour");
|
||||
this.OpenChatBubbleHook!.Original(manager, @object, text, a4);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenChatBubbleDetourInner(IntPtr manager, IntPtr objectPtr, IntPtr textPtr, byte a4) {
|
||||
var @object = this.ObjectTable.CreateObjectReference(objectPtr);
|
||||
if (@object == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var text = Util.ReadSeString(textPtr);
|
||||
|
||||
try {
|
||||
this.OnChatBubble?.Invoke(ref @object, ref text);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in chat bubble event");
|
||||
}
|
||||
|
||||
var newText = text.Encode().Terminate();
|
||||
|
||||
unsafe {
|
||||
fixed (byte* newTextPtr = newText) {
|
||||
this.OpenChatBubbleHook!.Original(manager, @object.Address, (IntPtr) newTextPtr, a4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChatBubbleDetour(IntPtr bubblePtr, IntPtr @object) {
|
||||
try {
|
||||
this.UpdateChatBubbleDetourInner(bubblePtr, @object);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in update chat bubble detour");
|
||||
this.UpdateChatBubbleHook!.Original(bubblePtr, @object);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChatBubbleDetourInner(IntPtr bubblePtr, IntPtr objectPtr) {
|
||||
// var bubble = (ChatBubble*) bubblePtr;
|
||||
var @object = this.ObjectTable.CreateObjectReference(objectPtr);
|
||||
if (@object == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.OnUpdateBubble?.Invoke(ref @object);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in chat bubble update event");
|
||||
}
|
||||
|
||||
this.UpdateChatBubbleHook!.Original(bubblePtr, @object.Address);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
|
||||
internal unsafe struct ChatBubble {
|
||||
[FieldOffset(0x0)]
|
||||
internal readonly uint Id;
|
||||
|
||||
[FieldOffset(0x4)]
|
||||
internal float Timer;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
internal readonly uint Unk_8; // enum probably
|
||||
|
||||
[FieldOffset(0xC)]
|
||||
internal ChatBubbleStatus Status; // state of the bubble
|
||||
|
||||
[FieldOffset(0x10)]
|
||||
internal readonly byte* Text;
|
||||
|
||||
[FieldOffset(0x78)]
|
||||
internal readonly ulong Unk_78; // check whats in memory here
|
||||
}
|
||||
|
||||
internal enum ChatBubbleStatus : uint {
|
||||
GetData = 0,
|
||||
On = 1,
|
||||
Init = 2,
|
||||
Off = 3,
|
||||
}
|
||||
|
|
|
@ -5,77 +5,77 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// Duty Finder functions
|
||||
/// </summary>
|
||||
public class DutyFinder {
|
||||
private static class Signatures {
|
||||
internal const string OpenRegularDuty = "48 89 6C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B F9 41 0F B6 E8";
|
||||
internal const string OpenRoulette = "E9 ?? ?? ?? ?? 8B 93 ?? ?? ?? ?? 48 83 C4 20";
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// Duty Finder functions
|
||||
/// </summary>
|
||||
public class DutyFinder {
|
||||
private static class Signatures {
|
||||
internal const string OpenRegularDuty = "48 89 6C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B F9 41 0F B6 E8";
|
||||
internal const string OpenRoulette = "E9 ?? ?? ?? ?? 8B 93 ?? ?? ?? ?? 48 83 C4 20";
|
||||
}
|
||||
|
||||
private delegate IntPtr OpenDutyDelegate(IntPtr agent, uint contentFinderCondition, byte a3);
|
||||
|
||||
private delegate IntPtr OpenRouletteDelegate(IntPtr agent, byte roulette, byte a3);
|
||||
|
||||
private readonly OpenDutyDelegate? _openDuty;
|
||||
private readonly OpenRouletteDelegate? _openRoulette;
|
||||
|
||||
internal DutyFinder(ISigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.OpenRegularDuty, out var openDutyPtr, "Duty Finder (open duty)")) {
|
||||
this._openDuty = Marshal.GetDelegateForFunctionPointer<OpenDutyDelegate>(openDutyPtr);
|
||||
}
|
||||
|
||||
private delegate IntPtr OpenDutyDelegate(IntPtr agent, uint contentFinderCondition, byte a3);
|
||||
|
||||
private delegate IntPtr OpenRouletteDelegate(IntPtr agent, byte roulette, byte a3);
|
||||
|
||||
private readonly OpenDutyDelegate? _openDuty;
|
||||
private readonly OpenRouletteDelegate? _openRoulette;
|
||||
|
||||
internal DutyFinder(SigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.OpenRegularDuty, out var openDutyPtr, "Duty Finder (open duty)")) {
|
||||
this._openDuty = Marshal.GetDelegateForFunctionPointer<OpenDutyDelegate>(openDutyPtr);
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.OpenRoulette, out var openRoulettePtr, "Duty Finder (open roulette)")) {
|
||||
this._openRoulette = Marshal.GetDelegateForFunctionPointer<OpenRouletteDelegate>(openRoulettePtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given duty.
|
||||
/// </summary>
|
||||
/// <param name="condition">duty to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open duty function could not be found in memory</exception>
|
||||
public void OpenDuty(ContentFinderCondition condition) {
|
||||
this.OpenDuty(condition.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given duty ID.
|
||||
/// </summary>
|
||||
/// <param name="contentFinderCondition">ID of duty to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open duty function could not be found in memory</exception>
|
||||
public unsafe void OpenDuty(uint contentFinderCondition) {
|
||||
if (this._openDuty == null) {
|
||||
throw new InvalidOperationException("Could not find signature for open duty function");
|
||||
}
|
||||
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ContentsFinder);
|
||||
|
||||
this._openDuty(agent, contentFinderCondition, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given roulette.
|
||||
/// </summary>
|
||||
/// <param name="roulette">roulette to show</param>
|
||||
public void OpenRoulette(ContentRoulette roulette) {
|
||||
this.OpenRoulette((byte) roulette.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given roulette ID.
|
||||
/// </summary>
|
||||
/// <param name="roulette">ID of roulette to show</param>
|
||||
public unsafe void OpenRoulette(byte roulette) {
|
||||
if (this._openRoulette == null) {
|
||||
throw new InvalidOperationException("Could not find signature for open roulette function");
|
||||
}
|
||||
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ContentsFinder);
|
||||
|
||||
this._openRoulette(agent, roulette, 0);
|
||||
if (scanner.TryScanText(Signatures.OpenRoulette, out var openRoulettePtr, "Duty Finder (open roulette)")) {
|
||||
this._openRoulette = Marshal.GetDelegateForFunctionPointer<OpenRouletteDelegate>(openRoulettePtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given duty.
|
||||
/// </summary>
|
||||
/// <param name="condition">duty to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open duty function could not be found in memory</exception>
|
||||
public void OpenDuty(ContentFinderCondition condition) {
|
||||
this.OpenDuty(condition.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given duty ID.
|
||||
/// </summary>
|
||||
/// <param name="contentFinderCondition">ID of duty to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open duty function could not be found in memory</exception>
|
||||
public unsafe void OpenDuty(uint contentFinderCondition) {
|
||||
if (this._openDuty == null) {
|
||||
throw new InvalidOperationException("Could not find signature for open duty function");
|
||||
}
|
||||
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ContentsFinder);
|
||||
|
||||
this._openDuty(agent, contentFinderCondition, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given roulette.
|
||||
/// </summary>
|
||||
/// <param name="roulette">roulette to show</param>
|
||||
public void OpenRoulette(ContentRoulette roulette) {
|
||||
this.OpenRoulette((byte) roulette.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Duty Finder to the given roulette ID.
|
||||
/// </summary>
|
||||
/// <param name="roulette">ID of roulette to show</param>
|
||||
public unsafe void OpenRoulette(byte roulette) {
|
||||
if (this._openRoulette == null) {
|
||||
throw new InvalidOperationException("Could not find signature for open roulette function");
|
||||
}
|
||||
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ContentsFinder);
|
||||
|
||||
this._openRoulette(agent, roulette, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,64 +4,64 @@ using Dalamud.Game;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// Class containing examine functions
|
||||
/// </summary>
|
||||
public class Examine {
|
||||
private static class Signatures {
|
||||
internal const string RequestCharacterInfo = "40 53 48 83 EC 40 48 8B D9 48 8B 49 10 48 8B 01 FF 90 ?? ?? ?? ?? BA";
|
||||
}
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
private delegate long RequestCharInfoDelegate(IntPtr ptr);
|
||||
/// <summary>
|
||||
/// Class containing examine functions
|
||||
/// </summary>
|
||||
public class Examine {
|
||||
private static class Signatures {
|
||||
internal const string RequestCharacterInfo = "40 53 48 83 EC 40 48 8B D9 48 8B 49 10 48 8B 01 FF 90 ?? ?? ?? ?? BA";
|
||||
}
|
||||
|
||||
private RequestCharInfoDelegate? RequestCharacterInfo { get; }
|
||||
private delegate long RequestCharInfoDelegate(IntPtr ptr);
|
||||
|
||||
internal Examine(SigScanner scanner) {
|
||||
// got this by checking what accesses rciData below
|
||||
if (scanner.TryScanText(Signatures.RequestCharacterInfo, out var rciPtr, "Examine")) {
|
||||
this.RequestCharacterInfo = Marshal.GetDelegateForFunctionPointer<RequestCharInfoDelegate>(rciPtr);
|
||||
}
|
||||
}
|
||||
private RequestCharInfoDelegate? RequestCharacterInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Examine window for the specified object.
|
||||
/// </summary>
|
||||
/// <param name="object">Object to open window for</param>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public void OpenExamineWindow(GameObject @object) {
|
||||
this.OpenExamineWindow(@object.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Examine window for the object with the specified ID.
|
||||
/// </summary>
|
||||
/// <param name="objectId">Object ID to open window for</param>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public unsafe void OpenExamineWindow(uint objectId) {
|
||||
if (this.RequestCharacterInfo == null) {
|
||||
throw new InvalidOperationException("Could not find signature for Examine function");
|
||||
}
|
||||
|
||||
// NOTES LAST UPDATED: 6.0
|
||||
|
||||
// offsets and stuff come from the beginning of case 0x2c (around line 621 in IDA)
|
||||
// if 29f8 ever changes, I'd just scan for it in old binary and find what it is in the new binary at the same spot
|
||||
// 40 55 53 57 41 54 41 55 41 56 48 8D 6C 24
|
||||
// offset below is 4C 8B B0 ?? ?? ?? ?? 4D 85 F6 0F 84 ?? ?? ?? ?? 0F B6 83
|
||||
var agentModule = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule();
|
||||
var rciData = Marshal.ReadIntPtr(agentModule + 0x1B0);
|
||||
|
||||
// offsets at sig E8 ?? ?? ?? ?? 33 C0 EB 4C
|
||||
// this is called at the end of the 2c case
|
||||
var raw = (uint*) rciData;
|
||||
*(raw + 10) = objectId;
|
||||
*(raw + 11) = objectId;
|
||||
*(raw + 12) = objectId;
|
||||
*(raw + 13) = 0xE0000000;
|
||||
*(raw + 301) = 0;
|
||||
|
||||
this.RequestCharacterInfo(rciData);
|
||||
internal Examine(ISigScanner scanner) {
|
||||
// got this by checking what accesses rciData below
|
||||
if (scanner.TryScanText(Signatures.RequestCharacterInfo, out var rciPtr, "Examine")) {
|
||||
this.RequestCharacterInfo = Marshal.GetDelegateForFunctionPointer<RequestCharInfoDelegate>(rciPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Examine window for the specified object.
|
||||
/// </summary>
|
||||
/// <param name="object">Object to open window for</param>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public void OpenExamineWindow(GameObject @object) {
|
||||
this.OpenExamineWindow(@object.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Examine window for the object with the specified ID.
|
||||
/// </summary>
|
||||
/// <param name="objectId">Object ID to open window for</param>
|
||||
/// <exception cref="InvalidOperationException">If the signature for this function could not be found</exception>
|
||||
public unsafe void OpenExamineWindow(uint objectId) {
|
||||
if (this.RequestCharacterInfo == null) {
|
||||
throw new InvalidOperationException("Could not find signature for Examine function");
|
||||
}
|
||||
|
||||
// NOTES LAST UPDATED: 6.0
|
||||
|
||||
// offsets and stuff come from the beginning of case 0x2c (around line 621 in IDA)
|
||||
// if 29f8 ever changes, I'd just scan for it in old binary and find what it is in the new binary at the same spot
|
||||
// 40 55 53 57 41 54 41 55 41 56 48 8D 6C 24
|
||||
// offset below is 4C 8B B0 ?? ?? ?? ?? 4D 85 F6 0F 84 ?? ?? ?? ?? 0F B6 83
|
||||
var agentModule = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule();
|
||||
var rciData = Marshal.ReadIntPtr(agentModule + 0x1B0);
|
||||
|
||||
// offsets at sig E8 ?? ?? ?? ?? 33 C0 EB 4C
|
||||
// this is called at the end of the 2c case
|
||||
var raw = (uint*) rciData;
|
||||
*(raw + 10) = objectId;
|
||||
*(raw + 11) = objectId;
|
||||
*(raw + 12) = objectId;
|
||||
*(raw + 13) = 0xE0000000;
|
||||
*(raw + 301) = 0;
|
||||
|
||||
this.RequestCharacterInfo(rciData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,60 +3,60 @@ using System.Collections.Generic;
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
namespace XivCommon.Functions.FriendList {
|
||||
namespace XivCommon.Functions.FriendList;
|
||||
|
||||
/// <summary>
|
||||
/// The class containing friend list functionality
|
||||
/// </summary>
|
||||
public class FriendList {
|
||||
// Updated: 5.58-HF1
|
||||
private const int InfoOffset = 0x28;
|
||||
private const int LengthOffset = 0x10;
|
||||
private const int ListOffset = 0x98;
|
||||
|
||||
/// <summary>
|
||||
/// The class containing friend list functionality
|
||||
/// <para>
|
||||
/// A live list of the currently-logged-in player's friends.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The list is empty if not logged in.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class FriendList {
|
||||
// Updated: 5.58-HF1
|
||||
private const int InfoOffset = 0x28;
|
||||
private const int LengthOffset = 0x10;
|
||||
private const int ListOffset = 0x98;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// A live list of the currently-logged-in player's friends.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The list is empty if not logged in.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public unsafe IList<FriendListEntry> List {
|
||||
get {
|
||||
var friendListAgent = (IntPtr) Framework.Instance()
|
||||
->GetUiModule()
|
||||
public unsafe IList<FriendListEntry> List {
|
||||
get {
|
||||
var friendListAgent = (IntPtr) Framework.Instance()
|
||||
->GetUiModule()
|
||||
->GetAgentModule()
|
||||
->GetAgentByInternalId(AgentId.SocialFriendList);
|
||||
if (friendListAgent == IntPtr.Zero) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var info = *(IntPtr*) (friendListAgent + InfoOffset);
|
||||
if (info == IntPtr.Zero) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var length = *(ushort*) (info + LengthOffset);
|
||||
if (length == 0) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var list = *(IntPtr*) (info + ListOffset);
|
||||
if (list == IntPtr.Zero) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var entries = new List<FriendListEntry>(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
var entry = *(FriendListEntry*) (list + i * FriendListEntry.Size);
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
->GetAgentByInternalId(AgentId.SocialFriendList);
|
||||
if (friendListAgent == IntPtr.Zero) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
}
|
||||
|
||||
internal FriendList() {
|
||||
var info = *(IntPtr*) (friendListAgent + InfoOffset);
|
||||
if (info == IntPtr.Zero) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var length = *(ushort*) (info + LengthOffset);
|
||||
if (length == 0) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var list = *(IntPtr*) (info + ListOffset);
|
||||
if (list == IntPtr.Zero) {
|
||||
return Array.Empty<FriendListEntry>();
|
||||
}
|
||||
|
||||
var entries = new List<FriendListEntry>(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
var entry = *(FriendListEntry*) (list + i * FriendListEntry.Size);
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
internal FriendList() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,69 +3,69 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
namespace XivCommon.Functions.FriendList {
|
||||
namespace XivCommon.Functions.FriendList;
|
||||
|
||||
/// <summary>
|
||||
/// An entry in a player's friend list.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = Size)]
|
||||
public unsafe struct FriendListEntry {
|
||||
internal const int Size = 104;
|
||||
|
||||
/// <summary>
|
||||
/// An entry in a player's friend list.
|
||||
/// The content ID of the friend.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = Size)]
|
||||
public unsafe struct FriendListEntry {
|
||||
internal const int Size = 104;
|
||||
[FieldOffset(0x00)]
|
||||
public readonly ulong ContentId;
|
||||
|
||||
/// <summary>
|
||||
/// The content ID of the friend.
|
||||
/// </summary>
|
||||
[FieldOffset(0x00)]
|
||||
public readonly ulong ContentId;
|
||||
/// <summary>
|
||||
/// The current world of the friend.
|
||||
/// </summary>
|
||||
[FieldOffset(0x1E)]
|
||||
public readonly ushort CurrentWorld;
|
||||
|
||||
/// <summary>
|
||||
/// The current world of the friend.
|
||||
/// </summary>
|
||||
[FieldOffset(0x1E)]
|
||||
public readonly ushort CurrentWorld;
|
||||
/// <summary>
|
||||
/// The home world of the friend.
|
||||
/// </summary>
|
||||
[FieldOffset(0x20)]
|
||||
public readonly ushort HomeWorld;
|
||||
|
||||
/// <summary>
|
||||
/// The home world of the friend.
|
||||
/// </summary>
|
||||
[FieldOffset(0x20)]
|
||||
public readonly ushort HomeWorld;
|
||||
/// <summary>
|
||||
/// The job the friend is currently on.
|
||||
/// </summary>
|
||||
[FieldOffset(0x29)]
|
||||
public readonly byte Job;
|
||||
|
||||
/// <summary>
|
||||
/// The job the friend is currently on.
|
||||
/// </summary>
|
||||
[FieldOffset(0x29)]
|
||||
public readonly byte Job;
|
||||
/// <summary>
|
||||
/// The friend's raw SeString name. See <see cref="Name"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(0x2A)]
|
||||
public fixed byte RawName[32];
|
||||
|
||||
/// <summary>
|
||||
/// The friend's raw SeString name. See <see cref="Name"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(0x2A)]
|
||||
public fixed byte RawName[32];
|
||||
/// <summary>
|
||||
/// The friend's raw SeString free company tag. See <see cref="FreeCompany"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(0x4A)]
|
||||
public fixed byte RawFreeCompany[5];
|
||||
|
||||
/// <summary>
|
||||
/// The friend's raw SeString free company tag. See <see cref="FreeCompany"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(0x4A)]
|
||||
public fixed byte RawFreeCompany[5];
|
||||
|
||||
/// <summary>
|
||||
/// The friend's name.
|
||||
/// </summary>
|
||||
public SeString Name {
|
||||
get {
|
||||
fixed (byte* ptr = this.RawName) {
|
||||
return MemoryHelper.ReadSeStringNullTerminated((IntPtr) ptr);
|
||||
}
|
||||
/// <summary>
|
||||
/// The friend's name.
|
||||
/// </summary>
|
||||
public SeString Name {
|
||||
get {
|
||||
fixed (byte* ptr = this.RawName) {
|
||||
return MemoryHelper.ReadSeStringNullTerminated((IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The friend's free company tag.
|
||||
/// </summary>
|
||||
public SeString FreeCompany {
|
||||
get {
|
||||
fixed (byte* ptr = this.RawFreeCompany) {
|
||||
return MemoryHelper.ReadSeStringNullTerminated((IntPtr) ptr);
|
||||
}
|
||||
/// <summary>
|
||||
/// The friend's free company tag.
|
||||
/// </summary>
|
||||
public SeString FreeCompany {
|
||||
get {
|
||||
fixed (byte* ptr = this.RawFreeCompany) {
|
||||
return MemoryHelper.ReadSeStringNullTerminated((IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
using System;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace XivCommon.Functions.Housing {
|
||||
namespace XivCommon.Functions.Housing;
|
||||
|
||||
/// <summary>
|
||||
/// The class containing housing functionality
|
||||
/// </summary>
|
||||
public class Housing {
|
||||
private static class Signatures {
|
||||
internal const string HousingPointer = "48 8B 05 ?? ?? ?? ?? 48 83 78 ?? ?? 74 16 48 8D 8F ?? ?? ?? ?? 66 89 5C 24 ?? 48 8D 54 24 ?? E8 ?? ?? ?? ?? 48 8B 7C 24";
|
||||
}
|
||||
|
||||
private IntPtr HousingPointer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The class containing housing functionality
|
||||
/// Gets the raw struct containing information about the player's current location in a housing ward.
|
||||
///
|
||||
/// <returns>struct if player is in a housing ward, null otherwise</returns>
|
||||
/// </summary>
|
||||
public class Housing {
|
||||
private static class Signatures {
|
||||
internal const string HousingPointer = "48 8B 05 ?? ?? ?? ?? 48 83 78 ?? ?? 74 16 48 8D 8F ?? ?? ?? ?? 66 89 5C 24 ?? 48 8D 54 24 ?? E8 ?? ?? ?? ?? 48 8B 7C 24";
|
||||
}
|
||||
|
||||
private IntPtr HousingPointer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw struct containing information about the player's current location in a housing ward.
|
||||
///
|
||||
/// <returns>struct if player is in a housing ward, null otherwise</returns>
|
||||
/// </summary>
|
||||
// Updated: 6.0
|
||||
// 48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 20 49 8B 00 (ward?)
|
||||
public unsafe RawHousingLocation? RawLocation {
|
||||
get {
|
||||
if (this.HousingPointer == IntPtr.Zero) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var loc = Util.FollowPointerChain(this.HousingPointer, new[] { 0, 0 });
|
||||
if (loc == IntPtr.Zero) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var locPtr = (RawHousingLocation*) (loc + 0x96a0);
|
||||
return *locPtr;
|
||||
// Updated: 6.0
|
||||
// 48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 20 49 8B 00 (ward?)
|
||||
public unsafe RawHousingLocation? RawLocation {
|
||||
get {
|
||||
if (this.HousingPointer == IntPtr.Zero) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets process information about the player's current location in a housing ward.
|
||||
///
|
||||
/// <returns>information class if player is in a housing ward, null otherwise</returns>
|
||||
/// </summary>
|
||||
public HousingLocation? Location {
|
||||
get {
|
||||
var loc = this.RawLocation;
|
||||
return loc == null ? null : new HousingLocation(loc.Value);
|
||||
var loc = Util.FollowPointerChain(this.HousingPointer, new[] { 0, 0 });
|
||||
if (loc == IntPtr.Zero) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal Housing(SigScanner scanner) {
|
||||
if (scanner.TryGetStaticAddressFromSig(Signatures.HousingPointer, out var ptr)) {
|
||||
this.HousingPointer = ptr;
|
||||
}
|
||||
var locPtr = (RawHousingLocation*) (loc + 0x96a0);
|
||||
return *locPtr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets process information about the player's current location in a housing ward.
|
||||
///
|
||||
/// <returns>information class if player is in a housing ward, null otherwise</returns>
|
||||
/// </summary>
|
||||
public HousingLocation? Location {
|
||||
get {
|
||||
var loc = this.RawLocation;
|
||||
return loc == null ? null : new HousingLocation(loc.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal Housing(ISigScanner scanner) {
|
||||
if (scanner.TryGetStaticAddressFromSig(Signatures.HousingPointer, out var ptr)) {
|
||||
this.HousingPointer = ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
namespace XivCommon.Functions.Housing {
|
||||
namespace XivCommon.Functions.Housing;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a player's current location in a housing ward.
|
||||
/// </summary>
|
||||
public class HousingLocation {
|
||||
/// <summary>
|
||||
/// Information about a player's current location in a housing ward.
|
||||
/// The housing ward that the player is in.
|
||||
/// </summary>
|
||||
public class HousingLocation {
|
||||
/// <summary>
|
||||
/// The housing ward that the player is in.
|
||||
/// </summary>
|
||||
public ushort Ward;
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The yard that the player is in.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is the same as plot number but indicates that the player is in
|
||||
/// the exterior area (the yard) of that plot.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public ushort? Yard;
|
||||
/// <summary>
|
||||
/// The plot that the player is in.
|
||||
/// </summary>
|
||||
public ushort? Plot;
|
||||
/// <summary>
|
||||
/// The apartment wing (1 or 2 for normal or subdivision) that the
|
||||
/// player is in.
|
||||
/// </summary>
|
||||
public ushort? ApartmentWing;
|
||||
/// <summary>
|
||||
/// The apartment that the player is in.
|
||||
/// </summary>
|
||||
public ushort? Apartment;
|
||||
public ushort Ward;
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The yard that the player is in.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is the same as plot number but indicates that the player is in
|
||||
/// the exterior area (the yard) of that plot.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public ushort? Yard;
|
||||
/// <summary>
|
||||
/// The plot that the player is in.
|
||||
/// </summary>
|
||||
public ushort? Plot;
|
||||
/// <summary>
|
||||
/// The apartment wing (1 or 2 for normal or subdivision) that the
|
||||
/// player is in.
|
||||
/// </summary>
|
||||
public ushort? ApartmentWing;
|
||||
/// <summary>
|
||||
/// The apartment that the player is in.
|
||||
/// </summary>
|
||||
public ushort? Apartment;
|
||||
|
||||
internal HousingLocation(RawHousingLocation loc) {
|
||||
var ward = loc.CurrentWard;
|
||||
internal HousingLocation(RawHousingLocation loc) {
|
||||
var ward = loc.CurrentWard;
|
||||
|
||||
if ((loc.CurrentPlot & 0x80) > 0) {
|
||||
// the struct is in apartment mode
|
||||
this.ApartmentWing = (ushort?) ((loc.CurrentPlot & ~0x80) + 1);
|
||||
this.Apartment = (ushort?) (ward >> 6);
|
||||
this.Ward = (ushort) ((ward & 0x3F) + 1);
|
||||
if (this.Apartment == 0) {
|
||||
this.Apartment = null;
|
||||
}
|
||||
} else if (loc.InsideIndicator == 0) {
|
||||
// inside a plot
|
||||
this.Plot = (ushort?) (loc.CurrentPlot + 1);
|
||||
} else if (loc.CurrentYard != 0xFF) {
|
||||
// not inside a plot
|
||||
// yard is 0xFF when not in one
|
||||
this.Yard = (ushort?) (loc.CurrentYard + 1);
|
||||
if ((loc.CurrentPlot & 0x80) > 0) {
|
||||
// the struct is in apartment mode
|
||||
this.ApartmentWing = (ushort?) ((loc.CurrentPlot & ~0x80) + 1);
|
||||
this.Apartment = (ushort?) (ward >> 6);
|
||||
this.Ward = (ushort) ((ward & 0x3F) + 1);
|
||||
if (this.Apartment == 0) {
|
||||
this.Apartment = null;
|
||||
}
|
||||
} else if (loc.InsideIndicator == 0) {
|
||||
// inside a plot
|
||||
this.Plot = (ushort?) (loc.CurrentPlot + 1);
|
||||
} else if (loc.CurrentYard != 0xFF) {
|
||||
// not inside a plot
|
||||
// yard is 0xFF when not in one
|
||||
this.Yard = (ushort?) (loc.CurrentYard + 1);
|
||||
}
|
||||
|
||||
if (this.Ward == 0) {
|
||||
this.Ward = (ushort) (ward + 1);
|
||||
}
|
||||
if (this.Ward == 0) {
|
||||
this.Ward = (ushort) (ward + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +1,41 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace XivCommon.Functions.Housing {
|
||||
namespace XivCommon.Functions.Housing;
|
||||
|
||||
/// <summary>
|
||||
/// Information about the player's current location in a housing ward as
|
||||
/// kept by the game's internal structures.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct RawHousingLocation {
|
||||
/// <summary>
|
||||
/// Information about the player's current location in a housing ward as
|
||||
/// kept by the game's internal structures.
|
||||
/// The zero-indexed plot number that the player is in.
|
||||
///
|
||||
/// <para>
|
||||
/// Contains apartment data when inside an apartment building.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public readonly struct RawHousingLocation {
|
||||
/// <summary>
|
||||
/// The zero-indexed plot number that the player is in.
|
||||
///
|
||||
/// <para>
|
||||
/// Contains apartment data when inside an apartment building.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public readonly ushort CurrentPlot; // a0 -> a2
|
||||
/// <summary>
|
||||
/// The zero-indexed ward number that the player is in.
|
||||
///
|
||||
/// <para>
|
||||
/// Contains apartment data when inside an apartment building.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public readonly ushort CurrentWard; // a2 -> a4
|
||||
private readonly uint unknownBytes1; // a4 -> a8
|
||||
/// <summary>
|
||||
/// The zero-indexed yard number that the player is in.
|
||||
///
|
||||
/// <para>
|
||||
/// Is <c>0xFF</c> when not in a yard.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public readonly byte CurrentYard; // a8 -> a9
|
||||
private readonly byte unknownBytes2; // a9 -> aa
|
||||
/// <summary>
|
||||
/// A byte that is zero when the player is inside a plot.
|
||||
/// </summary>
|
||||
public readonly byte InsideIndicator; // aa -> ab
|
||||
}
|
||||
}
|
||||
public readonly ushort CurrentPlot; // a0 -> a2
|
||||
/// <summary>
|
||||
/// The zero-indexed ward number that the player is in.
|
||||
///
|
||||
/// <para>
|
||||
/// Contains apartment data when inside an apartment building.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public readonly ushort CurrentWard; // a2 -> a4
|
||||
private readonly uint unknownBytes1; // a4 -> a8
|
||||
/// <summary>
|
||||
/// The zero-indexed yard number that the player is in.
|
||||
///
|
||||
/// <para>
|
||||
/// Is <c>0xFF</c> when not in a yard.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public readonly byte CurrentYard; // a8 -> a9
|
||||
private readonly byte unknownBytes2; // a9 -> aa
|
||||
/// <summary>
|
||||
/// A byte that is zero when the player is inside a plot.
|
||||
/// </summary>
|
||||
public readonly byte InsideIndicator; // aa -> ab
|
||||
}
|
|
@ -5,79 +5,79 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// Journal functions
|
||||
/// </summary>
|
||||
public class Journal {
|
||||
private static class Signatures {
|
||||
internal const string OpenQuest = "E8 ?? ?? ?? ?? 48 8B 74 24 ?? 48 8B 7C 24 ?? 48 83 C4 30 5B C3 48 8B CB";
|
||||
internal const string IsQuestCompleted = "E8 ?? ?? ?? ?? 41 88 84 2C";
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// Journal functions
|
||||
/// </summary>
|
||||
public class Journal {
|
||||
private static class Signatures {
|
||||
internal const string OpenQuest = "E8 ?? ?? ?? ?? 48 8B 74 24 ?? 48 8B 7C 24 ?? 48 83 C4 30 5B C3 48 8B CB";
|
||||
internal const string IsQuestCompleted = "E8 ?? ?? ?? ?? 41 88 84 2C";
|
||||
}
|
||||
|
||||
private delegate IntPtr OpenQuestDelegate(IntPtr agent, int questId, int a3, ushort a4, byte a5);
|
||||
|
||||
private delegate byte IsQuestCompletedDelegate(ushort questId);
|
||||
|
||||
private readonly OpenQuestDelegate? _openQuest;
|
||||
private readonly IsQuestCompletedDelegate? _isQuestCompleted;
|
||||
|
||||
internal Journal(ISigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.OpenQuest, out var openQuestPtr, "Journal (open quest)")) {
|
||||
this._openQuest = Marshal.GetDelegateForFunctionPointer<OpenQuestDelegate>(openQuestPtr);
|
||||
}
|
||||
|
||||
private delegate IntPtr OpenQuestDelegate(IntPtr agent, int questId, int a3, ushort a4, byte a5);
|
||||
|
||||
private delegate byte IsQuestCompletedDelegate(ushort questId);
|
||||
|
||||
private readonly OpenQuestDelegate? _openQuest;
|
||||
private readonly IsQuestCompletedDelegate? _isQuestCompleted;
|
||||
|
||||
internal Journal(SigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.OpenQuest, out var openQuestPtr, "Journal (open quest)")) {
|
||||
this._openQuest = Marshal.GetDelegateForFunctionPointer<OpenQuestDelegate>(openQuestPtr);
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.IsQuestCompleted, out var questCompletedPtr, "Journal (quest completed)")) {
|
||||
this._isQuestCompleted = Marshal.GetDelegateForFunctionPointer<IsQuestCompletedDelegate>(questCompletedPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the quest journal to the given quest.
|
||||
/// </summary>
|
||||
/// <param name="quest">quest to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open quest function could not be found in memory</exception>
|
||||
public void OpenQuest(Quest quest) {
|
||||
this.OpenQuest(quest.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the quest journal to the given quest ID.
|
||||
/// </summary>
|
||||
/// <param name="questId">ID of quest to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open quest function could not be found in memory</exception>
|
||||
public unsafe void OpenQuest(uint questId) {
|
||||
if (this._openQuest == null) {
|
||||
throw new InvalidOperationException("Could not find signature for open quest function");
|
||||
}
|
||||
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Journal);
|
||||
|
||||
this._openQuest(agent, (int) (questId & 0xFFFF), 1, 0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given quest is completed.
|
||||
/// </summary>
|
||||
/// <param name="quest">quest to check</param>
|
||||
/// <returns>true if the quest is completed</returns>
|
||||
/// <exception cref="InvalidOperationException">if the function for checking quest completion could not be found in memory</exception>
|
||||
public bool IsQuestCompleted(Quest quest) {
|
||||
return this.IsQuestCompleted(quest.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given quest ID is completed.
|
||||
/// </summary>
|
||||
/// <param name="questId">ID of quest to check</param>
|
||||
/// <returns>true if the quest is completed</returns>
|
||||
/// <exception cref="InvalidOperationException">if the function for checking quest completion could not be found in memory</exception>
|
||||
public bool IsQuestCompleted(uint questId) {
|
||||
if (this._isQuestCompleted == null) {
|
||||
throw new InvalidOperationException("Could not find signature for quest completed function");
|
||||
}
|
||||
|
||||
return this._isQuestCompleted((ushort) (questId & 0xFFFF)) != 0;
|
||||
if (scanner.TryScanText(Signatures.IsQuestCompleted, out var questCompletedPtr, "Journal (quest completed)")) {
|
||||
this._isQuestCompleted = Marshal.GetDelegateForFunctionPointer<IsQuestCompletedDelegate>(questCompletedPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the quest journal to the given quest.
|
||||
/// </summary>
|
||||
/// <param name="quest">quest to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open quest function could not be found in memory</exception>
|
||||
public void OpenQuest(Quest quest) {
|
||||
this.OpenQuest(quest.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the quest journal to the given quest ID.
|
||||
/// </summary>
|
||||
/// <param name="questId">ID of quest to show</param>
|
||||
/// <exception cref="InvalidOperationException">if the open quest function could not be found in memory</exception>
|
||||
public unsafe void OpenQuest(uint questId) {
|
||||
if (this._openQuest == null) {
|
||||
throw new InvalidOperationException("Could not find signature for open quest function");
|
||||
}
|
||||
|
||||
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Journal);
|
||||
|
||||
this._openQuest(agent, (int) (questId & 0xFFFF), 1, 0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given quest is completed.
|
||||
/// </summary>
|
||||
/// <param name="quest">quest to check</param>
|
||||
/// <returns>true if the quest is completed</returns>
|
||||
/// <exception cref="InvalidOperationException">if the function for checking quest completion could not be found in memory</exception>
|
||||
public bool IsQuestCompleted(Quest quest) {
|
||||
return this.IsQuestCompleted(quest.RowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given quest ID is completed.
|
||||
/// </summary>
|
||||
/// <param name="questId">ID of quest to check</param>
|
||||
/// <returns>true if the quest is completed</returns>
|
||||
/// <exception cref="InvalidOperationException">if the function for checking quest completion could not be found in memory</exception>
|
||||
public bool IsQuestCompleted(uint questId) {
|
||||
if (this._isQuestCompleted == null) {
|
||||
throw new InvalidOperationException("Could not find signature for quest completed function");
|
||||
}
|
||||
|
||||
return this._isQuestCompleted((ushort) (questId & 0xFFFF)) != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
using System;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.NamePlates {
|
||||
namespace XivCommon.Functions.NamePlates;
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the name plate update event
|
||||
/// </summary>
|
||||
public class NamePlateUpdateEventArgs {
|
||||
/// <summary>
|
||||
/// Arguments for the name plate update event
|
||||
/// The object ID associated with this name plate.
|
||||
/// </summary>
|
||||
public class NamePlateUpdateEventArgs {
|
||||
/// <summary>
|
||||
/// The object ID associated with this name plate.
|
||||
/// </summary>
|
||||
public uint ObjectId { get; }
|
||||
public uint ObjectId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name string.
|
||||
/// </summary>
|
||||
public SeString Name { get; set; } = null!;
|
||||
/// <summary>
|
||||
/// The name string.
|
||||
/// </summary>
|
||||
public SeString Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The FC tag string for name plates that use it. Set to the empty string to disable.
|
||||
/// </summary>
|
||||
public SeString FreeCompany { get; set; } = null!;
|
||||
/// <summary>
|
||||
/// The FC tag string for name plates that use it. Set to the empty string to disable.
|
||||
/// </summary>
|
||||
public SeString FreeCompany { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The title string for name plates that use it. Set to the empty string to disable.
|
||||
/// </summary>
|
||||
public SeString Title { get; set; } = null!;
|
||||
/// <summary>
|
||||
/// The title string for name plates that use it. Set to the empty string to disable.
|
||||
/// </summary>
|
||||
public SeString Title { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The level string for name plates that use it. Set to the empty string to disable.
|
||||
/// </summary>
|
||||
///
|
||||
public SeString Level { get; set; } = null!;
|
||||
/// <summary>
|
||||
/// The level string for name plates that use it. Set to the empty string to disable.
|
||||
/// </summary>
|
||||
///
|
||||
public SeString Level { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The letter that appears after enemy names, such as A, B, etc.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Setting this property will always cause a memory leak.</b>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public SeString EnemyLetter {
|
||||
get;
|
||||
[Obsolete("Setting this property will always cause a memory leak.")]
|
||||
set;
|
||||
} = null!;
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The letter that appears after enemy names, such as A, B, etc.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Setting this property will always cause a memory leak.</b>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public SeString EnemyLetter {
|
||||
get;
|
||||
[Obsolete("Setting this property will always cause a memory leak.")]
|
||||
set;
|
||||
} = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The icon to be shown on this name plate. Use <see cref="uint.MaxValue"/> for no icon.
|
||||
/// </summary>
|
||||
public uint Icon { get; set; }
|
||||
/// <summary>
|
||||
/// The icon to be shown on this name plate. Use <see cref="uint.MaxValue"/> for no icon.
|
||||
/// </summary>
|
||||
public uint Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The colour of this name plate.
|
||||
/// </summary>
|
||||
public RgbaColour Colour { get; set; } = new();
|
||||
/// <summary>
|
||||
/// The colour of this name plate.
|
||||
/// </summary>
|
||||
public RgbaColour Colour { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The type of this name plate.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Changing this without setting the appropriate fields can cause the game to crash.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public PlateType Type { get; set; }
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The type of this name plate.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Changing this without setting the appropriate fields can cause the game to crash.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public PlateType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A bitmask of flags for the name plate.
|
||||
/// </summary>
|
||||
public int Flags { get; set; }
|
||||
/// <summary>
|
||||
/// A bitmask of flags for the name plate.
|
||||
/// </summary>
|
||||
public int Flags { get; set; }
|
||||
|
||||
internal NamePlateUpdateEventArgs(uint objectId) {
|
||||
this.ObjectId = objectId;
|
||||
}
|
||||
internal NamePlateUpdateEventArgs(uint objectId) {
|
||||
this.ObjectId = objectId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,224 +3,225 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon.Functions.NamePlates {
|
||||
namespace XivCommon.Functions.NamePlates;
|
||||
|
||||
/// <summary>
|
||||
/// The class containing name plate functionality
|
||||
/// </summary>
|
||||
public class NamePlates : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string NamePlateUpdate = "48 8B C4 41 56 48 81 EC ?? ?? ?? ?? 48 89 58 F0";
|
||||
}
|
||||
|
||||
private unsafe delegate IntPtr NamePlateUpdateDelegate(AddonNamePlate* addon, NumberArrayData** numberData, StringArrayData** stringData);
|
||||
|
||||
/// <summary>
|
||||
/// The class containing name plate functionality
|
||||
/// The delegate for name plate update events.
|
||||
/// </summary>
|
||||
public class NamePlates : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string NamePlateUpdate = "48 8B C4 41 56 48 81 EC ?? ?? ?? ?? 48 89 58 F0";
|
||||
public delegate void NamePlateUpdateEvent(NamePlateUpdateEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a name plate is due to update.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.NamePlates"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event NamePlateUpdateEvent? OnUpdate;
|
||||
|
||||
private GameFunctions Functions { get; }
|
||||
private readonly Hook<NamePlateUpdateDelegate>? _namePlateUpdateHook;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// If all name plates should be forced to redraw.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is useful for forcing your changes to apply to existing name plates when the plugin is hot-loaded.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool ForceRedraw { get; set; }
|
||||
|
||||
internal NamePlates(GameFunctions functions, ISigScanner scanner, IGameInteropProvider interop, bool hookEnabled) {
|
||||
this.Functions = functions;
|
||||
|
||||
if (!hookEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
private unsafe delegate IntPtr NamePlateUpdateDelegate(AddonNamePlate* addon, NumberArrayData** numberData, StringArrayData** stringData);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for name plate update events.
|
||||
/// </summary>
|
||||
public delegate void NamePlateUpdateEvent(NamePlateUpdateEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a name plate is due to update.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.NamePlates"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event NamePlateUpdateEvent? OnUpdate;
|
||||
|
||||
private GameFunctions Functions { get; }
|
||||
private readonly Hook<NamePlateUpdateDelegate>? _namePlateUpdateHook;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// If all name plates should be forced to redraw.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is useful for forcing your changes to apply to existing name plates when the plugin is hot-loaded.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool ForceRedraw { get; set; }
|
||||
|
||||
internal NamePlates(GameFunctions functions, SigScanner scanner, bool hookEnabled) {
|
||||
this.Functions = functions;
|
||||
|
||||
if (!hookEnabled) {
|
||||
return;
|
||||
if (scanner.TryScanText(Signatures.NamePlateUpdate, out var updatePtr)) {
|
||||
unsafe {
|
||||
this._namePlateUpdateHook = interop.HookFromAddress<NamePlateUpdateDelegate>(updatePtr, this.NamePlateUpdateDetour);
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.NamePlateUpdate, out var updatePtr)) {
|
||||
unsafe {
|
||||
this._namePlateUpdateHook = Hook<NamePlateUpdateDelegate>.FromAddress(updatePtr, this.NamePlateUpdateDetour);
|
||||
}
|
||||
this._namePlateUpdateHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
this._namePlateUpdateHook.Enable();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this._namePlateUpdateHook?.Dispose();
|
||||
}
|
||||
|
||||
private const int PlateTypeIndex = 1;
|
||||
private const int UpdateIndex = 2;
|
||||
private const int ColourIndex = 8;
|
||||
private const int IconIndex = 13;
|
||||
private const int NamePlateObjectIndex = 15;
|
||||
private const int FlagsIndex = 17;
|
||||
private const int NameIndex = 0;
|
||||
private const int TitleIndex = 50;
|
||||
private const int FreeCompanyIndex = 100;
|
||||
private const int EnemyLetterIndex = 150;
|
||||
private const int LevelIndex = 200;
|
||||
|
||||
private unsafe IntPtr NamePlateUpdateDetour(AddonNamePlate* addon, NumberArrayData** numberData, StringArrayData** stringData) {
|
||||
try {
|
||||
this.NamePlateUpdateDetourInner(numberData, stringData);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in NamePlateUpdateDetour");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this._namePlateUpdateHook?.Dispose();
|
||||
return this._namePlateUpdateHook!.Original(addon, numberData, stringData);
|
||||
}
|
||||
|
||||
private unsafe void NamePlateUpdateDetourInner(NumberArrayData** numberData, StringArrayData** stringData) {
|
||||
// don't skip to original if no subscribers because of ForceRedraw
|
||||
|
||||
var numbers = numberData[5];
|
||||
var strings = stringData[4];
|
||||
if (numbers == null || strings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private const int PlateTypeIndex = 1;
|
||||
private const int UpdateIndex = 2;
|
||||
private const int ColourIndex = 8;
|
||||
private const int IconIndex = 13;
|
||||
private const int NamePlateObjectIndex = 15;
|
||||
private const int FlagsIndex = 17;
|
||||
private const int NameIndex = 0;
|
||||
private const int TitleIndex = 50;
|
||||
private const int FreeCompanyIndex = 100;
|
||||
private const int EnemyLetterIndex = 150;
|
||||
private const int LevelIndex = 200;
|
||||
var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule();
|
||||
|
||||
private unsafe IntPtr NamePlateUpdateDetour(AddonNamePlate* addon, NumberArrayData** numberData, StringArrayData** stringData) {
|
||||
try {
|
||||
this.NamePlateUpdateDetourInner(numberData, stringData);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in NamePlateUpdateDetour");
|
||||
}
|
||||
var active = numbers->IntArray[0];
|
||||
|
||||
return this._namePlateUpdateHook!.Original(addon, numberData, stringData);
|
||||
var force = this.ForceRedraw;
|
||||
if (force) {
|
||||
this.ForceRedraw = false;
|
||||
}
|
||||
|
||||
private unsafe void NamePlateUpdateDetourInner(NumberArrayData** numberData, StringArrayData** stringData) {
|
||||
// don't skip to original if no subscribers because of ForceRedraw
|
||||
for (var i = 0; i < active; i++) {
|
||||
var numbersIndex = i * 19 + 5;
|
||||
|
||||
var numbers = numberData[5];
|
||||
var strings = stringData[4];
|
||||
if (numbers == null || strings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule();
|
||||
|
||||
var active = numbers->IntArray[0];
|
||||
|
||||
var force = this.ForceRedraw;
|
||||
if (force) {
|
||||
this.ForceRedraw = false;
|
||||
numbers->SetValue(numbersIndex + UpdateIndex, numbers->IntArray[numbersIndex + UpdateIndex] | 1 | 2);
|
||||
}
|
||||
|
||||
for (var i = 0; i < active; i++) {
|
||||
var numbersIndex = i * 19 + 5;
|
||||
if (this.OnUpdate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (force) {
|
||||
numbers->SetValue(numbersIndex + UpdateIndex, numbers->IntArray[numbersIndex + UpdateIndex] | 1 | 2);
|
||||
if (numbers->IntArray[numbersIndex + UpdateIndex] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var npObjIndex = numbers->IntArray[numbersIndex + NamePlateObjectIndex];
|
||||
var info = (&atkModule->NamePlateInfoArray)[npObjIndex];
|
||||
|
||||
var icon = numbers->IntArray[numbersIndex + IconIndex];
|
||||
var nameColour = *(ByteColor*) &numbers->IntArray[numbersIndex + ColourIndex];
|
||||
var plateType = numbers->IntArray[numbersIndex + PlateTypeIndex];
|
||||
var flags = numbers->IntArray[numbersIndex + FlagsIndex];
|
||||
|
||||
var nameRaw = strings->StringArray[NameIndex + i];
|
||||
var name = Util.ReadSeString((IntPtr) nameRaw);
|
||||
|
||||
var titleRaw = strings->StringArray[TitleIndex + i];
|
||||
var title = Util.ReadSeString((IntPtr) titleRaw);
|
||||
|
||||
var fcRaw = strings->StringArray[FreeCompanyIndex + i];
|
||||
var fc = Util.ReadSeString((IntPtr) fcRaw);
|
||||
|
||||
var levelRaw = strings->StringArray[LevelIndex + i];
|
||||
var level = Util.ReadSeString((IntPtr) levelRaw);
|
||||
|
||||
var letterRaw = strings->StringArray[EnemyLetterIndex + i];
|
||||
var letter = Util.ReadSeString((IntPtr) letterRaw);
|
||||
|
||||
var args = new NamePlateUpdateEventArgs(info.ObjectID.ObjectID) {
|
||||
Name = new SeString(name.Payloads),
|
||||
FreeCompany = new SeString(fc.Payloads),
|
||||
Title = new SeString(title.Payloads),
|
||||
Level = new SeString(level.Payloads),
|
||||
#pragma warning disable 0618
|
||||
EnemyLetter = new SeString(letter.Payloads),
|
||||
#pragma warning restore 0618
|
||||
Colour = nameColour,
|
||||
Icon = (uint) icon,
|
||||
Type = (PlateType) plateType,
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
try {
|
||||
this.OnUpdate?.Invoke(args);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in name plate update event");
|
||||
}
|
||||
|
||||
void Replace(byte[] bytes, int i, bool free = true) {
|
||||
// allocate new memory with the game for the new string
|
||||
var mem = this.Functions.UiAlloc.Alloc((ulong) bytes.Length + 1);
|
||||
// copy the new string over to the game's memory
|
||||
Marshal.Copy(bytes, 0, mem, bytes.Length);
|
||||
// terminate the new string
|
||||
*(byte*) (mem + bytes.Length) = 0;
|
||||
|
||||
// replace the pointer with our new one
|
||||
var old = strings->StringArray[i];
|
||||
strings->StringArray[i] = (byte*) mem;
|
||||
// free the old pointer
|
||||
if (free && old != null) {
|
||||
this.Functions.UiAlloc.Free((IntPtr) old);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.OnUpdate == null) {
|
||||
continue;
|
||||
}
|
||||
if (name != args.Name) {
|
||||
Replace(args.Name.Encode(), NameIndex + i);
|
||||
}
|
||||
|
||||
if (numbers->IntArray[numbersIndex + UpdateIndex] == 0) {
|
||||
continue;
|
||||
}
|
||||
if (title != args.Title) {
|
||||
Replace(args.Title.Encode(), TitleIndex + i);
|
||||
}
|
||||
|
||||
var npObjIndex = numbers->IntArray[numbersIndex + NamePlateObjectIndex];
|
||||
var info = (&atkModule->NamePlateInfoArray)[npObjIndex];
|
||||
if (fc != args.FreeCompany) {
|
||||
Replace(args.FreeCompany.Encode(), FreeCompanyIndex + i);
|
||||
}
|
||||
|
||||
var icon = numbers->IntArray[numbersIndex + IconIndex];
|
||||
var nameColour = *(ByteColor*) &numbers->IntArray[numbersIndex + ColourIndex];
|
||||
var plateType = numbers->IntArray[numbersIndex + PlateTypeIndex];
|
||||
var flags = numbers->IntArray[numbersIndex + FlagsIndex];
|
||||
if (level != args.Level) {
|
||||
Replace(args.Level.Encode(), LevelIndex + i);
|
||||
}
|
||||
|
||||
var nameRaw = strings->StringArray[NameIndex + i];
|
||||
var name = Util.ReadSeString((IntPtr) nameRaw);
|
||||
if (letter != args.EnemyLetter) {
|
||||
// FIXME: sometimes the pointer here in the game is garbage, so freeing is a heap corruption
|
||||
// figure out how to free this properly
|
||||
Replace(args.EnemyLetter.Encode(), EnemyLetterIndex + i, false);
|
||||
}
|
||||
|
||||
var titleRaw = strings->StringArray[TitleIndex + i];
|
||||
var title = Util.ReadSeString((IntPtr) titleRaw);
|
||||
if (icon != args.Icon) {
|
||||
numbers->SetValue(numbersIndex + IconIndex, (int) args.Icon);
|
||||
}
|
||||
|
||||
var fcRaw = strings->StringArray[FreeCompanyIndex + i];
|
||||
var fc = Util.ReadSeString((IntPtr) fcRaw);
|
||||
var colour = (ByteColor) args.Colour;
|
||||
var colourInt = *(int*) &colour;
|
||||
if (colourInt != numbers->IntArray[numbersIndex + ColourIndex]) {
|
||||
numbers->SetValue(numbersIndex + ColourIndex, colourInt);
|
||||
}
|
||||
|
||||
var levelRaw = strings->StringArray[LevelIndex + i];
|
||||
var level = Util.ReadSeString((IntPtr) levelRaw);
|
||||
if (plateType != (int) args.Type) {
|
||||
numbers->SetValue(numbersIndex + PlateTypeIndex, (int) args.Type);
|
||||
}
|
||||
|
||||
var letterRaw = strings->StringArray[EnemyLetterIndex + i];
|
||||
var letter = Util.ReadSeString((IntPtr) letterRaw);
|
||||
|
||||
var args = new NamePlateUpdateEventArgs(info.ObjectID.ObjectID) {
|
||||
Name = new SeString(name.Payloads),
|
||||
FreeCompany = new SeString(fc.Payloads),
|
||||
Title = new SeString(title.Payloads),
|
||||
Level = new SeString(level.Payloads),
|
||||
#pragma warning disable 0618
|
||||
EnemyLetter = new SeString(letter.Payloads),
|
||||
#pragma warning restore 0618
|
||||
Colour = nameColour,
|
||||
Icon = (uint) icon,
|
||||
Type = (PlateType) plateType,
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
try {
|
||||
this.OnUpdate?.Invoke(args);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in name plate update event");
|
||||
}
|
||||
|
||||
void Replace(byte[] bytes, int i, bool free = true) {
|
||||
// allocate new memory with the game for the new string
|
||||
var mem = this.Functions.UiAlloc.Alloc((ulong) bytes.Length + 1);
|
||||
// copy the new string over to the game's memory
|
||||
Marshal.Copy(bytes, 0, mem, bytes.Length);
|
||||
// terminate the new string
|
||||
*(byte*) (mem + bytes.Length) = 0;
|
||||
|
||||
// replace the pointer with our new one
|
||||
var old = strings->StringArray[i];
|
||||
strings->StringArray[i] = (byte*) mem;
|
||||
// free the old pointer
|
||||
if (free && old != null) {
|
||||
this.Functions.UiAlloc.Free((IntPtr) old);
|
||||
}
|
||||
}
|
||||
|
||||
if (name != args.Name) {
|
||||
Replace(args.Name.Encode(), NameIndex + i);
|
||||
}
|
||||
|
||||
if (title != args.Title) {
|
||||
Replace(args.Title.Encode(), TitleIndex + i);
|
||||
}
|
||||
|
||||
if (fc != args.FreeCompany) {
|
||||
Replace(args.FreeCompany.Encode(), FreeCompanyIndex + i);
|
||||
}
|
||||
|
||||
if (level != args.Level) {
|
||||
Replace(args.Level.Encode(), LevelIndex + i);
|
||||
}
|
||||
|
||||
if (letter != args.EnemyLetter) {
|
||||
// FIXME: sometimes the pointer here in the game is garbage, so freeing is a heap corruption
|
||||
// figure out how to free this properly
|
||||
Replace(args.EnemyLetter.Encode(), EnemyLetterIndex + i, false);
|
||||
}
|
||||
|
||||
if (icon != args.Icon) {
|
||||
numbers->SetValue(numbersIndex + IconIndex, (int) args.Icon);
|
||||
}
|
||||
|
||||
var colour = (ByteColor) args.Colour;
|
||||
var colourInt = *(int*) &colour;
|
||||
if (colourInt != numbers->IntArray[numbersIndex + ColourIndex]) {
|
||||
numbers->SetValue(numbersIndex + ColourIndex, colourInt);
|
||||
}
|
||||
|
||||
if (plateType != (int) args.Type) {
|
||||
numbers->SetValue(numbersIndex + PlateTypeIndex, (int) args.Type);
|
||||
}
|
||||
|
||||
if (flags != args.Flags) {
|
||||
numbers->SetValue(numbersIndex + FlagsIndex, args.Flags);
|
||||
}
|
||||
if (flags != args.Flags) {
|
||||
numbers->SetValue(numbersIndex + FlagsIndex, args.Flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,176 +1,176 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
|
||||
namespace XivCommon.Functions.NamePlates {
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x28)]
|
||||
internal unsafe struct NumberArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public AtkArrayData AtkArrayData;
|
||||
namespace XivCommon.Functions.NamePlates;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public int* IntArray;
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x28)]
|
||||
internal unsafe struct NumberArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public AtkArrayData AtkArrayData;
|
||||
|
||||
public void SetValue(int index, int value) {
|
||||
if (index >= this.AtkArrayData.Size) {
|
||||
return;
|
||||
}
|
||||
[FieldOffset(0x20)]
|
||||
public int* IntArray;
|
||||
|
||||
if (this.IntArray[index] == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.IntArray[index] = value;
|
||||
this.AtkArrayData.HasModifiedData = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
|
||||
internal unsafe struct AtkArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public void* vtbl;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
public int Size;
|
||||
|
||||
[FieldOffset(0x1C)]
|
||||
public byte Unk1C;
|
||||
|
||||
[FieldOffset(0x1D)]
|
||||
public byte Unk1D;
|
||||
|
||||
[FieldOffset(0x1E)]
|
||||
public byte HasModifiedData;
|
||||
|
||||
[FieldOffset(0x1F)]
|
||||
public byte Unk1F; // initialized to -1
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x30)]
|
||||
internal unsafe struct StringArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public AtkArrayData AtkArrayData;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public byte** StringArray; // char * *
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
public byte* UnkString; // char *
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The various different name plate types
|
||||
/// </summary>
|
||||
public enum PlateType {
|
||||
/// <summary>
|
||||
/// A normal player name plate
|
||||
/// </summary>
|
||||
Player = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with the icon and FC tag removed
|
||||
/// </summary>
|
||||
NoIconOrFc = 1, // 2, 5
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with a level string visible, title always below the name, and FC tag removed
|
||||
/// </summary>
|
||||
LevelNoFc = 3, // 4
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with only the name visible
|
||||
/// </summary>
|
||||
NameOnly = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with only the level string and name visible
|
||||
/// </summary>
|
||||
LevelAndName = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A name plate where the title always appears below the name and the FC tag is removed
|
||||
/// </summary>
|
||||
LowTitleNoFc = 8,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A colour, represented in the RGBA format.
|
||||
/// </summary>
|
||||
public class RgbaColour {
|
||||
/// <summary>
|
||||
/// The red component of the colour.
|
||||
/// </summary>
|
||||
public byte R { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The green component of the colour.
|
||||
/// </summary>
|
||||
public byte G { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The blue component of the colour.
|
||||
/// </summary>
|
||||
public byte B { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The alpha component of the colour.
|
||||
/// </summary>
|
||||
public byte A { get; set; } = byte.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Converts an unsigned integer into an RgbaColour.
|
||||
/// </summary>
|
||||
/// <param name="rgba">32-bit integer representing an RGBA colour</param>
|
||||
/// <returns>an RgbaColour equivalent to the integer representation</returns>
|
||||
public static implicit operator RgbaColour(uint rgba) {
|
||||
var r = (byte) ((rgba >> 24) & 0xFF);
|
||||
var g = (byte) ((rgba >> 16) & 0xFF);
|
||||
var b = (byte) ((rgba >> 8) & 0xFF);
|
||||
var a = (byte) (rgba & 0xFF);
|
||||
|
||||
return new RgbaColour {
|
||||
R = r,
|
||||
G = g,
|
||||
B = b,
|
||||
A = a,
|
||||
};
|
||||
public void SetValue(int index, int value) {
|
||||
if (index >= this.AtkArrayData.Size) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an RgbaColour into an unsigned integer representation.
|
||||
/// </summary>
|
||||
/// <param name="rgba">an RgbaColour to convert</param>
|
||||
/// <returns>32-bit integer representing an RGBA colour</returns>
|
||||
public static implicit operator uint(RgbaColour rgba) {
|
||||
return (uint) ((rgba.R << 24)
|
||||
| (rgba.G << 16)
|
||||
| (rgba.B << 8)
|
||||
| rgba.A);
|
||||
if (this.IntArray[index] == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a ByteColor into an RgbaColour.
|
||||
/// </summary>
|
||||
/// <param name="rgba">ByteColor</param>
|
||||
/// <returns>equivalent RgbaColour</returns>
|
||||
public static implicit operator RgbaColour(ByteColor rgba) {
|
||||
return (uint) ((rgba.R << 24)
|
||||
| (rgba.G << 16)
|
||||
| (rgba.B << 8)
|
||||
| rgba.A);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an RgbaColour into a ByteColor.
|
||||
/// </summary>
|
||||
/// <param name="rgba">RgbaColour</param>
|
||||
/// <returns>equivalent ByteColour</returns>
|
||||
public static implicit operator ByteColor(RgbaColour rgba) {
|
||||
return new() {
|
||||
R = rgba.R,
|
||||
G = rgba.G,
|
||||
B = rgba.B,
|
||||
A = rgba.A,
|
||||
};
|
||||
}
|
||||
this.IntArray[index] = value;
|
||||
this.AtkArrayData.HasModifiedData = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
|
||||
internal unsafe struct AtkArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public void* vtbl;
|
||||
|
||||
[FieldOffset(0x8)]
|
||||
public int Size;
|
||||
|
||||
[FieldOffset(0x1C)]
|
||||
public byte Unk1C;
|
||||
|
||||
[FieldOffset(0x1D)]
|
||||
public byte Unk1D;
|
||||
|
||||
[FieldOffset(0x1E)]
|
||||
public byte HasModifiedData;
|
||||
|
||||
[FieldOffset(0x1F)]
|
||||
public byte Unk1F; // initialized to -1
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x30)]
|
||||
internal unsafe struct StringArrayData {
|
||||
[FieldOffset(0x0)]
|
||||
public AtkArrayData AtkArrayData;
|
||||
|
||||
[FieldOffset(0x20)]
|
||||
public byte** StringArray; // char * *
|
||||
|
||||
[FieldOffset(0x28)]
|
||||
public byte* UnkString; // char *
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The various different name plate types
|
||||
/// </summary>
|
||||
public enum PlateType {
|
||||
/// <summary>
|
||||
/// A normal player name plate
|
||||
/// </summary>
|
||||
Player = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with the icon and FC tag removed
|
||||
/// </summary>
|
||||
NoIconOrFc = 1, // 2, 5
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with a level string visible, title always below the name, and FC tag removed
|
||||
/// </summary>
|
||||
LevelNoFc = 3, // 4
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with only the name visible
|
||||
/// </summary>
|
||||
NameOnly = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A name plate with only the level string and name visible
|
||||
/// </summary>
|
||||
LevelAndName = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A name plate where the title always appears below the name and the FC tag is removed
|
||||
/// </summary>
|
||||
LowTitleNoFc = 8,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A colour, represented in the RGBA format.
|
||||
/// </summary>
|
||||
public class RgbaColour {
|
||||
/// <summary>
|
||||
/// The red component of the colour.
|
||||
/// </summary>
|
||||
public byte R { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The green component of the colour.
|
||||
/// </summary>
|
||||
public byte G { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The blue component of the colour.
|
||||
/// </summary>
|
||||
public byte B { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The alpha component of the colour.
|
||||
/// </summary>
|
||||
public byte A { get; set; } = byte.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Converts an unsigned integer into an RgbaColour.
|
||||
/// </summary>
|
||||
/// <param name="rgba">32-bit integer representing an RGBA colour</param>
|
||||
/// <returns>an RgbaColour equivalent to the integer representation</returns>
|
||||
public static implicit operator RgbaColour(uint rgba) {
|
||||
var r = (byte) ((rgba >> 24) & 0xFF);
|
||||
var g = (byte) ((rgba >> 16) & 0xFF);
|
||||
var b = (byte) ((rgba >> 8) & 0xFF);
|
||||
var a = (byte) (rgba & 0xFF);
|
||||
|
||||
return new RgbaColour {
|
||||
R = r,
|
||||
G = g,
|
||||
B = b,
|
||||
A = a,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an RgbaColour into an unsigned integer representation.
|
||||
/// </summary>
|
||||
/// <param name="rgba">an RgbaColour to convert</param>
|
||||
/// <returns>32-bit integer representing an RGBA colour</returns>
|
||||
public static implicit operator uint(RgbaColour rgba) {
|
||||
return (uint) ((rgba.R << 24)
|
||||
| (rgba.G << 16)
|
||||
| (rgba.B << 8)
|
||||
| rgba.A);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a ByteColor into an RgbaColour.
|
||||
/// </summary>
|
||||
/// <param name="rgba">ByteColor</param>
|
||||
/// <returns>equivalent RgbaColour</returns>
|
||||
public static implicit operator RgbaColour(ByteColor rgba) {
|
||||
return (uint) ((rgba.R << 24)
|
||||
| (rgba.G << 16)
|
||||
| (rgba.B << 8)
|
||||
| rgba.A);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an RgbaColour into a ByteColor.
|
||||
/// </summary>
|
||||
/// <param name="rgba">RgbaColour</param>
|
||||
/// <returns>equivalent ByteColour</returns>
|
||||
public static implicit operator ByteColor(RgbaColour rgba) {
|
||||
return new() {
|
||||
R = rgba.R,
|
||||
G = rgba.G,
|
||||
B = rgba.B,
|
||||
A = rgba.A,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,180 +2,180 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui.PartyFinder;
|
||||
using Dalamud.Game.Gui.PartyFinder.Types;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// A class containing Party Finder functionality
|
||||
/// </summary>
|
||||
public class PartyFinder : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string RequestListings = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 0F 10 81";
|
||||
internal const string JoinCrossParty = "E8 ?? ?? ?? ?? 41 0F B7 07 49 8B CC";
|
||||
}
|
||||
|
||||
private delegate byte RequestPartyFinderListingsDelegate(IntPtr agent, byte categoryIdx);
|
||||
|
||||
private delegate IntPtr JoinPfDelegate(IntPtr manager, IntPtr a2, int type, IntPtr packetData, uint a5);
|
||||
|
||||
private RequestPartyFinderListingsDelegate? RequestPartyFinderListings { get; }
|
||||
private Hook<RequestPartyFinderListingsDelegate>? RequestPfListingsHook { get; }
|
||||
private Hook<JoinPfDelegate>? JoinPfHook { get; }
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// A class containing Party Finder functionality
|
||||
/// The delegate for party join events.
|
||||
/// </summary>
|
||||
public class PartyFinder : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string RequestListings = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 0F 10 81";
|
||||
internal const string JoinCrossParty = "E8 ?? ?? ?? ?? 0F B7 47 28";
|
||||
public delegate void JoinPfEventDelegate(PartyFinderListing listing);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when the player joins a <b>cross-world</b> party via Party Finder.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.PartyFinderJoins"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event JoinPfEventDelegate? JoinParty;
|
||||
|
||||
private IPartyFinderGui PartyFinderGui { get; }
|
||||
private bool JoinsEnabled { get; }
|
||||
private bool ListingsEnabled { get; }
|
||||
private IntPtr PartyFinderAgent { get; set; } = IntPtr.Zero;
|
||||
private Dictionary<uint, PartyFinderListing> Listings { get; } = new();
|
||||
private int LastBatch { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The current Party Finder listings that have been displayed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This dictionary is cleared and updated each time the Party Finder is requested, and it only contains the category selected in the Party Finder addon.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Keys are the listing ID for fast lookup by ID. Values are the listing itself.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<uint, PartyFinderListing> CurrentListings => this.Listings;
|
||||
|
||||
internal PartyFinder(ISigScanner scanner, IPartyFinderGui partyFinderGui, IGameInteropProvider interop, Hooks hooks) {
|
||||
this.PartyFinderGui = partyFinderGui;
|
||||
|
||||
this.ListingsEnabled = hooks.HasFlag(Hooks.PartyFinderListings);
|
||||
this.JoinsEnabled = hooks.HasFlag(Hooks.PartyFinderJoins);
|
||||
|
||||
if (this.ListingsEnabled || this.JoinsEnabled) {
|
||||
this.PartyFinderGui.ReceiveListing += this.ReceiveListing;
|
||||
}
|
||||
|
||||
private delegate byte RequestPartyFinderListingsDelegate(IntPtr agent, byte categoryIdx);
|
||||
if (scanner.TryScanText(Signatures.RequestListings, out var requestPfPtr, "Party Finder listings")) {
|
||||
this.RequestPartyFinderListings = Marshal.GetDelegateForFunctionPointer<RequestPartyFinderListingsDelegate>(requestPfPtr);
|
||||
|
||||
private delegate IntPtr JoinPfDelegate(IntPtr manager, IntPtr a2, int type, IntPtr packetData, uint a5);
|
||||
|
||||
private RequestPartyFinderListingsDelegate? RequestPartyFinderListings { get; }
|
||||
private Hook<RequestPartyFinderListingsDelegate>? RequestPfListingsHook { get; }
|
||||
private Hook<JoinPfDelegate>? JoinPfHook { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for party join events.
|
||||
/// </summary>
|
||||
public delegate void JoinPfEventDelegate(PartyFinderListing listing);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when the player joins a <b>cross-world</b> party via Party Finder.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.PartyFinderJoins"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event JoinPfEventDelegate? JoinParty;
|
||||
|
||||
private PartyFinderGui PartyFinderGui { get; }
|
||||
private bool JoinsEnabled { get; }
|
||||
private bool ListingsEnabled { get; }
|
||||
private IntPtr PartyFinderAgent { get; set; } = IntPtr.Zero;
|
||||
private Dictionary<uint, PartyFinderListing> Listings { get; } = new();
|
||||
private int LastBatch { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The current Party Finder listings that have been displayed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This dictionary is cleared and updated each time the Party Finder is requested, and it only contains the category selected in the Party Finder addon.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Keys are the listing ID for fast lookup by ID. Values are the listing itself.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<uint, PartyFinderListing> CurrentListings => this.Listings;
|
||||
|
||||
internal PartyFinder(SigScanner scanner, PartyFinderGui partyFinderGui, Hooks hooks) {
|
||||
this.PartyFinderGui = partyFinderGui;
|
||||
|
||||
this.ListingsEnabled = hooks.HasFlag(Hooks.PartyFinderListings);
|
||||
this.JoinsEnabled = hooks.HasFlag(Hooks.PartyFinderJoins);
|
||||
|
||||
if (this.ListingsEnabled || this.JoinsEnabled) {
|
||||
this.PartyFinderGui.ReceiveListing += this.ReceiveListing;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.RequestListings, out var requestPfPtr, "Party Finder listings")) {
|
||||
this.RequestPartyFinderListings = Marshal.GetDelegateForFunctionPointer<RequestPartyFinderListingsDelegate>(requestPfPtr);
|
||||
|
||||
if (this.ListingsEnabled) {
|
||||
this.RequestPfListingsHook = Hook<RequestPartyFinderListingsDelegate>.FromAddress(requestPfPtr, this.OnRequestPartyFinderListings);
|
||||
this.RequestPfListingsHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.JoinsEnabled && scanner.TryScanText(Signatures.JoinCrossParty, out var joinPtr, "Party Finder joins")) {
|
||||
this.JoinPfHook = Hook<JoinPfDelegate>.FromAddress(joinPtr, this.JoinPfDetour);
|
||||
this.JoinPfHook.Enable();
|
||||
if (this.ListingsEnabled) {
|
||||
this.RequestPfListingsHook = interop.HookFromAddress<RequestPartyFinderListingsDelegate>(requestPfPtr, this.OnRequestPartyFinderListings);
|
||||
this.RequestPfListingsHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.PartyFinderGui.ReceiveListing -= this.ReceiveListing;
|
||||
this.JoinPfHook?.Dispose();
|
||||
this.RequestPfListingsHook?.Dispose();
|
||||
if (this.JoinsEnabled && scanner.TryScanText(Signatures.JoinCrossParty, out var joinPtr, "Party Finder joins")) {
|
||||
this.JoinPfHook = interop.HookFromAddress<JoinPfDelegate>(joinPtr, this.JoinPfDetour);
|
||||
this.JoinPfHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.PartyFinderGui.ReceiveListing -= this.ReceiveListing;
|
||||
this.JoinPfHook?.Dispose();
|
||||
this.RequestPfListingsHook?.Dispose();
|
||||
}
|
||||
|
||||
private void ReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) {
|
||||
if (args.BatchNumber != this.LastBatch) {
|
||||
this.Listings.Clear();
|
||||
}
|
||||
|
||||
private void ReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) {
|
||||
if (args.BatchNumber != this.LastBatch) {
|
||||
this.Listings.Clear();
|
||||
}
|
||||
this.LastBatch = args.BatchNumber;
|
||||
|
||||
this.LastBatch = args.BatchNumber;
|
||||
this.Listings[listing.Id] = listing;
|
||||
}
|
||||
|
||||
this.Listings[listing.Id] = listing;
|
||||
}
|
||||
private byte OnRequestPartyFinderListings(IntPtr agent, byte categoryIdx) {
|
||||
this.PartyFinderAgent = agent;
|
||||
return this.RequestPfListingsHook!.Original(agent, categoryIdx);
|
||||
}
|
||||
|
||||
private byte OnRequestPartyFinderListings(IntPtr agent, byte categoryIdx) {
|
||||
this.PartyFinderAgent = agent;
|
||||
return this.RequestPfListingsHook!.Original(agent, categoryIdx);
|
||||
}
|
||||
private IntPtr JoinPfDetour(IntPtr manager, IntPtr a2, int type, IntPtr packetData, uint a5) {
|
||||
// Updated: 5.5
|
||||
const int idOffset = -0x20;
|
||||
|
||||
private IntPtr JoinPfDetour(IntPtr manager, IntPtr a2, int type, IntPtr packetData, uint a5) {
|
||||
// Updated: 5.5
|
||||
const int idOffset = -0x20;
|
||||
|
||||
var ret = this.JoinPfHook!.Original(manager, a2, type, packetData, a5);
|
||||
|
||||
if (this.JoinParty == null || (JoinType) type != JoinType.PartyFinder || packetData == IntPtr.Zero) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
try {
|
||||
var id = (uint) Marshal.ReadInt32(packetData + idOffset);
|
||||
if (this.Listings.TryGetValue(id, out var listing)) {
|
||||
this.JoinParty?.Invoke(listing);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in PF join detour");
|
||||
}
|
||||
var ret = this.JoinPfHook!.Original(manager, a2, type, packetData, a5);
|
||||
|
||||
if (this.JoinParty == null || (JoinType) type != JoinType.PartyFinder || packetData == IntPtr.Zero) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Refresh the Party Finder listings. This does not open the Party Finder.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This maintains the currently selected category.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If the <see cref="Hooks.PartyFinderListings"/> hook is not enabled or if the signature for this function could not be found</exception>
|
||||
public void RefreshListings() {
|
||||
if (this.RequestPartyFinderListings == null) {
|
||||
throw new InvalidOperationException("Could not find signature for Party Finder listings");
|
||||
try {
|
||||
var id = (uint) Marshal.ReadInt32(packetData + idOffset);
|
||||
if (this.Listings.TryGetValue(id, out var listing)) {
|
||||
this.JoinParty?.Invoke(listing);
|
||||
}
|
||||
|
||||
if (!this.ListingsEnabled) {
|
||||
throw new InvalidOperationException("PartyFinder hooks are not enabled");
|
||||
}
|
||||
|
||||
// Updated 6.0
|
||||
const int categoryOffset = 11_031;
|
||||
|
||||
if (this.PartyFinderAgent == IntPtr.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
var categoryIdx = Marshal.ReadByte(this.PartyFinderAgent + categoryOffset);
|
||||
this.RequestPartyFinderListings(this.PartyFinderAgent, categoryIdx);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in PF join detour");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal enum JoinType : byte {
|
||||
/// <summary>
|
||||
/// Join via invite or party conversion.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Refresh the Party Finder listings. This does not open the Party Finder.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This maintains the currently selected category.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If the <see cref="Hooks.PartyFinderListings"/> hook is not enabled or if the signature for this function could not be found</exception>
|
||||
public void RefreshListings() {
|
||||
if (this.RequestPartyFinderListings == null) {
|
||||
throw new InvalidOperationException("Could not find signature for Party Finder listings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Join via Party Finder.
|
||||
/// </summary>
|
||||
PartyFinder = 1,
|
||||
if (!this.ListingsEnabled) {
|
||||
throw new InvalidOperationException("PartyFinder hooks are not enabled");
|
||||
}
|
||||
|
||||
Unknown2 = 2,
|
||||
// Updated 6.0
|
||||
const int categoryOffset = 11_031;
|
||||
|
||||
/// <summary>
|
||||
/// Remain in cross-world party after leaving a duty.
|
||||
/// </summary>
|
||||
LeaveDuty = 3,
|
||||
if (this.PartyFinderAgent == IntPtr.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
Unknown4 = 4,
|
||||
var categoryIdx = Marshal.ReadByte(this.PartyFinderAgent + categoryOffset);
|
||||
this.RequestPartyFinderListings(this.PartyFinderAgent, categoryIdx);
|
||||
}
|
||||
}
|
||||
|
||||
internal enum JoinType : byte {
|
||||
/// <summary>
|
||||
/// Join via invite or party conversion.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Join via Party Finder.
|
||||
/// </summary>
|
||||
PartyFinder = 1,
|
||||
|
||||
Unknown2 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Remain in cross-world party after leaving a duty.
|
||||
/// </summary>
|
||||
LeaveDuty = 3,
|
||||
|
||||
Unknown4 = 4,
|
||||
}
|
||||
|
|
|
@ -3,157 +3,158 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing Talk events
|
||||
/// </summary>
|
||||
public class Talk : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string SetAtkValue = "E8 ?? ?? ?? ?? 41 03 ED";
|
||||
internal const string ShowMessageBox = "4C 8B DC 55 57 41 55 49 8D 6B 98";
|
||||
}
|
||||
|
||||
// Updated: 5.5
|
||||
private const int TextOffset = 0;
|
||||
private const int NameOffset = 0x10;
|
||||
private const int StyleOffset = 0x38;
|
||||
|
||||
private delegate void AddonTalkV45Delegate(IntPtr addon, IntPtr a2, IntPtr data);
|
||||
|
||||
private Hook<AddonTalkV45Delegate>? AddonTalkV45Hook { get; }
|
||||
|
||||
private delegate IntPtr SetAtkValueStringDelegate(IntPtr atkValue, IntPtr text);
|
||||
|
||||
private SetAtkValueStringDelegate SetAtkValueString { get; } = null!;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
/// <summary>
|
||||
/// Class containing Talk events
|
||||
/// The delegate for Talk events.
|
||||
/// </summary>
|
||||
public class Talk : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string SetAtkValue = "E8 ?? ?? ?? ?? 41 03 ED";
|
||||
internal const string ShowMessageBox = "4C 8B DC 55 57 41 55 49 8D 6B 98";
|
||||
public delegate void TalkEventDelegate(ref SeString name, ref SeString text, ref TalkStyle style);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when NPCs talk.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.Talk"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event TalkEventDelegate? OnTalk;
|
||||
|
||||
internal Talk(ISigScanner scanner, IGameInteropProvider interop, bool hooksEnabled) {
|
||||
if (scanner.TryScanText(Signatures.SetAtkValue, out var setAtkPtr, "Talk - set atk value")) {
|
||||
this.SetAtkValueString = Marshal.GetDelegateForFunctionPointer<SetAtkValueStringDelegate>(setAtkPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Updated: 5.5
|
||||
private const int TextOffset = 0;
|
||||
private const int NameOffset = 0x10;
|
||||
private const int StyleOffset = 0x38;
|
||||
|
||||
private delegate void AddonTalkV45Delegate(IntPtr addon, IntPtr a2, IntPtr data);
|
||||
|
||||
private Hook<AddonTalkV45Delegate>? AddonTalkV45Hook { get; }
|
||||
|
||||
private delegate IntPtr SetAtkValueStringDelegate(IntPtr atkValue, IntPtr text);
|
||||
|
||||
private SetAtkValueStringDelegate SetAtkValueString { get; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for Talk events.
|
||||
/// </summary>
|
||||
public delegate void TalkEventDelegate(ref SeString name, ref SeString text, ref TalkStyle style);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when NPCs talk.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.Talk"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event TalkEventDelegate? OnTalk;
|
||||
|
||||
internal Talk(SigScanner scanner, bool hooksEnabled) {
|
||||
if (scanner.TryScanText(Signatures.SetAtkValue, out var setAtkPtr, "Talk - set atk value")) {
|
||||
this.SetAtkValueString = Marshal.GetDelegateForFunctionPointer<SetAtkValueStringDelegate>(setAtkPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hooksEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.ShowMessageBox, out var showMessageBoxPtr, "Talk")) {
|
||||
this.AddonTalkV45Hook = Hook<AddonTalkV45Delegate>.FromAddress(showMessageBoxPtr, this.AddonTalkV45Detour);
|
||||
this.AddonTalkV45Hook.Enable();
|
||||
}
|
||||
if (!hooksEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.AddonTalkV45Hook?.Dispose();
|
||||
}
|
||||
|
||||
private void AddonTalkV45Detour(IntPtr addon, IntPtr a2, IntPtr data) {
|
||||
if (this.OnTalk == null) {
|
||||
goto Return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.AddonTalkV45DetourInner(data);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in Talk detour");
|
||||
}
|
||||
|
||||
Return:
|
||||
this.AddonTalkV45Hook!.Original(addon, a2, data);
|
||||
}
|
||||
|
||||
private void AddonTalkV45DetourInner(IntPtr data) {
|
||||
var rawName = Util.ReadTerminated(Marshal.ReadIntPtr(data + NameOffset + 8));
|
||||
var rawText = Util.ReadTerminated(Marshal.ReadIntPtr(data + TextOffset + 8));
|
||||
var style = (TalkStyle) Marshal.ReadByte(data + StyleOffset);
|
||||
|
||||
var name = SeString.Parse(rawName);
|
||||
var text = SeString.Parse(rawText);
|
||||
|
||||
try {
|
||||
this.OnTalk?.Invoke(ref name, ref text, ref style);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in Talk event");
|
||||
}
|
||||
|
||||
var newName = name.Encode().Terminate();
|
||||
var newText = text.Encode().Terminate();
|
||||
|
||||
Marshal.WriteByte(data + StyleOffset, (byte) style);
|
||||
|
||||
unsafe {
|
||||
fixed (byte* namePtr = newName, textPtr = newText) {
|
||||
this.SetAtkValueString(data + NameOffset, (IntPtr) namePtr);
|
||||
this.SetAtkValueString(data + TextOffset, (IntPtr) textPtr);
|
||||
}
|
||||
}
|
||||
if (scanner.TryScanText(Signatures.ShowMessageBox, out var showMessageBoxPtr, "Talk")) {
|
||||
this.AddonTalkV45Hook = interop.HookFromAddress<AddonTalkV45Delegate>(showMessageBoxPtr, this.AddonTalkV45Detour);
|
||||
this.AddonTalkV45Hook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Talk window styles.
|
||||
/// </summary>
|
||||
public enum TalkStyle : byte {
|
||||
/// <summary>
|
||||
/// The normal style with a white background.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.AddonTalkV45Hook?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A style with lights on the top and bottom border.
|
||||
/// </summary>
|
||||
Lights = 2,
|
||||
private void AddonTalkV45Detour(IntPtr addon, IntPtr a2, IntPtr data) {
|
||||
if (this.OnTalk == null) {
|
||||
goto Return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A style used for when characters are shouting.
|
||||
/// </summary>
|
||||
Shout = 3,
|
||||
try {
|
||||
this.AddonTalkV45DetourInner(data);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in Talk detour");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="Shout"/> but with flatter edges.
|
||||
/// </summary>
|
||||
FlatShout = 4,
|
||||
Return:
|
||||
this.AddonTalkV45Hook!.Original(addon, a2, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The style used when dragons (and some other NPCs) talk.
|
||||
/// </summary>
|
||||
Dragon = 5,
|
||||
private void AddonTalkV45DetourInner(IntPtr data) {
|
||||
var rawName = Util.ReadTerminated(Marshal.ReadIntPtr(data + NameOffset + 8));
|
||||
var rawText = Util.ReadTerminated(Marshal.ReadIntPtr(data + TextOffset + 8));
|
||||
var style = (TalkStyle) Marshal.ReadByte(data + StyleOffset);
|
||||
|
||||
/// <summary>
|
||||
/// The style used for Allagan machinery.
|
||||
/// </summary>
|
||||
Allagan = 6,
|
||||
var name = SeString.Parse(rawName);
|
||||
var text = SeString.Parse(rawText);
|
||||
|
||||
/// <summary>
|
||||
/// The style used for system messages.
|
||||
/// </summary>
|
||||
System = 7,
|
||||
try {
|
||||
this.OnTalk?.Invoke(ref name, ref text, ref style);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in Talk event");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mixture of the system message style and the dragon style.
|
||||
/// </summary>
|
||||
DragonSystem = 8,
|
||||
var newName = name.Encode().Terminate();
|
||||
var newText = text.Encode().Terminate();
|
||||
|
||||
/// <summary>
|
||||
/// The system message style with a purple background.
|
||||
/// </summary>
|
||||
PurpleSystem = 9,
|
||||
Marshal.WriteByte(data + StyleOffset, (byte) style);
|
||||
|
||||
unsafe {
|
||||
fixed (byte* namePtr = newName, textPtr = newText) {
|
||||
this.SetAtkValueString(data + NameOffset, (IntPtr) namePtr);
|
||||
this.SetAtkValueString(data + TextOffset, (IntPtr) textPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Talk window styles.
|
||||
/// </summary>
|
||||
public enum TalkStyle : byte {
|
||||
/// <summary>
|
||||
/// The normal style with a white background.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A style with lights on the top and bottom border.
|
||||
/// </summary>
|
||||
Lights = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A style used for when characters are shouting.
|
||||
/// </summary>
|
||||
Shout = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="Shout"/> but with flatter edges.
|
||||
/// </summary>
|
||||
FlatShout = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The style used when dragons (and some other NPCs) talk.
|
||||
/// </summary>
|
||||
Dragon = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The style used for Allagan machinery.
|
||||
/// </summary>
|
||||
Allagan = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The style used for system messages.
|
||||
/// </summary>
|
||||
System = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A mixture of the system message style and the dragon style.
|
||||
/// </summary>
|
||||
DragonSystem = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The system message style with a purple background.
|
||||
/// </summary>
|
||||
PurpleSystem = 9,
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.Tooltips {
|
||||
/// <summary>
|
||||
/// The class allowing for action tooltip manipulation
|
||||
/// </summary>
|
||||
public unsafe class ActionTooltip : BaseTooltip {
|
||||
internal ActionTooltip(Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** stringArrayData, int** numberArrayData) : base(sadSetString, stringArrayData, numberArrayData) {
|
||||
}
|
||||
namespace XivCommon.Functions.Tooltips;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SeString for the given string enum.
|
||||
/// </summary>
|
||||
/// <param name="ats">the string to retrieve/update</param>
|
||||
public SeString this[ActionTooltipString ats] {
|
||||
get => this[(int) ats];
|
||||
set => this[(int) ats] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which fields are visible on the tooltip.
|
||||
/// </summary>
|
||||
public ActionTooltipFields Fields {
|
||||
get => (ActionTooltipFields) (**(this.NumberArrayData + 4));
|
||||
set => **(this.NumberArrayData + 4) = (int) value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The class allowing for action tooltip manipulation
|
||||
/// </summary>
|
||||
public unsafe class ActionTooltip : BaseTooltip {
|
||||
internal ActionTooltip(Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** stringArrayData, int** numberArrayData) : base(sadSetString, stringArrayData, numberArrayData) {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SeString for the given string enum.
|
||||
/// </summary>
|
||||
/// <param name="ats">the string to retrieve/update</param>
|
||||
public SeString this[ActionTooltipString ats] {
|
||||
get => this[(int) ats];
|
||||
set => this[(int) ats] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which fields are visible on the tooltip.
|
||||
/// </summary>
|
||||
public ActionTooltipFields Fields {
|
||||
get => (ActionTooltipFields) (**(this.NumberArrayData + 4));
|
||||
set => **(this.NumberArrayData + 4) = (int) value;
|
||||
}
|
||||
}
|
|
@ -1,44 +1,44 @@
|
|||
using System;
|
||||
|
||||
namespace XivCommon.Functions.Tooltips {
|
||||
/// <summary>
|
||||
/// An enum containing the strings used in action tooltips.
|
||||
/// </summary>
|
||||
public enum ActionTooltipString {
|
||||
#pragma warning disable 1591
|
||||
Name = 0,
|
||||
Type = 1,
|
||||
RangeLabel = 3,
|
||||
Range = 4,
|
||||
RadiusLabel = 5,
|
||||
Radius = 6,
|
||||
CostLabel = 7,
|
||||
Cost = 8,
|
||||
RecastLabel = 9,
|
||||
Recast = 10,
|
||||
CastLabel = 11,
|
||||
Cast = 12,
|
||||
Description = 13,
|
||||
Acquired = 14,
|
||||
Affinity = 15,
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
namespace XivCommon.Functions.Tooltips;
|
||||
|
||||
/// <summary>
|
||||
/// An enum containing the fields that can be displayed in action tooltips.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ActionTooltipFields {
|
||||
#pragma warning disable 1591
|
||||
Range = 1 << 0,
|
||||
Radius = 1 << 1,
|
||||
Cost = 1 << 2,
|
||||
Recast = 1 << 3,
|
||||
Cast = 1 << 4,
|
||||
Description = 1 << 5,
|
||||
Acquired = 1 << 6,
|
||||
Affinity = 1 << 7,
|
||||
Unknown8 = 1 << 8,
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
/// <summary>
|
||||
/// An enum containing the strings used in action tooltips.
|
||||
/// </summary>
|
||||
public enum ActionTooltipString {
|
||||
#pragma warning disable 1591
|
||||
Name = 0,
|
||||
Type = 1,
|
||||
RangeLabel = 3,
|
||||
Range = 4,
|
||||
RadiusLabel = 5,
|
||||
Radius = 6,
|
||||
CostLabel = 7,
|
||||
Cost = 8,
|
||||
RecastLabel = 9,
|
||||
Recast = 10,
|
||||
CastLabel = 11,
|
||||
Cast = 12,
|
||||
Description = 13,
|
||||
Acquired = 14,
|
||||
Affinity = 15,
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enum containing the fields that can be displayed in action tooltips.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ActionTooltipFields {
|
||||
#pragma warning disable 1591
|
||||
Range = 1 << 0,
|
||||
Radius = 1 << 1,
|
||||
Cost = 1 << 2,
|
||||
Recast = 1 << 3,
|
||||
Cast = 1 << 4,
|
||||
Description = 1 << 5,
|
||||
Acquired = 1 << 6,
|
||||
Affinity = 1 << 7,
|
||||
Unknown8 = 1 << 8,
|
||||
#pragma warning restore 1591
|
||||
}
|
|
@ -1,50 +1,50 @@
|
|||
using System;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.Tooltips {
|
||||
namespace XivCommon.Functions.Tooltips;
|
||||
|
||||
/// <summary>
|
||||
/// The base class for tooltips
|
||||
/// </summary>
|
||||
public abstract unsafe class BaseTooltip {
|
||||
private Tooltips.StringArrayDataSetStringDelegate SadSetString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base class for tooltips
|
||||
/// A pointer to the StringArrayData class for this tooltip.
|
||||
/// </summary>
|
||||
public abstract unsafe class BaseTooltip {
|
||||
private Tooltips.StringArrayDataSetStringDelegate SadSetString { get; }
|
||||
private readonly byte*** _stringArrayData; // this is StringArrayData* when ClientStructs is updated
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the StringArrayData class for this tooltip.
|
||||
/// </summary>
|
||||
private readonly byte*** _stringArrayData; // this is StringArrayData* when ClientStructs is updated
|
||||
/// <summary>
|
||||
/// A pointer to the NumberArrayData class for this tooltip.
|
||||
/// </summary>
|
||||
protected readonly int** NumberArrayData;
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the NumberArrayData class for this tooltip.
|
||||
/// </summary>
|
||||
protected readonly int** NumberArrayData;
|
||||
internal BaseTooltip(Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** stringArrayData, int** numberArrayData) {
|
||||
this.SadSetString = sadSetString;
|
||||
this._stringArrayData = stringArrayData;
|
||||
this.NumberArrayData = numberArrayData;
|
||||
}
|
||||
|
||||
internal BaseTooltip(Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** stringArrayData, int** numberArrayData) {
|
||||
this.SadSetString = sadSetString;
|
||||
this._stringArrayData = stringArrayData;
|
||||
this.NumberArrayData = numberArrayData;
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Gets the SeString at the given index for this tooltip.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implementors should provide an enum accessor for this.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="index">string index to retrieve</param>
|
||||
protected SeString this[int index] {
|
||||
get {
|
||||
var ptr = *(this._stringArrayData + 4) + index;
|
||||
return Util.ReadSeString((IntPtr) (*ptr));
|
||||
}
|
||||
set {
|
||||
var encoded = value.Encode().Terminate();
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Gets the SeString at the given index for this tooltip.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implementors should provide an enum accessor for this.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="index">string index to retrieve</param>
|
||||
protected SeString this[int index] {
|
||||
get {
|
||||
var ptr = *(this._stringArrayData + 4) + index;
|
||||
return Util.ReadSeString((IntPtr) (*ptr));
|
||||
}
|
||||
set {
|
||||
var encoded = value.Encode().Terminate();
|
||||
|
||||
fixed (byte* encodedPtr = encoded) {
|
||||
this.SadSetString((IntPtr) this._stringArrayData, index, encodedPtr, 0, 1, 1);
|
||||
}
|
||||
fixed (byte* encodedPtr = encoded) {
|
||||
this.SadSetString((IntPtr) this._stringArrayData, index, encodedPtr, 0, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +1,28 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.Tooltips {
|
||||
/// <summary>
|
||||
/// The class allowing for item tooltip manipulation
|
||||
/// </summary>
|
||||
public unsafe class ItemTooltip : BaseTooltip {
|
||||
internal ItemTooltip(Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** stringArrayData, int** numberArrayData) : base(sadSetString, stringArrayData, numberArrayData) {
|
||||
}
|
||||
namespace XivCommon.Functions.Tooltips;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SeString for the given string enum.
|
||||
/// </summary>
|
||||
/// <param name="its">the string to retrieve/update</param>
|
||||
public SeString this[ItemTooltipString its] {
|
||||
get => this[(int) its];
|
||||
set => this[(int) its] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which fields are visible on the tooltip.
|
||||
/// </summary>
|
||||
public ItemTooltipFields Fields {
|
||||
get => (ItemTooltipFields) (*(*(this.NumberArrayData + 4) + 4));
|
||||
set => *(*(this.NumberArrayData + 4) + 4) = (int) value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The class allowing for item tooltip manipulation
|
||||
/// </summary>
|
||||
public unsafe class ItemTooltip : BaseTooltip {
|
||||
internal ItemTooltip(Tooltips.StringArrayDataSetStringDelegate sadSetString, byte*** stringArrayData, int** numberArrayData) : base(sadSetString, stringArrayData, numberArrayData) {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SeString for the given string enum.
|
||||
/// </summary>
|
||||
/// <param name="its">the string to retrieve/update</param>
|
||||
public SeString this[ItemTooltipString its] {
|
||||
get => this[(int) its];
|
||||
set => this[(int) its] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which fields are visible on the tooltip.
|
||||
/// </summary>
|
||||
public ItemTooltipFields Fields {
|
||||
get => (ItemTooltipFields) (*(*(this.NumberArrayData + 4) + 4));
|
||||
set => *(*(this.NumberArrayData + 4) + 4) = (int) value;
|
||||
}
|
||||
}
|
|
@ -1,94 +1,94 @@
|
|||
using System;
|
||||
|
||||
namespace XivCommon.Functions.Tooltips {
|
||||
/// <summary>
|
||||
/// An enum containing the strings used in item tooltips.
|
||||
/// </summary>
|
||||
public enum ItemTooltipString {
|
||||
#pragma warning disable 1591
|
||||
Name = 0,
|
||||
GlamourName = 1,
|
||||
Type = 2,
|
||||
Stat1Label = 4,
|
||||
Stat2Label = 5,
|
||||
Stat3Label = 6,
|
||||
Stat1 = 7,
|
||||
Stat2 = 8,
|
||||
Stat3 = 9,
|
||||
Stat1Delta = 10,
|
||||
Stat2Delta = 11,
|
||||
Stat3Delta = 12,
|
||||
Description = 13,
|
||||
Quantity = 14,
|
||||
EffectsLabel = 15,
|
||||
Effects = 16,
|
||||
EquipJobs = 22,
|
||||
EquipLevel = 23,
|
||||
VendorSellPrice = 25,
|
||||
Crafter = 26,
|
||||
Level = 27,
|
||||
Condition = 28,
|
||||
SpiritbondLabel = 29,
|
||||
Spiritbond = 30,
|
||||
RepairLevel = 31,
|
||||
Materials = 32,
|
||||
QuickRepairs = 33,
|
||||
MateriaMelding = 34,
|
||||
Capabilities = 35,
|
||||
BonusesLabel = 36,
|
||||
Bonus1 = 37,
|
||||
Bonus2 = 38,
|
||||
Bonus3 = 39,
|
||||
Bonus4 = 40,
|
||||
MateriaLabel = 52,
|
||||
Materia1 = 53,
|
||||
Materia2 = 54,
|
||||
Materia3 = 55,
|
||||
Materia4 = 56,
|
||||
Materia5 = 57,
|
||||
Materia1Effect = 58,
|
||||
Materia2Effect = 59,
|
||||
Materia3Effect = 60,
|
||||
Materia4Effect = 61,
|
||||
Materia5Effect = 62,
|
||||
ShopSellingPrice = 63,
|
||||
ControllerControls = 64,
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
namespace XivCommon.Functions.Tooltips;
|
||||
|
||||
/// <summary>
|
||||
/// An enum containing the fields that can be displayed in item tooltips.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ItemTooltipFields {
|
||||
#pragma warning disable 1591
|
||||
Crafter = 1 << 0,
|
||||
Description = 1 << 1,
|
||||
VendorSellPrice = 1 << 2,
|
||||
|
||||
// makes the tooltip much smaller when hovered over gear and unset
|
||||
// something to do with EquipLevel maybe?
|
||||
Unknown3 = 1 << 3,
|
||||
Bonuses = 1 << 4,
|
||||
Materia = 1 << 5,
|
||||
CraftingAndRepairs = 1 << 6,
|
||||
Effects = 1 << 8,
|
||||
DyeableIndicator = 1 << 10,
|
||||
Stat1 = 1 << 11,
|
||||
Stat2 = 1 << 12,
|
||||
Stat3 = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Shows item level and equip level.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Item level is always visible, but if equip level is set to an empty string, it will be hidden.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
Levels = 1 << 15,
|
||||
GlamourIndicator = 1 << 16,
|
||||
Unknown19 = 1 << 19,
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
/// <summary>
|
||||
/// An enum containing the strings used in item tooltips.
|
||||
/// </summary>
|
||||
public enum ItemTooltipString {
|
||||
#pragma warning disable 1591
|
||||
Name = 0,
|
||||
GlamourName = 1,
|
||||
Type = 2,
|
||||
Stat1Label = 4,
|
||||
Stat2Label = 5,
|
||||
Stat3Label = 6,
|
||||
Stat1 = 7,
|
||||
Stat2 = 8,
|
||||
Stat3 = 9,
|
||||
Stat1Delta = 10,
|
||||
Stat2Delta = 11,
|
||||
Stat3Delta = 12,
|
||||
Description = 13,
|
||||
Quantity = 14,
|
||||
EffectsLabel = 15,
|
||||
Effects = 16,
|
||||
EquipJobs = 22,
|
||||
EquipLevel = 23,
|
||||
VendorSellPrice = 25,
|
||||
Crafter = 26,
|
||||
Level = 27,
|
||||
Condition = 28,
|
||||
SpiritbondLabel = 29,
|
||||
Spiritbond = 30,
|
||||
RepairLevel = 31,
|
||||
Materials = 32,
|
||||
QuickRepairs = 33,
|
||||
MateriaMelding = 34,
|
||||
Capabilities = 35,
|
||||
BonusesLabel = 36,
|
||||
Bonus1 = 37,
|
||||
Bonus2 = 38,
|
||||
Bonus3 = 39,
|
||||
Bonus4 = 40,
|
||||
MateriaLabel = 52,
|
||||
Materia1 = 53,
|
||||
Materia2 = 54,
|
||||
Materia3 = 55,
|
||||
Materia4 = 56,
|
||||
Materia5 = 57,
|
||||
Materia1Effect = 58,
|
||||
Materia2Effect = 59,
|
||||
Materia3Effect = 60,
|
||||
Materia4Effect = 61,
|
||||
Materia5Effect = 62,
|
||||
ShopSellingPrice = 63,
|
||||
ControllerControls = 64,
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enum containing the fields that can be displayed in item tooltips.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ItemTooltipFields {
|
||||
#pragma warning disable 1591
|
||||
Crafter = 1 << 0,
|
||||
Description = 1 << 1,
|
||||
VendorSellPrice = 1 << 2,
|
||||
|
||||
// makes the tooltip much smaller when hovered over gear and unset
|
||||
// something to do with EquipLevel maybe?
|
||||
Unknown3 = 1 << 3,
|
||||
Bonuses = 1 << 4,
|
||||
Materia = 1 << 5,
|
||||
CraftingAndRepairs = 1 << 6,
|
||||
Effects = 1 << 8,
|
||||
DyeableIndicator = 1 << 10,
|
||||
Stat1 = 1 << 11,
|
||||
Stat2 = 1 << 12,
|
||||
Stat3 = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Shows item level and equip level.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Item level is always visible, but if equip level is set to an empty string, it will be hidden.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
Levels = 1 << 15,
|
||||
GlamourIndicator = 1 << 16,
|
||||
Unknown19 = 1 << 19,
|
||||
#pragma warning restore 1591
|
||||
}
|
|
@ -3,149 +3,150 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace XivCommon.Functions.Tooltips;
|
||||
|
||||
/// <summary>
|
||||
/// The class containing tooltip functionality
|
||||
/// </summary>
|
||||
public class Tooltips : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string AgentItemDetailUpdateTooltip = "E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ?? 48 89 AE";
|
||||
internal const string AgentActionDetailUpdateTooltip = "E8 ?? ?? ?? ?? EB 68 FF 50 40";
|
||||
internal const string SadSetString = "E8 ?? ?? ?? ?? F6 47 14 08";
|
||||
}
|
||||
|
||||
// Last checked: 6.0
|
||||
// E8 ?? ?? ?? ?? EB 68 FF 50 40
|
||||
private const int AgentActionDetailUpdateFlagOffset = 0x58;
|
||||
|
||||
internal unsafe delegate void StringArrayDataSetStringDelegate(IntPtr self, int index, byte* str, byte updatePtr, byte copyToUi, byte dontSetModified);
|
||||
|
||||
private unsafe delegate ulong ItemUpdateTooltipDelegate(IntPtr agent, int** numberArrayData, byte*** stringArrayData, float a4);
|
||||
|
||||
private unsafe delegate void ActionUpdateTooltipDelegate(IntPtr agent, int** numberArrayData, byte*** stringArrayData);
|
||||
|
||||
private StringArrayDataSetStringDelegate? SadSetString { get; }
|
||||
private Hook<ItemUpdateTooltipDelegate>? ItemUpdateTooltipHook { get; }
|
||||
private Hook<ActionUpdateTooltipDelegate>? ActionGenerateTooltipHook { get; }
|
||||
|
||||
namespace XivCommon.Functions.Tooltips {
|
||||
/// <summary>
|
||||
/// The class containing tooltip functionality
|
||||
/// The delegate for item tooltip events.
|
||||
/// </summary>
|
||||
public class Tooltips : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string AgentItemDetailUpdateTooltip = "E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ?? 48 89 AE";
|
||||
internal const string AgentActionDetailUpdateTooltip = "E8 ?? ?? ?? ?? EB 68 FF 50 40";
|
||||
internal const string SadSetString = "E8 ?? ?? ?? ?? F6 47 14 08";
|
||||
public delegate void ItemTooltipEventDelegate(ItemTooltip itemTooltip, ulong itemId);
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip for action tooltip events.
|
||||
/// </summary>
|
||||
public delegate void ActionTooltipEventDelegate(ActionTooltip actionTooltip, HoveredAction action);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when an item tooltip is being generated for display.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.Tooltips"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event ItemTooltipEventDelegate? OnItemTooltip;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when an action tooltip is being generated for display.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.Tooltips"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event ActionTooltipEventDelegate? OnActionTooltip;
|
||||
|
||||
private IGameGui GameGui { get; }
|
||||
private ItemTooltip? ItemTooltip { get; set; }
|
||||
private ActionTooltip? ActionTooltip { get; set; }
|
||||
|
||||
internal Tooltips(ISigScanner scanner, IGameGui gui, IGameInteropProvider interop, bool enabled) {
|
||||
this.GameGui = gui;
|
||||
|
||||
if (scanner.TryScanText(Signatures.SadSetString, out var setStringPtr, "Tooltips - StringArrayData::SetString")) {
|
||||
this.SadSetString = Marshal.GetDelegateForFunctionPointer<StringArrayDataSetStringDelegate>(setStringPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Last checked: 6.0
|
||||
// E8 ?? ?? ?? ?? EB 68 FF 50 40
|
||||
private const int AgentActionDetailUpdateFlagOffset = 0x58;
|
||||
|
||||
internal unsafe delegate void StringArrayDataSetStringDelegate(IntPtr self, int index, byte* str, byte updatePtr, byte copyToUi, byte dontSetModified);
|
||||
|
||||
private unsafe delegate ulong ItemUpdateTooltipDelegate(IntPtr agent, int** numberArrayData, byte*** stringArrayData, float a4);
|
||||
|
||||
private unsafe delegate void ActionUpdateTooltipDelegate(IntPtr agent, int** numberArrayData, byte*** stringArrayData);
|
||||
|
||||
private StringArrayDataSetStringDelegate? SadSetString { get; }
|
||||
private Hook<ItemUpdateTooltipDelegate>? ItemUpdateTooltipHook { get; }
|
||||
private Hook<ActionUpdateTooltipDelegate>? ActionGenerateTooltipHook { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for item tooltip events.
|
||||
/// </summary>
|
||||
public delegate void ItemTooltipEventDelegate(ItemTooltip itemTooltip, ulong itemId);
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip for action tooltip events.
|
||||
/// </summary>
|
||||
public delegate void ActionTooltipEventDelegate(ActionTooltip actionTooltip, HoveredAction action);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when an item tooltip is being generated for display.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.Tooltips"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event ItemTooltipEventDelegate? OnItemTooltip;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when an action tooltip is being generated for display.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.Tooltips"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event ActionTooltipEventDelegate? OnActionTooltip;
|
||||
|
||||
private GameGui GameGui { get; }
|
||||
private ItemTooltip? ItemTooltip { get; set; }
|
||||
private ActionTooltip? ActionTooltip { get; set; }
|
||||
|
||||
internal Tooltips(SigScanner scanner, GameGui gui, bool enabled) {
|
||||
this.GameGui = gui;
|
||||
|
||||
if (scanner.TryScanText(Signatures.SadSetString, out var setStringPtr, "Tooltips - StringArrayData::SetString")) {
|
||||
this.SadSetString = Marshal.GetDelegateForFunctionPointer<StringArrayDataSetStringDelegate>(setStringPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.AgentItemDetailUpdateTooltip, out var updateItemPtr, "Tooltips - Items")) {
|
||||
unsafe {
|
||||
this.ItemUpdateTooltipHook = Hook<ItemUpdateTooltipDelegate>.FromAddress(updateItemPtr, this.ItemUpdateTooltipDetour);
|
||||
}
|
||||
|
||||
this.ItemUpdateTooltipHook.Enable();
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.AgentActionDetailUpdateTooltip, out var updateActionPtr, "Tooltips - Actions")) {
|
||||
unsafe {
|
||||
this.ActionGenerateTooltipHook = Hook<ActionUpdateTooltipDelegate>.FromAddress(updateActionPtr, this.ActionUpdateTooltipDetour);
|
||||
}
|
||||
|
||||
this.ActionGenerateTooltipHook.Enable();
|
||||
}
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.ActionGenerateTooltipHook?.Dispose();
|
||||
this.ItemUpdateTooltipHook?.Dispose();
|
||||
}
|
||||
|
||||
private unsafe ulong ItemUpdateTooltipDetour(IntPtr agent, int** numberArrayData, byte*** stringArrayData, float a4) {
|
||||
var ret = this.ItemUpdateTooltipHook!.Original(agent, numberArrayData, stringArrayData, a4);
|
||||
|
||||
if (ret > 0) {
|
||||
try {
|
||||
this.ItemUpdateTooltipDetourInner(numberArrayData, stringArrayData);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in item tooltip detour");
|
||||
}
|
||||
if (scanner.TryScanText(Signatures.AgentItemDetailUpdateTooltip, out var updateItemPtr, "Tooltips - Items")) {
|
||||
unsafe {
|
||||
this.ItemUpdateTooltipHook = interop.HookFromAddress<ItemUpdateTooltipDelegate>(updateItemPtr, this.ItemUpdateTooltipDetour);
|
||||
}
|
||||
|
||||
return ret;
|
||||
this.ItemUpdateTooltipHook.Enable();
|
||||
}
|
||||
|
||||
private unsafe void ItemUpdateTooltipDetourInner(int** numberArrayData, byte*** stringArrayData) {
|
||||
this.ItemTooltip = new ItemTooltip(this.SadSetString!, stringArrayData, numberArrayData);
|
||||
if (scanner.TryScanText(Signatures.AgentActionDetailUpdateTooltip, out var updateActionPtr, "Tooltips - Actions")) {
|
||||
unsafe {
|
||||
this.ActionGenerateTooltipHook = interop.HookFromAddress<ActionUpdateTooltipDelegate>(updateActionPtr, this.ActionUpdateTooltipDetour);
|
||||
}
|
||||
|
||||
this.ActionGenerateTooltipHook.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.ActionGenerateTooltipHook?.Dispose();
|
||||
this.ItemUpdateTooltipHook?.Dispose();
|
||||
}
|
||||
|
||||
private unsafe ulong ItemUpdateTooltipDetour(IntPtr agent, int** numberArrayData, byte*** stringArrayData, float a4) {
|
||||
var ret = this.ItemUpdateTooltipHook!.Original(agent, numberArrayData, stringArrayData, a4);
|
||||
|
||||
if (ret > 0) {
|
||||
try {
|
||||
this.OnItemTooltip?.Invoke(this.ItemTooltip, this.GameGui.HoveredItem);
|
||||
this.ItemUpdateTooltipDetourInner(numberArrayData, stringArrayData);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in OnItemTooltip event");
|
||||
Logger.Log.Error(ex, "Exception in item tooltip detour");
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void ActionUpdateTooltipDetour(IntPtr agent, int** numberArrayData, byte*** stringArrayData) {
|
||||
var flag = *(byte*) (agent + AgentActionDetailUpdateFlagOffset);
|
||||
this.ActionGenerateTooltipHook!.Original(agent, numberArrayData, stringArrayData);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (flag == 0) {
|
||||
return;
|
||||
}
|
||||
private unsafe void ItemUpdateTooltipDetourInner(int** numberArrayData, byte*** stringArrayData) {
|
||||
this.ItemTooltip = new ItemTooltip(this.SadSetString!, stringArrayData, numberArrayData);
|
||||
|
||||
try {
|
||||
this.ActionUpdateTooltipDetourInner(numberArrayData, stringArrayData);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in action tooltip detour");
|
||||
}
|
||||
try {
|
||||
this.OnItemTooltip?.Invoke(this.ItemTooltip, this.GameGui.HoveredItem);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in OnItemTooltip event");
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void ActionUpdateTooltipDetour(IntPtr agent, int** numberArrayData, byte*** stringArrayData) {
|
||||
var flag = *(byte*) (agent + AgentActionDetailUpdateFlagOffset);
|
||||
this.ActionGenerateTooltipHook!.Original(agent, numberArrayData, stringArrayData);
|
||||
|
||||
if (flag == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
private unsafe void ActionUpdateTooltipDetourInner(int** numberArrayData, byte*** stringArrayData) {
|
||||
this.ActionTooltip = new ActionTooltip(this.SadSetString!, stringArrayData, numberArrayData);
|
||||
try {
|
||||
this.ActionUpdateTooltipDetourInner(numberArrayData, stringArrayData);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in action tooltip detour");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.OnActionTooltip?.Invoke(this.ActionTooltip, this.GameGui.HoveredAction);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in OnActionTooltip event");
|
||||
}
|
||||
private unsafe void ActionUpdateTooltipDetourInner(int** numberArrayData, byte*** stringArrayData) {
|
||||
this.ActionTooltip = new ActionTooltip(this.SadSetString!, stringArrayData, numberArrayData);
|
||||
|
||||
try {
|
||||
this.OnActionTooltip?.Invoke(this.ActionTooltip, this.GameGui.HoveredAction);
|
||||
} catch (Exception ex) {
|
||||
Logger.Log.Error(ex, "Exception in OnActionTooltip event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,58 +2,58 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace XivCommon.Functions {
|
||||
internal class UiAlloc {
|
||||
private static class Signatures {
|
||||
internal const string GameAlloc = "E8 ?? ?? ?? ?? 49 83 CC FF 4C 8B F0";
|
||||
internal const string GameFree = "E8 ?? ?? ?? ?? 4C 89 7B 60";
|
||||
internal const string GetGameAllocator = "E8 ?? ?? ?? ?? 8B 75 08";
|
||||
namespace XivCommon.Functions;
|
||||
|
||||
internal class UiAlloc {
|
||||
private static class Signatures {
|
||||
internal const string GameAlloc = "E8 ?? ?? ?? ?? 49 83 CC FF 4C 8B F0";
|
||||
internal const string GameFree = "E8 ?? ?? ?? ?? 4C 89 7B 60";
|
||||
internal const string GetGameAllocator = "E8 ?? ?? ?? ?? 8B 75 08";
|
||||
}
|
||||
|
||||
private delegate IntPtr GameAllocDelegate(ulong size, IntPtr unk, IntPtr allocator, IntPtr alignment);
|
||||
|
||||
private readonly GameAllocDelegate? _gameAlloc;
|
||||
|
||||
private delegate IntPtr GameFreeDelegate(IntPtr a1);
|
||||
|
||||
private readonly GameFreeDelegate? _gameFree;
|
||||
|
||||
private delegate IntPtr GetGameAllocatorDelegate();
|
||||
|
||||
private readonly GetGameAllocatorDelegate? _getGameAllocator;
|
||||
|
||||
internal UiAlloc(ISigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.GameAlloc, out var gameAllocPtr, "UiAlloc (GameAlloc)")) {
|
||||
this._gameAlloc = Marshal.GetDelegateForFunctionPointer<GameAllocDelegate>(gameAllocPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
private delegate IntPtr GameAllocDelegate(ulong size, IntPtr unk, IntPtr allocator, IntPtr alignment);
|
||||
|
||||
private readonly GameAllocDelegate? _gameAlloc;
|
||||
|
||||
private delegate IntPtr GameFreeDelegate(IntPtr a1);
|
||||
|
||||
private readonly GameFreeDelegate? _gameFree;
|
||||
|
||||
private delegate IntPtr GetGameAllocatorDelegate();
|
||||
|
||||
private readonly GetGameAllocatorDelegate? _getGameAllocator;
|
||||
|
||||
internal UiAlloc(SigScanner scanner) {
|
||||
if (scanner.TryScanText(Signatures.GameAlloc, out var gameAllocPtr, "UiAlloc (GameAlloc)")) {
|
||||
this._gameAlloc = Marshal.GetDelegateForFunctionPointer<GameAllocDelegate>(gameAllocPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GameFree, out var gameFreePtr, "UiAlloc (GameFree)")) {
|
||||
this._gameFree = Marshal.GetDelegateForFunctionPointer<GameFreeDelegate>(gameFreePtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GetGameAllocator, out var getAllocatorPtr, "UiAlloc (GetGameAllocator)")) {
|
||||
this._getGameAllocator = Marshal.GetDelegateForFunctionPointer<GetGameAllocatorDelegate>(getAllocatorPtr);
|
||||
}
|
||||
if (scanner.TryScanText(Signatures.GameFree, out var gameFreePtr, "UiAlloc (GameFree)")) {
|
||||
this._gameFree = Marshal.GetDelegateForFunctionPointer<GameFreeDelegate>(gameFreePtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
internal IntPtr Alloc(ulong size) {
|
||||
if (this._getGameAllocator == null || this._gameAlloc == null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return this._gameAlloc(size, IntPtr.Zero, this._getGameAllocator(), IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal void Free(IntPtr ptr) {
|
||||
if (this._gameFree == null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
this._gameFree(ptr);
|
||||
if (scanner.TryScanText(Signatures.GetGameAllocator, out var getAllocatorPtr, "UiAlloc (GetGameAllocator)")) {
|
||||
this._getGameAllocator = Marshal.GetDelegateForFunctionPointer<GetGameAllocatorDelegate>(getAllocatorPtr);
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr Alloc(ulong size) {
|
||||
if (this._getGameAllocator == null || this._gameAlloc == null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return this._gameAlloc(size, IntPtr.Zero, this._getGameAllocator(), IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal void Free(IntPtr ptr) {
|
||||
if (this._gameFree == null) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
this._gameFree(ptr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Gui.PartyFinder;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using XivCommon.Functions;
|
||||
|
@ -12,165 +10,168 @@ using XivCommon.Functions.NamePlates;
|
|||
using XivCommon.Functions.Tooltips;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace XivCommon {
|
||||
namespace XivCommon;
|
||||
|
||||
/// <summary>
|
||||
/// A class containing game functions
|
||||
/// </summary>
|
||||
public class GameFunctions : IDisposable {
|
||||
private IGameGui GameGui { get; }
|
||||
|
||||
private IFramework Framework { get; }
|
||||
|
||||
internal UiAlloc UiAlloc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A class containing game functions
|
||||
/// Chat functions
|
||||
/// </summary>
|
||||
public class GameFunctions : IDisposable {
|
||||
private GameGui GameGui { get; }
|
||||
public Chat Chat { get; }
|
||||
|
||||
private Dalamud.Game.Framework Framework { get; }
|
||||
/// <summary>
|
||||
/// Party Finder functions and events
|
||||
/// </summary>
|
||||
public PartyFinder PartyFinder { get; }
|
||||
|
||||
internal UiAlloc UiAlloc { get; }
|
||||
/// <summary>
|
||||
/// BattleTalk functions and events
|
||||
/// </summary>
|
||||
public BattleTalk BattleTalk { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Chat functions
|
||||
/// </summary>
|
||||
public Chat Chat { get; }
|
||||
/// <summary>
|
||||
/// Examine functions
|
||||
/// </summary>
|
||||
public Examine Examine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Party Finder functions and events
|
||||
/// </summary>
|
||||
public PartyFinder PartyFinder { get; }
|
||||
/// <summary>
|
||||
/// Talk events
|
||||
/// </summary>
|
||||
public Talk Talk { get; }
|
||||
|
||||
/// <summary>
|
||||
/// BattleTalk functions and events
|
||||
/// </summary>
|
||||
public BattleTalk BattleTalk { get; }
|
||||
/// <summary>
|
||||
/// Chat bubble functions and events
|
||||
/// </summary>
|
||||
public ChatBubbles ChatBubbles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Examine functions
|
||||
/// </summary>
|
||||
public Examine Examine { get; }
|
||||
/// <summary>
|
||||
/// Tooltip events
|
||||
/// </summary>
|
||||
public Tooltips Tooltips { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Talk events
|
||||
/// </summary>
|
||||
public Talk Talk { get; }
|
||||
/// <summary>
|
||||
/// Name plate tools and events
|
||||
/// </summary>
|
||||
public NamePlates NamePlates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Chat bubble functions and events
|
||||
/// </summary>
|
||||
public ChatBubbles ChatBubbles { get; }
|
||||
/// <summary>
|
||||
/// Duty Finder functions
|
||||
/// </summary>
|
||||
public DutyFinder DutyFinder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip events
|
||||
/// </summary>
|
||||
public Tooltips Tooltips { get; }
|
||||
/// <summary>
|
||||
/// Friend list functions
|
||||
/// </summary>
|
||||
public FriendList FriendList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name plate tools and events
|
||||
/// </summary>
|
||||
public NamePlates NamePlates { get; }
|
||||
/// <summary>
|
||||
/// Journal functions
|
||||
/// </summary>
|
||||
public Journal Journal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Duty Finder functions
|
||||
/// </summary>
|
||||
public DutyFinder DutyFinder { get; }
|
||||
/// <summary>
|
||||
/// Housing functions
|
||||
/// </summary>
|
||||
public Housing Housing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Friend list functions
|
||||
/// </summary>
|
||||
public FriendList FriendList { get; }
|
||||
internal GameFunctions(Hooks hooks) {
|
||||
Logger.Log = Util.GetService<IPluginLog>();
|
||||
|
||||
/// <summary>
|
||||
/// Journal functions
|
||||
/// </summary>
|
||||
public Journal Journal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Housing functions
|
||||
/// </summary>
|
||||
public Housing Housing { get; }
|
||||
this.Framework = Util.GetService<IFramework>();
|
||||
this.GameGui = Util.GetService<IGameGui>();
|
||||
|
||||
internal GameFunctions(Hooks hooks) {
|
||||
this.Framework = Util.GetService<Dalamud.Game.Framework>();
|
||||
this.GameGui = Util.GetService<GameGui>();
|
||||
var interop = Util.GetService<IGameInteropProvider>();
|
||||
var objectTable = Util.GetService<IObjectTable>();
|
||||
var partyFinderGui = Util.GetService<IPartyFinderGui>();
|
||||
var scanner = Util.GetService<ISigScanner>();
|
||||
|
||||
var objectTable = Util.GetService<ObjectTable>();
|
||||
var partyFinderGui = Util.GetService<PartyFinderGui>();
|
||||
var scanner = Util.GetService<SigScanner>();
|
||||
this.UiAlloc = new UiAlloc(scanner);
|
||||
this.Chat = new Chat(scanner);
|
||||
this.PartyFinder = new PartyFinder(scanner, partyFinderGui, interop, hooks);
|
||||
this.BattleTalk = new BattleTalk(interop, hooks.HasFlag(Hooks.BattleTalk));
|
||||
this.Examine = new Examine(scanner);
|
||||
this.Talk = new Talk(scanner, interop, hooks.HasFlag(Hooks.Talk));
|
||||
this.ChatBubbles = new ChatBubbles(objectTable, scanner, interop, hooks.HasFlag(Hooks.ChatBubbles));
|
||||
this.Tooltips = new Tooltips(scanner, this.GameGui, interop, hooks.HasFlag(Hooks.Tooltips));
|
||||
this.NamePlates = new NamePlates(this, scanner, interop, hooks.HasFlag(Hooks.NamePlates));
|
||||
this.DutyFinder = new DutyFinder(scanner);
|
||||
this.Journal = new Journal(scanner);
|
||||
this.FriendList = new FriendList();
|
||||
this.Housing = new Housing(scanner);
|
||||
}
|
||||
|
||||
this.UiAlloc = new UiAlloc(scanner);
|
||||
this.Chat = new Chat(scanner);
|
||||
this.PartyFinder = new PartyFinder(scanner, partyFinderGui, hooks);
|
||||
this.BattleTalk = new BattleTalk(hooks.HasFlag(Hooks.BattleTalk));
|
||||
this.Examine = new Examine(scanner);
|
||||
this.Talk = new Talk(scanner, hooks.HasFlag(Hooks.Talk));
|
||||
this.ChatBubbles = new ChatBubbles(objectTable, scanner, hooks.HasFlag(Hooks.ChatBubbles));
|
||||
this.Tooltips = new Tooltips(scanner, this.GameGui, hooks.HasFlag(Hooks.Tooltips));
|
||||
this.NamePlates = new NamePlates(this, scanner, hooks.HasFlag(Hooks.NamePlates));
|
||||
this.DutyFinder = new DutyFinder(scanner);
|
||||
this.Journal = new Journal(scanner);
|
||||
this.FriendList = new FriendList();
|
||||
this.Housing = new Housing(scanner);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.NamePlates.Dispose();
|
||||
this.Tooltips.Dispose();
|
||||
this.ChatBubbles.Dispose();
|
||||
this.Talk.Dispose();
|
||||
this.BattleTalk.Dispose();
|
||||
this.PartyFinder.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.NamePlates.Dispose();
|
||||
this.Tooltips.Dispose();
|
||||
this.ChatBubbles.Dispose();
|
||||
this.Talk.Dispose();
|
||||
this.BattleTalk.Dispose();
|
||||
this.PartyFinder.Dispose();
|
||||
}
|
||||
/// <summary>
|
||||
/// Convenience method to get a pointer to <see cref="Framework"/>.
|
||||
/// </summary>
|
||||
/// <returns>pointer to struct</returns>
|
||||
[Obsolete("Use Framework.Instance()")]
|
||||
public unsafe Framework* GetFramework() {
|
||||
return FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to get a pointer to <see cref="Framework"/>.
|
||||
/// </summary>
|
||||
/// <returns>pointer to struct</returns>
|
||||
[Obsolete("Use Framework.Instance()")]
|
||||
public unsafe Framework* GetFramework() {
|
||||
return (Framework*) this.Framework.Address.BaseAddress;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the pointer to the UI module
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()")]
|
||||
public unsafe IntPtr GetUiModule() {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the UI module
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()")]
|
||||
public unsafe IntPtr GetUiModule() {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the pointer to the RaptureAtkModule
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()->GetRaptureAtkModule()")]
|
||||
public unsafe IntPtr GetAtkModule() {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule()->GetRaptureAtkModule();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the RaptureAtkModule
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()->GetRaptureAtkModule()")]
|
||||
public unsafe IntPtr GetAtkModule() {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule()->GetRaptureAtkModule();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the pointer to the agent module
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()->GetAgentModule()")]
|
||||
public unsafe IntPtr GetAgentModule() {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule()->GetAgentModule();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the agent module
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()->GetAgentModule()")]
|
||||
public unsafe IntPtr GetAgentModule() {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule()->GetAgentModule();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the pointer to an agent from its internal ID.
|
||||
/// </summary>
|
||||
/// <param name="id">internal id of agent</param>
|
||||
/// <returns>Pointer</returns>
|
||||
/// <exception cref="InvalidOperationException">if the signature for the function could not be found</exception>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId)")]
|
||||
public unsafe IntPtr GetAgentByInternalId(uint id) {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule()->GetAgentModule()->GetAgentByInternalId((AgentId) id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to an agent from its internal ID.
|
||||
/// </summary>
|
||||
/// <param name="id">internal id of agent</param>
|
||||
/// <returns>Pointer</returns>
|
||||
/// <exception cref="InvalidOperationException">if the signature for the function could not be found</exception>
|
||||
[Obsolete("Use Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId)")]
|
||||
public unsafe IntPtr GetAgentByInternalId(uint id) {
|
||||
return (IntPtr) this.GetFramework()->GetUiModule()->GetAgentModule()->GetAgentByInternalId((AgentId) id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the AtkStage singleton
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
/// <exception cref="InvalidOperationException">if the signature for the function could not be found</exception>
|
||||
[Obsolete("Use AtkStage.GetSingleton()")]
|
||||
public unsafe IntPtr GetAtkStageSingleton() {
|
||||
return (IntPtr) AtkStage.GetSingleton();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the pointer to the AtkStage singleton
|
||||
/// </summary>
|
||||
/// <returns>Pointer</returns>
|
||||
/// <exception cref="InvalidOperationException">if the signature for the function could not be found</exception>
|
||||
[Obsolete("Use AtkStage.GetSingleton()")]
|
||||
public unsafe IntPtr GetAtkStageSingleton() {
|
||||
return (IntPtr) AtkStage.GetSingleton();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +1,74 @@
|
|||
using System;
|
||||
|
||||
namespace XivCommon {
|
||||
namespace XivCommon;
|
||||
|
||||
/// <summary>
|
||||
/// Flags for which hooks to use
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Hooks {
|
||||
/// <summary>
|
||||
/// Flags for which hooks to use
|
||||
/// No hook.
|
||||
///
|
||||
/// This flag is used to disable all hooking.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum Hooks {
|
||||
/// <summary>
|
||||
/// No hook.
|
||||
///
|
||||
/// This flag is used to disable all hooking.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The Tooltips hooks.
|
||||
///
|
||||
/// This hook is used in order to enable the tooltip events.
|
||||
/// </summary>
|
||||
Tooltips = 1 << 0,
|
||||
/// <summary>
|
||||
/// The Tooltips hooks.
|
||||
///
|
||||
/// This hook is used in order to enable the tooltip events.
|
||||
/// </summary>
|
||||
Tooltips = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The BattleTalk hook.
|
||||
///
|
||||
/// This hook is used in order to enable the BattleTalk events.
|
||||
/// </summary>
|
||||
BattleTalk = 1 << 1,
|
||||
/// <summary>
|
||||
/// The BattleTalk hook.
|
||||
///
|
||||
/// This hook is used in order to enable the BattleTalk events.
|
||||
/// </summary>
|
||||
BattleTalk = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Hooks used for refreshing Party Finder listings.
|
||||
/// </summary>
|
||||
PartyFinderListings = 1 << 2,
|
||||
/// <summary>
|
||||
/// Hooks used for refreshing Party Finder listings.
|
||||
/// </summary>
|
||||
PartyFinderListings = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Hooks used for Party Finder join events.
|
||||
/// </summary>
|
||||
PartyFinderJoins = 1 << 3,
|
||||
/// <summary>
|
||||
/// Hooks used for Party Finder join events.
|
||||
/// </summary>
|
||||
PartyFinderJoins = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// All Party Finder hooks.
|
||||
///
|
||||
/// This hook is used in order to enable all Party Finder functions.
|
||||
/// </summary>
|
||||
PartyFinder = PartyFinderListings | PartyFinderJoins,
|
||||
/// <summary>
|
||||
/// All Party Finder hooks.
|
||||
///
|
||||
/// This hook is used in order to enable all Party Finder functions.
|
||||
/// </summary>
|
||||
PartyFinder = PartyFinderListings | PartyFinderJoins,
|
||||
|
||||
/// <summary>
|
||||
/// The Talk hooks.
|
||||
///
|
||||
/// This hook is used in order to enable the Talk events.
|
||||
/// </summary>
|
||||
Talk = 1 << 4,
|
||||
/// <summary>
|
||||
/// The Talk hooks.
|
||||
///
|
||||
/// This hook is used in order to enable the Talk events.
|
||||
/// </summary>
|
||||
Talk = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// The chat bubbles hooks.
|
||||
///
|
||||
/// This hook is used in order to enable the chat bubbles events.
|
||||
/// </summary>
|
||||
ChatBubbles = 1 << 5,
|
||||
/// <summary>
|
||||
/// The chat bubbles hooks.
|
||||
///
|
||||
/// This hook is used in order to enable the chat bubbles events.
|
||||
/// </summary>
|
||||
ChatBubbles = 1 << 5,
|
||||
|
||||
// 1 << 6 used to be ContextMenu
|
||||
// 1 << 6 used to be ContextMenu
|
||||
|
||||
/// <summary>
|
||||
/// The name plate hooks.
|
||||
///
|
||||
/// This hook is used in order to enable name plate functions.
|
||||
/// </summary>
|
||||
NamePlates = 1 << 7,
|
||||
}
|
||||
|
||||
internal static class HooksExt {
|
||||
internal const Hooks DefaultHooks = Hooks.None;
|
||||
}
|
||||
/// <summary>
|
||||
/// The name plate hooks.
|
||||
///
|
||||
/// This hook is used in order to enable name plate functions.
|
||||
/// </summary>
|
||||
NamePlates = 1 << 7,
|
||||
}
|
||||
|
||||
internal static class HooksExt {
|
||||
internal const Hooks DefaultHooks = Hooks.None;
|
||||
}
|
|
@ -1,26 +1,7 @@
|
|||
using System;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace XivCommon {
|
||||
internal static class Logger {
|
||||
private static string Format(string msg) {
|
||||
return $"[XIVCommon] {msg}";
|
||||
}
|
||||
namespace XivCommon;
|
||||
|
||||
internal static void Log(string msg) {
|
||||
PluginLog.Log(Format(msg));
|
||||
}
|
||||
|
||||
internal static void LogWarning(string msg) {
|
||||
PluginLog.LogWarning(Format(msg));
|
||||
}
|
||||
|
||||
internal static void LogError(string msg) {
|
||||
PluginLog.LogError(Format(msg));
|
||||
}
|
||||
|
||||
internal static void LogError(Exception ex, string msg) {
|
||||
PluginLog.LogError(ex, Format(msg));
|
||||
}
|
||||
}
|
||||
internal static class Logger {
|
||||
internal static IPluginLog Log { get; set; } = null!;
|
||||
}
|
||||
|
|
|
@ -2,28 +2,28 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace XivCommon {
|
||||
internal static class SigScannerExt {
|
||||
/// <summary>
|
||||
/// Scan for a signature in memory.
|
||||
/// </summary>
|
||||
/// <param name="scanner">SigScanner to use for scanning</param>
|
||||
/// <param name="sig">signature to search for</param>
|
||||
/// <param name="result">pointer where signature was found or <see cref="IntPtr.Zero"/> if not found</param>
|
||||
/// <param name="name">name of this signature - if specified, a warning will be printed if the signature could not be found</param>
|
||||
/// <returns>true if signature was found</returns>
|
||||
internal static bool TryScanText(this SigScanner scanner, string sig, out IntPtr result, string? name = null) {
|
||||
result = IntPtr.Zero;
|
||||
try {
|
||||
result = scanner.ScanText(sig);
|
||||
return true;
|
||||
} catch (KeyNotFoundException) {
|
||||
if (name != null) {
|
||||
Util.PrintMissingSig(name);
|
||||
}
|
||||
namespace XivCommon;
|
||||
|
||||
return false;
|
||||
internal static class SigScannerExt {
|
||||
/// <summary>
|
||||
/// Scan for a signature in memory.
|
||||
/// </summary>
|
||||
/// <param name="scanner">SigScanner to use for scanning</param>
|
||||
/// <param name="sig">signature to search for</param>
|
||||
/// <param name="result">pointer where signature was found or <see cref="IntPtr.Zero"/> if not found</param>
|
||||
/// <param name="name">name of this signature - if specified, a warning will be printed if the signature could not be found</param>
|
||||
/// <returns>true if signature was found</returns>
|
||||
internal static bool TryScanText(this ISigScanner scanner, string sig, out IntPtr result, string? name = null) {
|
||||
result = IntPtr.Zero;
|
||||
try {
|
||||
result = scanner.ScanText(sig);
|
||||
return true;
|
||||
} catch (KeyNotFoundException) {
|
||||
if (name != null) {
|
||||
Util.PrintMissingSig(name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,60 +4,60 @@ using System.Reflection;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace XivCommon {
|
||||
internal static class Util {
|
||||
internal static byte[] Terminate(this byte[] array) {
|
||||
var terminated = new byte[array.Length + 1];
|
||||
Array.Copy(array, terminated, array.Length);
|
||||
terminated[^1] = 0;
|
||||
namespace XivCommon;
|
||||
|
||||
return terminated;
|
||||
internal static class Util {
|
||||
internal static byte[] Terminate(this byte[] array) {
|
||||
var terminated = new byte[array.Length + 1];
|
||||
Array.Copy(array, terminated, array.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
internal static unsafe byte[] ReadTerminated(IntPtr memory) {
|
||||
if (memory == IntPtr.Zero) {
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
internal static unsafe byte[] ReadTerminated(IntPtr memory) {
|
||||
if (memory == IntPtr.Zero) {
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
var buf = new List<byte>();
|
||||
|
||||
var buf = new List<byte>();
|
||||
|
||||
var ptr = (byte*) memory;
|
||||
while (*ptr != 0) {
|
||||
buf.Add(*ptr);
|
||||
ptr += 1;
|
||||
}
|
||||
|
||||
return buf.ToArray();
|
||||
var ptr = (byte*) memory;
|
||||
while (*ptr != 0) {
|
||||
buf.Add(*ptr);
|
||||
ptr += 1;
|
||||
}
|
||||
|
||||
internal static SeString ReadSeString(IntPtr memory) {
|
||||
var terminated = ReadTerminated(memory);
|
||||
return SeString.Parse(terminated);
|
||||
return buf.ToArray();
|
||||
}
|
||||
|
||||
internal static SeString ReadSeString(IntPtr memory) {
|
||||
var terminated = ReadTerminated(memory);
|
||||
return SeString.Parse(terminated);
|
||||
}
|
||||
|
||||
internal static void PrintMissingSig(string name) {
|
||||
Logger.Log.Warning($"Could not find signature for {name}. This functionality will be disabled.");
|
||||
}
|
||||
|
||||
internal static T GetService<T>() {
|
||||
var service = typeof(IDalamudPlugin).Assembly.GetType("Dalamud.Service`1")!.MakeGenericType(typeof(T));
|
||||
var get = service.GetMethod("Get", BindingFlags.Public | BindingFlags.Static)!;
|
||||
return (T) get.Invoke(null, null)!;
|
||||
}
|
||||
|
||||
internal static unsafe IntPtr FollowPointerChain(IntPtr start, IEnumerable<int> offsets) {
|
||||
if (start == IntPtr.Zero) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal static void PrintMissingSig(string name) {
|
||||
Logger.LogWarning($"Could not find signature for {name}. This functionality will be disabled.");
|
||||
}
|
||||
|
||||
internal static T GetService<T>() {
|
||||
var service = typeof(IDalamudPlugin).Assembly.GetType("Dalamud.Service`1")!.MakeGenericType(typeof(T));
|
||||
var get = service.GetMethod("Get", BindingFlags.Public | BindingFlags.Static)!;
|
||||
return (T) get.Invoke(null, null)!;
|
||||
}
|
||||
|
||||
internal static unsafe IntPtr FollowPointerChain(IntPtr start, IEnumerable<int> offsets) {
|
||||
foreach (var offset in offsets) {
|
||||
start = *(IntPtr*) (start + offset);
|
||||
if (start == IntPtr.Zero) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
foreach (var offset in offsets) {
|
||||
start = *(IntPtr*) (start + offset);
|
||||
if (start == IntPtr.Zero) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Title>XivCommon</Title>
|
||||
<Authors>ascclemens</Authors>
|
||||
<RepositoryUrl>https://git.annaclemens.io/ascclemens/XivCommon</RepositoryUrl>
|
||||
<RepositoryUrl>https://git.anna.lgbt/ascclemens/XivCommon</RepositoryUrl>
|
||||
<Description>A set of common functions, hooks, and events not included in Dalamud.</Description>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
using System;
|
||||
|
||||
namespace XivCommon {
|
||||
namespace XivCommon;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for accessing XivCommon functionality.
|
||||
/// </summary>
|
||||
public class XivCommonBase : IDisposable {
|
||||
/// <summary>
|
||||
/// A base class for accessing XivCommon functionality.
|
||||
/// Game functions and events
|
||||
/// </summary>
|
||||
public class XivCommonBase : IDisposable {
|
||||
/// <summary>
|
||||
/// Game functions and events
|
||||
/// </summary>
|
||||
public GameFunctions Functions { get; }
|
||||
public GameFunctions Functions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Construct a new XivCommon base.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This will automatically enable hooks based on the hooks parameter.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="hooks">Flags indicating which hooks to enable</param>
|
||||
public XivCommonBase(Hooks hooks = HooksExt.DefaultHooks) {
|
||||
this.Functions = new GameFunctions(hooks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.Functions.Dispose();
|
||||
}
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Construct a new XivCommon base.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This will automatically enable hooks based on the hooks parameter.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="hooks">Flags indicating which hooks to enable</param>
|
||||
public XivCommonBase(Hooks hooks = HooksExt.DefaultHooks) {
|
||||
this.Functions = new GameFunctions(hooks);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.Functions.Dispose();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue