Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Global State Init to an Attribute-based system #89

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions Ktisis/GlobalDisposeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

using JetBrains.Annotations;

namespace Ktisis {
/**
* <summary>Marks a static method as a global destructor. It will be called during plugin disposing.</summary>
*/
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
[MeansImplicitUse(ImplicitUseKindFlags.Access)]
public class GlobalDisposeAttribute : Attribute {}
}
12 changes: 12 additions & 0 deletions Ktisis/GlobalInitAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

using JetBrains.Annotations;

namespace Ktisis {
/**
* <summary>Marks a static method as a global initializer. It will be called during plugin construction.</summary>
*/
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
[MeansImplicitUse(ImplicitUseKindFlags.Access)]
public class GlobalInitAttribute : Attribute {}
}
11 changes: 11 additions & 0 deletions Ktisis/GlobalStateAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Ktisis {
/**
* <summary>Indicates that this class has global state and may contain static methods annotated with <see cref="GlobalInitAttribute"/> and/or <see cref="GlobalDisposeAttribute"/>.</summary>
*/
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class GlobalStateAttribute : Attribute {
public Type[] InitAfter { get; set; } = Array.Empty<Type>();
}
}
7 changes: 5 additions & 2 deletions Ktisis/History/HistoryManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Ktisis.Structs.Actor.State;

