diff --git a/XivCommon/Functions/BattleTalk.cs b/XivCommon/Functions/BattleTalk.cs index 89b0e2a..c712f1a 100755 --- a/XivCommon/Functions/BattleTalk.cs +++ b/XivCommon/Functions/BattleTalk.cs @@ -10,6 +10,10 @@ namespace XivCommon.Functions { /// The class containing BattleTalk functionality /// public class BattleTalk : IDisposable { + private static class Signatures { + internal const string AddBattleTalk = "48 89 5C 24 ?? 57 48 83 EC 50 48 8B 01 49 8B D8 0F 29 74 24 ?? 48 8B FA 0F 28 F3 FF 50 40 C7 44 24 ?? ?? ?? ?? ??"; + } + private GameFunctions Functions { get; } private SeStringManager SeStringManager { get; } private bool HookEnabled { get; } @@ -31,7 +35,7 @@ namespace XivCommon.Functions { private delegate byte AddBattleTalkDelegate(IntPtr uiModule, IntPtr sender, IntPtr message, float duration, byte style); - private AddBattleTalkDelegate AddBattleTalk { get; } + private AddBattleTalkDelegate? AddBattleTalk { get; } private Hook? AddBattleTalkHook { get; } internal BattleTalk(GameFunctions functions, SigScanner scanner, SeStringManager seStringManager, bool hook) { @@ -39,15 +43,14 @@ namespace XivCommon.Functions { this.SeStringManager = seStringManager; this.HookEnabled = hook; - var addBattleTalkPtr = scanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 50 48 8B 01 49 8B D8 0F 29 74 24 ?? 48 8B FA 0F 28 F3 FF 50 40 C7 44 24 ?? ?? ?? ?? ??"); - this.AddBattleTalk = Marshal.GetDelegateForFunctionPointer(addBattleTalkPtr); + if (scanner.ScanTextSafe(Signatures.AddBattleTalk, out var addBattleTalkPtr, "battle talk")) { + this.AddBattleTalk = Marshal.GetDelegateForFunctionPointer(addBattleTalkPtr); - if (!this.HookEnabled) { - return; + if (this.HookEnabled) { + this.AddBattleTalkHook = new Hook(addBattleTalkPtr, new AddBattleTalkDelegate(this.AddBattleTalkDetour)); + this.AddBattleTalkHook.Enable(); + } } - - this.AddBattleTalkHook = new Hook(addBattleTalkPtr, new AddBattleTalkDelegate(this.AddBattleTalkDetour)); - this.AddBattleTalkHook.Enable(); } /// @@ -108,6 +111,7 @@ namespace XivCommon.Functions { /// The message to show in the window /// Optional options for the window /// If sender or message are empty + /// If the signature for this function could not be found public void Show(SeString sender, SeString message, BattleTalkOptions? options = null) { this.Show(sender.Encode(), message.Encode(), options); } @@ -121,6 +125,10 @@ namespace XivCommon.Functions { throw new ArgumentException("message cannot be empty", nameof(message)); } + if (this.AddBattleTalk == null) { + throw new InvalidOperationException("Signature for battle talk could not be found"); + } + options ??= new BattleTalkOptions(); var uiModule = this.Functions.GetUiModule(); diff --git a/XivCommon/Functions/Chat.cs b/XivCommon/Functions/Chat.cs index b15dc4d..4a0e6c4 100755 --- a/XivCommon/Functions/Chat.cs +++ b/XivCommon/Functions/Chat.cs @@ -9,24 +9,34 @@ namespace XivCommon.Functions { /// A class containing chat functionality /// public class Chat { + private static class Signatures { + internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9"; + } + private GameFunctions Functions { get; } private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4); - private ProcessChatBoxDelegate ProcessChatBox { get; } + private ProcessChatBoxDelegate? ProcessChatBox { get; } internal Chat(GameFunctions functions, SigScanner scanner) { this.Functions = functions; - var processChatBoxPtr = scanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9"); - this.ProcessChatBox = Marshal.GetDelegateForFunctionPointer(processChatBoxPtr); + if (scanner.ScanTextSafe(Signatures.SendChat, out var processChatBoxPtr, "chat sending")) { + this.ProcessChatBox = Marshal.GetDelegateForFunctionPointer(processChatBoxPtr); + } } /// /// Send a given message to the chat box. This can send chat to the server. /// /// Message to send + /// If the signature for this function could not be found public void SendMessage(string message) { + if (this.ProcessChatBox == null) { + throw new InvalidOperationException("Could not find signature for chat sending"); + } + var uiModule = this.Functions.GetUiModule(); using var payload = new ChatPayload(message); diff --git a/XivCommon/Functions/ChatBubbles.cs b/XivCommon/Functions/ChatBubbles.cs index 55c7f37..fa6d96f 100755 --- a/XivCommon/Functions/ChatBubbles.cs +++ b/XivCommon/Functions/ChatBubbles.cs @@ -11,6 +11,11 @@ namespace XivCommon.Functions { /// Class containing chat bubble events and functions /// public class ChatBubbles : IDisposable { + private static class Signatures { + internal const string ChatBubbleOpen = "E8 ?? ?? ?? ?? 80 BF ?? ?? ?? ?? ?? C7 07 ?? ?? ?? ??"; + internal const string ChatBubbleUpdate = "48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C"; + } + private Dalamud.Dalamud Dalamud { get; } private SeStringManager SeStringManager { get; } @@ -60,16 +65,18 @@ namespace XivCommon.Functions { return; } - var openPtr = scanner.ScanText("E8 ?? ?? ?? ?? 80 BF ?? ?? ?? ?? ?? C7 07 ?? ?? ?? ??"); - this.OpenChatBubbleHook = new Hook(openPtr, new OpenChatBubbleDelegate(this.OpenChatBubbleDetour)); - this.OpenChatBubbleHook.Enable(); - - var updatePtr = scanner.ScanText("48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C"); - unsafe { - this.UpdateChatBubbleHook = new Hook(updatePtr + 9, new UpdateChatBubbleDelegate(this.UpdateChatBubbleDetour)); + if (scanner.ScanTextSafe(Signatures.ChatBubbleOpen, out var openPtr, "chat bubbles open")) { + this.OpenChatBubbleHook = new Hook(openPtr, new OpenChatBubbleDelegate(this.OpenChatBubbleDetour)); + this.OpenChatBubbleHook.Enable(); } - this.UpdateChatBubbleHook.Enable(); + if (scanner.ScanTextSafe(Signatures.ChatBubbleUpdate, out var updatePtr, "chat bubbles update")) { + unsafe { + this.UpdateChatBubbleHook = new Hook(updatePtr + 9, new UpdateChatBubbleDelegate(this.UpdateChatBubbleDetour)); + } + + this.UpdateChatBubbleHook.Enable(); + } } /// diff --git a/XivCommon/Functions/Examine.cs b/XivCommon/Functions/Examine.cs index 2757971..139cc3c 100755 --- a/XivCommon/Functions/Examine.cs +++ b/XivCommon/Functions/Examine.cs @@ -9,20 +9,25 @@ namespace XivCommon.Functions { /// Class containing examine functions /// public class Examine { + private static class Signatures { + internal const string RequestCharacterInfo = "48 89 5C 24 ?? 57 48 83 EC 40 BA ?? ?? ?? ?? 48 8B D9 E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 74 16"; + } + private GameFunctions Functions { get; } private delegate IntPtr GetAgentModuleDelegate(IntPtr basePtr); private delegate long RequestCharInfoDelegate(IntPtr ptr); - private RequestCharInfoDelegate RequestCharacterInfo { get; } + private RequestCharInfoDelegate? RequestCharacterInfo { get; } internal Examine(GameFunctions functions, SigScanner scanner) { this.Functions = functions; // got this by checking what accesses rciData below - var rciPtr = scanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 40 BA ?? ?? ?? ?? 48 8B D9 E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 74 16"); - this.RequestCharacterInfo = Marshal.GetDelegateForFunctionPointer(rciPtr); + if (scanner.ScanTextSafe(Signatures.RequestCharacterInfo, out var rciPtr, "Examine")) { + this.RequestCharacterInfo = Marshal.GetDelegateForFunctionPointer(rciPtr); + } } private static IntPtr FollowPtrChain(IntPtr start, IEnumerable offsets) { @@ -40,6 +45,7 @@ namespace XivCommon.Functions { /// Opens the Examine window for the specified actor. /// /// Actor to open window for + /// If the signature for this function could not be found public void OpenExamineWindow(Actor actor) { this.OpenExamineWindow(actor.ActorId); } @@ -48,7 +54,12 @@ namespace XivCommon.Functions { /// Opens the Examine window for the actor with the specified ID. /// /// Actor ID to open window for + /// If the signature for this function could not be found public void OpenExamineWindow(int actorId) { + if (this.RequestCharacterInfo == null) { + throw new InvalidOperationException("Could not find signature for Examine function"); + } + // NOTES LAST UPDATED: 5.45 // offsets and stuff come from the beginning of case 0x2c (around line 621 in IDA) diff --git a/XivCommon/Functions/PartyFinder.cs b/XivCommon/Functions/PartyFinder.cs index ee8edf6..549f653 100755 --- a/XivCommon/Functions/PartyFinder.cs +++ b/XivCommon/Functions/PartyFinder.cs @@ -12,11 +12,16 @@ namespace XivCommon.Functions { /// A class containing Party Finder functionality /// public class PartyFinder : IDisposable { + private static class Signatures { + internal const string RequestListings = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 0F 10 81 ?? ?? ?? ??"; + internal const string JoinCrossParty = "E8 ?? ?? ?? ?? 0F B7 47 28"; + } + private delegate byte RequestPartyFinderListingsDelegate(IntPtr agent, byte categoryIdx); private delegate IntPtr JoinPfDelegate(IntPtr manager, IntPtr a2, int type, IntPtr packetData, uint a5); - private RequestPartyFinderListingsDelegate RequestPartyFinderListings { get; } + private RequestPartyFinderListingsDelegate? RequestPartyFinderListings { get; } private Hook? RequestPfListingsHook { get; } private Hook? JoinPfHook { get; } @@ -48,21 +53,22 @@ namespace XivCommon.Functions { this.ListingsEnabled = hooks.HasFlag(Hooks.PartyFinderListings); this.JoinsEnabled = hooks.HasFlag(Hooks.PartyFinderJoins); - var requestPfPtr = scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 40 0F 10 81 ?? ?? ?? ??"); + if (scanner.ScanTextSafe(Signatures.RequestListings, out var requestPfPtr, "Party Finder listings")) { + this.RequestPartyFinderListings = Marshal.GetDelegateForFunctionPointer(requestPfPtr); - this.RequestPartyFinderListings = Marshal.GetDelegateForFunctionPointer(requestPfPtr); - - if (this.ListingsEnabled) { - this.RequestPfListingsHook = new Hook(requestPfPtr, new RequestPartyFinderListingsDelegate(this.OnRequestPartyFinderListings)); - this.RequestPfListingsHook.Enable(); + if (this.ListingsEnabled) { + this.RequestPfListingsHook = new Hook(requestPfPtr, new RequestPartyFinderListingsDelegate(this.OnRequestPartyFinderListings)); + this.RequestPfListingsHook.Enable(); + } } if (this.JoinsEnabled) { - var joinPtr = scanner.ScanText("E8 ?? ?? ?? ?? 0F B7 47 28"); - this.JoinPfHook = new Hook(joinPtr, new JoinPfDelegate(this.JoinPfDetour)); - this.JoinPfHook.Enable(); + if (scanner.ScanTextSafe(Signatures.JoinCrossParty, out var joinPtr, "Party Finder joins")) { + this.JoinPfHook = new Hook(joinPtr, new JoinPfDelegate(this.JoinPfDetour)); + this.JoinPfHook.Enable(); - this.PartyFinderGui.ReceiveListing += this.ReceiveListing; + this.PartyFinderGui.ReceiveListing += this.ReceiveListing; + } } } @@ -121,8 +127,12 @@ namespace XivCommon.Functions { /// This maintains the currently selected category. /// /// - /// If the hook is not enabled + /// If the hook is not enabled or if the signature for this function could not be found public void RefreshListings() { + if (this.RequestPartyFinderListings == null) { + throw new InvalidOperationException("Could not find signature for Party Finder listings"); + } + if (!this.ListingsEnabled) { throw new InvalidOperationException("PartyFinder hooks are not enabled"); } diff --git a/XivCommon/Functions/Talk.cs b/XivCommon/Functions/Talk.cs index a28f387..d6af404 100755 --- a/XivCommon/Functions/Talk.cs +++ b/XivCommon/Functions/Talk.cs @@ -10,6 +10,11 @@ namespace XivCommon.Functions { /// Class containing Talk events /// public class Talk : IDisposable { + private static class Signatures { + internal const string SetAtkValue = "E8 ?? ?? ?? ?? 41 03 ED"; + internal const string ShowMessageBox = "4C 8B DC 55 57 41 55 49 8D 6B 98"; + } + // Updated: 5.5 private const int TextOffset = 0; private const int NameOffset = 0x10; @@ -23,7 +28,7 @@ namespace XivCommon.Functions { private delegate IntPtr SetAtkValueStringDelegate(IntPtr atkValue, IntPtr text); - private SetAtkValueStringDelegate SetAtkValueString { get; } + private SetAtkValueStringDelegate SetAtkValueString { get; } = null!; /// /// The delegate for Talk events. @@ -43,16 +48,20 @@ namespace XivCommon.Functions { internal Talk(SigScanner scanner, SeStringManager manager, bool hooksEnabled) { this.SeStringManager = manager; - var setAtkPtr = scanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); - this.SetAtkValueString = Marshal.GetDelegateForFunctionPointer(setAtkPtr); + if (scanner.ScanTextSafe(Signatures.SetAtkValue, out var setAtkPtr, "Talk - set atk value")) { + this.SetAtkValueString = Marshal.GetDelegateForFunctionPointer(setAtkPtr); + } else { + return; + } if (!hooksEnabled) { return; } - var showMessageBoxPtr = scanner.ScanText("4C 8B DC 55 57 41 55 49 8D 6B 98"); - this.AddonTalkV45Hook = new Hook(showMessageBoxPtr, new AddonTalkV45Delegate(this.AddonTalkV45Detour)); - this.AddonTalkV45Hook.Enable(); + if (scanner.ScanTextSafe(Signatures.ShowMessageBox, out var showMessageBoxPtr, "Talk")) { + this.AddonTalkV45Hook = new Hook(showMessageBoxPtr, new AddonTalkV45Delegate(this.AddonTalkV45Detour)); + this.AddonTalkV45Hook.Enable(); + } } /// diff --git a/XivCommon/SigScannerExt.cs b/XivCommon/SigScannerExt.cs new file mode 100755 index 0000000..e8e24e5 --- /dev/null +++ b/XivCommon/SigScannerExt.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using Dalamud.Game; +using Dalamud.Plugin; + +namespace XivCommon { + internal static class SigScannerExt { + /// + /// Scan for a signature in memory. + /// + /// SigScanner to use for scanning + /// signature to search for + /// pointer where signature was found or if not found + /// name of this signature - if specified, a warning will be printed if the signature could not be found + /// true if signature was found + internal static bool ScanTextSafe(this SigScanner scanner, string sig, out IntPtr result, string? name = null) { + result = IntPtr.Zero; + try { + result = scanner.ScanText(sig); + return true; + } catch (KeyNotFoundException) { + if (name != null) { + Util.PrintMissingSig(name); + } + + return false; + } + } + } +} diff --git a/XivCommon/Util.cs b/XivCommon/Util.cs index 10603bd..440add1 100755 --- a/XivCommon/Util.cs +++ b/XivCommon/Util.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using Dalamud.Plugin; namespace XivCommon { internal static class Util { - public static byte[] Terminate(this byte[] array) { + internal static byte[] Terminate(this byte[] array) { var terminated = new byte[array.Length + 1]; Array.Copy(array, terminated, array.Length); terminated[terminated.Length - 1] = 0; @@ -11,7 +12,7 @@ namespace XivCommon { return terminated; } - public static unsafe byte[] ReadTerminated(IntPtr memory) { + internal static unsafe byte[] ReadTerminated(IntPtr memory) { var buf = new List(); var ptr = (byte*) memory; @@ -22,5 +23,9 @@ namespace XivCommon { return buf.ToArray(); } + + internal static void PrintMissingSig(string name) { + PluginLog.LogWarning($"Could not find signature for {name}. This functionality will be disabled."); + } } }