Skip to content

Commit

Permalink
[Improved Crafting Log] Disable for Everyone, Try to fix some thing…
Browse files Browse the repository at this point in the history
…s, Experimental
  • Loading branch information
Caraxi committed Oct 9, 2023
1 parent 6f33a6c commit 23f3daf
Showing 1 changed file with 65 additions and 113 deletions.
Original file line number Diff line number Diff line change
@@ -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*<RecipeNote*, void*> passThroughFunction;
private delegate void* CancelCrafting(RecipeNote* recipeNote);

private delegate*<CraftingState*, void*, void*> passthroughFunction;
private delegate void* CancelCrafting(CraftingState* craftingState, void* a2);
private HookWrapper<CancelCrafting> cancelCraftingHook;
[TweakHook, Signature("E8 ?? ?? ?? ?? 48 8B 4B 10 33 FF C6 83", DetourName = nameof(CancelCraftingDetour))]
private readonly HookWrapper<CancelCrafting> cancelCraftingHook = null!;

private HookWrapper<ClickSynthesisButton> 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> clickSynthesisButton = null!;

protected override void Enable() {
clickSysnthesisButtonHook ??= Common.Hook<ClickSynthesisButton>("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*<CraftingState*, void*, void*>) Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 0F B6 C3 48 8B 5C 24 ?? 48 83 C4 20 5D");
passThroughFunction = (delegate*<RecipeNote*, void*>) Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 0F B6 C3 48 8B 5C 24 ?? 48 83 C4 20 5D");

cancelCraftingHook ??= Common.Hook<CancelCrafting>("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;
}
}
Expand All @@ -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<AddonRecipeNote>(out var addon, "RecipeNote")) {
CraftingLogUpdated(addon);
}
} catch (Exception ex) {
SimpleLog.Error(ex);
}
Expand All @@ -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;
}
Expand All @@ -176,15 +121,15 @@ private void ForceUpdate() {
}


return clickSysnthesisButtonHook.Original(a1, a2);
return clickSynthesisButton.Original(addon, a2);
}

private void ForceUpdateFramework() {
if (removeFrameworkUpdateEventStopwatch.ElapsedMilliseconds > 5000) Common.FrameworkUpdate -= 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;
}
}
Expand Down Expand Up @@ -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<Addon>()?.GetRow(1404)?.Text?.ToDalamudString(),
CraftReadyState.WrongClass => "Switch Job",
Expand All @@ -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<AddonRecipeNote>(out var addon)) {
var text = Service.Data.Excel.GetSheet<Addon>()?.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();
}
}

0 comments on commit 23f3daf

Please sign in to comment.