diff --git a/Dalamud/Game/Internal/Framework.cs b/Dalamud/Game/Internal/Framework.cs index cf314ba70..7deb56211 100644 --- a/Dalamud/Game/Internal/Framework.cs +++ b/Dalamud/Game/Internal/Framework.cs @@ -120,6 +120,7 @@ namespace Dalamud.Game.Internal { private bool HandleFrameworkUpdate(IntPtr framework) { try { Gui.Chat.UpdateQueue(this); + Gui.Toast.UpdateQueue(); Network.UpdateQueue(this); } catch (Exception ex) { Log.Error(ex, "Exception while handling Framework::Update hook."); diff --git a/Dalamud/Game/Internal/Gui/GameGui.cs b/Dalamud/Game/Internal/Gui/GameGui.cs index a1c53c504..cc8cb5b64 100644 --- a/Dalamud/Game/Internal/Gui/GameGui.cs +++ b/Dalamud/Game/Internal/Gui/GameGui.cs @@ -13,6 +13,7 @@ namespace Dalamud.Game.Internal.Gui { public ChatGui Chat { get; private set; } public PartyFinderGui PartyFinder { get; private set; } + public ToastGui Toast { get; private set; } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr SetGlobalBgmDelegate(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6); @@ -68,6 +69,9 @@ namespace Dalamud.Game.Internal.Gui { private delegate IntPtr GetUIObjectByNameDelegate(IntPtr thisPtr, string uiName, int index); private readonly GetUIObjectByNameDelegate getUIObjectByName; + private delegate IntPtr GetUiModuleDelegate(IntPtr basePtr); + private readonly GetUiModuleDelegate getUiModule; + public bool GameUiHidden { get; private set; } /// @@ -110,6 +114,7 @@ namespace Dalamud.Game.Internal.Gui { Chat = new ChatGui(Address.ChatManager, scanner, dalamud); PartyFinder = new PartyFinderGui(scanner, dalamud); + Toast = new ToastGui(scanner, dalamud); this.setGlobalBgmHook = new Hook(Address.SetGlobalBgm, @@ -146,6 +151,8 @@ namespace Dalamud.Game.Internal.Gui { this.GetBaseUIObject = Marshal.GetDelegateForFunctionPointer(Address.GetBaseUIObject); this.getUIObjectByName = Marshal.GetDelegateForFunctionPointer(Address.GetUIObjectByName); + + this.getUiModule = Marshal.GetDelegateForFunctionPointer(Address.GetUIModule); } private IntPtr HandleSetGlobalBgmDetour(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6) { @@ -411,6 +418,15 @@ namespace Dalamud.Game.Internal.Gui { return this.toggleUiHideHook.Original(thisPtr, unknownByte); } + /// + /// Gets a pointer to the game's UI module. + /// + /// IntPtr pointing to UI module + public IntPtr GetUIModule() + { + return this.getUiModule(Marshal.ReadIntPtr(Address.GetUIModulePtr)); + } + /// /// Gets the pointer to the UI Object with the given name and index. /// @@ -436,6 +452,7 @@ namespace Dalamud.Game.Internal.Gui { public void Enable() { Chat.Enable(); + Toast.Enable(); PartyFinder.Enable(); this.setGlobalBgmHook.Enable(); this.handleItemHoverHook.Enable(); @@ -447,6 +464,7 @@ namespace Dalamud.Game.Internal.Gui { public void Dispose() { Chat.Dispose(); + Toast.Dispose(); PartyFinder.Dispose(); this.setGlobalBgmHook.Dispose(); this.handleItemHoverHook.Dispose(); diff --git a/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs index 7a06ae264..ca617bc84 100644 --- a/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs @@ -19,6 +19,8 @@ namespace Dalamud.Game.Internal.Gui { public IntPtr ToggleUiHide { get; set; } public IntPtr GetBaseUIObject { get; private set; } public IntPtr GetUIObjectByName { get; private set; } + public IntPtr GetUIModule { get; private set; } + public IntPtr GetUIModulePtr { get; private set; } public GameGuiAddressResolver(IntPtr baseAddress) { BaseAddress = baseAddress; @@ -45,6 +47,8 @@ namespace Dalamud.Game.Internal.Gui { ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??"); GetBaseUIObject = sig.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF"); GetUIObjectByName = sig.ScanText("E8 ?? ?? ?? ?? 48 8B CF 48 89 87 ?? ?? 00 00 E8 ?? ?? ?? ?? 41 B8 01 00 00 00"); + GetUIModule = sig.ScanText("E8 ?? ?? ?? ?? 83 3B 01"); + GetUIModulePtr = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 10 E8 ?? ?? ?? ??"); } } } diff --git a/Dalamud/Game/Internal/Gui/ToastGui.cs b/Dalamud/Game/Internal/Gui/ToastGui.cs new file mode 100755 index 000000000..c98980477 --- /dev/null +++ b/Dalamud/Game/Internal/Gui/ToastGui.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Hooking; + +namespace Dalamud.Game.Internal.Gui +{ + public class ToastGui : IDisposable + { + #region Events + + public delegate void OnToastDelegate(ref SeString message, ref bool isHandled); + + /// + /// Event that will be fired when a toast is sent by the game or a plugin. + /// + public event OnToastDelegate OnToast; + + #endregion + + #region Hooks + + private readonly Hook showToastHook; + + #endregion + + private delegate IntPtr ShowToastDelegate(IntPtr manager, IntPtr text, int layer, byte bool1, byte bool2, int logMessageId); + + private Dalamud Dalamud { get; } + + private ToastGuiAddressResolver Address { get; } + + private Queue ToastQueue { get; } = new Queue(); + + + public ToastGui(SigScanner scanner, Dalamud dalamud) + { + this.Dalamud = dalamud; + + this.Address = new ToastGuiAddressResolver(); + this.Address.Setup(scanner); + + Marshal.GetDelegateForFunctionPointer(this.Address.ShowToast); + this.showToastHook = new Hook(this.Address.ShowToast, new ShowToastDelegate(this.HandleToastDetour)); + } + + public void Enable() + { + this.showToastHook.Enable(); + } + + public void Dispose() + { + this.showToastHook.Dispose(); + } + + /// + /// Show a toast message with the given content. + /// + /// The message to be shown + public void Show(string message) + { + this.ToastQueue.Enqueue(Encoding.UTF8.GetBytes(message)); + } + + /// + /// Show a toast message with the given content. + /// + /// The message to be shown + public void Show(SeString message) + { + this.ToastQueue.Enqueue(message.Encode()); + } + + /// + /// Process the toast queue. + /// + internal void UpdateQueue() + { + while (this.ToastQueue.Count > 0) + { + var message = this.ToastQueue.Dequeue(); + this.Show(message); + } + } + + private void Show(byte[] bytes) + { + var manager = this.Dalamud.Framework.Gui.GetUIModule(); + + // terminate the string + var terminated = new byte[bytes.Length + 1]; + Array.Copy(bytes, 0, terminated, 0, bytes.Length); + terminated[^1] = 0; + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleToastDetour(manager, (IntPtr)ptr, 5, 0, 1, 0); + } + } + } + + private IntPtr HandleToastDetour(IntPtr manager, IntPtr text, int layer, byte bool1, byte bool2, int logMessageId) + { + if (text == IntPtr.Zero) + { + return IntPtr.Zero; + } + + // get the message as an sestring + var bytes = new List(); + unsafe + { + var ptr = (byte*)text; + while (*ptr != 0) + { + bytes.Add(*ptr); + ptr += 1; + } + } + + // call events + var isHandled = false; + var str = this.Dalamud.SeStringManager.Parse(bytes.ToArray()); + + this.OnToast?.Invoke(ref str, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return IntPtr.Zero; + } + + var encoded = str.Encode(); + var terminated = new byte[encoded.Length + 1]; + Array.Copy(encoded, 0, terminated, 0, encoded.Length); + terminated[^1] = 0; + + unsafe + { + fixed (byte* message = terminated) + { + return this.showToastHook.Original(manager, (IntPtr)message, layer, bool1, bool2, logMessageId); + } + } + } + } +} diff --git a/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs b/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs new file mode 100755 index 000000000..e5ee511e1 --- /dev/null +++ b/Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs @@ -0,0 +1,14 @@ +using System; + +namespace Dalamud.Game.Internal.Gui +{ + public class ToastGuiAddressResolver : BaseAddressResolver + { + public IntPtr ShowToast { get; private set; } + + protected override void Setup64Bit(SigScanner sig) + { + ShowToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); + } + } +}