From e930c9876907b2bc0cdbbf5dc179e6ff3ad4052d Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:39:09 +0200 Subject: [PATCH] feat: add aers menu thingy --- Dalamud/Dalamud.cs | 13 +- Dalamud/Game/Addon/DalamudSystemMenu.cs | 168 ++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Game/Addon/DalamudSystemMenu.cs diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 84b125171..122eedc03 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -6,6 +6,7 @@ using System.Threading; using Dalamud.Configuration; using Dalamud.Data; using Dalamud.Game; +using Dalamud.Game.Addon; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.ClientState; using Dalamud.Game.Command; @@ -164,6 +165,11 @@ namespace Dalamud /// internal NetworkHandlers NetworkHandlers { get; private set; } + /// + /// Gets subsystem responsible for adding the Dalamud menu items to the game's system menu. + /// + internal DalamudSystemMenu SystemMenu { get; private set; } + #endregion /// @@ -198,7 +204,7 @@ namespace Dalamud this.AntiDebug = new AntiDebug(this.SigScanner); #if DEBUG - AntiDebug.Enable(); + this.AntiDebug.Enable(); #endif Log.Information("[START] AntiDebug OK!"); @@ -315,6 +321,9 @@ namespace Dalamud Log.Information("[START] DUI OK!"); + this.SystemMenu = new DalamudSystemMenu(this); + this.SystemMenu.Enable(); + this.IsReady = true; Troubleshooting.LogTroubleshooting(this, isInterfaceLoaded); @@ -404,6 +413,8 @@ namespace Dalamud this.AntiDebug?.Dispose(); + this.SystemMenu?.Dispose(); + Log.Debug("Dalamud::Dispose() OK!"); } catch (Exception ex) diff --git a/Dalamud/Game/Addon/DalamudSystemMenu.cs b/Dalamud/Game/Addon/DalamudSystemMenu.cs new file mode 100644 index 000000000..e67fc2132 --- /dev/null +++ b/Dalamud/Game/Addon/DalamudSystemMenu.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Component.GUI; + +using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; + +namespace Dalamud.Game.Addon +{ + internal unsafe class DalamudSystemMenu + { + private readonly Dalamud dalamud; + + private delegate void AgentHudOpenSystemMenuProtoype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); + + private Hook hookAgentHudOpenSystemMenu; + + private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); + + private AtkValueChangeType atkValueChangeType; + + private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes); + + private AtkValueSetString atkValueSetString; + + private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); + + // TODO: Make this into events in Framework.Gui + private Hook hookUiModuleRequestMainCommand; + + /// + /// Initializes a new instance of the class. + /// + /// The dalamud instance to act on. + public DalamudSystemMenu(Dalamud dalamud) + { + this.dalamud = dalamud; + + var openSystemMenuAddress = this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); + + this.hookAgentHudOpenSystemMenu = new Hook( + openSystemMenuAddress, + new AgentHudOpenSystemMenuProtoype(this.AgentHudOpenSystemMenuDetour), + this); + + var atkValueChangeTypeAddress = + this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); + this.atkValueChangeType = + Marshal.GetDelegateForFunctionPointer(atkValueChangeTypeAddress); + + var atkValueSetStringAddress = + this.dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); + this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress); + + var uiModuleRequestMainCommmandAddress = this.dalamud.SigScanner.ScanText( + "40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); + this.hookUiModuleRequestMainCommand = new Hook( + uiModuleRequestMainCommmandAddress, + new UiModuleRequestMainCommand(this.UiModuleRequestMainCommandDetour), + this); + } + + public void Enable() + { + this.hookAgentHudOpenSystemMenu.Enable(); + this.hookUiModuleRequestMainCommand.Enable(); + } + + private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) + { + // the max size (hardcoded) is 0xE/15, but the system menu currently uses 0xC/12 + // this is a just in case that doesnt really matter + // see if we can add 2 entries + if (menuSize >= 0xD) + { + hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } + + // atkValueArgs is actually an array of AtkValues used as args. all their UI code works like this. + // in this case, menu size is stored in atkValueArgs[4], and the next 15 slots are the MainCommand + // the 15 slots after that, if they exist, are the entry names, but they are otherwise pulled from MainCommand EXD + // reference the original function for more details :) + + // step 1) move all the current menu items down so we can put Dalamud at the top like it deserves + atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int + atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int); + + for (uint i = menuSize+2; i > 1; i--) + { + var curEntry = &atkValueArgs[i + 5 - 2]; + var nextEntry = &atkValueArgs[i + 5]; + + nextEntry->Int = curEntry->Int; + } + + // step 2) set our new entries to dummy commands + var firstEntry = &atkValueArgs[5]; + firstEntry->Int = 69420; + var secondEntry = &atkValueArgs[6]; + secondEntry->Int = 69421; + + // step 3) create strings for them + // since the game first checks for strings in the AtkValue argument before pulling them from the exd, if we create strings we dont have to worry + // about hooking the exd reader, thank god + var firstStringEntry = &atkValueArgs[5 + 15]; + atkValueChangeType(firstStringEntry, ValueType.String); + var secondStringEntry = &atkValueArgs[6 + 15]; + atkValueChangeType(secondStringEntry, ValueType.String); + + // do this the most terrible way possible since im lazy + var bytes = stackalloc byte[17]; + Marshal.Copy(System.Text.Encoding.ASCII.GetBytes("Dalamud Settings"), 0, new IntPtr(bytes), 16); + bytes[16] = 0x0; + + atkValueSetString(firstStringEntry, bytes); // this allocs the string properly using the game's allocators and copies it, so we dont have to worry about memory fuckups + + var bytes2 = stackalloc byte[16]; + Marshal.Copy(System.Text.Encoding.ASCII.GetBytes("Dalamud Plugins"), 0, new IntPtr(bytes2), 15); + bytes2[15] = 0x0; + + atkValueSetString(secondStringEntry, bytes2); + + // open menu with new size + var sizeEntry = &atkValueArgs[4]; + sizeEntry->UInt = menuSize + 2; + + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); + } + + private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) + { + if (commandId == 69420) + { + this.dalamud.DalamudUi.OpenSettings(); + } + else if (commandId == 69421) + { + this.dalamud.DalamudUi.OpenPluginInstaller(); + } + else + { + this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); + } + } + + #region IDisposable Support + protected virtual void Dispose(bool disposing) + { + if (!disposing) return; + + this.hookAgentHudOpenSystemMenu.Dispose(); + this.hookUiModuleRequestMainCommand.Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +}