From 23f3daf0019b48154a4f37f1e8325a7929cf34ad Mon Sep 17 00:00:00 2001 From: Caraxi Date: Mon, 9 Oct 2023 20:18:32 +1030 Subject: [PATCH] [`Improved Crafting Log`] Disable for Everyone, Try to fix some things, Experimental --- ...aftingButton.cs => ImprovedCraftingLog.cs} | 178 +++++++----------- 1 file changed, 65 insertions(+), 113 deletions(-) rename Tweaks/UiAdjustment/{StopCraftingButton.cs => ImprovedCraftingLog.cs} (50%) diff --git a/Tweaks/UiAdjustment/StopCraftingButton.cs b/Tweaks/UiAdjustment/ImprovedCraftingLog.cs similarity index 50% rename from Tweaks/UiAdjustment/StopCraftingButton.cs rename to Tweaks/UiAdjustment/ImprovedCraftingLog.cs index 50de034e..576164fd 100644 --- a/Tweaks/UiAdjustment/StopCraftingButton.cs +++ b/Tweaks/UiAdjustment/ImprovedCraftingLog.cs @@ -1,101 +1,53 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; +using System.Diagnostics; using Dalamud.Game.Command; using Dalamud.Utility; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Misc; -using FFXIVClientStructs.FFXIV.Component.GUI; using Lumina.Excel.GeneratedSheets; using SimpleTweaksPlugin.Events; +using SimpleTweaksPlugin.TweakSystem; using SimpleTweaksPlugin.Utility; namespace SimpleTweaksPlugin.Tweaks.UiAdjustment; // E8 ?? ?? ?? ?? 48 8B 4B 10 33 FF C6 83 -public unsafe class StopCraftingButton : UiAdjustments.SubTweak { - - private Stopwatch removeFrameworkUpdateEventStopwatch = new(); +[TweakName("Improved Crafting Log")] +[TweakDescription("Modifies the Synthesize button in the Crafting Log to switch job or stand up from the crafting position, allowing you to stop crafting without closing the crafting log.")] +[Changelog("1.8.2.1", "Fixed a potential crash in specific circumstances.")] +[Changelog(UnreleasedVersion, "Made attempt to fix some issues", "Tweak has been disabled for everyone and marked as experimental.")] +public unsafe class ImprovedCraftingLog : Tweak { + public override bool Experimental => true; + private readonly Stopwatch removeFrameworkUpdateEventStopwatch = new(); private bool standingUp; - - public override string Name => "Improved Crafting Log"; - - public override uint Version => 2; - public override string Description => "Modifies the Synthesize button in the Crafting Log to switch job or stand up from the crafting position, allowing you to stop crafting without closing the crafting log."; + private delegate void* ClickSynthesisButton(AddonRecipeNote* a1, void* a2); - private delegate void* ClickSynthesisButton(void* a1, void* a2); + private delegate* passThroughFunction; + private delegate void* CancelCrafting(RecipeNote* recipeNote); - private delegate* passthroughFunction; - private delegate void* CancelCrafting(CraftingState* craftingState, void* a2); - private HookWrapper cancelCraftingHook; + [TweakHook, Signature("E8 ?? ?? ?? ?? 48 8B 4B 10 33 FF C6 83", DetourName = nameof(CancelCraftingDetour))] + private readonly HookWrapper cancelCraftingHook = null!; - private HookWrapper clickSysnthesisButtonHook; - - [StructLayout(LayoutKind.Explicit)] - private struct CraftingState { - [FieldOffset(0x144)] public ushort Unknown; - } - - [StructLayout(LayoutKind.Explicit, Size = 0xC04)] - public struct CraftingLogNumberArray { - [FieldOffset(140 * 4)] public int RecipeCount; - [FieldOffset(141 * 4)] public int Unknown140; - [FieldOffset(142 * 4)] public RecipeList Recipes; - - [StructLayout(LayoutKind.Sequential, Size = 4 * 5 * 100)] - public struct RecipeList { - private fixed byte _data[4 * 5 * 100]; - public RecipeEntry* this[int i] { - get { - if (i is < 0 or > 99) return null; - fixed (byte* p = _data) { - return (RecipeEntry*)(p + sizeof(RecipeEntry) * i); - } - } - } - } - - [StructLayout(LayoutKind.Explicit, Size = 4 * 5)] - public struct RecipeEntry { - [FieldOffset(0 * 4)] public uint ResultItemId; - [FieldOffset(1 * 4)] public uint ResultIconId; - [FieldOffset(2 * 4)] public int Flags; - [FieldOffset(3 * 4)] public int Level; - [FieldOffset(4 * 4)] public ushort RecipeID; - } - - } - - public override void Setup() { - AddChangelog("1.8.2.1", "Fixed a potential crash in specific circumstances."); - base.Setup(); - } + [TweakHook, Signature("E9 ?? ?? ?? ?? 4C 8B 44 24 ?? 49 8B D2 48 8B CB 48 83 C4 30 5B E9 ?? ?? ?? ?? 4C 8B 44 24 ?? 49 8B D2 48 8B CB 48 83 C4 30 5B E9 ?? ?? ?? ?? 33 D2", DetourName = nameof(ClickSynthesisButtonDetour))] + private readonly HookWrapper clickSynthesisButton = null!; protected override void Enable() { - clickSysnthesisButtonHook ??= Common.Hook("E9 ?? ?? ?? ?? 4C 8B 44 24 ?? 49 8B D2 48 8B CB 48 83 C4 30 5B E9 ?? ?? ?? ?? 4C 8B 44 24 ?? 49 8B D2 48 8B CB 48 83 C4 30 5B E9 ?? ?? ?? ?? 33 D2", ClickSynthesisButtonDetour); - clickSysnthesisButtonHook?.Enable(); - - passthroughFunction = (delegate*) Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 0F B6 C3 48 8B 5C 24 ?? 48 83 C4 20 5D"); + passThroughFunction = (delegate*) Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 0F B6 C3 48 8B 5C 24 ?? 48 83 C4 20 5D"); - cancelCraftingHook ??= Common.Hook("E8 ?? ?? ?? ?? 48 8B 4B 10 33 FF C6 83", CancelCraftingDetour); - cancelCraftingHook?.Enable(); - - Service.Commands.AddHandler("/stopcrafting", new CommandInfo(((command, arguments) => { + Service.Commands.AddHandler("/stopcrafting", new CommandInfo(((_, _) => { if (Service.ClientState.LocalPlayer != null && !standingUp) { var localPlayer = (Character*) Service.ClientState.LocalPlayer.Address; var addon = Common.GetUnitBase("RecipeNote"); if (addon != null) { GetCraftReadyState(out var selectedRecipeId); - if (selectedRecipeId > 0 && localPlayer->EventState == 5) { - addon->Hide(true, false, 0); - AgentRecipeNote.Instance()->OpenRecipeByRecipeIdInternal((uint)(0x10000 + selectedRecipeId)); - standingUp = true; - Common.FrameworkUpdate += ForceUpdateFramework; + if (selectedRecipeId > 0 && localPlayer->Mode == Character.CharacterModes.Crafting) { + ReopenCraftingLog(); return; } } @@ -107,18 +59,19 @@ protected override void Enable() { ShowInHelp = true }); - base.Enable(); + ForceUpdate(); } - private void* CancelCraftingDetour(CraftingState* craftingstate, void* a2) { - if (standingUp) return passthroughFunction(craftingstate, a2); - return cancelCraftingHook.Original(craftingstate, a2); + private void* CancelCraftingDetour(RecipeNote* recipeNote) { + if (standingUp) return passThroughFunction(recipeNote); + return cancelCraftingHook!.Original(recipeNote); } private void ForceUpdate() { try { - var addon = Common.GetUnitBase("RecipeNote"); - if (addon != null) CraftingLogUpdated(addon); + if (Common.GetUnitBase(out var addon, "RecipeNote")) { + CraftingLogUpdated(addon); + } } catch (Exception ex) { SimpleLog.Error(ex); } @@ -136,24 +89,16 @@ private void ForceUpdate() { return null; } - private void* ClickSynthesisButtonDetour(void* a1, void* a2) { + private void* ClickSynthesisButtonDetour(AddonRecipeNote* addon, void* a2) { try { uint requiredClass = 0; - var readyState = GetCraftReadyState(ref requiredClass, out var selectedRecipeId); + var readyState = GetCraftReadyState(ref requiredClass, out _); switch (readyState) { case CraftReadyState.AlreadyCrafting: { if (Service.ClientState.LocalPlayer != null && !standingUp) { - var addon = Common.GetUnitBase("RecipeNote"); - if (addon != null) { - addon->Hide(true, true, 0); - AgentRecipeNote.Instance()->OpenRecipeByRecipeIdInternal((uint) (0x10000 + selectedRecipeId)); - removeFrameworkUpdateEventStopwatch.Restart(); - standingUp = true; - Common.FrameworkUpdate += ForceUpdateFramework; - return null; - } + ReopenCraftingLog(); } else { return null; } @@ -176,7 +121,7 @@ private void ForceUpdate() { } - return clickSysnthesisButtonHook.Original(a1, a2); + return clickSynthesisButton.Original(addon, a2); } private void ForceUpdateFramework() { @@ -184,7 +129,7 @@ private void ForceUpdateFramework() { ForceUpdate(); if (standingUp == false || Service.ClientState.LocalPlayer == null) return; var localPlayer = (Character*) Service.ClientState.LocalPlayer.Address; - if (localPlayer->EventState != 5) { + if (localPlayer->Mode != Character.CharacterModes.Crafting) { standingUp = false; } } @@ -215,18 +160,15 @@ private CraftReadyState GetCraftReadyState(ref uint requiredClass, out ushort se if (requiredJob == null) return CraftReadyState.NotReady; if (Service.ClientState.LocalPlayer.ClassJob.Id == requiredClass) return CraftReadyState.Ready; var localPlayer = (Character*) Service.ClientState.LocalPlayer.Address; - return localPlayer->EventState == 5 ? CraftReadyState.AlreadyCrafting : CraftReadyState.WrongClass; + return localPlayer->Mode == Character.CharacterModes.Crafting ? CraftReadyState.AlreadyCrafting : CraftReadyState.WrongClass; } [AddonPostRequestedUpdate("RecipeNote")] - private void CraftingLogUpdated(AtkUnitBase* atkUnitBase) { + private void CraftingLogUpdated(AddonRecipeNote* addon) { + SimpleLog.Log("Updating"); var ready = GetCraftReadyState(out _); if (ready == CraftReadyState.NotReady) return; - var craftButton = (AtkComponentNode*) atkUnitBase->GetNodeById(104); - if (craftButton->AtkResNode.Type != (NodeType)1005) return; - var craftButtonComp = (AtkComponentButton*) craftButton->Component; - if (craftButtonComp == null || craftButtonComp->ButtonTextNode == null) return; - + var buttonText = ready switch { CraftReadyState.Ready => Service.Data.Excel.GetSheet()?.GetRow(1404)?.Text?.ToDalamudString(), CraftReadyState.WrongClass => "Switch Job", @@ -235,28 +177,38 @@ private void CraftingLogUpdated(AtkUnitBase* atkUnitBase) { }; if (buttonText != null) { - craftButtonComp->ButtonTextNode->SetText(buttonText.Encode()); + addon->SynthesizeButton->ButtonTextNode->SetText(buttonText.Encode()); } } - private void CloseCraftingLog() { - var rl = Common.GetUnitBase("RecipeNote"); - if (rl == null) return; - rl->Hide(true, false, 0); - } + private void ReopenCraftingLog() { + if (!Common.GetUnitBase("RecipeNote", out var addon)) return; + var localPlayer = (Character*)(Service.ClientState.LocalPlayer?.Address ?? nint.Zero); + if (localPlayer == null) return; + + GetCraftReadyState(out var selectedRecipeId); + if (selectedRecipeId > 0 && localPlayer->Mode == Character.CharacterModes.Crafting) { + var agent = AgentRecipeNote.Instance(); + addon->Hide(true, true, 0); + agent->OpenRecipeByRecipeId(selectedRecipeId); + + standingUp = true; + Common.FrameworkUpdate += ForceUpdateFramework; + } + } + protected override void Disable() { + Common.FrameworkUpdate -= ForceUpdateFramework; + if (Common.GetUnitBase(out var addon)) { + var text = Service.Data.Excel.GetSheet()?.GetRow(1404)?.Text?.ToDalamudString().Encode(); + if (text != null) { + SimpleLog.Log($"Resetting Button Test: {(ulong)addon->SynthesizeButton->ButtonTextNode:X}"); + addon->SynthesizeButton->ButtonTextNode->NodeText.SetString(text); + } + + } + Service.Commands.RemoveHandler("/stopcrafting"); - clickSysnthesisButtonHook?.Disable(); - CloseCraftingLog(); - cancelCraftingHook?.Disable(); - base.Disable(); - } - - public override void Dispose() { - clickSysnthesisButtonHook?.Dispose(); - cancelCraftingHook?.Dispose(); - base.Dispose(); } } -