namespace Ktisis.History {
[GlobalState]
public static class HistoryManager {
public static List<HistoryItem>? History { get; set; }
private static int _currentIdx = -1;
Expand All @@ -21,14 +22,16 @@ public static class HistoryManager {

// Init & Dispose

public static void Init() {
[GlobalInit]
public static void GlobalInit() {
EventManager.OnKeyPressed += OnInput;
EventManager.OnGPoseChange += OnGPoseChange;
EventManager.OnGizmoChange += OnGizmoChange;
EventManager.OnTransformationMatrixChange += OnGizmoChange;
}

public static void Dispose() {
[GlobalDispose]
public static void GlobalDispose() {
EventManager.OnKeyPressed -= OnInput;
EventManager.OnGPoseChange -= OnGPoseChange;
EventManager.OnGizmoChange -= OnGizmoChange;
Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interface/Input.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Ktisis.Interface.Components;

namespace Ktisis.Interface {
[GlobalState]
public static class Input {
// When adding a new keybind:
// - add the logic in Monitor
Expand Down Expand Up @@ -176,11 +177,13 @@ public enum Purpose {

// Init & dispose

public static void Init() {
[GlobalInit]
public static void GlobalInit() {
EventManager.OnKeyPressed += OnKeyPressed;
EventManager.OnKeyReleased += OnKeyReleased;
}
public static void Dispose() {
[GlobalDispose]
public static void GlobalDispose() {
EventManager.OnKeyPressed -= OnKeyPressed;
EventManager.OnKeyReleased -= OnKeyReleased;
}
Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interop/Alloc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using FFXIVClientStructs.Havok;

namespace Ktisis.Interop {
[GlobalState]
internal static class Alloc {
// Allocations
private static IntPtr MatrixAlloc;
Expand All @@ -21,14 +22,16 @@ internal unsafe static void SetMatrix(hkQsTransformf* transform, Matrix4x4 matri
}

// Init & disspose
public unsafe static void Init() {
[GlobalInit]
public unsafe static void GlobalInit() {
// Allocate space for our matrix to be aligned on a 16-byte boundary.
// This is required due to ffxiv's use of the MOVAPS instruction.
// Thanks to Fayti1703 for helping with debugging and coming up with this fix.
MatrixAlloc = Marshal.AllocHGlobal(sizeof(float) * 16 + 16);
Matrix = (Matrix4x4*)(16 * ((long)(MatrixAlloc + 15) / 16));
}
public static void Dispose() {
[GlobalDispose]
public static void GlobalDispose() {
Marshal.FreeHGlobal(MatrixAlloc);
}
}
Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interop/Hooks/ActorHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Ktisis.Interface.Windows.Workspace;

namespace Ktisis.Interop.Hooks {
[GlobalState]
internal static class ActorHooks {
// Control actor gaze
// a1 = Actor + 0xC20
Expand All @@ -21,13 +22,15 @@ internal unsafe static IntPtr ControlGaze(IntPtr a1) {

// Init & Dispose

internal static void Init() {
[GlobalInit]
internal static void GlobalInit() {
var controlGaze = Services.SigScanner.ScanText("40 53 41 54 41 55 48 81 EC ?? ?? ?? ?? 48 8B D9");
ControlGazeHook = Hook<ControlGazeDelegate>.FromAddress(controlGaze, ControlGaze);
ControlGazeHook.Enable();
}

internal static void Dispose() {
[GlobalDispose]
internal static void GlobalDispose() {
ControlGazeHook.Disable();
ControlGazeHook.Dispose();
}
Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interop/Hooks/ControlHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Ktisis.Structs.Input;

namespace Ktisis.Interop.Hooks {
[GlobalState]
internal static class ControlHooks {
public static KeyboardState KeyboardState = new();

Expand Down Expand Up @@ -76,7 +77,8 @@ internal static IntPtr InputDetour2(ulong a1, uint a2, ulong a3, uint a4) {

// Init & dispose

internal static void Init() {
[GlobalInit]
internal static void GlobalInit() {
unsafe {
var addr = Services.SigScanner.ScanText("E8 ?? ?? ?? ?? 83 7B 58 00");
InputHook = Hook<InputDelegate>.FromAddress(addr, InputDetour);
Expand All @@ -88,7 +90,8 @@ internal static void Init() {
}
}

internal static void Dispose() {
[GlobalDispose]
internal static void GlobalDispose() {
InputHook.Disable();
InputHook.Dispose();

Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interop/Hooks/EventsHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
using Ktisis.Structs.Actor.Equip.SetSources;

namespace Ktisis.Interop.Hooks {
[GlobalState]
public class EventsHooks {
public static void Init() {
[GlobalInit]
public static void GlobalInit() {
Services.AddonManager = new AddonManager();
Services.ClientState.Login += OnLogin;
Services.ClientState.Logout += OnLogout;
Expand All @@ -25,7 +27,8 @@ public static void Init() {
OnLogin(null!, null!);
}

public static void Dispose() {
[GlobalDispose]
public static void GlobalDispose() {
Services.AddonManager.Dispose();
Services.ClientState.Logout -= OnLogout;
Services.ClientState.Login -= OnLogin;
Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interop/Hooks/GuiHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Ktisis.Structs.Actor;

namespace Ktisis.Interop.Hooks {
[GlobalState]
internal class GuiHooks {
// Target name in the GPose window

Expand Down Expand Up @@ -37,13 +38,15 @@ internal unsafe static void UpdateTarName(IntPtr a1) {

// Init & dispose

internal static void Init() {
[GlobalInit]
internal static void GlobalInit() {
var tarName = Services.SigScanner.ScanText("40 56 48 83 EC 50 48 8B 05 ?? ?? ?? ?? 48 8B F1 48 85 C0");
TarNameHook = Hook<TarNameDelegate>.FromAddress(tarName, UpdateTarName);
TarNameHook.Enable();
}

internal static void Dispose() {
[GlobalDispose]
internal static void GlobalDispose() {
TarNameHook.Disable();
TarNameHook.Dispose();
}
Expand Down
7 changes: 5 additions & 2 deletions Ktisis/Interop/Hooks/PoseHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Dalamud.Logging;

namespace Ktisis.Interop.Hooks {
[GlobalState]
public static class PoseHooks {
internal delegate ulong SetBoneModelSpaceFfxivDelegate(IntPtr partialSkeleton, ushort boneId, IntPtr transform, bool enableSecondary, bool enablePropagate);
internal static Hook<SetBoneModelSpaceFfxivDelegate> SetBoneModelSpaceFfxivHook = null!;
Expand Down Expand Up @@ -43,7 +44,8 @@ public static class PoseHooks {

internal static Dictionary<uint, PoseContainer> PreservedPoses = new();

internal static unsafe void Init() {
[GlobalInit]
internal static unsafe void GlobalInit() {
var setBoneModelSpaceFfxiv = Services.SigScanner.ScanText("48 8B C4 48 89 58 18 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 0F 29 70 B8 0F 29 78 A8 44 0F 29 40 ?? 44 0F 29 48 ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B B1");
SetBoneModelSpaceFfxivHook = Hook<SetBoneModelSpaceFfxivDelegate>.FromAddress(setBoneModelSpaceFfxiv, SetBoneModelSpaceFfxivDetour);

Expand Down Expand Up @@ -233,7 +235,8 @@ public static unsafe bool IsGamePlaybackRunning(GameObject? gPoseTarget) {
return csObject->DrawObject->Skeleton->PartialSkeletons->GetHavokAnimatedSkeleton(0)->AnimationControls[0];
}

internal static void Dispose() {
[GlobalDispose]
internal static void GlobalDispose() {
SetBoneModelSpaceFfxivHook.Disable();
SetBoneModelSpaceFfxivHook.Dispose();
CalculateBoneModelSpaceHook.Disable();
Expand Down
4 changes: 3 additions & 1 deletion Ktisis/Interop/Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Ktisis.Structs.FFXIV;

namespace Ktisis.Interop {
[GlobalState]
internal class Methods {
// Make actor look at co-ordinate point
// a1 = Actor + 0xC20, a2 = TrackPos*, a3 = bodypart, a4 = ?
Expand Down Expand Up @@ -32,7 +33,8 @@ internal class Methods {
private static TDelegate Retrieve<TDelegate>(string sig)
=> Marshal.GetDelegateForFunctionPointer<TDelegate>(Services.SigScanner.ScanText(sig));

internal static void Init() {
[GlobalInit]
internal static void GlobalInit() {
ActorLookAt = Retrieve<LookAtDelegate>("40 53 55 57 41 56 41 57 48 83 EC 70");
ActorChangeEquip = Retrieve<ChangeEquipDelegate>("E8 ?? ?? ?? ?? 41 B5 01 FF C6");
ActorChangeWeapon = Retrieve<ChangeWeaponDelegate>("E8 ?? ?? ?? ?? 80 7F 25 00");
Expand Down
4 changes: 3 additions & 1 deletion Ktisis/Interop/StaticOffsets.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;

namespace Ktisis.Interop {
[GlobalState]
internal static class StaticOffsets {
// Address of loaded FFXIV_CHARA files in memory.
internal static IntPtr CharaDatData;
Expand All @@ -17,7 +18,8 @@ internal static class StaticOffsets {
internal static bool IsAnamPosing => IsPositionFrozen || IsRotationFrozen || IsScalingFrozen;

// Init
internal unsafe static void Init() {
[GlobalInit]
internal unsafe static void GlobalInit() {
var qword_14200E548 = *(IntPtr*)Services.SigScanner.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 C7 44 24 24 05 00 00 00 C6 84 24");
CharaDatData = *(IntPtr*)(qword_14200E548 + 1392);

Expand Down
78 changes: 54 additions & 24 deletions Ktisis/Ktisis.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

using Dalamud.Plugin;
using Dalamud.Game.Command;
Expand Down Expand Up @@ -54,20 +58,9 @@ public Ktisis(DalamudPluginInterface pluginInterface) {

// Init interop stuff

Interop.Alloc.Init();
Interop.Methods.Init();
Interop.StaticOffsets.Init();
EventManager.OnGPoseChange += Workspace.OnEnterGposeToggle; // must be placed before ActorStateWatcher.GlobalInit()

Interop.Hooks.ActorHooks.Init();
Interop.Hooks.ControlHooks.Init();
Interop.Hooks.EventsHooks.Init();
Interop.Hooks.GuiHooks.Init();
Interop.Hooks.PoseHooks.Init();

EventManager.OnGPoseChange += Workspace.OnEnterGposeToggle; // must be placed before ActorStateWatcher.Init()

Input.Init();
ActorStateWatcher.Init();
GlobalInit();

// Register command

Expand All @@ -84,7 +77,6 @@ public Ktisis(DalamudPluginInterface pluginInterface) {
pluginInterface.UiBuilder.DisableGposeUiHide = true;
pluginInterface.UiBuilder.Draw += KtisisGui.Draw;

HistoryManager.Init();
References.LoadReferences(Configuration);
}

Expand All @@ -95,13 +87,7 @@ public void Dispose() {

OverlayWindow.DeselectGizmo();

Interop.Hooks.ActorHooks.Dispose();
Interop.Hooks.ControlHooks.Dispose();
Interop.Hooks.EventsHooks.Dispose();
Interop.Hooks.GuiHooks.Dispose();
Interop.Hooks.PoseHooks.Dispose();

Interop.Alloc.Dispose();
GlobalDispose();
ActorStateWatcher.Instance.Dispose();
EventManager.OnGPoseChange -= Workspace.OnEnterGposeToggle;

Expand All @@ -110,9 +96,6 @@ public void Dispose() {
if (EditEquip.Items != null)
EditEquip.Items = null;

Input.Dispose();
HistoryManager.Dispose();

foreach (var (_, texture) in References.Textures) {
texture.Dispose();
}
Expand All @@ -136,5 +119,52 @@ private void OnCommand(string command, string arguments) {
break;
}
}

private static readonly Stack<MethodInfo> ToGloballyDispose = new();

private static void GlobalInit() {
foreach (Type globalStateContainer in GetGlobalInitTypes()) {
foreach (var method in globalStateContainer.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) {
foreach (var attr in method.CustomAttributes) {
if (attr.AttributeType == typeof(GlobalInitAttribute))
method.Invoke(null, Array.Empty<object>());
else if (attr.AttributeType == typeof(GlobalDisposeAttribute))
ToGloballyDispose.Push(method);
}
}
}
ToGloballyDispose.TrimExcess();
}

private static IEnumerable<Type> GetGlobalInitTypes() =>
typeof(Ktisis).Assembly.GetTypes().Select(type => (type, globalStateAttr: type.GetCustomAttribute<GlobalStateAttribute>()))
.Where(x => x.globalStateAttr != null)
.OrderBy(x => (x.type, initAfter: new HashSet<Type>(x.globalStateAttr!.InitAfter)), new DelegateComparer<(Type type, HashSet<Type> initAfter)>((a, b) => {
if(a.initAfter.Contains(b.type))
return 1;
if(b.initAfter.Contains(a.type))
return -1;
return 0;
})).Select(x => x.type);

private static void GlobalDispose() {
while (ToGloballyDispose.TryPop(out MethodInfo? toDispose)) {
toDispose.Invoke(null, Array.Empty<object>());
}
}
}

public struct DelegateComparer<T> : IComparer<T> {

public delegate int Comparer(T? x, T? y);

public Comparer comparer;

public DelegateComparer(Comparer comparer) {
this.comparer = comparer;
}

public int Compare(T? x, T? y) => this.comparer(x, y);
}

}
Loading