Skip to content

Commit e72386f

Browse files
committed
2 parents 68171c0 + e78ffc5 commit e72386f

File tree

20 files changed

+245
-90
lines changed

20 files changed

+245
-90
lines changed

.github/workflows/CompileAndPublish.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ jobs:
3232
repository: 'NightmareXIV/NightmareXIVNugetUploader'
3333
submodules: true
3434
ref: 'main'
35+
- name: Checkout current repository into 'repo'
36+
uses: actions/checkout@v4
37+
with:
38+
path: repo
3539
- name: print
3640
run: find -type d -printf '%d\t%P\n' | sort -r -nk1 | cut -f2-
3741
- name: Set up .NET

ECommons/Automation/Callback.cs

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using Dalamud.Hooking;
2-
using Dalamud.Memory;
32
using ECommons.DalamudServices;
43
using ECommons.Logging;
54
using FFXIVClientStructs.FFXIV.Component.GUI;
65
using System;
76
using System.Collections.Generic;
7+
using System.Data;
88
using System.Linq;
99
using System.Runtime.InteropServices;
1010
using System.Text;
@@ -15,24 +15,17 @@ namespace ECommons.Automation;
1515

1616
public static unsafe class Callback
1717
{
18-
public const string Sig = "48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? BF"; //keep as const
19-
public delegate byte AtkUnitBase_FireCallbackDelegate(AtkUnitBase* Base, int valueCount, AtkValue* values, byte updateState);
20-
internal static AtkUnitBase_FireCallbackDelegate FireCallback = null;
21-
private static Hook<AtkUnitBase_FireCallbackDelegate> AtkUnitBase_FireCallbackHook;
18+
private static Hook<AtkUnitBase.Delegates.FireCallback> AtkUnitBase_FireCallbackHook;
2219

2320
public static readonly AtkValue ZeroAtkValue = new() { Type = 0, Int = 0 };
2421

25-
internal static void Initialize()
26-
{
27-
var ptr = Svc.SigScanner.ScanText(Sig);
28-
FireCallback = Marshal.GetDelegateForFunctionPointer<AtkUnitBase_FireCallbackDelegate>(ptr);
29-
PluginLog.Information($"Initialized Callback module, FireCallback = 0x{ptr:X16}");
30-
}
31-
3222
public static void InstallHook()
3323
{
34-
if(FireCallback == null) Initialize();
35-
AtkUnitBase_FireCallbackHook ??= Svc.Hook.HookFromSignature<AtkUnitBase_FireCallbackDelegate>(Sig, AtkUnitBase_FireCallbackDetour);
24+
AtkUnitBase_FireCallbackHook ??= Svc.Hook.HookFromAddress<AtkUnitBase.Delegates.FireCallback>(
25+
AtkUnitBase.MemberFunctionPointers.FireCallback,
26+
AtkUnitBase_FireCallbackDetour
27+
);
28+
3629
if(AtkUnitBase_FireCallbackHook.IsEnabled)
3730
{
3831
PluginLog.Error("AtkUnitBase_FireCallbackHook is already enabled");
@@ -46,10 +39,6 @@ public static void InstallHook()
4639

4740
public static void UninstallHook()
4841
{
49-
if(FireCallback == null)
50-
{
51-
PluginLog.Error("AtkUnitBase_FireCallbackHook not initialized yet");
52-
}
5342
if(!AtkUnitBase_FireCallbackHook.IsEnabled)
5443
{
5544
PluginLog.Error("AtkUnitBase_FireCallbackHook is already disabled");
@@ -61,24 +50,26 @@ public static void UninstallHook()
6150
}
6251
}
6352

64-
private static byte AtkUnitBase_FireCallbackDetour(AtkUnitBase* Base, int valueCount, AtkValue* values, byte updateState)
53+
private static bool AtkUnitBase_FireCallbackDetour(AtkUnitBase* Base, uint valueCount, AtkValue* values, bool updateState)
6554
{
6655
var ret = AtkUnitBase_FireCallbackHook?.Original(Base, valueCount, values, updateState);
6756
try
6857
{
69-
PluginLog.Debug($"Callback on {Base->Name.Read()}, valueCount={valueCount}, updateState={updateState}\n{DecodeValues(valueCount, values).Select(x => $" {x}").Join("\n")}");
58+
int valueCountInt = Convert.ToInt32(valueCount); // Could throw OverflowException since DecodeValues takes an int
59+
PluginLog.Debug($"Callback on {Base->Name.Read()}, valueCount={valueCount}, updateState={updateState}\n{DecodeValues(valueCountInt, values).Select(x => $" {x}").Join("\n")}");
7060
}
7161
catch(Exception e)
7262
{
7363
e.Log();
7464
}
75-
return ret ?? 0;
65+
return ret ?? false;
7666
}
7767

68+
// Modified and kept for compatibility, but obsolete since you can just call FireCallback on the addon directly
69+
// could add obsolete attribute
7870
public static void FireRaw(AtkUnitBase* Base, int valueCount, AtkValue* values, byte updateState = 0)
7971
{
80-
if(FireCallback == null) Initialize();
81-
FireCallback(Base, valueCount, values, updateState);
72+
Base->FireCallback((uint)valueCount, values, updateState != 0);
8273
}
8374

8475
public static void Fire(AtkUnitBase* Base, bool updateState, params object[] values)
@@ -134,7 +125,7 @@ public static void Fire(AtkUnitBase* Base, bool updateState, params object[] val
134125
CallbackValues.Add($" Value {i}: [input: {values[i]}/{values[i]?.GetType().Name}] -> {DecodeValue(atkValues[i])})");
135126
}
136127
PluginLog.Verbose($"Firing callback: {Base->Name.Read()}, valueCount = {values.Length}, updateStatte = {updateState}, values:\n");
137-
FireRaw(Base, values.Length, atkValues, (byte)(updateState ? 1 : 0));
128+
Base->FireCallback((uint)values.Length, atkValues, updateState);
138129
}
139130
finally
140131
{
@@ -149,6 +140,8 @@ public static void Fire(AtkUnitBase* Base, bool updateState, params object[] val
149140
}
150141
}
151142

143+
// Would need to convert cnt to uint but for compatibility kept as int
144+
// Only an issue in detour which will just throw OverflowException and get logged
152145
public static List<string> DecodeValues(int cnt, AtkValue* values)
153146
{
154147
var atkValueList = new List<string>();
@@ -207,6 +200,5 @@ internal static void Dispose()
207200
{
208201
AtkUnitBase_FireCallbackHook?.Dispose();
209202
AtkUnitBase_FireCallbackHook = null;
210-
FireCallback = null;
211203
}
212204
}

ECommons/DalamudServices/Svc.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class Svc
4343
[PluginService] public static IObjectTable Objects { get; private set; }
4444
[PluginService] public static IPartyFinderGui PfGui { get; private set; }
4545
[PluginService] public static IPartyList Party { get; private set; }
46+
[PluginService] public static IPlayerState PlayerState { get; private set; }
4647
[PluginService] public static IPluginLog Log { get; private set; }
4748
[PluginService] public static ISeStringEvaluator SeStringEvaluator { get; private set; }
4849
[PluginService] public static ISigScanner SigScanner { get; private set; }
@@ -69,14 +70,7 @@ public static void Init(IDalamudPluginInterface pi)
6970
{
7071
PluginLog.Debug("Services already initialized, skipping");
7172
}
73+
pi.Create<Svc>();
7274
IsInitialized = true;
73-
try
74-
{
75-
pi.Create<Svc>();
76-
}
77-
catch(Exception ex)
78-
{
79-
ex.Log();
80-
}
8175
}
8276
}

ECommons/ECommons.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project Sdk="Microsoft.NET.Sdk">
33
<PropertyGroup>
44
<Authors>NightmareXIV</Authors>
5-
<BaseVersion>3.0.1.28</BaseVersion>
5+
<BaseVersion>3.0.1.32</BaseVersion>
66
<PackageKind></PackageKind>
77
<VersionSuffix Condition="'$(PackageKind)' != ''">-$(PackageKind)</VersionSuffix>
88
<Version>$(BaseVersion)$(VersionSuffix)</Version>

ECommons/ECommonsMain.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using ECommons.GameHelpers;
1515
using ECommons.Hooks;
1616
using ECommons.ImGuiMethods;
17+
using ECommons.Interop;
1718
using ECommons.LazyDataHelpers;
1819
using ECommons.Loader;
1920
using ECommons.Logging;
@@ -28,6 +29,8 @@
2829
using System;
2930
using System.Linq;
3031
using System.Reflection;
32+
using System.Threading;
33+
using TerraFX.Interop.Windows;
3134
using Callback = ECommons.Automation.Callback;
3235

3336

@@ -45,10 +48,34 @@ public static class ECommonsMain
4548
/// </summary>
4649
public static bool ReducedLogging = false;
4750

48-
public static void Init(IDalamudPluginInterface pluginInterface, IDalamudPlugin instance, params Module[] modules)
51+
public unsafe static void Init(IDalamudPluginInterface pluginInterface, IDalamudPlugin instance, params Module[] modules)
4952
{
5053
Instance = instance;
51-
GenericHelpers.Safe(() => Svc.Init(pluginInterface));
54+
try
55+
{
56+
Svc.Init(pluginInterface);
57+
}
58+
catch(Exception ex)
59+
{
60+
new Thread(() =>
61+
{
62+
try
63+
{
64+
var title = "Error initializing ECommons";
65+
var message = $"Error initializing ECommons services: {ex.Message}\nPlugin {pluginInterface?.Manifest?.InternalName} can not be loaded.\nPlease contact the developer.\nSee dalamud.boot.log for stack trace.";
66+
Console.WriteLine($"Error initializing ECommons services\n{ex.ToStringFull()}");
67+
var windowd = WindowFunctions.TryFindGameWindow(out var handle);
68+
fixed(char* titlePtr = title)
69+
fixed(char* messagePtr = message)
70+
TerraFX.Interop.Windows.Windows.MessageBox(windowd ? handle : default, (ushort*)messagePtr, (ushort*)titlePtr, 0);
71+
}
72+
catch(Exception e)
73+
{
74+
Console.WriteLine($"Error showing MessageBox: {e.ToStringFull()}\nError initializing ECommons services\n{ex.ToStringFull()}");
75+
}
76+
}).Start();
77+
throw;
78+
}
5279
#if DEBUG
5380
var type = "debug build";
5481
#elif RELEASE

ECommons/Events/ProperOnLogin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static class ProperOnLogin
1616
private static bool EventRegisteredAvailable = false;
1717
private static bool EventRegisteredInteractable = false;
1818

19-
public static bool PlayerPresent => Svc.ClientState.LocalPlayer != null && Svc.ClientState.LocalContentId != 0;
19+
public static bool PlayerPresent => Svc.Objects.LocalPlayer != null && Svc.PlayerState.ContentId != 0;
2020

2121
public static void FireArtificially()
2222
{

ECommons/ExcelServices/Enums/Job.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using System.Reflection;
1+
using ECommons.DalamudServices;
2+
using Lumina.Excel;
3+
using Lumina.Excel.Sheets;
4+
using System.Reflection;
25

36
namespace ECommons.ExcelServices;
47

@@ -220,3 +223,8 @@ public enum Job : byte
220223
/// </summary>
221224
PCT = 42,
222225
}
226+
227+
public static class JobExtensions
228+
{
229+
public static RowRef<ClassJob> GetGameData(this Job job) => ClassJob.GetRef((uint)job);
230+
}

ECommons/EzIpcManager/EzIPC.cs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,19 @@ private static EzIPCDisposalToken[] Init(object? instance, Type instanceType, st
112112
var ipcName = attr.IPCName ?? reference.Name;
113113
ipcName = ipcName.Replace("%m", reference.Name);
114114
ipcName = ipcName.Replace("%p", Svc.PluginInterface.InternalName);
115-
var isNonGenericAction = reference.UnionType == typeof(Action);
116-
if(isNonGenericAction || reference.UnionType.GetGenericTypeDefinition().EqualsAny([.. FuncTypes, .. ActionTypes]))
115+
var delegateInfo = ReflectionHelper.AnalyzeDelegateField(reference);
116+
if(delegateInfo.IsDelegate)
117117
{
118118
var wrapper = attr.Wrapper == SafeWrapper.Inherit ? safeWrapper : attr.Wrapper;
119119
if(!ECommonsMain.ReducedLogging)
120120
{
121121
PluginLog.Debug($"[EzIPC Subscriber] Attempting to assign IPC method to {instanceType.Name}.{reference.Name} with wrapper {wrapper}");
122122
}
123-
var isAction = isNonGenericAction || reference.UnionType.GetGenericTypeDefinition().EqualsAny(ActionTypes);
124-
var genericArgsLen = reference.UnionType.GetGenericArguments().Length;
125-
var reg = FindIpcSubscriber(genericArgsLen + (isAction ? 1 : 0)) ?? throw new NullReferenceException("Could not retrieve GetIpcSubscriber. Did you called EzIPC.Init before ECommonsMain.Init or specified more than 9 arguments?");
126-
var genericArgs = reference.UnionType.IsGenericType ? reference.UnionType.GetGenericArguments() : [];
127-
var adjustedGenericArgs = isAction ? [.. genericArgs, attr.ActionLastGenericType] : genericArgs;
123+
var isAction = delegateInfo.ReturnType == null;
124+
var genericArgsLen = delegateInfo.ParameterTypes.Length;
125+
var reg = FindIpcSubscriber(genericArgsLen + 1) ?? throw new NullReferenceException("Could not retrieve GetIpcSubscriber. Did you called EzIPC.Init before ECommonsMain.Init or specified more than 9 arguments?");
126+
var genericArgs = delegateInfo.ParameterTypes;
127+
Type[] adjustedGenericArgs = isAction ? [.. genericArgs, attr.ActionLastGenericType] : [.. genericArgs, delegateInfo.ReturnType];
128128
var genericMethod = reg.MakeGenericMethod(adjustedGenericArgs);
129129
var name = attr.ApplyPrefix ? $"{prefix}.{ipcName}" : ipcName;
130130
var callerInfo = genericMethod.Invoke(Svc.PluginInterface, [name])!;
@@ -134,13 +134,17 @@ private static EzIPCDisposalToken[] Init(object? instance, Type instanceType, st
134134
var safeWrapperObj = CreateSafeWrapper(wrapper, adjustedGenericArgs) ?? throw new NullReferenceException("Safe wrapper creation failed. Please report this exception to developer.");
135135
var safeWrapperMethod = safeWrapperObj.GetType().GetMethod(isAction ? "InvokeAction" : "InvokeFunction", ReflectionHelper.AllFlags);
136136
safeWrapperObj.SetFoP(isAction ? "Action" : "Function", invocationDelegate);
137-
reference.SetValue(instance, ReflectionHelper.CreateDelegate(safeWrapperMethod, safeWrapperObj));
137+
ReflectionHelper.AssignDelegateToField(reference, instance, ReflectionHelper.CreateDelegate(safeWrapperMethod, safeWrapperObj));
138138
}
139139
else
140140
{
141-
reference.SetValue(instance, invocationDelegate);
141+
ReflectionHelper.AssignDelegateToField(reference, instance, invocationDelegate);
142142
}
143143
}
144+
else
145+
{
146+
PluginLog.Warning($"Not a delegate: {reference.Name}");
147+
}
144148
}
145149
}
146150
catch(Exception e)
@@ -207,19 +211,28 @@ private static EzIPCDisposalToken[] Init(object? instance, Type instanceType, st
207211
var ipcName = attr.IPCName ?? reference.Name;
208212
ipcName = ipcName.Replace("%m", reference.Name);
209213
ipcName = ipcName.Replace("%p", Svc.PluginInterface.InternalName);
210-
var isNonGenericAction = reference.UnionType == typeof(Action);
211-
if(isNonGenericAction || reference.UnionType.GetGenericTypeDefinition().EqualsAny(ActionTypes))
214+
var delegateInfo = ReflectionHelper.AnalyzeDelegateField(reference);
215+
if(delegateInfo.IsDelegate)
212216
{
213217
if(!ECommonsMain.ReducedLogging)
214218
{
215219
PluginLog.Debug($"[EzIPC Provider] Attempting to assign IPC event to {instanceType.Name}.{reference.Name}");
216220
}
217-
var reg = FindIpcProvider(reference.UnionType.GetGenericArguments().Length + 1) ?? throw new NullReferenceException("Could not retrieve GetIpcProvider. Did you called EzIPC.Init before ECommonsMain.Init or specified more than 9 arguments?");
218-
var genericArgs = reference.UnionType.IsGenericType ? reference.UnionType.GetGenericArguments() : [];
221+
if(delegateInfo.ReturnType != null)
222+
{
223+
throw new InvalidOperationException($"Only void-returning delegates may be registered as events");
224+
}
225+
var genericArgsLen = delegateInfo.ParameterTypes.Length;
226+
var reg = FindIpcProvider(genericArgsLen + 1) ?? throw new NullReferenceException("Could not retrieve GetIpcProvider. Did you called EzIPC.Init before ECommonsMain.Init or specified more than 9 arguments?");
227+
var genericArgs = delegateInfo.ParameterTypes;
219228
var genericMethod = reg.MakeGenericMethod([.. genericArgs, attr.ActionLastGenericType]);
220229
var name = attr.ApplyPrefix ? $"{prefix}.{ipcName}" : ipcName;
221230
var callerInfo = genericMethod.Invoke(Svc.PluginInterface, [name])!;
222-
reference.SetValue(instance, ReflectionHelper.CreateDelegate(callerInfo.GetType().GetMethod("SendMessage"), callerInfo));
231+
ReflectionHelper.AssignDelegateToField(reference, instance, ReflectionHelper.CreateDelegate(callerInfo.GetType().GetMethod("SendMessage"), callerInfo));
232+
}
233+
else
234+
{
235+
PluginLog.Warning($"Not a delegate: {reference.Name}");
223236
}
224237
}
225238
}

ECommons/GameFunctions/ExtendedPronoun.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ public static void Remove(delegate*<string, string, string, GameObject*> extensi
192192
{
193193
if(modifier == "target")
194194
{
195-
return (GameObject*)Svc.ClientState.LocalPlayer?.TargetObject?.Address;
195+
return (GameObject*)Svc.Objects.LocalPlayer?.TargetObject?.Address;
196196
}
197-
return (GameObject*)Svc.ClientState.LocalPlayer?.Address;
197+
return (GameObject*)Svc.Objects.LocalPlayer?.Address;
198198
}
199199
}

ECommons/GameFunctions/ObjectFunctions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ public static int GetAttackableEnemyCountAroundPoint(Vector3 point, float radius
8484

8585
public static bool TryGetPartyMemberObjectByObjectId(uint objectId, out IGameObject partyMemberObject)
8686
{
87-
if(objectId == Svc.ClientState.LocalPlayer?.GameObjectId)
87+
if(objectId == Svc.Objects.LocalPlayer?.GameObjectId)
8888
{
89-
partyMemberObject = Svc.ClientState.LocalPlayer;
89+
partyMemberObject = Svc.Objects.LocalPlayer;
9090
return true;
9191
}
9292
foreach(var p in Svc.Party)
@@ -103,9 +103,9 @@ public static bool TryGetPartyMemberObjectByObjectId(uint objectId, out IGameObj
103103

104104
public static bool TryGetPartyMemberObjectByAddress(IntPtr address, out IGameObject partyMemberObject)
105105
{
106-
if(address == Svc.ClientState.LocalPlayer?.Address)
106+
if(address == Svc.Objects.LocalPlayer?.Address)
107107
{
108-
partyMemberObject = Svc.ClientState.LocalPlayer;
108+
partyMemberObject = Svc.Objects.LocalPlayer;
109109
return true;
110110
}
111111
foreach(var p in Svc.Party)

0 commit comments

Comments
 (0)