From ab3b35dfc1e3cdd03358d4c3eb0d58c20d4696d7 Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 6 Apr 2021 05:09:20 -0400 Subject: [PATCH 1/5] feat: add functions for toasting --- Dalamud/Game/Internal/Gui/GameGui.cs | 12 +++++ .../Internal/Gui/GameGuiAddressResolver.cs | 4 ++ Dalamud/Game/Internal/Gui/ToastGui.cs | 51 +++++++++++++++++++ .../Internal/Gui/ToastGuiAddressResolver.cs | 14 +++++ 4 files changed, 81 insertions(+) create mode 100755 Dalamud/Game/Internal/Gui/ToastGui.cs create mode 100755 Dalamud/Game/Internal/Gui/ToastGuiAddressResolver.cs diff --git a/Dalamud/Game/Internal/Gui/GameGui.cs b/Dalamud/Game/Internal/Gui/GameGui.cs index a1c53c504..6cbbe37f7 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,11 @@ namespace Dalamud.Game.Internal.Gui { return this.toggleUiHideHook.Original(thisPtr, unknownByte); } + public IntPtr GetUIModule() + { + return this.getUiModule(Marshal.ReadIntPtr(Address.GetUIModulePtr)); + } + /// /// Gets the pointer to the UI Object with the given name and index. /// 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..21c6e92a4 --- /dev/null +++ b/Dalamud/Game/Internal/Gui/ToastGui.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using Dalamud.Game.Text.SeStringHandling; + +namespace Dalamud.Game.Internal.Gui +{ + public class ToastGui + { + private Dalamud Dalamud { get; } + + private ToastGuiAddressResolver Address { get; } + + private delegate IntPtr ShowToastDelegate(IntPtr manager, IntPtr text, int layer, byte bool1, byte bool2, int logMessageId); + + private readonly ShowToastDelegate showToast; + + public ToastGui(SigScanner scanner, Dalamud dalamud) + { + Dalamud = dalamud; + + Address = new ToastGuiAddressResolver(); + Address.Setup(scanner); + + this.showToast = Marshal.GetDelegateForFunctionPointer(Address.ShowToast); + } + + public void Show(string message) + { + this.Show(Encoding.UTF8.GetBytes(message)); + } + + public void Show(SeString message) + { + this.Show(message.Encode()); + } + + private void Show(byte[] bytes) + { + var manager = Dalamud.Framework.Gui.GetUIModule(); + + unsafe + { + fixed (byte* ptr = bytes) + { + this.showToast(manager, (IntPtr) ptr, 5, 0, 1, 0); + } + } + } + } +} 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 ?? ?? ?? ?? ??"); + } + } +} From 94d27b24287f7559c80d522539673057f7cddc29 Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 6 Apr 2021 05:23:42 -0400 Subject: [PATCH 2/5] docs: add missing docs --- Dalamud/Game/Internal/Gui/GameGui.cs | 4 ++++ Dalamud/Game/Internal/Gui/ToastGui.cs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/Dalamud/Game/Internal/Gui/GameGui.cs b/Dalamud/Game/Internal/Gui/GameGui.cs index 6cbbe37f7..70ba8f8cd 100644 --- a/Dalamud/Game/Internal/Gui/GameGui.cs +++ b/Dalamud/Game/Internal/Gui/GameGui.cs @@ -418,6 +418,10 @@ 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)); diff --git a/Dalamud/Game/Internal/Gui/ToastGui.cs b/Dalamud/Game/Internal/Gui/ToastGui.cs index 21c6e92a4..89b166c11 100755 --- a/Dalamud/Game/Internal/Gui/ToastGui.cs +++ b/Dalamud/Game/Internal/Gui/ToastGui.cs @@ -25,11 +25,19 @@ namespace Dalamud.Game.Internal.Gui this.showToast = Marshal.GetDelegateForFunctionPointer(Address.ShowToast); } + /// + /// Show a toast message with the given content. + /// + /// The message to be shown public void Show(string message) { this.Show(Encoding.UTF8.GetBytes(message)); } + /// + /// Show a toast message with the given content. + /// + /// The message to be shown public void Show(SeString message) { this.Show(message.Encode()); From 5845af4fe3aab91d3dead1ca7d112943615ddd30 Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 6 Apr 2021 06:23:13 -0400 Subject: [PATCH 3/5] feat: add toast queue and event --- Dalamud/Game/Internal/Framework.cs | 1 + Dalamud/Game/Internal/Gui/GameGui.cs | 2 + Dalamud/Game/Internal/Gui/ToastGui.cs | 99 +++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 12 deletions(-) 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 70ba8f8cd..cc8cb5b64 100644 --- a/Dalamud/Game/Internal/Gui/GameGui.cs +++ b/Dalamud/Game/Internal/Gui/GameGui.cs @@ -452,6 +452,7 @@ namespace Dalamud.Game.Internal.Gui { public void Enable() { Chat.Enable(); + Toast.Enable(); PartyFinder.Enable(); this.setGlobalBgmHook.Enable(); this.handleItemHoverHook.Enable(); @@ -463,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/ToastGui.cs b/Dalamud/Game/Internal/Gui/ToastGui.cs index 89b166c11..fefce42b7 100755 --- a/Dalamud/Game/Internal/Gui/ToastGui.cs +++ b/Dalamud/Game/Internal/Gui/ToastGui.cs @@ -1,28 +1,60 @@ 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 + 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 delegate IntPtr ShowToastDelegate(IntPtr manager, IntPtr text, int layer, byte bool1, byte bool2, int logMessageId); + private Queue ToastQueue { get; } = new Queue(); - private readonly ShowToastDelegate showToast; public ToastGui(SigScanner scanner, Dalamud dalamud) { - Dalamud = dalamud; + this.Dalamud = dalamud; - Address = new ToastGuiAddressResolver(); - Address.Setup(scanner); + this.Address = new ToastGuiAddressResolver(); + this.Address.Setup(scanner); - this.showToast = Marshal.GetDelegateForFunctionPointer(Address.ShowToast); + 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(); } /// @@ -31,7 +63,7 @@ namespace Dalamud.Game.Internal.Gui /// The message to be shown public void Show(string message) { - this.Show(Encoding.UTF8.GetBytes(message)); + this.ToastQueue.Enqueue(Encoding.UTF8.GetBytes(message)); } /// @@ -40,20 +72,63 @@ namespace Dalamud.Game.Internal.Gui /// The message to be shown public void Show(SeString message) { - this.Show(message.Encode()); + 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 = Dalamud.Framework.Gui.GetUIModule(); + 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 = bytes) + fixed (byte* ptr = terminated) { - this.showToast(manager, (IntPtr) ptr, 5, 0, 1, 0); + this.HandleToastDetour(manager, (IntPtr)ptr, 5, 0, 1, 0); } } } + + private IntPtr HandleToastDetour(IntPtr manager, IntPtr text, int layer, byte bool1, byte bool2, int logMessageId) + { + // 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 or show the toast + return isHandled + ? IntPtr.Zero + : this.showToastHook.Original(manager, text, layer, bool1, bool2, logMessageId); + } } } From 94d642d1b5f359fe683af3ce9dbab747997106fa Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 6 Apr 2021 06:31:23 -0400 Subject: [PATCH 4/5] fix: use potentially modified toast message --- Dalamud/Game/Internal/Gui/ToastGui.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/Internal/Gui/ToastGui.cs b/Dalamud/Game/Internal/Gui/ToastGui.cs index fefce42b7..df7ca0d39 100755 --- a/Dalamud/Game/Internal/Gui/ToastGui.cs +++ b/Dalamud/Game/Internal/Gui/ToastGui.cs @@ -125,10 +125,24 @@ namespace Dalamud.Game.Internal.Gui this.OnToast?.Invoke(ref str, ref isHandled); - // do nothing if handled or show the toast - return isHandled - ? IntPtr.Zero - : this.showToastHook.Original(manager, text, layer, bool1, bool2, logMessageId); + // 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); + } + } } } } From 90ed4510aaa743aacd7b00f456261f8b508b7226 Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 6 Apr 2021 06:35:28 -0400 Subject: [PATCH 5/5] fix: add null check just in case --- Dalamud/Game/Internal/Gui/ToastGui.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dalamud/Game/Internal/Gui/ToastGui.cs b/Dalamud/Game/Internal/Gui/ToastGui.cs index df7ca0d39..c98980477 100755 --- a/Dalamud/Game/Internal/Gui/ToastGui.cs +++ b/Dalamud/Game/Internal/Gui/ToastGui.cs @@ -107,6 +107,11 @@ namespace Dalamud.Game.Internal.Gui 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