mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-02 05:43:40 +01:00
chore: convert Dalamud to file-scoped namespaces
This commit is contained in:
parent
b093323acc
commit
987ff8dc8f
368 changed files with 55081 additions and 55450 deletions
|
|
@ -14,446 +14,445 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Utility;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native chat UI.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class ChatGui : IDisposable, IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native chat UI.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class ChatGui : IDisposable, IServiceType
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly LibcFunction libcFunction = Service<LibcFunction>.Get();
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ChatGui(SigScanner sigScanner)
|
||||
{
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
this.address = new ChatGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
}
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly LibcFunction libcFunction = Service<LibcFunction>.Get();
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ChatGui(SigScanner sigScanner)
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||
/// </summary>
|
||||
public event OnMessageDelegate ChatMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||
/// </summary>
|
||||
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageHandledDelegate ChatMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the last linked item.
|
||||
/// </summary>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
/// </summary>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat)
|
||||
{
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(string message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.address = new ChatGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
Message = message,
|
||||
Type = this.configuration.GeneralChatType,
|
||||
});
|
||||
}
|
||||
|
||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
/// <param name="isHandled">A value indicating whether the message was handled or should be propagated.</param>
|
||||
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of chat.</param>
|
||||
/// <param name="senderId">The sender ID.</param>
|
||||
/// <param name="sender">The sender name.</param>
|
||||
/// <param name="message">The message sent.</param>
|
||||
public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is sent to chat by the game.
|
||||
/// </summary>
|
||||
public event OnMessageDelegate ChatMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
|
||||
/// </summary>
|
||||
public event OnCheckMessageHandledDelegate CheckMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageHandledDelegate ChatMessageHandled;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
|
||||
/// </summary>
|
||||
public event OnMessageUnhandledDelegate ChatMessageUnhandled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the last linked item.
|
||||
/// </summary>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
/// </summary>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(SeString message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
Message = message,
|
||||
Type = this.configuration.GeneralChatType,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat)
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(string message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(string message)
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(SeString message)
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
public void UpdateQueue()
|
||||
{
|
||||
while (this.chatQueue.Count > 0)
|
||||
{
|
||||
var chat = this.chatQueue.Dequeue();
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
{
|
||||
Message = message,
|
||||
Type = this.configuration.GeneralChatType,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
||||
using var senderOwned = this.libcFunction.NewString(senderRaw);
|
||||
|
||||
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
||||
using var messageOwned = this.libcFunction.NewString(messageRaw);
|
||||
|
||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void Print(SeString message)
|
||||
/// <summary>
|
||||
/// Create a link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to run.</param>
|
||||
/// <param name="commandAction">The command action itself.</param>
|
||||
/// <returns>A payload for handling.</returns>
|
||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
||||
{
|
||||
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all handlers owned by a plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the links.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName)
|
||||
{
|
||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = this.configuration.GeneralChatType,
|
||||
});
|
||||
this.dalamudLinkHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(string message)
|
||||
/// <summary>
|
||||
/// Remove a registered link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to be removed.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to
|
||||
/// the queue, later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to send.</param>
|
||||
public void PrintError(SeString message)
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction)
|
||||
{
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue);
|
||||
this.PrintChat(new XivChatEntry
|
||||
{
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
public void UpdateQueue()
|
||||
catch (Exception ex)
|
||||
{
|
||||
while (this.chatQueue.Count > 0)
|
||||
{
|
||||
var chat = this.chatQueue.Dequeue();
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
var sender = StdString.ReadFromPointer(pSenderName);
|
||||
var parsedSender = SeString.Parse(sender.RawData);
|
||||
var originalSenderData = (byte[])sender.RawData.Clone();
|
||||
var oldEditedSender = parsedSender.Encode();
|
||||
var senderPtr = pSenderName;
|
||||
OwnedStdString allocatedString = null;
|
||||
|
||||
var message = StdString.ReadFromPointer(pMessage);
|
||||
var parsedMessage = SeString.Parse(message.RawData);
|
||||
var originalMessageData = (byte[])message.RawData.Clone();
|
||||
var oldEdited = parsedMessage.Encode();
|
||||
var messagePtr = pMessage;
|
||||
OwnedStdString allocatedStringSender = null;
|
||||
|
||||
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||
|
||||
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
|
||||
var invocationList = this.CheckMessageHandled.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
{
|
||||
try
|
||||
{
|
||||
continue;
|
||||
var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate;
|
||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name);
|
||||
}
|
||||
|
||||
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
||||
using var senderOwned = this.libcFunction.NewString(senderRaw);
|
||||
|
||||
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
||||
using var messageOwned = this.libcFunction.NewString(messageRaw);
|
||||
|
||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to run.</param>
|
||||
/// <param name="commandAction">The command action itself.</param>
|
||||
/// <returns>A payload for handling.</returns>
|
||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction)
|
||||
{
|
||||
var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId };
|
||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all handlers owned by a plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the links.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName)
|
||||
{
|
||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName))
|
||||
if (!isHandled)
|
||||
{
|
||||
this.dalamudLinkHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a registered link handler.
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The name of the plugin handling the link.</param>
|
||||
/// <param name="commandId">The ID of the command to be removed.</param>
|
||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId)))
|
||||
{
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
}
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction)
|
||||
{
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
var sender = StdString.ReadFromPointer(pSenderName);
|
||||
var parsedSender = SeString.Parse(sender.RawData);
|
||||
var originalSenderData = (byte[])sender.RawData.Clone();
|
||||
var oldEditedSender = parsedSender.Encode();
|
||||
var senderPtr = pSenderName;
|
||||
OwnedStdString allocatedString = null;
|
||||
|
||||
var message = StdString.ReadFromPointer(pMessage);
|
||||
var parsedMessage = SeString.Parse(message.RawData);
|
||||
var originalMessageData = (byte[])message.RawData.Clone();
|
||||
var oldEdited = parsedMessage.Encode();
|
||||
var messagePtr = pMessage;
|
||||
OwnedStdString allocatedStringSender = null;
|
||||
|
||||
// Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);
|
||||
|
||||
// Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
|
||||
var invocationList = this.CheckMessageHandled.GetInvocationList();
|
||||
invocationList = this.ChatMessage.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as OnCheckMessageHandledDelegate;
|
||||
var messageHandledDelegate = @delegate as OnMessageDelegate;
|
||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name);
|
||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isHandled)
|
||||
{
|
||||
invocationList = this.ChatMessage.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as OnMessageDelegate;
|
||||
messageHandledDelegate!.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
var newEdited = parsedMessage.Encode();
|
||||
if (!Util.FastByteArrayCompare(oldEdited, newEdited))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
message.RawData = newEdited;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
|
||||
var newEdited = parsedMessage.Encode();
|
||||
if (!Util.FastByteArrayCompare(oldEdited, newEdited))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
message.RawData = newEdited;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
|
||||
{
|
||||
allocatedString = this.libcFunction.NewString(message.RawData);
|
||||
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||
messagePtr = allocatedString.Address;
|
||||
}
|
||||
|
||||
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
|
||||
{
|
||||
allocatedString = this.libcFunction.NewString(message.RawData);
|
||||
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||
messagePtr = allocatedString.Address;
|
||||
}
|
||||
var newEditedSender = parsedSender.Encode();
|
||||
if (!Util.FastByteArrayCompare(oldEditedSender, newEditedSender))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
sender.RawData = newEditedSender;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
|
||||
var newEditedSender = parsedSender.Encode();
|
||||
if (!Util.FastByteArrayCompare(oldEditedSender, newEditedSender))
|
||||
{
|
||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
||||
sender.RawData = newEditedSender;
|
||||
// Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
||||
}
|
||||
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
|
||||
{
|
||||
allocatedStringSender = this.libcFunction.NewString(sender.RawData);
|
||||
Log.Debug(
|
||||
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
||||
senderPtr = allocatedStringSender.Address;
|
||||
}
|
||||
|
||||
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
|
||||
{
|
||||
allocatedStringSender = this.libcFunction.NewString(sender.RawData);
|
||||
Log.Debug(
|
||||
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
||||
senderPtr = allocatedStringSender.Address;
|
||||
}
|
||||
// Print the original chat if it's handled.
|
||||
if (isHandled)
|
||||
{
|
||||
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
||||
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
}
|
||||
|
||||
// Print the original chat if it's handled.
|
||||
if (isHandled)
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
this.baseAddress = manager;
|
||||
|
||||
allocatedString?.Dispose();
|
||||
allocatedStringSender?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var messageSize = 0;
|
||||
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
||||
var payloadBytes = new byte[messageSize];
|
||||
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
||||
var seStr = SeString.Parse(payloadBytes);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||
{
|
||||
this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter);
|
||||
this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
this.baseAddress = manager;
|
||||
|
||||
allocatedString?.Dispose();
|
||||
allocatedStringSender?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var messageSize = 0;
|
||||
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
||||
var payloadBytes = new byte[messageSize];
|
||||
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
||||
var seStr = SeString.Parse(payloadBytes);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
{
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId)))
|
||||
{
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
}
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,104 +1,103 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// Gets the address of the native PrintMessage method.
|
||||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native PopulateItemLinkObject method.
|
||||
/// </summary>
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native InteractableLinkClicked method.
|
||||
/// </summary>
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
|
||||
/*
|
||||
--- for reference: 4.57 ---
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native PrintMessage method.
|
||||
/// </summary>
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native PopulateItemLinkObject method.
|
||||
/// </summary>
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native InteractableLinkClicked method.
|
||||
/// </summary>
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
/*
|
||||
--- for reference: 4.57 ---
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
*/
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
||||
this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05");
|
||||
// PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||
|
||||
// PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
}
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,314 +10,313 @@ using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
|||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.Dtr
|
||||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
/// Class used to interface with the server info bar.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed unsafe class DtrBar : IDisposable, IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Class used to interface with the server info bar.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed unsafe class DtrBar : IDisposable, IServiceType
|
||||
private const uint BaseNodeId = 1000;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private List<DtrBarEntry> entries = new();
|
||||
private uint runningNodeIds = BaseNodeId;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DtrBar()
|
||||
{
|
||||
private const uint BaseNodeId = 1000;
|
||||
this.framework.Update += this.Update;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
this.configuration.DtrOrder ??= new List<string>();
|
||||
this.configuration.DtrIgnore ??= new List<string>();
|
||||
this.configuration.Save();
|
||||
}
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
/// <summary>
|
||||
/// Get a DTR bar entry.
|
||||
/// This allows you to add your own text, and users to sort it.
|
||||
/// </summary>
|
||||
/// <param name="title">A user-friendly name for sorting.</param>
|
||||
/// <param name="text">The text the entry shows.</param>
|
||||
/// <returns>The entry object used to update, hide and remove the entry.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception>
|
||||
public DtrBarEntry Get(string title, SeString? text = null)
|
||||
{
|
||||
if (this.entries.Any(x => x.Title == title))
|
||||
throw new ArgumentException("An entry with the same title already exists.");
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
var node = this.MakeNode(++this.runningNodeIds);
|
||||
var entry = new DtrBarEntry(title, node);
|
||||
entry.Text = text;
|
||||
|
||||
private List<DtrBarEntry> entries = new();
|
||||
private uint runningNodeIds = BaseNodeId;
|
||||
// Add the entry to the end of the order list, if it's not there already.
|
||||
if (!this.configuration.DtrOrder!.Contains(title))
|
||||
this.configuration.DtrOrder!.Add(title);
|
||||
this.entries.Add(entry);
|
||||
this.ApplySort();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DtrBar()
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
foreach (var entry in this.entries)
|
||||
this.RemoveNode(entry.TextNode);
|
||||
|
||||
this.entries.Clear();
|
||||
this.framework.Update -= this.Update;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove nodes marked as "should be removed" from the bar.
|
||||
/// </summary>
|
||||
internal void HandleRemovedNodes()
|
||||
{
|
||||
foreach (var data in this.entries.Where(d => d.ShouldBeRemoved))
|
||||
{
|
||||
this.framework.Update += this.Update;
|
||||
|
||||
this.configuration.DtrOrder ??= new List<string>();
|
||||
this.configuration.DtrIgnore ??= new List<string>();
|
||||
this.configuration.Save();
|
||||
this.RemoveNode(data.TextNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a DTR bar entry.
|
||||
/// This allows you to add your own text, and users to sort it.
|
||||
/// </summary>
|
||||
/// <param name="title">A user-friendly name for sorting.</param>
|
||||
/// <param name="text">The text the entry shows.</param>
|
||||
/// <returns>The entry object used to update, hide and remove the entry.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when an entry with the specified title exists.</exception>
|
||||
public DtrBarEntry Get(string title, SeString? text = null)
|
||||
{
|
||||
if (this.entries.Any(x => x.Title == title))
|
||||
throw new ArgumentException("An entry with the same title already exists.");
|
||||
this.entries.RemoveAll(d => d.ShouldBeRemoved);
|
||||
}
|
||||
|
||||
var node = this.MakeNode(++this.runningNodeIds);
|
||||
var entry = new DtrBarEntry(title, node);
|
||||
entry.Text = text;
|
||||
|
||||
// Add the entry to the end of the order list, if it's not there already.
|
||||
if (!this.configuration.DtrOrder!.Contains(title))
|
||||
this.configuration.DtrOrder!.Add(title);
|
||||
this.entries.Add(entry);
|
||||
this.ApplySort();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
foreach (var entry in this.entries)
|
||||
this.RemoveNode(entry.TextNode);
|
||||
|
||||
this.entries.Clear();
|
||||
this.framework.Update -= this.Update;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove nodes marked as "should be removed" from the bar.
|
||||
/// </summary>
|
||||
internal void HandleRemovedNodes()
|
||||
{
|
||||
foreach (var data in this.entries.Where(d => d.ShouldBeRemoved))
|
||||
{
|
||||
this.RemoveNode(data.TextNode);
|
||||
}
|
||||
|
||||
this.entries.RemoveAll(d => d.ShouldBeRemoved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether an entry with the specified title exists.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to check for.</param>
|
||||
/// <returns>Whether or not an entry with that title is registered.</returns>
|
||||
internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title);
|
||||
|
||||
/// <summary>
|
||||
/// Dirty the DTR bar entry with the specified title.
|
||||
/// </summary>
|
||||
/// <param name="title">Title of the entry to dirty.</param>
|
||||
/// <returns>Whether the entry was found.</returns>
|
||||
internal bool MakeDirty(string title)
|
||||
{
|
||||
var entry = this.entries.FirstOrDefault(x => x.Title == title);
|
||||
if (entry == null)
|
||||
return false;
|
||||
|
||||
entry.Dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reapply the DTR entry ordering from <see cref="DalamudConfiguration"/>.
|
||||
/// </summary>
|
||||
internal void ApplySort()
|
||||
{
|
||||
// Sort the current entry list, based on the order in the configuration.
|
||||
var positions = this.configuration
|
||||
.DtrOrder!
|
||||
.Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry)))
|
||||
.ToDictionary(x => x.entry, x => x.index);
|
||||
|
||||
this.entries.Sort((x, y) =>
|
||||
{
|
||||
var xPos = positions.TryGetValue(x.Title, out var xIndex) ? xIndex : int.MaxValue;
|
||||
var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue;
|
||||
return xPos.CompareTo(yPos);
|
||||
});
|
||||
}
|
||||
|
||||
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer();
|
||||
|
||||
private void Update(Framework unused)
|
||||
{
|
||||
this.HandleRemovedNodes();
|
||||
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null) return;
|
||||
|
||||
// The collision node on the DTR element is always the width of its content
|
||||
if (dtr->UldManager.NodeList == null) return;
|
||||
|
||||
// If we have an unmodified DTR but still have entries, we need to
|
||||
// work to reset our state.
|
||||
if (!this.CheckForDalamudNodes())
|
||||
this.RecreateNodes();
|
||||
|
||||
var collisionNode = dtr->UldManager.NodeList[1];
|
||||
if (collisionNode == null) return;
|
||||
|
||||
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
||||
// collisionNode->X + collisionNode->Width.
|
||||
var runningXPos = this.configuration.DtrSwapDirection
|
||||
? collisionNode->X + collisionNode->Width
|
||||
: collisionNode->X;
|
||||
|
||||
for (var i = 0; i < this.entries.Count; i++)
|
||||
{
|
||||
var data = this.entries[i];
|
||||
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
||||
|
||||
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
||||
{
|
||||
var node = data.TextNode;
|
||||
node->SetText(data.Text?.Encode());
|
||||
ushort w = 0, h = 0;
|
||||
|
||||
if (isHide)
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(true);
|
||||
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
||||
node->AtkResNode.SetWidth(w);
|
||||
}
|
||||
|
||||
data.Dirty = false;
|
||||
}
|
||||
|
||||
if (!data.Added)
|
||||
{
|
||||
data.Added = this.AddNode(data.TextNode);
|
||||
}
|
||||
|
||||
if (!isHide)
|
||||
{
|
||||
var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing;
|
||||
|
||||
if (this.configuration.DtrSwapDirection)
|
||||
{
|
||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
runningXPos += elementWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
runningXPos -= elementWidth;
|
||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
}
|
||||
}
|
||||
|
||||
this.entries[i] = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there are any Dalamud nodes in the DTR.
|
||||
/// </summary>
|
||||
/// <returns>True if there are nodes with an ID > 1000.</returns>
|
||||
private bool CheckForDalamudNodes()
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null) return false;
|
||||
|
||||
for (var i = 0; i < dtr->UldManager.NodeListCount; i++)
|
||||
{
|
||||
if (dtr->UldManager.NodeList[i]->NodeID > 1000)
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Check whether an entry with the specified title exists.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to check for.</param>
|
||||
/// <returns>Whether or not an entry with that title is registered.</returns>
|
||||
internal bool HasEntry(string title) => this.entries.Any(x => x.Title == title);
|
||||
|
||||
/// <summary>
|
||||
/// Dirty the DTR bar entry with the specified title.
|
||||
/// </summary>
|
||||
/// <param name="title">Title of the entry to dirty.</param>
|
||||
/// <returns>Whether the entry was found.</returns>
|
||||
internal bool MakeDirty(string title)
|
||||
{
|
||||
var entry = this.entries.FirstOrDefault(x => x.Title == title);
|
||||
if (entry == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RecreateNodes()
|
||||
entry.Dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reapply the DTR entry ordering from <see cref="DalamudConfiguration"/>.
|
||||
/// </summary>
|
||||
internal void ApplySort()
|
||||
{
|
||||
// Sort the current entry list, based on the order in the configuration.
|
||||
var positions = this.configuration
|
||||
.DtrOrder!
|
||||
.Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry)))
|
||||
.ToDictionary(x => x.entry, x => x.index);
|
||||
|
||||
this.entries.Sort((x, y) =>
|
||||
{
|
||||
this.runningNodeIds = BaseNodeId;
|
||||
foreach (var entry in this.entries)
|
||||
var xPos = positions.TryGetValue(x.Title, out var xIndex) ? xIndex : int.MaxValue;
|
||||
var yPos = positions.TryGetValue(y.Title, out var yIndex) ? yIndex : int.MaxValue;
|
||||
return xPos.CompareTo(yPos);
|
||||
});
|
||||
}
|
||||
|
||||
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer();
|
||||
|
||||
private void Update(Framework unused)
|
||||
{
|
||||
this.HandleRemovedNodes();
|
||||
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null) return;
|
||||
|
||||
// The collision node on the DTR element is always the width of its content
|
||||
if (dtr->UldManager.NodeList == null) return;
|
||||
|
||||
// If we have an unmodified DTR but still have entries, we need to
|
||||
// work to reset our state.
|
||||
if (!this.CheckForDalamudNodes())
|
||||
this.RecreateNodes();
|
||||
|
||||
var collisionNode = dtr->UldManager.NodeList[1];
|
||||
if (collisionNode == null) return;
|
||||
|
||||
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
||||
// collisionNode->X + collisionNode->Width.
|
||||
var runningXPos = this.configuration.DtrSwapDirection
|
||||
? collisionNode->X + collisionNode->Width
|
||||
: collisionNode->X;
|
||||
|
||||
for (var i = 0; i < this.entries.Count; i++)
|
||||
{
|
||||
var data = this.entries[i];
|
||||
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
||||
|
||||
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
||||
{
|
||||
entry.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||
entry.Added = false;
|
||||
}
|
||||
}
|
||||
var node = data.TextNode;
|
||||
node->SetText(data.Text?.Encode());
|
||||
ushort w = 0, h = 0;
|
||||
|
||||
private bool AddNode(AtkTextNode* node)
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
if (isHide)
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->AtkResNode.ToggleVisibility(true);
|
||||
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
||||
node->AtkResNode.SetWidth(w);
|
||||
}
|
||||
|
||||
var lastChild = dtr->RootNode->ChildNode;
|
||||
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
||||
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
|
||||
lastChild->PrevSiblingNode = (AtkResNode*)node;
|
||||
node->AtkResNode.ParentNode = lastChild->ParentNode;
|
||||
node->AtkResNode.NextSiblingNode = lastChild;
|
||||
|
||||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount + 1);
|
||||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool RemoveNode(AtkTextNode* node)
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
|
||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
||||
|
||||
// if (tmpNextNode != null)
|
||||
tmpNextNode->PrevSiblingNode = tmpPrevNode;
|
||||
if (tmpPrevNode != null)
|
||||
tmpPrevNode->NextSiblingNode = tmpNextNode;
|
||||
node->AtkResNode.Destroy(true);
|
||||
|
||||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1);
|
||||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private AtkTextNode* MakeNode(uint nodeId)
|
||||
{
|
||||
var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8);
|
||||
if (newTextNode == null)
|
||||
{
|
||||
Log.Debug("Failed to allocate memory for text node");
|
||||
return null;
|
||||
data.Dirty = false;
|
||||
}
|
||||
|
||||
IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode));
|
||||
newTextNode->Ctor();
|
||||
if (!data.Added)
|
||||
{
|
||||
data.Added = this.AddNode(data.TextNode);
|
||||
}
|
||||
|
||||
newTextNode->AtkResNode.NodeID = nodeId;
|
||||
newTextNode->AtkResNode.Type = NodeType.Text;
|
||||
newTextNode->AtkResNode.Flags = (short)(NodeFlags.AnchorLeft | NodeFlags.AnchorTop);
|
||||
newTextNode->AtkResNode.DrawFlags = 12;
|
||||
newTextNode->AtkResNode.SetWidth(22);
|
||||
newTextNode->AtkResNode.SetHeight(22);
|
||||
newTextNode->AtkResNode.SetPositionFloat(-200, 2);
|
||||
if (!isHide)
|
||||
{
|
||||
var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing;
|
||||
|
||||
newTextNode->LineSpacing = 12;
|
||||
newTextNode->AlignmentFontType = 5;
|
||||
newTextNode->FontSize = 14;
|
||||
newTextNode->TextFlags = (byte)TextFlags.Edge;
|
||||
newTextNode->TextFlags2 = 0;
|
||||
if (this.configuration.DtrSwapDirection)
|
||||
{
|
||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
runningXPos += elementWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
runningXPos -= elementWidth;
|
||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||
}
|
||||
}
|
||||
|
||||
newTextNode->SetText(" ");
|
||||
|
||||
newTextNode->TextColor.R = 255;
|
||||
newTextNode->TextColor.G = 255;
|
||||
newTextNode->TextColor.B = 255;
|
||||
newTextNode->TextColor.A = 255;
|
||||
|
||||
newTextNode->EdgeColor.R = 142;
|
||||
newTextNode->EdgeColor.G = 106;
|
||||
newTextNode->EdgeColor.B = 12;
|
||||
newTextNode->EdgeColor.A = 255;
|
||||
|
||||
return newTextNode;
|
||||
this.entries[i] = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there are any Dalamud nodes in the DTR.
|
||||
/// </summary>
|
||||
/// <returns>True if there are nodes with an ID > 1000.</returns>
|
||||
private bool CheckForDalamudNodes()
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null) return false;
|
||||
|
||||
for (var i = 0; i < dtr->UldManager.NodeListCount; i++)
|
||||
{
|
||||
if (dtr->UldManager.NodeList[i]->NodeID > 1000)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RecreateNodes()
|
||||
{
|
||||
this.runningNodeIds = BaseNodeId;
|
||||
foreach (var entry in this.entries)
|
||||
{
|
||||
entry.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||
entry.Added = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddNode(AtkTextNode* node)
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
|
||||
var lastChild = dtr->RootNode->ChildNode;
|
||||
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
|
||||
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
|
||||
lastChild->PrevSiblingNode = (AtkResNode*)node;
|
||||
node->AtkResNode.ParentNode = lastChild->ParentNode;
|
||||
node->AtkResNode.NextSiblingNode = lastChild;
|
||||
|
||||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount + 1);
|
||||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool RemoveNode(AtkTextNode* node)
|
||||
{
|
||||
var dtr = this.GetDtr();
|
||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||
|
||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||
var tmpNextNode = node->AtkResNode.NextSiblingNode;
|
||||
|
||||
// if (tmpNextNode != null)
|
||||
tmpNextNode->PrevSiblingNode = tmpPrevNode;
|
||||
if (tmpPrevNode != null)
|
||||
tmpPrevNode->NextSiblingNode = tmpNextNode;
|
||||
node->AtkResNode.Destroy(true);
|
||||
|
||||
dtr->RootNode->ChildCount = (ushort)(dtr->RootNode->ChildCount - 1);
|
||||
Log.Debug("Set last sibling of DTR and updated child count");
|
||||
dtr->UldManager.UpdateDrawNodeList();
|
||||
Log.Debug("Updated node draw list");
|
||||
return true;
|
||||
}
|
||||
|
||||
private AtkTextNode* MakeNode(uint nodeId)
|
||||
{
|
||||
var newTextNode = (AtkTextNode*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkTextNode), 8);
|
||||
if (newTextNode == null)
|
||||
{
|
||||
Log.Debug("Failed to allocate memory for text node");
|
||||
return null;
|
||||
}
|
||||
|
||||
IMemorySpace.Memset(newTextNode, 0, (ulong)sizeof(AtkTextNode));
|
||||
newTextNode->Ctor();
|
||||
|
||||
newTextNode->AtkResNode.NodeID = nodeId;
|
||||
newTextNode->AtkResNode.Type = NodeType.Text;
|
||||
newTextNode->AtkResNode.Flags = (short)(NodeFlags.AnchorLeft | NodeFlags.AnchorTop);
|
||||
newTextNode->AtkResNode.DrawFlags = 12;
|
||||
newTextNode->AtkResNode.SetWidth(22);
|
||||
newTextNode->AtkResNode.SetHeight(22);
|
||||
newTextNode->AtkResNode.SetPositionFloat(-200, 2);
|
||||
|
||||
newTextNode->LineSpacing = 12;
|
||||
newTextNode->AlignmentFontType = 5;
|
||||
newTextNode->FontSize = 14;
|
||||
newTextNode->TextFlags = (byte)TextFlags.Edge;
|
||||
newTextNode->TextFlags2 = 0;
|
||||
|
||||
newTextNode->SetText(" ");
|
||||
|
||||
newTextNode->TextColor.R = 255;
|
||||
newTextNode->TextColor.G = 255;
|
||||
newTextNode->TextColor.B = 255;
|
||||
newTextNode->TextColor.A = 255;
|
||||
|
||||
newTextNode->EdgeColor.R = 142;
|
||||
newTextNode->EdgeColor.G = 106;
|
||||
newTextNode->EdgeColor.B = 12;
|
||||
newTextNode->EdgeColor.A = 255;
|
||||
|
||||
return newTextNode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,91 +3,90 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace Dalamud.Game.Gui.Dtr
|
||||
namespace Dalamud.Game.Gui.Dtr;
|
||||
|
||||
/// <summary>
|
||||
/// Class representing an entry in the server info bar.
|
||||
/// </summary>
|
||||
public sealed unsafe class DtrBarEntry : IDisposable
|
||||
{
|
||||
private bool shownBacking = true;
|
||||
private SeString? textBacking = null;
|
||||
|
||||
/// <summary>
|
||||
/// Class representing an entry in the server info bar.
|
||||
/// Initializes a new instance of the <see cref="DtrBarEntry"/> class.
|
||||
/// </summary>
|
||||
public sealed unsafe class DtrBarEntry : IDisposable
|
||||
/// <param name="title">The title of the bar entry.</param>
|
||||
/// <param name="textNode">The corresponding text node.</param>
|
||||
internal DtrBarEntry(string title, AtkTextNode* textNode)
|
||||
{
|
||||
private bool shownBacking = true;
|
||||
private SeString? textBacking = null;
|
||||
this.Title = title;
|
||||
this.TextNode = textNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DtrBarEntry"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the bar entry.</param>
|
||||
/// <param name="textNode">The corresponding text node.</param>
|
||||
internal DtrBarEntry(string title, AtkTextNode* textNode)
|
||||
/// <summary>
|
||||
/// Gets the title of this entry.
|
||||
/// </summary>
|
||||
public string Title { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text of this entry.
|
||||
/// </summary>
|
||||
public SeString? Text
|
||||
{
|
||||
get => this.textBacking;
|
||||
set
|
||||
{
|
||||
this.Title = title;
|
||||
this.TextNode = textNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of this entry.
|
||||
/// </summary>
|
||||
public string Title { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text of this entry.
|
||||
/// </summary>
|
||||
public SeString? Text
|
||||
{
|
||||
get => this.textBacking;
|
||||
set
|
||||
{
|
||||
this.textBacking = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry is visible.
|
||||
/// </summary>
|
||||
public bool Shown
|
||||
{
|
||||
get => this.shownBacking;
|
||||
set
|
||||
{
|
||||
this.shownBacking = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the internal text node of this entry.
|
||||
/// </summary>
|
||||
internal AtkTextNode* TextNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this entry should be removed.
|
||||
/// </summary>
|
||||
internal bool ShouldBeRemoved { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry is dirty.
|
||||
/// </summary>
|
||||
internal bool Dirty { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry has just been added.
|
||||
/// </summary>
|
||||
internal bool Added { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Remove this entry from the bar.
|
||||
/// You will need to re-acquire it from DtrBar to reuse it.
|
||||
/// </summary>
|
||||
public void Remove()
|
||||
{
|
||||
this.ShouldBeRemoved = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Remove();
|
||||
this.textBacking = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry is visible.
|
||||
/// </summary>
|
||||
public bool Shown
|
||||
{
|
||||
get => this.shownBacking;
|
||||
set
|
||||
{
|
||||
this.shownBacking = value;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the internal text node of this entry.
|
||||
/// </summary>
|
||||
internal AtkTextNode* TextNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this entry should be removed.
|
||||
/// </summary>
|
||||
internal bool ShouldBeRemoved { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry is dirty.
|
||||
/// </summary>
|
||||
internal bool Dirty { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entry has just been added.
|
||||
/// </summary>
|
||||
internal bool Added { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Remove this entry from the bar.
|
||||
/// You will need to re-acquire it from DtrBar to reuse it.
|
||||
/// </summary>
|
||||
public void Remove()
|
||||
{
|
||||
this.ShouldBeRemoved = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Remove();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,303 +9,302 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Memory;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class FlyTextGui : IDisposable, IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native in-game "fly text".
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class FlyTextGui : IDisposable, IServiceType
|
||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||
|
||||
/// <summary>
|
||||
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FlyTextGui(SigScanner sigScanner)
|
||||
{
|
||||
/// <summary>
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
/// <summary>
|
||||
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
}
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FlyTextGui(SigScanner sigScanner)
|
||||
/// <summary>
|
||||
/// The delegate defining the type for the FlyText event.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
||||
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
||||
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
||||
public delegate void OnFlyTextCreatedDelegate(
|
||||
ref FlyTextKind kind,
|
||||
ref int val1,
|
||||
ref int val2,
|
||||
ref SeString text1,
|
||||
ref SeString text2,
|
||||
ref uint color,
|
||||
ref uint icon,
|
||||
ref float yOffset,
|
||||
ref bool handled);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
private delegate IntPtr CreateFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native AddFlyText function pointer.
|
||||
/// </summary>
|
||||
private delegate void AddFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
uint actorIndex,
|
||||
uint messageMax,
|
||||
IntPtr numbers,
|
||||
uint offsetNum,
|
||||
uint offsetNumMax,
|
||||
IntPtr strings,
|
||||
uint offsetStr,
|
||||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
||||
/// <summary>
|
||||
/// The FlyText event that can be subscribed to.
|
||||
/// </summary>
|
||||
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private Dalamud Dalamud { get; }
|
||||
|
||||
private FlyTextGuiAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.createFlyTextHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a fly text in-game on the local player.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
var numIndex = 28;
|
||||
var strIndex = 25;
|
||||
var numOffset = 147u;
|
||||
var strOffset = 28u;
|
||||
|
||||
// Get the UI module and flytext addon pointers
|
||||
var gameGui = Service<GameGui>.GetNullable();
|
||||
if (gameGui == null)
|
||||
return;
|
||||
|
||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
||||
|
||||
if (ui == null || flytext == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
// Get the number and string arrays we need
|
||||
var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||
|
||||
// Write the values to the arrays using a known valid flytext region
|
||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||
numArray->IntArray[numOffset + 1] = (int)kind;
|
||||
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
|
||||
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
|
||||
numArray->IntArray[numOffset + 4] = 5; // Unknown
|
||||
numArray->IntArray[numOffset + 5] = unchecked((int)color);
|
||||
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
|
||||
numArray->IntArray[numOffset + 7] = 0; // Unknown
|
||||
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
|
||||
|
||||
fixed (byte* pText1 = text1.Encode())
|
||||
{
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The delegate defining the type for the FlyText event.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
/// <param name="yOffset">The vertical offset to place the flytext at. 0 is default. Negative values result
|
||||
/// in text appearing higher on the screen. This does not change where the element begins to fade.</param>
|
||||
/// <param name="handled">Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear.</param>
|
||||
public delegate void OnFlyTextCreatedDelegate(
|
||||
ref FlyTextKind kind,
|
||||
ref int val1,
|
||||
ref int val2,
|
||||
ref SeString text1,
|
||||
ref SeString text2,
|
||||
ref uint color,
|
||||
ref uint icon,
|
||||
ref float yOffset,
|
||||
ref bool handled);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
private delegate IntPtr CreateFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native AddFlyText function pointer.
|
||||
/// </summary>
|
||||
private delegate void AddFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
uint actorIndex,
|
||||
uint messageMax,
|
||||
IntPtr numbers,
|
||||
uint offsetNum,
|
||||
uint offsetNumMax,
|
||||
IntPtr strings,
|
||||
uint offsetStr,
|
||||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
||||
/// <summary>
|
||||
/// The FlyText event that can be subscribed to.
|
||||
/// </summary>
|
||||
public event OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private Dalamud Dalamud { get; }
|
||||
|
||||
private FlyTextGuiAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.createFlyTextHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a fly text in-game on the local player.
|
||||
/// </summary>
|
||||
/// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param>
|
||||
/// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param>
|
||||
/// <param name="val1">Value1 passed to the native flytext function.</param>
|
||||
/// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param>
|
||||
/// <param name="text1">Text1 passed to the native flytext function.</param>
|
||||
/// <param name="text2">Text2 passed to the native flytext function.</param>
|
||||
/// <param name="color">Color passed to the native flytext function. Changes flytext color.</param>
|
||||
/// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param>
|
||||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
var numIndex = 28;
|
||||
var strIndex = 25;
|
||||
var numOffset = 147u;
|
||||
var strOffset = 28u;
|
||||
|
||||
// Get the UI module and flytext addon pointers
|
||||
var gameGui = Service<GameGui>.GetNullable();
|
||||
if (gameGui == null)
|
||||
return;
|
||||
|
||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||
var flytext = gameGui.GetAddonByName("_FlyText", 1);
|
||||
|
||||
if (ui == null || flytext == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
// Get the number and string arrays we need
|
||||
var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||
|
||||
// Write the values to the arrays using a known valid flytext region
|
||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||
numArray->IntArray[numOffset + 1] = (int)kind;
|
||||
numArray->IntArray[numOffset + 2] = unchecked((int)val1);
|
||||
numArray->IntArray[numOffset + 3] = unchecked((int)val2);
|
||||
numArray->IntArray[numOffset + 4] = 5; // Unknown
|
||||
numArray->IntArray[numOffset + 5] = unchecked((int)color);
|
||||
numArray->IntArray[numOffset + 6] = unchecked((int)icon);
|
||||
numArray->IntArray[numOffset + 7] = 0; // Unknown
|
||||
numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset
|
||||
|
||||
fixed (byte* pText1 = text1.Encode())
|
||||
fixed (byte* pText2 = text2.Encode())
|
||||
{
|
||||
fixed (byte* pText2 = text2.Encode())
|
||||
{
|
||||
strArray->StringArray[strOffset + 0] = pText1;
|
||||
strArray->StringArray[strOffset + 1] = pText2;
|
||||
strArray->StringArray[strOffset + 0] = pText1;
|
||||
strArray->StringArray[strOffset + 1] = pText2;
|
||||
|
||||
this.addFlyTextNative(
|
||||
flytext,
|
||||
actorIndex,
|
||||
1,
|
||||
(IntPtr)numArray,
|
||||
numOffset,
|
||||
9,
|
||||
(IntPtr)strArray,
|
||||
strOffset,
|
||||
2,
|
||||
0);
|
||||
}
|
||||
this.addFlyTextNative(
|
||||
flytext,
|
||||
actorIndex,
|
||||
1,
|
||||
(IntPtr)numArray,
|
||||
numOffset,
|
||||
9,
|
||||
(IntPtr)strArray,
|
||||
strOffset,
|
||||
2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui)
|
||||
{
|
||||
this.createFlyTextHook.Enable();
|
||||
}
|
||||
|
||||
private IntPtr CreateFlyTextDetour(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||
|
||||
var handled = false;
|
||||
|
||||
var tmpKind = kind;
|
||||
var tmpVal1 = val1;
|
||||
var tmpVal2 = val2;
|
||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||
var tmpColor = color;
|
||||
var tmpIcon = icon;
|
||||
var tmpYOffset = yOffset;
|
||||
|
||||
var cmpText1 = tmpText1.ToString();
|
||||
var cmpText2 = tmpText2.ToString();
|
||||
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||
$"kind({kind}) val1({val1}) val2({val2}) " +
|
||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||
Log.Verbose("[FlyText] Calling flytext events!");
|
||||
this.FlyTextCreated?.Invoke(
|
||||
ref tmpKind,
|
||||
ref tmpVal1,
|
||||
ref tmpVal2,
|
||||
ref tmpText1,
|
||||
ref tmpText2,
|
||||
ref tmpColor,
|
||||
ref tmpIcon,
|
||||
ref tmpYOffset,
|
||||
ref handled);
|
||||
|
||||
// If handled, ignore the original call
|
||||
if (handled)
|
||||
{
|
||||
Log.Verbose("[FlyText] FlyText was handled.");
|
||||
|
||||
// Returning null to AddFlyText from CreateFlyText will result
|
||||
// in the operation being dropped entirely.
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
// Check if any values have changed
|
||||
var dirty = tmpKind != kind ||
|
||||
tmpVal1 != val1 ||
|
||||
tmpVal2 != val2 ||
|
||||
tmpText1.ToString() != cmpText1 ||
|
||||
tmpText2.ToString() != cmpText2 ||
|
||||
tmpColor != color ||
|
||||
tmpIcon != icon ||
|
||||
Math.Abs(tmpYOffset - yOffset) > float.Epsilon;
|
||||
|
||||
// If not dirty, make the original call
|
||||
if (!dirty)
|
||||
{
|
||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
|
||||
}
|
||||
|
||||
var terminated1 = Terminate(tmpText1.Encode());
|
||||
var terminated2 = Terminate(tmpText2.Encode());
|
||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||
|
||||
retVal = this.createFlyTextHook.Original(
|
||||
addonFlyText,
|
||||
tmpKind,
|
||||
tmpVal1,
|
||||
tmpVal2,
|
||||
pText2,
|
||||
tmpColor,
|
||||
tmpIcon,
|
||||
pText1,
|
||||
tmpYOffset);
|
||||
|
||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||
|
||||
Task.Delay(2000).ContinueWith(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.FreeHGlobal(pText1);
|
||||
Marshal.FreeHGlobal(pText2);
|
||||
Log.Verbose("[FlyText] Freed strings.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task.");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception occurred in CreateFlyTextDetour!");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui)
|
||||
{
|
||||
this.createFlyTextHook.Enable();
|
||||
}
|
||||
|
||||
private IntPtr CreateFlyTextDetour(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
IntPtr text1,
|
||||
float yOffset)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||
|
||||
var handled = false;
|
||||
|
||||
var tmpKind = kind;
|
||||
var tmpVal1 = val1;
|
||||
var tmpVal2 = val2;
|
||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||
var tmpColor = color;
|
||||
var tmpIcon = icon;
|
||||
var tmpYOffset = yOffset;
|
||||
|
||||
var cmpText1 = tmpText1.ToString();
|
||||
var cmpText2 = tmpText2.ToString();
|
||||
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||
$"kind({kind}) val1({val1}) val2({val2}) " +
|
||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||
Log.Verbose("[FlyText] Calling flytext events!");
|
||||
this.FlyTextCreated?.Invoke(
|
||||
ref tmpKind,
|
||||
ref tmpVal1,
|
||||
ref tmpVal2,
|
||||
ref tmpText1,
|
||||
ref tmpText2,
|
||||
ref tmpColor,
|
||||
ref tmpIcon,
|
||||
ref tmpYOffset,
|
||||
ref handled);
|
||||
|
||||
// If handled, ignore the original call
|
||||
if (handled)
|
||||
{
|
||||
Log.Verbose("[FlyText] FlyText was handled.");
|
||||
|
||||
// Returning null to AddFlyText from CreateFlyText will result
|
||||
// in the operation being dropped entirely.
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
// Check if any values have changed
|
||||
var dirty = tmpKind != kind ||
|
||||
tmpVal1 != val1 ||
|
||||
tmpVal2 != val2 ||
|
||||
tmpText1.ToString() != cmpText1 ||
|
||||
tmpText2.ToString() != cmpText2 ||
|
||||
tmpColor != color ||
|
||||
tmpIcon != icon ||
|
||||
Math.Abs(tmpYOffset - yOffset) > float.Epsilon;
|
||||
|
||||
// If not dirty, make the original call
|
||||
if (!dirty)
|
||||
{
|
||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset);
|
||||
}
|
||||
|
||||
var terminated1 = Terminate(tmpText1.Encode());
|
||||
var terminated2 = Terminate(tmpText2.Encode());
|
||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||
|
||||
retVal = this.createFlyTextHook.Original(
|
||||
addonFlyText,
|
||||
tmpKind,
|
||||
tmpVal1,
|
||||
tmpVal2,
|
||||
pText2,
|
||||
tmpColor,
|
||||
tmpIcon,
|
||||
pText1,
|
||||
tmpYOffset);
|
||||
|
||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||
|
||||
Task.Delay(2000).ContinueWith(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.FreeHGlobal(pText1);
|
||||
Marshal.FreeHGlobal(pText2);
|
||||
Log.Verbose("[FlyText] Freed strings.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task.");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception occurred in CreateFlyTextDetour!");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||
/// Gets the address of the native AddFlyText method, which occurs
|
||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
||||
/// elements can be added in a single AddFlyText call.
|
||||
/// </summary>
|
||||
public class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
public IntPtr AddFlyText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native CreateFlyText method, which occurs
|
||||
/// when the game creates a new fly text element. This method is called
|
||||
/// once per fly text element, and can be called multiple times per
|
||||
/// AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr CreateFlyText { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native AddFlyText method, which occurs
|
||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
||||
/// elements can be added in a single AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr AddFlyText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native CreateFlyText method, which occurs
|
||||
/// when the game creates a new fly text element. This method is called
|
||||
/// once per fly text element, and can be called multiple times per
|
||||
/// AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr CreateFlyText { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
||||
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
||||
}
|
||||
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
||||
this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,298 +1,297 @@
|
|||
namespace Dalamud.Game.Gui.FlyText
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// Enum of FlyTextKind values. Members suffixed with
|
||||
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||
/// </summary>
|
||||
public enum FlyTextKind : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum of FlyTextKind values. Members suffixed with
|
||||
/// a number seem to be a duplicate, or perform duplicate behavior.
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Used for autos and incoming DoTs.
|
||||
/// </summary>
|
||||
public enum FlyTextKind : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Used for autos and incoming DoTs.
|
||||
/// </summary>
|
||||
AutoAttack = 0,
|
||||
AutoAttack = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit = 1,
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle.
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit = 2,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle.
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit = 3,
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in
|
||||
/// sans-serif as subtitle. Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit = 3,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack = 4,
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedAttack = 4,
|
||||
|
||||
/// <summary>
|
||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedDirectHit = 5,
|
||||
/// <summary>
|
||||
/// DirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedDirectHit = 5,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalHit = 6,
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalHit = 6,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalDirectHit = 7,
|
||||
/// <summary>
|
||||
/// CriticalDirectHit with sans-serif Text1 to the left of the Val1.
|
||||
/// </summary>
|
||||
NamedCriticalDirectHit = 7,
|
||||
|
||||
/// <summary>
|
||||
/// All caps, serif MISS.
|
||||
/// </summary>
|
||||
Miss = 8,
|
||||
/// <summary>
|
||||
/// All caps, serif MISS.
|
||||
/// </summary>
|
||||
Miss = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif MISS.
|
||||
/// </summary>
|
||||
NamedMiss = 9,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif MISS.
|
||||
/// </summary>
|
||||
NamedMiss = 9,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif DODGE.
|
||||
/// </summary>
|
||||
Dodge = 10,
|
||||
/// <summary>
|
||||
/// All caps serif DODGE.
|
||||
/// </summary>
|
||||
Dodge = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif DODGE.
|
||||
/// </summary>
|
||||
NamedDodge = 11,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to all caps serif DODGE.
|
||||
/// </summary>
|
||||
NamedDodge = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIcon = 12,
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1.
|
||||
/// </summary>
|
||||
NamedIcon = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 (2).
|
||||
/// </summary>
|
||||
NamedIcon2 = 13,
|
||||
/// <summary>
|
||||
/// Icon next to sans-serif Text1 (2).
|
||||
/// </summary>
|
||||
NamedIcon2 = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
Exp = 14,
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
Exp = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font ISLAND EXP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
IslandExp = 15,
|
||||
/// <summary>
|
||||
/// Serif Val1 with all caps condensed font ISLAND EXP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
IslandExp = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp = 16,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedMp = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp = 17,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedTp = 17,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedAttack2 = 18,
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedAttack2 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedMp2 = 19,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedMp2 = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedTp2 = 20,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2).
|
||||
/// </summary>
|
||||
NamedTp2 = 20,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedEp = 21,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedEp = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedCp = 22,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font CP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedCp = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedGp = 23,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font GP with Text2 in sans-serif as subtitle.
|
||||
/// </summary>
|
||||
NamedGp = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Displays nothing.
|
||||
/// </summary>
|
||||
None = 24,
|
||||
/// <summary>
|
||||
/// Displays nothing.
|
||||
/// </summary>
|
||||
None = 24,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif INVULNERABLE.
|
||||
/// </summary>
|
||||
Invulnerable = 25,
|
||||
/// <summary>
|
||||
/// All caps serif INVULNERABLE.
|
||||
/// </summary>
|
||||
Invulnerable = 25,
|
||||
|
||||
/// <summary>
|
||||
/// All caps sans-serif condensed font INTERRUPTED!
|
||||
/// Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
Interrupted = 26,
|
||||
/// <summary>
|
||||
/// All caps sans-serif condensed font INTERRUPTED!
|
||||
/// Does a large bounce effect on appearance.
|
||||
/// Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
Interrupted = 26,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2.
|
||||
/// </summary>
|
||||
AutoAttackNoText = 27,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2.
|
||||
/// </summary>
|
||||
AutoAttackNoText = 27,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (2).
|
||||
/// </summary>
|
||||
AutoAttackNoText2 = 28,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (2).
|
||||
/// </summary>
|
||||
AutoAttackNoText2 = 28,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||
/// </summary>
|
||||
CriticalHit2 = 29,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2).
|
||||
/// </summary>
|
||||
CriticalHit2 = 29,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (3).
|
||||
/// </summary>
|
||||
AutoAttackNoText3 = 30,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (3).
|
||||
/// </summary>
|
||||
AutoAttackNoText3 = 30,
|
||||
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedCriticalHit2 = 31,
|
||||
/// <summary>
|
||||
/// CriticalHit with sans-serif Text1 to the left of the Val1 (2).
|
||||
/// </summary>
|
||||
NamedCriticalHit2 = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithMp = 32,
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithMp = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithTp = 33,
|
||||
/// <summary>
|
||||
/// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1.
|
||||
/// Does a jiggle effect to the right on appearance.
|
||||
/// </summary>
|
||||
NamedCriticalHitWithTp = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedIconHasNoEffect = 34,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedIconHasNoEffect = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded = 35,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||
/// Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded2 = 36,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but Text1 is slightly faded (2).
|
||||
/// Used for buff expiration.
|
||||
/// </summary>
|
||||
NamedIconFaded2 = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 in sans-serif font.
|
||||
/// </summary>
|
||||
Named = 37,
|
||||
/// <summary>
|
||||
/// Text1 in sans-serif font.
|
||||
/// </summary>
|
||||
Named = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedIconFullyResisted = 38,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedIconFullyResisted = 38,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif 'INCAPACITATED!'.
|
||||
/// </summary>
|
||||
Incapacitated = 39,
|
||||
/// <summary>
|
||||
/// All caps serif 'INCAPACITATED!'.
|
||||
/// </summary>
|
||||
Incapacitated = 39,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedFullyResisted = 40,
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "(fully resisted)" to the right.
|
||||
/// </summary>
|
||||
NamedFullyResisted = 40,
|
||||
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedHasNoEffect = 41,
|
||||
/// <summary>
|
||||
/// Text1 with sans-serif "has no effect!" to the right.
|
||||
/// </summary>
|
||||
NamedHasNoEffect = 41,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||
/// </summary>
|
||||
NamedAttack3 = 42,
|
||||
/// <summary>
|
||||
/// AutoAttack with sans-serif Text1 to the left of the Val1 (3).
|
||||
/// </summary>
|
||||
NamedAttack3 = 42,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedMp3 = 43,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedMp3 = 43,
|
||||
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedTp3 = 44,
|
||||
/// <summary>
|
||||
/// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3).
|
||||
/// </summary>
|
||||
NamedTp3 = 44,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// </summary>
|
||||
NamedIconInvulnerable = 45,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1.
|
||||
/// </summary>
|
||||
NamedIconInvulnerable = 45,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif RESIST.
|
||||
/// </summary>
|
||||
Resist = 46,
|
||||
/// <summary>
|
||||
/// All caps serif RESIST.
|
||||
/// </summary>
|
||||
Resist = 46,
|
||||
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||
/// </summary>
|
||||
NamedIconWithItemOutline = 47,
|
||||
/// <summary>
|
||||
/// Same as NamedIcon but places the given icon in the item icon outline.
|
||||
/// </summary>
|
||||
NamedIconWithItemOutline = 47,
|
||||
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (4).
|
||||
/// </summary>
|
||||
AutoAttackNoText4 = 48,
|
||||
/// <summary>
|
||||
/// AutoAttack with no Text2 (4).
|
||||
/// </summary>
|
||||
AutoAttackNoText4 = 48,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit3 = 49,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit3 = 49,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECT.
|
||||
/// </summary>
|
||||
Reflect = 50,
|
||||
/// <summary>
|
||||
/// All caps serif REFLECT.
|
||||
/// </summary>
|
||||
Reflect = 50,
|
||||
|
||||
/// <summary>
|
||||
/// All caps serif REFLECTED.
|
||||
/// </summary>
|
||||
Reflected = 51,
|
||||
/// <summary>
|
||||
/// All caps serif REFLECTED.
|
||||
/// </summary>
|
||||
Reflected = 51,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit2 = 52,
|
||||
/// <summary>
|
||||
/// Val1 in serif font, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a bounce effect on appearance.
|
||||
/// </summary>
|
||||
DirectHit2 = 52,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit4 = 53,
|
||||
/// <summary>
|
||||
/// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4).
|
||||
/// Does a bigger bounce effect on appearance.
|
||||
/// </summary>
|
||||
CriticalHit4 = 53,
|
||||
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit2 = 54,
|
||||
}
|
||||
/// <summary>
|
||||
/// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2).
|
||||
/// Does a large bounce effect on appearance. Does not scroll up or down the screen.
|
||||
/// </summary>
|
||||
CriticalDirectHit2 = 54,
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,80 +1,79 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="GameGui"/> class.
|
||||
/// </summary>
|
||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="GameGui"/> class.
|
||||
/// Gets the base address of the native GuiManager class.
|
||||
/// </summary>
|
||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native SetGlobalBgm method.
|
||||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleImm method.
|
||||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetMatrixSingleton method.
|
||||
/// </summary>
|
||||
public IntPtr GetMatrixSingleton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ScreenToWorld method.
|
||||
/// </summary>
|
||||
public IntPtr ScreenToWorld { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ToggleUiHide method.
|
||||
/// </summary>
|
||||
public IntPtr ToggleUiHide { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native Utf8StringFromSequence method.
|
||||
/// </summary>
|
||||
public IntPtr Utf8StringFromSequence { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base address of the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native SetGlobalBgm method.
|
||||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleImm method.
|
||||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetMatrixSingleton method.
|
||||
/// </summary>
|
||||
public IntPtr GetMatrixSingleton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ScreenToWorld method.
|
||||
/// </summary>
|
||||
public IntPtr ScreenToWorld { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ToggleUiHide method.
|
||||
/// </summary>
|
||||
public IntPtr ToggleUiHide { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native Utf8StringFromSequence method.
|
||||
/// </summary>
|
||||
public IntPtr Utf8StringFromSequence { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||
}
|
||||
this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,48 @@
|
|||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// ActionKinds used in AgentActionDetail.
|
||||
/// These describe the possible kinds of actions being hovered.
|
||||
/// </summary>
|
||||
public enum HoverActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// ActionKinds used in AgentActionDetail.
|
||||
/// These describe the possible kinds of actions being hovered.
|
||||
/// No action is hovered.
|
||||
/// </summary>
|
||||
public enum HoverActionKind
|
||||
{
|
||||
/// <summary>
|
||||
/// No action is hovered.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A regular action is hovered.
|
||||
/// </summary>
|
||||
Action = 21,
|
||||
/// <summary>
|
||||
/// A regular action is hovered.
|
||||
/// </summary>
|
||||
Action = 21,
|
||||
|
||||
/// <summary>
|
||||
/// A general action is hovered.
|
||||
/// </summary>
|
||||
GeneralAction = 23,
|
||||
/// <summary>
|
||||
/// A general action is hovered.
|
||||
/// </summary>
|
||||
GeneralAction = 23,
|
||||
|
||||
/// <summary>
|
||||
/// A companion order type of action is hovered.
|
||||
/// </summary>
|
||||
CompanionOrder = 24,
|
||||
/// <summary>
|
||||
/// A companion order type of action is hovered.
|
||||
/// </summary>
|
||||
CompanionOrder = 24,
|
||||
|
||||
/// <summary>
|
||||
/// A main command type of action is hovered.
|
||||
/// </summary>
|
||||
MainCommand = 25,
|
||||
/// <summary>
|
||||
/// A main command type of action is hovered.
|
||||
/// </summary>
|
||||
MainCommand = 25,
|
||||
|
||||
/// <summary>
|
||||
/// An extras command type of action is hovered.
|
||||
/// </summary>
|
||||
ExtraCommand = 26,
|
||||
/// <summary>
|
||||
/// An extras command type of action is hovered.
|
||||
/// </summary>
|
||||
ExtraCommand = 26,
|
||||
|
||||
/// <summary>
|
||||
/// A pet order type of action is hovered.
|
||||
/// </summary>
|
||||
PetOrder = 28,
|
||||
/// <summary>
|
||||
/// A pet order type of action is hovered.
|
||||
/// </summary>
|
||||
PetOrder = 28,
|
||||
|
||||
/// <summary>
|
||||
/// A trait is hovered.
|
||||
/// </summary>
|
||||
Trait = 29,
|
||||
}
|
||||
/// <summary>
|
||||
/// A trait is hovered.
|
||||
/// </summary>
|
||||
Trait = 29,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace Dalamud.Game.Gui
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||
/// </summary>
|
||||
public class HoveredAction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the hotbar action currently hovered over by the cursor.
|
||||
/// Gets or sets the base action ID.
|
||||
/// </summary>
|
||||
public class HoveredAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the base action ID.
|
||||
/// </summary>
|
||||
public uint BaseActionID { get; set; } = 0;
|
||||
public uint BaseActionID { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action ID accounting for automatic upgrades.
|
||||
/// </summary>
|
||||
public uint ActionID { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the action ID accounting for automatic upgrades.
|
||||
/// </summary>
|
||||
public uint ActionID { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of action.
|
||||
/// </summary>
|
||||
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the type of action.
|
||||
/// </summary>
|
||||
public HoverActionKind ActionKind { get; set; } = HoverActionKind.None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,261 +14,261 @@ using PInvoke;
|
|||
|
||||
using static Dalamud.NativeFunctions;
|
||||
|
||||
namespace Dalamud.Game.Gui.Internal
|
||||
namespace Dalamud.Game.Gui.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class DalamudIME : IDisposable, IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class DalamudIME : IDisposable, IServiceType
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
|
||||
private AsmHook imguiTextInputCursorHook;
|
||||
private Vector2* cursorPos;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudIME()
|
||||
{
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
}
|
||||
|
||||
private AsmHook imguiTextInputCursorHook;
|
||||
private Vector2* cursorPos;
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the module is enabled.
|
||||
/// </summary>
|
||||
internal bool IsEnabled { get; private set; }
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudIME()
|
||||
/// <summary>
|
||||
/// Gets the index of the first imm candidate in relation to the full list.
|
||||
/// </summary>
|
||||
internal CandidateList ImmCandNative { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the imm candidates.
|
||||
/// </summary>
|
||||
internal List<string> ImmCand { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected imm component.
|
||||
/// </summary>
|
||||
internal string ImmComp { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.imguiTextInputCursorHook?.Dispose();
|
||||
Marshal.FreeHGlobal((IntPtr)this.cursorPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes window messages.
|
||||
/// </summary>
|
||||
/// <param name="hWnd">Handle of the window.</param>
|
||||
/// <param name="msg">Type of window message.</param>
|
||||
/// <param name="wParamPtr">wParam or the pointer to it.</param>
|
||||
/// <param name="lParamPtr">lParam or the pointer to it.</param>
|
||||
/// <returns>Return value, if not doing further processing.</returns>
|
||||
public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParamPtr, void* lParamPtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the module is enabled.
|
||||
/// </summary>
|
||||
internal bool IsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the first imm candidate in relation to the full list.
|
||||
/// </summary>
|
||||
internal CandidateList ImmCandNative { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the imm candidates.
|
||||
/// </summary>
|
||||
internal List<string> ImmCand { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected imm component.
|
||||
/// </summary>
|
||||
internal string ImmComp { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.imguiTextInputCursorHook?.Dispose();
|
||||
Marshal.FreeHGlobal((IntPtr)this.cursorPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes window messages.
|
||||
/// </summary>
|
||||
/// <param name="hWnd">Handle of the window.</param>
|
||||
/// <param name="msg">Type of window message.</param>
|
||||
/// <param name="wParamPtr">wParam or the pointer to it.</param>
|
||||
/// <param name="lParamPtr">lParam or the pointer to it.</param>
|
||||
/// <returns>Return value, if not doing further processing.</returns>
|
||||
public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParamPtr, void* lParamPtr)
|
||||
{
|
||||
try
|
||||
if (ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
||||
{
|
||||
if (ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
||||
var io = ImGui.GetIO();
|
||||
var wmsg = (WindowsMessage)msg;
|
||||
long wParam = (long)wParamPtr, lParam = (long)lParamPtr;
|
||||
try
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
var wmsg = (WindowsMessage)msg;
|
||||
long wParam = (long)wParamPtr, lParam = (long)lParamPtr;
|
||||
try
|
||||
{
|
||||
wParam = Marshal.ReadInt32((IntPtr)wParamPtr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
wParam = Marshal.ReadInt32((IntPtr)wParamPtr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lParam = Marshal.ReadInt32((IntPtr)lParamPtr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
try
|
||||
{
|
||||
lParam = Marshal.ReadInt32((IntPtr)lParamPtr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
switch (wmsg)
|
||||
{
|
||||
case WindowsMessage.WM_IME_NOTIFY:
|
||||
switch ((IMECommand)(IntPtr)wParam)
|
||||
{
|
||||
case IMECommand.ChangeCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.LoadCand(hWnd);
|
||||
break;
|
||||
case IMECommand.OpenCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.ImmCandNative = default;
|
||||
// this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
case IMECommand.CloseCandidate:
|
||||
this.ToggleWindow(false);
|
||||
this.ImmCandNative = default;
|
||||
// this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case WindowsMessage.WM_IME_COMPOSITION:
|
||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & (long)(IntPtr)lParam) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
this.ImmComp = lpstr;
|
||||
if (lpstr == string.Empty)
|
||||
{
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LoadCand(hWnd);
|
||||
}
|
||||
}
|
||||
|
||||
if (((long)(IntPtr)lParam & (long)IMEComposition.ResultStr) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
io.AddInputCharactersUTF8(lpstr);
|
||||
|
||||
this.ImmComp = string.Empty;
|
||||
switch (wmsg)
|
||||
{
|
||||
case WindowsMessage.WM_IME_NOTIFY:
|
||||
switch ((IMECommand)(IntPtr)wParam)
|
||||
{
|
||||
case IMECommand.ChangeCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.LoadCand(hWnd);
|
||||
break;
|
||||
case IMECommand.OpenCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
// this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
case IMECommand.CloseCandidate:
|
||||
this.ToggleWindow(false);
|
||||
this.ImmCandNative = default;
|
||||
// this.ImmCand.Clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case WindowsMessage.WM_IME_COMPOSITION:
|
||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & (long)(IntPtr)lParam) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
this.ImmComp = lpstr;
|
||||
if (lpstr == string.Empty)
|
||||
{
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LoadCand(hWnd);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
if (((long)(IntPtr)lParam & (long)IMEComposition.ResultStr) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
io.AddInputCharactersUTF8(lpstr);
|
||||
|
||||
this.ImmComp = string.Empty;
|
||||
this.ImmCandNative = default;
|
||||
this.ImmCand.Clear();
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of the cursor.
|
||||
/// </summary>
|
||||
/// <returns>The position of the cursor.</returns>
|
||||
internal Vector2 GetCursorPos()
|
||||
{
|
||||
return new Vector2(this.cursorPos->X, this.cursorPos->Y);
|
||||
}
|
||||
|
||||
private unsafe void LoadCand(IntPtr hWnd)
|
||||
{
|
||||
if (hWnd == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var hImc = ImmGetContext(hWnd);
|
||||
if (hImc == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var size = ImmGetCandidateListW(hImc, 0, IntPtr.Zero, 0);
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
||||
size = ImmGetCandidateListW(hImc, 0, candlistPtr, (uint)size);
|
||||
|
||||
var candlist = this.ImmCandNative = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
||||
var pageSize = candlist.PageSize;
|
||||
var candCount = candlist.Count;
|
||||
|
||||
if (pageSize > 0 && candCount > 1)
|
||||
{
|
||||
var dwOffsets = new int[candCount];
|
||||
for (var i = 0; i < candCount; i++)
|
||||
{
|
||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
var pageStart = candlist.PageStart;
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of the cursor.
|
||||
/// </summary>
|
||||
/// <returns>The position of the cursor.</returns>
|
||||
internal Vector2 GetCursorPos()
|
||||
{
|
||||
return new Vector2(this.cursorPos->X, this.cursorPos->Y);
|
||||
}
|
||||
var cand = new string[pageSize];
|
||||
this.ImmCand.Clear();
|
||||
|
||||
private unsafe void LoadCand(IntPtr hWnd)
|
||||
{
|
||||
if (hWnd == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var hImc = ImmGetContext(hWnd);
|
||||
if (hImc == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var size = ImmGetCandidateListW(hImc, 0, IntPtr.Zero, 0);
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
||||
size = ImmGetCandidateListW(hImc, 0, candlistPtr, (uint)size);
|
||||
|
||||
var candlist = this.ImmCandNative = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
||||
var pageSize = candlist.PageSize;
|
||||
var candCount = candlist.Count;
|
||||
|
||||
if (pageSize > 0 && candCount > 1)
|
||||
for (var i = 0; i < pageSize; i++)
|
||||
{
|
||||
var dwOffsets = new int[candCount];
|
||||
for (var i = 0; i < candCount; i++)
|
||||
var offStart = dwOffsets[i + pageStart];
|
||||
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
|
||||
|
||||
var pStrStart = candlistPtr + (int)offStart;
|
||||
var pStrEnd = candlistPtr + (int)offEnd;
|
||||
|
||||
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
|
||||
if (len > 0)
|
||||
{
|
||||
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
|
||||
var candBytes = new byte[len];
|
||||
Marshal.Copy(pStrStart, candBytes, 0, len);
|
||||
|
||||
var candStr = Encoding.Unicode.GetString(candBytes);
|
||||
cand[i] = candStr;
|
||||
|
||||
this.ImmCand.Add(candStr);
|
||||
}
|
||||
|
||||
var pageStart = candlist.PageStart;
|
||||
|
||||
var cand = new string[pageSize];
|
||||
this.ImmCand.Clear();
|
||||
|
||||
for (var i = 0; i < pageSize; i++)
|
||||
{
|
||||
var offStart = dwOffsets[i + pageStart];
|
||||
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
|
||||
|
||||
var pStrStart = candlistPtr + (int)offStart;
|
||||
var pStrEnd = candlistPtr + (int)offEnd;
|
||||
|
||||
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
|
||||
if (len > 0)
|
||||
{
|
||||
var candBytes = new byte[len];
|
||||
Marshal.Copy(pStrStart, candBytes, 0, len);
|
||||
|
||||
var candStr = Encoding.Unicode.GetString(candBytes);
|
||||
cand[i] = candStr;
|
||||
|
||||
this.ImmCand.Add(candStr);
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(candlistPtr);
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(candlistPtr);
|
||||
}
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "cimgui.dll");
|
||||
var scanner = new SigScanner(module);
|
||||
var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF");
|
||||
Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}");
|
||||
|
||||
this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2));
|
||||
this.cursorPos->X = 0f;
|
||||
this.cursorPos->Y = 0f;
|
||||
|
||||
var asm = new[]
|
||||
{
|
||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "cimgui.dll");
|
||||
var scanner = new SigScanner(module);
|
||||
var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF");
|
||||
Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}");
|
||||
|
||||
this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2));
|
||||
this.cursorPos->X = 0f;
|
||||
this.cursorPos->Y = 0f;
|
||||
|
||||
var asm = new[]
|
||||
{
|
||||
"use64",
|
||||
$"push rax",
|
||||
$"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}",
|
||||
|
|
@ -276,27 +276,26 @@ namespace Dalamud.Game.Gui.Internal
|
|||
$"mov rax, {(IntPtr)this.cursorPos}",
|
||||
$"movss [rax],xmm6",
|
||||
$"pop rax",
|
||||
};
|
||||
};
|
||||
|
||||
Log.Debug($"Asm Code:\n{string.Join("\n", asm)}");
|
||||
this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook");
|
||||
this.imguiTextInputCursorHook?.Enable();
|
||||
Log.Debug($"Asm Code:\n{string.Join("\n", asm)}");
|
||||
this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook");
|
||||
this.imguiTextInputCursorHook?.Enable();
|
||||
|
||||
this.IsEnabled = true;
|
||||
Log.Information("Enabled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Enable failed");
|
||||
}
|
||||
this.IsEnabled = true;
|
||||
Log.Information("Enabled!");
|
||||
}
|
||||
|
||||
private void ToggleWindow(bool visible)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (visible)
|
||||
Service<DalamudInterface>.GetNullable()?.OpenImeWindow();
|
||||
else
|
||||
Service<DalamudInterface>.GetNullable()?.CloseImeWindow();
|
||||
Log.Information(ex, "Enable failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleWindow(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
Service<DalamudInterface>.GetNullable()?.OpenImeWindow();
|
||||
else
|
||||
Service<DalamudInterface>.GetNullable()?.CloseImeWindow();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// The structure of the PartyFinder packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// The structure of the PartyFinder packet.
|
||||
/// Gets the size of this packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacket
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the size of this packet.
|
||||
/// </summary>
|
||||
internal static int PacketSize { get; } = Marshal.SizeOf<PartyFinderPacket>();
|
||||
internal static int PacketSize { get; } = Marshal.SizeOf<PartyFinderPacket>();
|
||||
|
||||
internal readonly int BatchNumber;
|
||||
internal readonly int BatchNumber;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] padding1;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] padding1;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
internal readonly PartyFinderPacketListing[] Listings;
|
||||
}
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
internal readonly PartyFinderPacketListing[] Listings;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,98 +2,97 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// The structure of an individual listing within a packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacketListing
|
||||
{
|
||||
/// <summary>
|
||||
/// The structure of an individual listing within a packet.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly struct PartyFinderPacketListing
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header1;
|
||||
internal readonly uint Id;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header2;
|
||||
|
||||
internal readonly uint ContentIdLower;
|
||||
private readonly ushort unknownShort1;
|
||||
private readonly ushort unknownShort2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
private readonly byte[] header3;
|
||||
|
||||
internal readonly byte Category;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header4;
|
||||
|
||||
internal readonly ushort Duty;
|
||||
internal readonly byte DutyType;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
||||
private readonly byte[] header5;
|
||||
|
||||
internal readonly ushort World;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] header6;
|
||||
|
||||
internal readonly byte Objective;
|
||||
internal readonly byte BeginnersWelcome;
|
||||
internal readonly byte Conditions;
|
||||
internal readonly byte DutyFinderSettings;
|
||||
internal readonly byte LootRules;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header7; // all zero in every pf I've examined
|
||||
|
||||
internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted?
|
||||
internal readonly ushort SecondsRemaining;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
||||
|
||||
internal readonly ushort MinimumItemLevel;
|
||||
internal readonly ushort HomeWorld;
|
||||
internal readonly ushort CurrentWorld;
|
||||
|
||||
private readonly byte header9;
|
||||
|
||||
internal readonly byte NumSlots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header10;
|
||||
|
||||
internal readonly byte SearchArea;
|
||||
|
||||
private readonly byte header11;
|
||||
|
||||
internal readonly byte NumParties;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly uint[] Slots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly byte[] JobsPresent;
|
||||
|
||||
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
internal readonly byte[] Name;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
||||
internal readonly byte[] Description;
|
||||
|
||||
internal bool IsNull()
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header1;
|
||||
internal readonly uint Id;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
private readonly byte[] header2;
|
||||
|
||||
internal readonly uint ContentIdLower;
|
||||
private readonly ushort unknownShort1;
|
||||
private readonly ushort unknownShort2;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
|
||||
private readonly byte[] header3;
|
||||
|
||||
internal readonly byte Category;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header4;
|
||||
|
||||
internal readonly ushort Duty;
|
||||
internal readonly byte DutyType;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
||||
private readonly byte[] header5;
|
||||
|
||||
internal readonly ushort World;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
private readonly byte[] header6;
|
||||
|
||||
internal readonly byte Objective;
|
||||
internal readonly byte BeginnersWelcome;
|
||||
internal readonly byte Conditions;
|
||||
internal readonly byte DutyFinderSettings;
|
||||
internal readonly byte LootRules;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header7; // all zero in every pf I've examined
|
||||
|
||||
internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted?
|
||||
internal readonly ushort SecondsRemaining;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined
|
||||
|
||||
internal readonly ushort MinimumItemLevel;
|
||||
internal readonly ushort HomeWorld;
|
||||
internal readonly ushort CurrentWorld;
|
||||
|
||||
private readonly byte header9;
|
||||
|
||||
internal readonly byte NumSlots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
private readonly byte[] header10;
|
||||
|
||||
internal readonly byte SearchArea;
|
||||
|
||||
private readonly byte header11;
|
||||
|
||||
internal readonly byte NumParties;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32?
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly uint[] Slots;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
internal readonly byte[] JobsPresent;
|
||||
|
||||
// Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#.
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
internal readonly byte[] Name;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)]
|
||||
internal readonly byte[] Description;
|
||||
|
||||
internal bool IsNull()
|
||||
{
|
||||
// a valid party finder must have at least one slot set
|
||||
return this.Slots.All(slot => slot == 0);
|
||||
}
|
||||
// a valid party finder must have at least one slot set
|
||||
return this.Slots.All(slot => slot == 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
public class PartyFinderAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||
/// Gets the address of the native ReceiveListing method.
|
||||
/// </summary>
|
||||
public class PartyFinderAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ReceiveListing method.
|
||||
/// </summary>
|
||||
public IntPtr ReceiveListing { get; private set; }
|
||||
public IntPtr ReceiveListing { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,134 +8,133 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class PartyFinderGui : IDisposable, IServiceType
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
|
||||
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class PartyFinderGui : IDisposable, IServiceType
|
||||
/// <param name="sigScanner">Sig scanner to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyFinderGui(SigScanner sigScanner)
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly IntPtr memory;
|
||||
this.address = new PartyFinderAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sigScanner">Sig scanner to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyFinderGui(SigScanner sigScanner)
|
||||
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event type fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
/// <param name="listing">The listings received.</param>
|
||||
/// <param name="args">Additional arguments passed by the game.</param>
|
||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.receiveListingHook.Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
this.address = new PartyFinderAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
Marshal.FreeHGlobal(this.memory);
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
Log.Warning("Could not free PartyFinderGui memory.");
|
||||
}
|
||||
}
|
||||
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui)
|
||||
{
|
||||
this.receiveListingHook.Enable();
|
||||
}
|
||||
|
||||
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleListingEvents(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event type fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
/// <param name="listing">The listings received.</param>
|
||||
/// <param name="args">Additional arguments passed by the game.</param>
|
||||
public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args);
|
||||
this.receiveListingHook.Original(managerPtr, data);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data);
|
||||
private void HandleListingEvents(IntPtr data)
|
||||
{
|
||||
var dataPtr = data + 0x10;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time the game receives an individual Party Finder listing.
|
||||
/// Cannot modify listings but can hide them.
|
||||
/// </summary>
|
||||
public event PartyFinderListingEventDelegate ReceiveListing;
|
||||
var packet = Marshal.PtrToStructure<PartyFinderPacket>(dataPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
// rewriting is an expensive operation, so only do it if necessary
|
||||
var needToRewrite = false;
|
||||
|
||||
for (var i = 0; i < packet.Listings.Length; i++)
|
||||
{
|
||||
this.receiveListingHook.Dispose();
|
||||
// these are empty slots that are not shown to the player
|
||||
if (packet.Listings[i].IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
var listing = new PartyFinderListing(packet.Listings[i]);
|
||||
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
||||
this.ReceiveListing?.Invoke(listing, args);
|
||||
|
||||
if (args.Visible)
|
||||
{
|
||||
Marshal.FreeHGlobal(this.memory);
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
Log.Warning("Could not free PartyFinderGui memory.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// hide the listing from the player by setting it to a null listing
|
||||
packet.Listings[i] = default;
|
||||
needToRewrite = true;
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui)
|
||||
if (!needToRewrite)
|
||||
{
|
||||
this.receiveListingHook.Enable();
|
||||
return;
|
||||
}
|
||||
|
||||
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
|
||||
// write our struct into the memory (doing this directly crashes the game)
|
||||
Marshal.StructureToPtr(packet, this.memory, false);
|
||||
|
||||
// copy our new memory over the game's
|
||||
unsafe
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleListingEvents(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||
}
|
||||
|
||||
this.receiveListingHook.Original(managerPtr, data);
|
||||
}
|
||||
|
||||
private void HandleListingEvents(IntPtr data)
|
||||
{
|
||||
var dataPtr = data + 0x10;
|
||||
|
||||
var packet = Marshal.PtrToStructure<PartyFinderPacket>(dataPtr);
|
||||
|
||||
// rewriting is an expensive operation, so only do it if necessary
|
||||
var needToRewrite = false;
|
||||
|
||||
for (var i = 0; i < packet.Listings.Length; i++)
|
||||
{
|
||||
// these are empty slots that are not shown to the player
|
||||
if (packet.Listings[i].IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var listing = new PartyFinderListing(packet.Listings[i]);
|
||||
var args = new PartyFinderListingEventArgs(packet.BatchNumber);
|
||||
this.ReceiveListing?.Invoke(listing, args);
|
||||
|
||||
if (args.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// hide the listing from the player by setting it to a null listing
|
||||
packet.Listings[i] = default;
|
||||
needToRewrite = true;
|
||||
}
|
||||
|
||||
if (!needToRewrite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// write our struct into the memory (doing this directly crashes the game)
|
||||
Marshal.StructureToPtr(packet, this.memory, false);
|
||||
|
||||
// copy our new memory over the game's
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize);
|
||||
}
|
||||
Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Condition flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConditionFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No duty condition.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConditionFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No duty condition.
|
||||
/// </summary>
|
||||
None = 1 << 0,
|
||||
None = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The duty complete condition.
|
||||
/// </summary>
|
||||
DutyComplete = 1 << 1,
|
||||
/// <summary>
|
||||
/// The duty complete condition.
|
||||
/// </summary>
|
||||
DutyComplete = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The duty complete (weekly reward unclaimed) condition. This condition is
|
||||
/// only available for savage fights prior to echo release.
|
||||
/// </summary>
|
||||
DutyCompleteWeeklyRewardUnclaimed = 1 << 3,
|
||||
/// <summary>
|
||||
/// The duty complete (weekly reward unclaimed) condition. This condition is
|
||||
/// only available for savage fights prior to echo release.
|
||||
/// </summary>
|
||||
DutyCompleteWeeklyRewardUnclaimed = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The duty incomplete condition.
|
||||
/// </summary>
|
||||
DutyIncomplete = 1 << 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// The duty incomplete condition.
|
||||
/// </summary>
|
||||
DutyIncomplete = 1 << 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,47 @@
|
|||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Category flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
public enum DutyCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// Category flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// The duty category.
|
||||
/// </summary>
|
||||
public enum DutyCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// The duty category.
|
||||
/// </summary>
|
||||
Duty = 0,
|
||||
Duty = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The quest battle category.
|
||||
/// </summary>
|
||||
QuestBattles = 1 << 0,
|
||||
/// <summary>
|
||||
/// The quest battle category.
|
||||
/// </summary>
|
||||
QuestBattles = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The fate category.
|
||||
/// </summary>
|
||||
Fates = 1 << 1,
|
||||
/// <summary>
|
||||
/// The fate category.
|
||||
/// </summary>
|
||||
Fates = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The treasure hunt category.
|
||||
/// </summary>
|
||||
TreasureHunt = 1 << 2,
|
||||
/// <summary>
|
||||
/// The treasure hunt category.
|
||||
/// </summary>
|
||||
TreasureHunt = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// The hunt category.
|
||||
/// </summary>
|
||||
TheHunt = 1 << 3,
|
||||
/// <summary>
|
||||
/// The hunt category.
|
||||
/// </summary>
|
||||
TheHunt = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The gathering forays category.
|
||||
/// </summary>
|
||||
GatheringForays = 1 << 4,
|
||||
/// <summary>
|
||||
/// The gathering forays category.
|
||||
/// </summary>
|
||||
GatheringForays = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// The deep dungeons category.
|
||||
/// </summary>
|
||||
DeepDungeons = 1 << 5,
|
||||
/// <summary>
|
||||
/// The deep dungeons category.
|
||||
/// </summary>
|
||||
DeepDungeons = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// The adventuring forays category.
|
||||
/// </summary>
|
||||
AdventuringForays = 1 << 6,
|
||||
}
|
||||
/// <summary>
|
||||
/// The adventuring forays category.
|
||||
/// </summary>
|
||||
AdventuringForays = 1 << 6,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Duty finder settings flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DutyFinderSettingsFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Duty finder settings flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No duty finder settings.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DutyFinderSettingsFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No duty finder settings.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The undersized party setting.
|
||||
/// </summary>
|
||||
UndersizedParty = 1 << 0,
|
||||
/// <summary>
|
||||
/// The undersized party setting.
|
||||
/// </summary>
|
||||
UndersizedParty = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The minimum item level setting.
|
||||
/// </summary>
|
||||
MinimumItemLevel = 1 << 1,
|
||||
/// <summary>
|
||||
/// The minimum item level setting.
|
||||
/// </summary>
|
||||
MinimumItemLevel = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The silence echo setting.
|
||||
/// </summary>
|
||||
SilenceEcho = 1 << 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// The silence echo setting.
|
||||
/// </summary>
|
||||
SilenceEcho = 1 << 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Duty type flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
public enum DutyType
|
||||
{
|
||||
/// <summary>
|
||||
/// Duty type flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No duty type.
|
||||
/// </summary>
|
||||
public enum DutyType
|
||||
{
|
||||
/// <summary>
|
||||
/// No duty type.
|
||||
/// </summary>
|
||||
Other = 0,
|
||||
Other = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The roulette duty type.
|
||||
/// </summary>
|
||||
Roulette = 1 << 0,
|
||||
/// <summary>
|
||||
/// The roulette duty type.
|
||||
/// </summary>
|
||||
Roulette = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The normal duty type.
|
||||
/// </summary>
|
||||
Normal = 1 << 1,
|
||||
}
|
||||
/// <summary>
|
||||
/// The normal duty type.
|
||||
/// </summary>
|
||||
Normal = 1 << 1,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,156 +1,155 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Job flags for the <see cref="PartyFinder"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum JobFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Job flags for the <see cref="PartyFinder"/> class.
|
||||
/// Gladiator (GLD).
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum JobFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Gladiator (GLD).
|
||||
/// </summary>
|
||||
Gladiator = 1 << 1,
|
||||
Gladiator = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Pugilist (PGL).
|
||||
/// </summary>
|
||||
Pugilist = 1 << 2,
|
||||
/// <summary>
|
||||
/// Pugilist (PGL).
|
||||
/// </summary>
|
||||
Pugilist = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Marauder (MRD).
|
||||
/// </summary>
|
||||
Marauder = 1 << 3,
|
||||
/// <summary>
|
||||
/// Marauder (MRD).
|
||||
/// </summary>
|
||||
Marauder = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Lancer (LNC).
|
||||
/// </summary>
|
||||
Lancer = 1 << 4,
|
||||
/// <summary>
|
||||
/// Lancer (LNC).
|
||||
/// </summary>
|
||||
Lancer = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Archer (ARC).
|
||||
/// </summary>
|
||||
Archer = 1 << 5,
|
||||
/// <summary>
|
||||
/// Archer (ARC).
|
||||
/// </summary>
|
||||
Archer = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// Conjurer (CNJ).
|
||||
/// </summary>
|
||||
Conjurer = 1 << 6,
|
||||
/// <summary>
|
||||
/// Conjurer (CNJ).
|
||||
/// </summary>
|
||||
Conjurer = 1 << 6,
|
||||
|
||||
/// <summary>
|
||||
/// Thaumaturge (THM).
|
||||
/// </summary>
|
||||
Thaumaturge = 1 << 7,
|
||||
/// <summary>
|
||||
/// Thaumaturge (THM).
|
||||
/// </summary>
|
||||
Thaumaturge = 1 << 7,
|
||||
|
||||
/// <summary>
|
||||
/// Paladin (PLD).
|
||||
/// </summary>
|
||||
Paladin = 1 << 8,
|
||||
/// <summary>
|
||||
/// Paladin (PLD).
|
||||
/// </summary>
|
||||
Paladin = 1 << 8,
|
||||
|
||||
/// <summary>
|
||||
/// Monk (MNK).
|
||||
/// </summary>
|
||||
Monk = 1 << 9,
|
||||
/// <summary>
|
||||
/// Monk (MNK).
|
||||
/// </summary>
|
||||
Monk = 1 << 9,
|
||||
|
||||
/// <summary>
|
||||
/// Warrior (WAR).
|
||||
/// </summary>
|
||||
Warrior = 1 << 10,
|
||||
/// <summary>
|
||||
/// Warrior (WAR).
|
||||
/// </summary>
|
||||
Warrior = 1 << 10,
|
||||
|
||||
/// <summary>
|
||||
/// Dragoon (DRG).
|
||||
/// </summary>
|
||||
Dragoon = 1 << 11,
|
||||
/// <summary>
|
||||
/// Dragoon (DRG).
|
||||
/// </summary>
|
||||
Dragoon = 1 << 11,
|
||||
|
||||
/// <summary>
|
||||
/// Bard (BRD).
|
||||
/// </summary>
|
||||
Bard = 1 << 12,
|
||||
/// <summary>
|
||||
/// Bard (BRD).
|
||||
/// </summary>
|
||||
Bard = 1 << 12,
|
||||
|
||||
/// <summary>
|
||||
/// White mage (WHM).
|
||||
/// </summary>
|
||||
WhiteMage = 1 << 13,
|
||||
/// <summary>
|
||||
/// White mage (WHM).
|
||||
/// </summary>
|
||||
WhiteMage = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// Black mage (BLM).
|
||||
/// </summary>
|
||||
BlackMage = 1 << 14,
|
||||
/// <summary>
|
||||
/// Black mage (BLM).
|
||||
/// </summary>
|
||||
BlackMage = 1 << 14,
|
||||
|
||||
/// <summary>
|
||||
/// Arcanist (ACN).
|
||||
/// </summary>
|
||||
Arcanist = 1 << 15,
|
||||
/// <summary>
|
||||
/// Arcanist (ACN).
|
||||
/// </summary>
|
||||
Arcanist = 1 << 15,
|
||||
|
||||
/// <summary>
|
||||
/// Summoner (SMN).
|
||||
/// </summary>
|
||||
Summoner = 1 << 16,
|
||||
/// <summary>
|
||||
/// Summoner (SMN).
|
||||
/// </summary>
|
||||
Summoner = 1 << 16,
|
||||
|
||||
/// <summary>
|
||||
/// Scholar (SCH).
|
||||
/// </summary>
|
||||
Scholar = 1 << 17,
|
||||
/// <summary>
|
||||
/// Scholar (SCH).
|
||||
/// </summary>
|
||||
Scholar = 1 << 17,
|
||||
|
||||
/// <summary>
|
||||
/// Rogue (ROG).
|
||||
/// </summary>
|
||||
Rogue = 1 << 18,
|
||||
/// <summary>
|
||||
/// Rogue (ROG).
|
||||
/// </summary>
|
||||
Rogue = 1 << 18,
|
||||
|
||||
/// <summary>
|
||||
/// Ninja (NIN).
|
||||
/// </summary>
|
||||
Ninja = 1 << 19,
|
||||
/// <summary>
|
||||
/// Ninja (NIN).
|
||||
/// </summary>
|
||||
Ninja = 1 << 19,
|
||||
|
||||
/// <summary>
|
||||
/// Machinist (MCH).
|
||||
/// </summary>
|
||||
Machinist = 1 << 20,
|
||||
/// <summary>
|
||||
/// Machinist (MCH).
|
||||
/// </summary>
|
||||
Machinist = 1 << 20,
|
||||
|
||||
/// <summary>
|
||||
/// Dark Knight (DRK).
|
||||
/// </summary>
|
||||
DarkKnight = 1 << 21,
|
||||
/// <summary>
|
||||
/// Dark Knight (DRK).
|
||||
/// </summary>
|
||||
DarkKnight = 1 << 21,
|
||||
|
||||
/// <summary>
|
||||
/// Astrologian (AST).
|
||||
/// </summary>
|
||||
Astrologian = 1 << 22,
|
||||
/// <summary>
|
||||
/// Astrologian (AST).
|
||||
/// </summary>
|
||||
Astrologian = 1 << 22,
|
||||
|
||||
/// <summary>
|
||||
/// Samurai (SAM).
|
||||
/// </summary>
|
||||
Samurai = 1 << 23,
|
||||
/// <summary>
|
||||
/// Samurai (SAM).
|
||||
/// </summary>
|
||||
Samurai = 1 << 23,
|
||||
|
||||
/// <summary>
|
||||
/// Red mage (RDM).
|
||||
/// </summary>
|
||||
RedMage = 1 << 24,
|
||||
/// <summary>
|
||||
/// Red mage (RDM).
|
||||
/// </summary>
|
||||
RedMage = 1 << 24,
|
||||
|
||||
/// <summary>
|
||||
/// Blue mage (BLM).
|
||||
/// </summary>
|
||||
BlueMage = 1 << 25,
|
||||
/// <summary>
|
||||
/// Blue mage (BLM).
|
||||
/// </summary>
|
||||
BlueMage = 1 << 25,
|
||||
|
||||
/// <summary>
|
||||
/// Gunbreaker (GNB).
|
||||
/// </summary>
|
||||
Gunbreaker = 1 << 26,
|
||||
/// <summary>
|
||||
/// Gunbreaker (GNB).
|
||||
/// </summary>
|
||||
Gunbreaker = 1 << 26,
|
||||
|
||||
/// <summary>
|
||||
/// Dancer (DNC).
|
||||
/// </summary>
|
||||
Dancer = 1 << 27,
|
||||
/// <summary>
|
||||
/// Dancer (DNC).
|
||||
/// </summary>
|
||||
Dancer = 1 << 27,
|
||||
|
||||
/// <summary>
|
||||
/// Reaper (RPR).
|
||||
/// </summary>
|
||||
Reaper = 1 << 28,
|
||||
/// <summary>
|
||||
/// Reaper (RPR).
|
||||
/// </summary>
|
||||
Reaper = 1 << 28,
|
||||
|
||||
/// <summary>
|
||||
/// Sage (SGE).
|
||||
/// </summary>
|
||||
Sage = 1 << 29,
|
||||
}
|
||||
/// <summary>
|
||||
/// Sage (SGE).
|
||||
/// </summary>
|
||||
Sage = 1 << 29,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,57 @@
|
|||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="JobFlags"/> enum.
|
||||
/// </summary>
|
||||
public static class JobFlagsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="JobFlags"/> enum.
|
||||
/// Get the actual ClassJob from the in-game sheets for this JobFlags.
|
||||
/// </summary>
|
||||
public static class JobFlagsExtensions
|
||||
/// <param name="job">A JobFlags enum member.</param>
|
||||
/// <param name="data">A DataManager to get the ClassJob from.</param>
|
||||
/// <returns>A ClassJob if found or null if not.</returns>
|
||||
public static ClassJob ClassJob(this JobFlags job, DataManager data)
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the actual ClassJob from the in-game sheets for this JobFlags.
|
||||
/// </summary>
|
||||
/// <param name="job">A JobFlags enum member.</param>
|
||||
/// <param name="data">A DataManager to get the ClassJob from.</param>
|
||||
/// <returns>A ClassJob if found or null if not.</returns>
|
||||
public static ClassJob ClassJob(this JobFlags job, DataManager data)
|
||||
var jobs = data.GetExcelSheet<ClassJob>();
|
||||
|
||||
uint? row = job switch
|
||||
{
|
||||
var jobs = data.GetExcelSheet<ClassJob>();
|
||||
JobFlags.Gladiator => 1,
|
||||
JobFlags.Pugilist => 2,
|
||||
JobFlags.Marauder => 3,
|
||||
JobFlags.Lancer => 4,
|
||||
JobFlags.Archer => 5,
|
||||
JobFlags.Conjurer => 6,
|
||||
JobFlags.Thaumaturge => 7,
|
||||
JobFlags.Paladin => 19,
|
||||
JobFlags.Monk => 20,
|
||||
JobFlags.Warrior => 21,
|
||||
JobFlags.Dragoon => 22,
|
||||
JobFlags.Bard => 23,
|
||||
JobFlags.WhiteMage => 24,
|
||||
JobFlags.BlackMage => 25,
|
||||
JobFlags.Arcanist => 26,
|
||||
JobFlags.Summoner => 27,
|
||||
JobFlags.Scholar => 28,
|
||||
JobFlags.Rogue => 29,
|
||||
JobFlags.Ninja => 30,
|
||||
JobFlags.Machinist => 31,
|
||||
JobFlags.DarkKnight => 32,
|
||||
JobFlags.Astrologian => 33,
|
||||
JobFlags.Samurai => 34,
|
||||
JobFlags.RedMage => 35,
|
||||
JobFlags.BlueMage => 36,
|
||||
JobFlags.Gunbreaker => 37,
|
||||
JobFlags.Dancer => 38,
|
||||
JobFlags.Reaper => 39,
|
||||
JobFlags.Sage => 40,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
uint? row = job switch
|
||||
{
|
||||
JobFlags.Gladiator => 1,
|
||||
JobFlags.Pugilist => 2,
|
||||
JobFlags.Marauder => 3,
|
||||
JobFlags.Lancer => 4,
|
||||
JobFlags.Archer => 5,
|
||||
JobFlags.Conjurer => 6,
|
||||
JobFlags.Thaumaturge => 7,
|
||||
JobFlags.Paladin => 19,
|
||||
JobFlags.Monk => 20,
|
||||
JobFlags.Warrior => 21,
|
||||
JobFlags.Dragoon => 22,
|
||||
JobFlags.Bard => 23,
|
||||
JobFlags.WhiteMage => 24,
|
||||
JobFlags.BlackMage => 25,
|
||||
JobFlags.Arcanist => 26,
|
||||
JobFlags.Summoner => 27,
|
||||
JobFlags.Scholar => 28,
|
||||
JobFlags.Rogue => 29,
|
||||
JobFlags.Ninja => 30,
|
||||
JobFlags.Machinist => 31,
|
||||
JobFlags.DarkKnight => 32,
|
||||
JobFlags.Astrologian => 33,
|
||||
JobFlags.Samurai => 34,
|
||||
JobFlags.RedMage => 35,
|
||||
JobFlags.BlueMage => 36,
|
||||
JobFlags.Gunbreaker => 37,
|
||||
JobFlags.Dancer => 38,
|
||||
JobFlags.Reaper => 39,
|
||||
JobFlags.Sage => 40,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return row == null ? null : jobs.GetRow((uint)row);
|
||||
}
|
||||
return row == null ? null : jobs.GetRow((uint)row);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Loot rule flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LootRuleFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Loot rule flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No loot rules.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LootRuleFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No loot rules.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The greed only rule.
|
||||
/// </summary>
|
||||
GreedOnly = 1,
|
||||
/// <summary>
|
||||
/// The greed only rule.
|
||||
/// </summary>
|
||||
GreedOnly = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The lootmaster rule.
|
||||
/// </summary>
|
||||
Lootmaster = 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// The lootmaster rule.
|
||||
/// </summary>
|
||||
Lootmaster = 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Objective flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ObjectiveFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Objective flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// No objective.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ObjectiveFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// No objective.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The duty completion objective.
|
||||
/// </summary>
|
||||
DutyCompletion = 1,
|
||||
/// <summary>
|
||||
/// The duty completion objective.
|
||||
/// </summary>
|
||||
DutyCompletion = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The practice objective.
|
||||
/// </summary>
|
||||
Practice = 2,
|
||||
/// <summary>
|
||||
/// The practice objective.
|
||||
/// </summary>
|
||||
Practice = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The loot objective.
|
||||
/// </summary>
|
||||
Loot = 4,
|
||||
}
|
||||
/// <summary>
|
||||
/// The loot objective.
|
||||
/// </summary>
|
||||
Loot = 4,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,228 +7,227 @@ using Dalamud.Game.Gui.PartyFinder.Internal;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// A single listing in party finder.
|
||||
/// </summary>
|
||||
public class PartyFinderListing
|
||||
{
|
||||
private readonly byte objective;
|
||||
private readonly byte conditions;
|
||||
private readonly byte dutyFinderSettings;
|
||||
private readonly byte lootRules;
|
||||
private readonly byte searchArea;
|
||||
private readonly PartyFinderSlot[] slots;
|
||||
private readonly byte[] jobsPresent;
|
||||
|
||||
/// <summary>
|
||||
/// A single listing in party finder.
|
||||
/// Initializes a new instance of the <see cref="PartyFinderListing"/> class.
|
||||
/// </summary>
|
||||
public class PartyFinderListing
|
||||
/// <param name="listing">The interop listing data.</param>
|
||||
internal PartyFinderListing(PartyFinderPacketListing listing)
|
||||
{
|
||||
private readonly byte objective;
|
||||
private readonly byte conditions;
|
||||
private readonly byte dutyFinderSettings;
|
||||
private readonly byte lootRules;
|
||||
private readonly byte searchArea;
|
||||
private readonly PartyFinderSlot[] slots;
|
||||
private readonly byte[] jobsPresent;
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderListing"/> class.
|
||||
/// </summary>
|
||||
/// <param name="listing">The interop listing data.</param>
|
||||
internal PartyFinderListing(PartyFinderPacketListing listing)
|
||||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
this.objective = listing.Objective;
|
||||
this.conditions = listing.Conditions;
|
||||
this.dutyFinderSettings = listing.DutyFinderSettings;
|
||||
this.lootRules = listing.LootRules;
|
||||
this.searchArea = listing.SearchArea;
|
||||
this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray();
|
||||
this.jobsPresent = listing.JobsPresent;
|
||||
|
||||
this.Id = listing.Id;
|
||||
this.ContentIdLower = listing.ContentIdLower;
|
||||
this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray());
|
||||
this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray());
|
||||
this.World = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.World));
|
||||
this.HomeWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.HomeWorld));
|
||||
this.CurrentWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.CurrentWorld));
|
||||
this.Category = (DutyCategory)listing.Category;
|
||||
this.RawDuty = listing.Duty;
|
||||
this.Duty = new Lazy<ContentFinderCondition>(() => dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(listing.Duty));
|
||||
this.DutyType = (DutyType)listing.DutyType;
|
||||
this.BeginnersWelcome = listing.BeginnersWelcome == 1;
|
||||
this.SecondsRemaining = listing.SecondsRemaining;
|
||||
this.MinimumItemLevel = listing.MinimumItemLevel;
|
||||
this.Parties = listing.NumParties;
|
||||
this.SlotsAvailable = listing.NumSlots;
|
||||
this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp;
|
||||
this.JobsPresent = listing.JobsPresent
|
||||
.Select(id => new Lazy<ClassJob>(
|
||||
() => id == 0
|
||||
? null
|
||||
: dataManager.GetExcelSheet<ClassJob>().GetRow(id)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID assigned to this listing by the game's server.
|
||||
/// </summary>
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lower bits of the player's content ID.
|
||||
/// </summary>
|
||||
public uint ContentIdLower { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the player hosting this listing.
|
||||
/// </summary>
|
||||
public SeString Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of this listing as set by the host. May be multiple lines.
|
||||
/// </summary>
|
||||
public SeString Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world that this listing was created on.
|
||||
/// </summary>
|
||||
public Lazy<World> World { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home world of the listing's host.
|
||||
/// </summary>
|
||||
public Lazy<World> HomeWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current world of the listing's host.
|
||||
/// </summary>
|
||||
public Lazy<World> CurrentWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Party Finder category this listing is listed under.
|
||||
/// </summary>
|
||||
public DutyCategory Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings.
|
||||
/// </summary>
|
||||
public ushort RawDuty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duty this listing is for. May be null for non-duty listings.
|
||||
/// </summary>
|
||||
public Lazy<ContentFinderCondition> Duty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of duty this listing is for.
|
||||
/// </summary>
|
||||
public DutyType DutyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game.
|
||||
/// </summary>
|
||||
public bool BeginnersWelcome { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets how many seconds this listing will continue to be available for. It may end before this time if the party
|
||||
/// fills or the host ends it early.
|
||||
/// </summary>
|
||||
public ushort SecondsRemaining { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum item level required to join this listing.
|
||||
/// </summary>
|
||||
public ushort MinimumItemLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of parties this listing is recruiting for.
|
||||
/// </summary>
|
||||
public byte Parties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of player slots this listing is recruiting for.
|
||||
/// </summary>
|
||||
public byte SlotsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time at which the server this listings is on last restarted for a patch/hotfix.
|
||||
/// Probably.
|
||||
/// </summary>
|
||||
public uint LastPatchHotfixTimestamp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of player slots that the Party Finder is accepting.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<PartyFinderSlot> Slots => this.slots;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the objective of this listing.
|
||||
/// </summary>
|
||||
public ObjectiveFlags Objective => (ObjectiveFlags)this.objective;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conditions of this listing.
|
||||
/// </summary>
|
||||
public ConditionFlags Conditions => (ConditionFlags)this.conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Duty Finder settings that will be used for this listing.
|
||||
/// </summary>
|
||||
public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loot rules that will be used for this listing.
|
||||
/// </summary>
|
||||
public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules;
|
||||
|
||||
/// <summary>
|
||||
/// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one
|
||||
/// player per job.
|
||||
/// </summary>
|
||||
public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the class/job IDs that are currently present in the party.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the classes/jobs that are currently present in the party.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
|
||||
|
||||
#region Indexers
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0;
|
||||
|
||||
#endregion
|
||||
this.objective = listing.Objective;
|
||||
this.conditions = listing.Conditions;
|
||||
this.dutyFinderSettings = listing.DutyFinderSettings;
|
||||
this.lootRules = listing.LootRules;
|
||||
this.searchArea = listing.SearchArea;
|
||||
this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray();
|
||||
this.jobsPresent = listing.JobsPresent;
|
||||
|
||||
this.Id = listing.Id;
|
||||
this.ContentIdLower = listing.ContentIdLower;
|
||||
this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray());
|
||||
this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray());
|
||||
this.World = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.World));
|
||||
this.HomeWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.HomeWorld));
|
||||
this.CurrentWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.CurrentWorld));
|
||||
this.Category = (DutyCategory)listing.Category;
|
||||
this.RawDuty = listing.Duty;
|
||||
this.Duty = new Lazy<ContentFinderCondition>(() => dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(listing.Duty));
|
||||
this.DutyType = (DutyType)listing.DutyType;
|
||||
this.BeginnersWelcome = listing.BeginnersWelcome == 1;
|
||||
this.SecondsRemaining = listing.SecondsRemaining;
|
||||
this.MinimumItemLevel = listing.MinimumItemLevel;
|
||||
this.Parties = listing.NumParties;
|
||||
this.SlotsAvailable = listing.NumSlots;
|
||||
this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp;
|
||||
this.JobsPresent = listing.JobsPresent
|
||||
.Select(id => new Lazy<ClassJob>(
|
||||
() => id == 0
|
||||
? null
|
||||
: dataManager.GetExcelSheet<ClassJob>().GetRow(id)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID assigned to this listing by the game's server.
|
||||
/// </summary>
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lower bits of the player's content ID.
|
||||
/// </summary>
|
||||
public uint ContentIdLower { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the player hosting this listing.
|
||||
/// </summary>
|
||||
public SeString Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of this listing as set by the host. May be multiple lines.
|
||||
/// </summary>
|
||||
public SeString Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world that this listing was created on.
|
||||
/// </summary>
|
||||
public Lazy<World> World { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home world of the listing's host.
|
||||
/// </summary>
|
||||
public Lazy<World> HomeWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current world of the listing's host.
|
||||
/// </summary>
|
||||
public Lazy<World> CurrentWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Party Finder category this listing is listed under.
|
||||
/// </summary>
|
||||
public DutyCategory Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings.
|
||||
/// </summary>
|
||||
public ushort RawDuty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duty this listing is for. May be null for non-duty listings.
|
||||
/// </summary>
|
||||
public Lazy<ContentFinderCondition> Duty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of duty this listing is for.
|
||||
/// </summary>
|
||||
public DutyType DutyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game.
|
||||
/// </summary>
|
||||
public bool BeginnersWelcome { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets how many seconds this listing will continue to be available for. It may end before this time if the party
|
||||
/// fills or the host ends it early.
|
||||
/// </summary>
|
||||
public ushort SecondsRemaining { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum item level required to join this listing.
|
||||
/// </summary>
|
||||
public ushort MinimumItemLevel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of parties this listing is recruiting for.
|
||||
/// </summary>
|
||||
public byte Parties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of player slots this listing is recruiting for.
|
||||
/// </summary>
|
||||
public byte SlotsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time at which the server this listings is on last restarted for a patch/hotfix.
|
||||
/// Probably.
|
||||
/// </summary>
|
||||
public uint LastPatchHotfixTimestamp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of player slots that the Party Finder is accepting.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<PartyFinderSlot> Slots => this.slots;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the objective of this listing.
|
||||
/// </summary>
|
||||
public ObjectiveFlags Objective => (ObjectiveFlags)this.objective;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conditions of this listing.
|
||||
/// </summary>
|
||||
public ConditionFlags Conditions => (ConditionFlags)this.conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Duty Finder settings that will be used for this listing.
|
||||
/// </summary>
|
||||
public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loot rules that will be used for this listing.
|
||||
/// </summary>
|
||||
public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules;
|
||||
|
||||
/// <summary>
|
||||
/// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one
|
||||
/// player per job.
|
||||
/// </summary>
|
||||
public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the class/job IDs that are currently present in the party.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the classes/jobs that are currently present in the party.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
|
||||
|
||||
#region Indexers
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given flag is present.
|
||||
/// </summary>
|
||||
/// <param name="flag">The flag to check for.</param>
|
||||
/// <returns>A value indicating whether the flag is present.</returns>
|
||||
public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents additional arguments passed by the game.
|
||||
/// </summary>
|
||||
public class PartyFinderListingEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents additional arguments passed by the game.
|
||||
/// Initializes a new instance of the <see cref="PartyFinderListingEventArgs"/> class.
|
||||
/// </summary>
|
||||
public class PartyFinderListingEventArgs
|
||||
/// <param name="batchNumber">The batch number.</param>
|
||||
internal PartyFinderListingEventArgs(int batchNumber)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderListingEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="batchNumber">The batch number.</param>
|
||||
internal PartyFinderListingEventArgs(int batchNumber)
|
||||
{
|
||||
this.BatchNumber = batchNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the batch number.
|
||||
/// </summary>
|
||||
public int BatchNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the listing is visible.
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
this.BatchNumber = batchNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the batch number.
|
||||
/// </summary>
|
||||
public int BatchNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the listing is visible.
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,50 +2,49 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// A player slot in a Party Finder listing.
|
||||
/// </summary>
|
||||
public class PartyFinderSlot
|
||||
{
|
||||
private readonly uint accepting;
|
||||
private JobFlags[] listAccepting;
|
||||
|
||||
/// <summary>
|
||||
/// A player slot in a Party Finder listing.
|
||||
/// Initializes a new instance of the <see cref="PartyFinderSlot"/> class.
|
||||
/// </summary>
|
||||
public class PartyFinderSlot
|
||||
/// <param name="accepting">The flag value of accepted jobs.</param>
|
||||
internal PartyFinderSlot(uint accepting)
|
||||
{
|
||||
private readonly uint accepting;
|
||||
private JobFlags[] listAccepting;
|
||||
this.accepting = accepting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderSlot"/> class.
|
||||
/// </summary>
|
||||
/// <param name="accepting">The flag value of accepted jobs.</param>
|
||||
internal PartyFinderSlot(uint accepting)
|
||||
/// <summary>
|
||||
/// Gets a list of jobs that this slot is accepting.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<JobFlags> Accepting
|
||||
{
|
||||
get
|
||||
{
|
||||
this.accepting = accepting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of jobs that this slot is accepting.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<JobFlags> Accepting
|
||||
{
|
||||
get
|
||||
if (this.listAccepting != null)
|
||||
{
|
||||
if (this.listAccepting != null)
|
||||
{
|
||||
return this.listAccepting;
|
||||
}
|
||||
|
||||
this.listAccepting = Enum.GetValues(typeof(JobFlags))
|
||||
.Cast<JobFlags>()
|
||||
.Where(flag => this[flag])
|
||||
.ToArray();
|
||||
|
||||
return this.listAccepting;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if this slot is accepting a job.
|
||||
/// </summary>
|
||||
/// <param name="flag">Job to test.</param>
|
||||
public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0;
|
||||
this.listAccepting = Enum.GetValues(typeof(JobFlags))
|
||||
.Cast<JobFlags>()
|
||||
.Where(flag => this[flag])
|
||||
.ToArray();
|
||||
|
||||
return this.listAccepting;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if this slot is accepting a job.
|
||||
/// </summary>
|
||||
/// <param name="flag">Job to test.</param>
|
||||
public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,35 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Search area flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum SearchAreaFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Search area flags for the <see cref="PartyFinderGui"/> class.
|
||||
/// Datacenter.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum SearchAreaFlags : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Datacenter.
|
||||
/// </summary>
|
||||
DataCentre = 1 << 0,
|
||||
DataCentre = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Private.
|
||||
/// </summary>
|
||||
Private = 1 << 1,
|
||||
/// <summary>
|
||||
/// Private.
|
||||
/// </summary>
|
||||
Private = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Alliance raid.
|
||||
/// </summary>
|
||||
AllianceRaid = 1 << 2,
|
||||
/// <summary>
|
||||
/// Alliance raid.
|
||||
/// </summary>
|
||||
AllianceRaid = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// World.
|
||||
/// </summary>
|
||||
World = 1 << 3,
|
||||
/// <summary>
|
||||
/// World.
|
||||
/// </summary>
|
||||
World = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// One player per job.
|
||||
/// </summary>
|
||||
OnePlayerPerJob = 1 << 5,
|
||||
}
|
||||
/// <summary>
|
||||
/// One player per job.
|
||||
/// </summary>
|
||||
OnePlayerPerJob = 1 << 5,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,31 @@
|
|||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents options that can be used with the <see cref="ToastGui"/> class for the quest toast variant.
|
||||
/// </summary>
|
||||
public sealed class QuestToastOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents options that can be used with the <see cref="ToastGui"/> class for the quest toast variant.
|
||||
/// Gets or sets the position of the toast on the screen.
|
||||
/// </summary>
|
||||
public sealed class QuestToastOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the toast on the screen.
|
||||
/// </summary>
|
||||
public QuestToastPosition Position { get; set; } = QuestToastPosition.Centre;
|
||||
public QuestToastPosition Position { get; set; } = QuestToastPosition.Centre;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the icon that will appear in the toast.
|
||||
///
|
||||
/// This may be 0 for no icon.
|
||||
/// </summary>
|
||||
public uint IconId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the icon that will appear in the toast.
|
||||
///
|
||||
/// This may be 0 for no icon.
|
||||
/// </summary>
|
||||
public uint IconId { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the toast will show a checkmark after appearing.
|
||||
/// </summary>
|
||||
public bool DisplayCheckmark { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the toast will show a checkmark after appearing.
|
||||
/// </summary>
|
||||
public bool DisplayCheckmark { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the toast will play a completion sound.
|
||||
///
|
||||
/// This only works if <see cref="IconId"/> is non-zero or <see cref="DisplayCheckmark"/> is true.
|
||||
/// </summary>
|
||||
public bool PlaySound { get; set; } = false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the toast will play a completion sound.
|
||||
///
|
||||
/// This only works if <see cref="IconId"/> is non-zero or <see cref="DisplayCheckmark"/> is true.
|
||||
/// </summary>
|
||||
public bool PlaySound { get; set; } = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// The alignment of native quest toast windows.
|
||||
/// </summary>
|
||||
public enum QuestToastPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// The alignment of native quest toast windows.
|
||||
/// The toast will be aligned screen centre.
|
||||
/// </summary>
|
||||
public enum QuestToastPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen centre.
|
||||
/// </summary>
|
||||
Centre = 0,
|
||||
Centre = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen right.
|
||||
/// </summary>
|
||||
Right = 1,
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen right.
|
||||
/// </summary>
|
||||
Right = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen left.
|
||||
/// </summary>
|
||||
Left = 2,
|
||||
}
|
||||
/// <summary>
|
||||
/// The toast will be aligned screen left.
|
||||
/// </summary>
|
||||
Left = 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,429 +7,428 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
|
||||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native toast windows.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class ToastGui : IDisposable, IServiceType
|
||||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
||||
private readonly ToastGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new();
|
||||
private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new();
|
||||
private readonly Queue<byte[]> errorQueue = new();
|
||||
|
||||
private readonly Hook<ShowNormalToastDelegate> showNormalToastHook;
|
||||
private readonly Hook<ShowQuestToastDelegate> showQuestToastHook;
|
||||
private readonly Hook<ShowErrorToastDelegate> showErrorToastHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sigScanner">Sig scanner to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ToastGui(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new ToastGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
||||
}
|
||||
|
||||
#region Event delegates
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when a normal toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="options">Assorted toast options.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when a quest toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="options">Assorted toast options.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when an error toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Marshal delegates
|
||||
|
||||
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
||||
|
||||
private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||
|
||||
private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnNormalToastDelegate Toast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a quest toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnQuestToastDelegate QuestToast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when an error toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnErrorToastDelegate ErrorToast;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.showNormalToastHook.Dispose();
|
||||
this.showQuestToastHook.Dispose();
|
||||
this.showErrorToastHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the toast queue.
|
||||
/// </summary>
|
||||
internal void UpdateQueue()
|
||||
{
|
||||
while (this.normalQueue.Count > 0)
|
||||
{
|
||||
var (message, options) = this.normalQueue.Dequeue();
|
||||
this.ShowNormal(message, options);
|
||||
}
|
||||
|
||||
while (this.questQueue.Count > 0)
|
||||
{
|
||||
var (message, options) = this.questQueue.Dequeue();
|
||||
this.ShowQuest(message, options);
|
||||
}
|
||||
|
||||
while (this.errorQueue.Count > 0)
|
||||
{
|
||||
var message = this.errorQueue.Dequeue();
|
||||
this.ShowError(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui)
|
||||
{
|
||||
this.showNormalToastHook.Enable();
|
||||
this.showQuestToastHook.Enable();
|
||||
this.showErrorToastHook.Enable();
|
||||
}
|
||||
|
||||
private SeString ParseString(IntPtr text)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
unsafe
|
||||
{
|
||||
var ptr = (byte*)text;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
bytes.Add(*ptr);
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// call events
|
||||
return SeString.Parse(bytes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles normal toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// This class facilitates interacting with and creating native toast windows.
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class ToastGui : IDisposable, IServiceType
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(string message, ToastOptions options = null)
|
||||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
||||
private readonly ToastGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new();
|
||||
private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new();
|
||||
private readonly Queue<byte[]> errorQueue = new();
|
||||
|
||||
private readonly Hook<ShowNormalToastDelegate> showNormalToastHook;
|
||||
private readonly Hook<ShowQuestToastDelegate> showQuestToastHook;
|
||||
private readonly Hook<ShowErrorToastDelegate> showErrorToastHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sigScanner">Sig scanner to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ToastGui(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new ToastGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
||||
}
|
||||
|
||||
#region Event delegates
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when a normal toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="options">Assorted toast options.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when a quest toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="options">Assorted toast options.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used when an error toast window appears.
|
||||
/// </summary>
|
||||
/// <param name="message">The message displayed.</param>
|
||||
/// <param name="isHandled">Whether the toast has been handled or should be propagated.</param>
|
||||
public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Marshal delegates
|
||||
|
||||
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
||||
|
||||
private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||
|
||||
private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnNormalToastDelegate Toast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when a quest toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnQuestToastDelegate QuestToast;
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be fired when an error toast is sent by the game or a plugin.
|
||||
/// </summary>
|
||||
public event OnErrorToastDelegate ErrorToast;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.showNormalToastHook.Dispose();
|
||||
this.showQuestToastHook.Dispose();
|
||||
this.showErrorToastHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the toast queue.
|
||||
/// </summary>
|
||||
internal void UpdateQueue()
|
||||
{
|
||||
while (this.normalQueue.Count > 0)
|
||||
{
|
||||
var (message, options) = this.normalQueue.Dequeue();
|
||||
this.ShowNormal(message, options);
|
||||
}
|
||||
|
||||
while (this.questQueue.Count > 0)
|
||||
{
|
||||
var (message, options) = this.questQueue.Dequeue();
|
||||
this.ShowQuest(message, options);
|
||||
}
|
||||
|
||||
while (this.errorQueue.Count > 0)
|
||||
{
|
||||
var message = this.errorQueue.Dequeue();
|
||||
this.ShowError(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(GameGui gameGui)
|
||||
{
|
||||
this.showNormalToastHook.Enable();
|
||||
this.showQuestToastHook.Enable();
|
||||
this.showErrorToastHook.Enable();
|
||||
}
|
||||
|
||||
private SeString ParseString(IntPtr text)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
unsafe
|
||||
{
|
||||
var ptr = (byte*)text;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
bytes.Add(*ptr);
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// call events
|
||||
return SeString.Parse(bytes.ToArray());
|
||||
}
|
||||
options ??= new ToastOptions();
|
||||
this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles normal toasts.
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(SeString message, ToastOptions options = null)
|
||||
{
|
||||
/// <summary>
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(string message, ToastOptions options = null)
|
||||
options ??= new ToastOptions();
|
||||
this.normalQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowNormal(byte[] bytes, ToastOptions options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowNormal(SeString message, ToastOptions options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
this.normalQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowNormal(byte[] bytes, ToastOptions options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var options = new ToastOptions
|
||||
{
|
||||
Position = (ToastPosition)isTop,
|
||||
Speed = (ToastSpeed)isFast,
|
||||
};
|
||||
|
||||
this.Toast?.Invoke(ref str, ref options, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
|
||||
}
|
||||
this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles quest toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
|
||||
{
|
||||
/// <summary>
|
||||
/// Show a quest toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowQuest(string message, QuestToastOptions options = null)
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a quest toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowQuest(SeString message, QuestToastOptions options = null)
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var options = new ToastOptions
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
this.questQueue.Enqueue((message.Encode(), options));
|
||||
Position = (ToastPosition)isTop,
|
||||
Speed = (ToastSpeed)isFast,
|
||||
};
|
||||
|
||||
this.Toast?.Invoke(ref str, ref options, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleQuestToastDetour(
|
||||
manager!.Value,
|
||||
(int)options.Position,
|
||||
(IntPtr)ptr,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var options = new QuestToastOptions
|
||||
{
|
||||
Position = (QuestToastPosition)position,
|
||||
DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic,
|
||||
IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1,
|
||||
PlaySound = playSound == 1,
|
||||
};
|
||||
|
||||
this.QuestToast?.Invoke(ref str, ref options, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showQuestToastHook.Original(
|
||||
manager,
|
||||
(int)options.Position,
|
||||
(IntPtr)message,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options)
|
||||
{
|
||||
return options.DisplayCheckmark
|
||||
? (QuestToastCheckmarkMagic, options.IconId)
|
||||
: (options.IconId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles error toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Show an error toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
public void ShowError(string message)
|
||||
{
|
||||
this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show an error toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
public void ShowError(SeString message)
|
||||
{
|
||||
this.errorQueue.Enqueue(message.Encode());
|
||||
}
|
||||
|
||||
private void ShowError(byte[] bytes)
|
||||
{
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
|
||||
this.ErrorToast?.Invoke(ref str, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
|
||||
}
|
||||
return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles quest toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Show a quest toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowQuest(string message, QuestToastOptions options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a quest toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
/// <param name="options">Options for the toast.</param>
|
||||
public void ShowQuest(SeString message, QuestToastOptions options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
this.questQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowQuest(byte[] bytes, QuestToastOptions options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleQuestToastDetour(
|
||||
manager!.Value,
|
||||
(int)options.Position,
|
||||
(IntPtr)ptr,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var options = new QuestToastOptions
|
||||
{
|
||||
Position = (QuestToastPosition)position,
|
||||
DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic,
|
||||
IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1,
|
||||
PlaySound = playSound == 1,
|
||||
};
|
||||
|
||||
this.QuestToast?.Invoke(ref str, ref options, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showQuestToastHook.Original(
|
||||
manager,
|
||||
(int)options.Position,
|
||||
(IntPtr)message,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options)
|
||||
{
|
||||
return options.DisplayCheckmark
|
||||
? (QuestToastCheckmarkMagic, options.IconId)
|
||||
: (options.IconId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles error toasts.
|
||||
/// </summary>
|
||||
public sealed partial class ToastGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Show an error toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
public void ShowError(string message)
|
||||
{
|
||||
this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show an error toast message with the given content.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to be shown.</param>
|
||||
public void ShowError(SeString message)
|
||||
{
|
||||
this.errorQueue.Enqueue(message.Encode());
|
||||
}
|
||||
|
||||
private void ShowError(byte[] bytes)
|
||||
{
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
|
||||
this.ErrorToast?.Invoke(ref str, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
public class ToastGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||
/// Gets the address of the native ShowNormalToast method.
|
||||
/// </summary>
|
||||
public class ToastGuiAddressResolver : BaseAddressResolver
|
||||
public IntPtr ShowNormalToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowQuestToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowQuestToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ShowErrorToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowErrorToast { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowNormalToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowNormalToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowQuestToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowQuestToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ShowErrorToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowErrorToast { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0");
|
||||
}
|
||||
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents options that can be used with the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
public sealed class ToastOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents options that can be used with the <see cref="ToastGui"/> class.
|
||||
/// Gets or sets the position of the toast on the screen.
|
||||
/// </summary>
|
||||
public sealed class ToastOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the toast on the screen.
|
||||
/// </summary>
|
||||
public ToastPosition Position { get; set; } = ToastPosition.Bottom;
|
||||
public ToastPosition Position { get; set; } = ToastPosition.Bottom;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the speed of the toast.
|
||||
/// </summary>
|
||||
public ToastSpeed Speed { get; set; } = ToastSpeed.Slow;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the speed of the toast.
|
||||
/// </summary>
|
||||
public ToastSpeed Speed { get; set; } = ToastSpeed.Slow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// The positioning of native toast windows.
|
||||
/// </summary>
|
||||
public enum ToastPosition : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The positioning of native toast windows.
|
||||
/// The toast will be towards the bottom.
|
||||
/// </summary>
|
||||
public enum ToastPosition : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The toast will be towards the bottom.
|
||||
/// </summary>
|
||||
Bottom = 0,
|
||||
Bottom = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will be towards the top.
|
||||
/// </summary>
|
||||
Top = 1,
|
||||
}
|
||||
/// <summary>
|
||||
/// The toast will be towards the top.
|
||||
/// </summary>
|
||||
Top = 1,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
namespace Dalamud.Game.Gui.Toast
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// The speed at which native toast windows will persist.
|
||||
/// </summary>
|
||||
public enum ToastSpeed : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The speed at which native toast windows will persist.
|
||||
/// The toast will take longer to disappear (around four seconds).
|
||||
/// </summary>
|
||||
public enum ToastSpeed : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The toast will take longer to disappear (around four seconds).
|
||||
/// </summary>
|
||||
Slow = 0,
|
||||
Slow = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The toast will disappear more quickly (around two seconds).
|
||||
/// </summary>
|
||||
Fast = 1,
|
||||
}
|
||||
/// <summary>
|
||||
/// The toast will disappear more quickly (around two seconds).
|
||||
/// </summary>
|
||||
Fast = 1,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue