mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-13 11:27:42 +01:00
Compare commits
135 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1779d2681a | ||
|
|
4651397808 | ||
|
|
1ba18e54bf | ||
|
|
907b585b75 | ||
|
|
ec450da054 | ||
|
|
990c4fd7e8 | ||
|
|
b1b99bae13 | ||
|
|
c4faf84a2d | ||
|
|
abe27891c3 | ||
|
|
1286dbd279 | ||
|
|
0f14f5dab7 | ||
|
|
49e281e573 | ||
|
|
0a070970a0 | ||
|
|
3de8c511bf | ||
|
|
e2297661f3 | ||
|
|
8285aa1014 | ||
|
|
256ab9dc9c | ||
|
|
332d0d0cf5 | ||
|
|
78912c1552 | ||
|
|
28e39ab9e2 | ||
|
|
526e651750 | ||
|
|
4a33d34a3f | ||
|
|
0aa746e3bf | ||
|
|
5044aeda2b | ||
|
|
34f13b3823 | ||
|
|
73447f205d | ||
|
|
0490a71990 | ||
|
|
2b347eaff9 | ||
|
|
f4defb735b | ||
|
|
4a75fe73df | ||
|
|
b30a93816b | ||
|
|
7d2f12c6e2 | ||
|
|
fc2220c4d9 | ||
|
|
d3b9c75e50 | ||
|
|
bcf4f396d6 | ||
|
|
dc77235c96 | ||
|
|
d8a13a72aa | ||
|
|
33a7cdefa8 | ||
|
|
aa4ace976e | ||
|
|
252b7eeb9b | ||
|
|
73edaadbca | ||
|
|
934df7da8a | ||
|
|
2b51a2a54e | ||
|
|
5c250c1725 | ||
|
|
470267a185 | ||
|
|
5c7a5295d1 | ||
|
|
e598013e30 | ||
|
|
c0077b1e26 | ||
|
|
10ef40ddf5 | ||
|
|
5da79a7dba | ||
|
|
3abf7bb00b | ||
|
|
afa7b0c1f3 | ||
|
|
8f8f4faa12 | ||
|
|
672636c3bf | ||
|
|
b9c4c97eba | ||
|
|
ac7c4e889a | ||
|
|
951290cac7 | ||
|
|
61423f1791 | ||
|
|
b601bfdbfb | ||
|
|
3b8f0bc92f | ||
|
|
c1df0da9be | ||
|
|
214d9027b5 | ||
|
|
745b3a4939 | ||
|
|
f3694a41ff | ||
|
|
39e60f27f2 | ||
|
|
a03e37f700 | ||
|
|
c545205e66 | ||
|
|
0c2ce097ed | ||
|
|
d689c4763a | ||
|
|
dd94d10722 | ||
|
|
8bb6cdd8d6 | ||
|
|
fab7eef244 | ||
|
|
5bfbcbb8f5 | ||
|
|
55eb7e41d8 | ||
|
|
b5028add57 | ||
|
|
5ee339b5a8 | ||
|
|
7fb43f8707 | ||
|
|
b2fb6949d2 | ||
|
|
6c8b2b4a6d | ||
|
|
f635673ce9 | ||
|
|
156abbdcbe | ||
|
|
47f60eb391 | ||
|
|
ef0d680f06 | ||
|
|
8afc02b364 | ||
|
|
035be9d67d | ||
|
|
86396946e9 | ||
|
|
b29b7851d9 | ||
|
|
90c29e5646 | ||
|
|
290ad9fc41 | ||
|
|
0cc5d301e5 | ||
|
|
c93f04f0e4 | ||
|
|
9b9a66bdd2 | ||
|
|
e94ded628a | ||
|
|
d0caf98eb3 | ||
|
|
27414d33dd | ||
|
|
bd05f4c1a5 | ||
|
|
bcc16c9b0e | ||
|
|
36c3429566 | ||
|
|
1398054216 | ||
|
|
6e19aca481 | ||
|
|
790669e60a | ||
|
|
9b55b020ca | ||
|
|
8b0f0fb44e | ||
|
|
09a1fd1925 | ||
|
|
5fe6df3887 | ||
|
|
8c26d67739 | ||
|
|
e4ef56b878 | ||
|
|
e44fda1911 | ||
|
|
a1d2e275a7 | ||
|
|
9538af0554 | ||
|
|
5a0257e40e | ||
|
|
bbb6e438b1 | ||
|
|
e32e4a0c8e | ||
|
|
49abb19673 | ||
|
|
79ce2fff0a | ||
|
|
fc130e325c | ||
|
|
a659cd8a49 | ||
|
|
62b8b0834c | ||
|
|
61ba319e98 | ||
|
|
392e027ae3 | ||
|
|
689d2f01d9 | ||
|
|
558a011e00 | ||
|
|
c00363badf | ||
|
|
c559426d8b | ||
|
|
31cbf4d8eb | ||
|
|
65c604f827 | ||
|
|
bf75937cc0 | ||
|
|
186b1b8376 | ||
|
|
f76d77f79d | ||
|
|
9da178ad56 | ||
|
|
3aca09d0fb | ||
|
|
b2397efb25 | ||
|
|
282fa87571 | ||
|
|
3be14d4135 | ||
|
|
8ccfac2318 |
358 changed files with 5059 additions and 3410 deletions
26
Dalamud.sln
26
Dalamud.sln
|
|
@ -75,6 +75,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel.Generator", "l
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source Generators", "Source Generators", "{50BEC23B-FFFD-427B-A95D-27E1D1958FFF}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
generators\Directory.Build.props = generators\Directory.Build.props
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj", "{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Sample", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Sample\Dalamud.EnumGenerator.Sample.csproj", "{8CDAEB2D-5022-450A-A97F-181C6270185F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.EnumGenerator.Tests", "generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator.Tests\Dalamud.EnumGenerator.Tests.csproj", "{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -173,6 +184,18 @@ Global
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -197,6 +220,9 @@ Global
|
||||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
||||||
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
|
{27AA9F87-D2AA-41D9-A559-0F1EBA38C5F8} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||||
|
{8CDAEB2D-5022-450A-A97F-181C6270185F} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||||
|
{F5D92D2D-D36F-4471-B657-8B9AA6C98AD6} = {50BEC23B-FFFD-427B-A95D-27E1D1958FFF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
|
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ using System.Threading.Tasks;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.FontIdentifier;
|
using Dalamud.Interface.FontIdentifier;
|
||||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Internal.ReShadeHandling;
|
using Dalamud.Interface.Internal.ReShadeHandling;
|
||||||
using Dalamud.Interface.Style;
|
using Dalamud.Interface.Style;
|
||||||
|
|
@ -20,9 +19,12 @@ using Dalamud.Plugin.Internal.AutoUpdate;
|
||||||
using Dalamud.Plugin.Internal.Profiles;
|
using Dalamud.Plugin.Internal.Profiles;
|
||||||
using Dalamud.Storage;
|
using Dalamud.Storage;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
using Windows.Win32.UI.WindowsAndMessaging;
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
namespace Dalamud.Configuration.Internal;
|
namespace Dalamud.Configuration.Internal;
|
||||||
|
|
@ -91,7 +93,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a dictionary of seen FTUE levels.
|
/// Gets or sets a dictionary of seen FTUE levels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, int> SeenFtueLevels { get; set; } = new();
|
public Dictionary<string, int> SeenFtueLevels { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
|
|
@ -111,7 +113,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of custom repos.
|
/// Gets or sets a list of custom repos.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = new();
|
public List<ThirdPartyRepoSettings> ThirdRepoList { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether a disclaimer regarding third-party repos has been dismissed.
|
/// Gets or sets a value indicating whether a disclaimer regarding third-party repos has been dismissed.
|
||||||
|
|
@ -121,12 +123,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of hidden plugins.
|
/// Gets or sets a list of hidden plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> HiddenPluginInternalName { get; set; } = new();
|
public List<string> HiddenPluginInternalName { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of seen plugins.
|
/// Gets or sets a list of seen plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> SeenPluginInternalName { get; set; } = new();
|
public List<string> SeenPluginInternalName { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
|
/// Gets or sets a list of additional settings for devPlugins. The key is the absolute path
|
||||||
|
|
@ -134,14 +136,14 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
/// However by specifiying this value manually, you can add arbitrary files outside the normal
|
/// However by specifiying this value manually, you can add arbitrary files outside the normal
|
||||||
/// file paths.
|
/// file paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = new();
|
public Dictionary<string, DevPluginSettings> DevPluginSettings { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
|
/// Gets or sets a list of additional locations that dev plugins should be loaded from. This can
|
||||||
/// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
|
/// be either a DLL or folder, but should be the absolute path, or a path relative to the currently
|
||||||
/// injected Dalamud instance.
|
/// injected Dalamud instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = new();
|
public List<DevPluginLocationSettings> DevPluginLoadLocations { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the global UI scale.
|
/// Gets or sets the global UI scale.
|
||||||
|
|
@ -223,7 +225,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list representing the command history for the Dalamud Console.
|
/// Gets or sets a list representing the command history for the Dalamud Console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> LogCommandHistory { get; set; } = new();
|
public List<string> LogCommandHistory { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the dev bar should open at startup.
|
/// Gets or sets a value indicating whether the dev bar should open at startup.
|
||||||
|
|
@ -599,7 +601,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
|
||||||
{
|
{
|
||||||
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
|
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
|
||||||
var winAnimEnabled = 0;
|
var winAnimEnabled = 0;
|
||||||
var success = false;
|
bool success;
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
success = Windows.Win32.PInvoke.SystemParametersInfo(
|
success = Windows.Win32.PInvoke.SystemParametersInfo(
|
||||||
|
|
|
||||||
|
|
@ -31,5 +31,5 @@ internal sealed class DevPluginSettings
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of validation problems that have been dismissed by the user.
|
/// Gets or sets a list of validation problems that have been dismissed by the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> DismissedValidationProblems { get; set; } = new();
|
public List<string> DismissedValidationProblems { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
@ -17,9 +17,9 @@ namespace Dalamud.Console;
|
||||||
[ServiceManager.BlockingEarlyLoadedService("Console is needed by other blocking early loaded services.")]
|
[ServiceManager.BlockingEarlyLoadedService("Console is needed by other blocking early loaded services.")]
|
||||||
internal partial class ConsoleManager : IServiceType
|
internal partial class ConsoleManager : IServiceType
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("CON");
|
private static readonly ModuleLog Log = ModuleLog.Create<ConsoleManager>();
|
||||||
|
|
||||||
private Dictionary<string, IConsoleEntry> entries = new();
|
private Dictionary<string, IConsoleEntry> entries = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConsoleManager"/> class.
|
/// Initializes a new instance of the <see cref="ConsoleManager"/> class.
|
||||||
|
|
@ -99,10 +99,7 @@ internal partial class ConsoleManager : IServiceType
|
||||||
ArgumentNullException.ThrowIfNull(name);
|
ArgumentNullException.ThrowIfNull(name);
|
||||||
ArgumentNullException.ThrowIfNull(alias);
|
ArgumentNullException.ThrowIfNull(alias);
|
||||||
|
|
||||||
var target = this.FindEntry(name);
|
var target = this.FindEntry(name) ?? throw new EntryNotFoundException(name);
|
||||||
if (target == null)
|
|
||||||
throw new EntryNotFoundException(name);
|
|
||||||
|
|
||||||
if (this.FindEntry(alias) != null)
|
if (this.FindEntry(alias) != null)
|
||||||
throw new InvalidOperationException($"Entry '{alias}' already exists.");
|
throw new InvalidOperationException($"Entry '{alias}' already exists.");
|
||||||
|
|
||||||
|
|
@ -346,7 +343,7 @@ internal partial class ConsoleManager : IServiceType
|
||||||
|
|
||||||
private static class Traits
|
private static class Traits
|
||||||
{
|
{
|
||||||
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression("argument")] string? paramName = null)
|
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
|
||||||
{
|
{
|
||||||
if (argument == null && !typeof(T).IsValueType)
|
if (argument == null && !typeof(T).IsValueType)
|
||||||
throw new ArgumentNullException(paramName);
|
throw new ArgumentNullException(paramName);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ internal class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly ConsoleManager console = Service<ConsoleManager>.Get();
|
private readonly ConsoleManager console = Service<ConsoleManager>.Get();
|
||||||
|
|
||||||
private readonly List<IConsoleEntry> trackedEntries = new();
|
private readonly List<IConsoleEntry> trackedEntries = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConsoleManagerPluginScoped"/> class.
|
/// Initializes a new instance of the <see cref="ConsoleManagerPluginScoped"/> class.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Storage;
|
using Dalamud.Storage;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Timing;
|
using Dalamud.Utility.Timing;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
using Windows.Win32.Foundation;
|
using Windows.Win32.Foundation;
|
||||||
using Windows.Win32.Security;
|
using Windows.Win32.Security;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<PropertyGroup Label="Feature">
|
<PropertyGroup Label="Feature">
|
||||||
<Description>XIV Launcher addon framework</Description>
|
<Description>XIV Launcher addon framework</Description>
|
||||||
<DalamudVersion>14.0.0.2</DalamudVersion>
|
<DalamudVersion>14.0.2.1</DalamudVersion>
|
||||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||||
<Version>$(DalamudVersion)</Version>
|
<Version>$(DalamudVersion)</Version>
|
||||||
<FileVersion>$(DalamudVersion)</FileVersion>
|
<FileVersion>$(DalamudVersion)</FileVersion>
|
||||||
|
|
@ -65,7 +65,6 @@
|
||||||
<PackageReference Include="CheapLoc" />
|
<PackageReference Include="CheapLoc" />
|
||||||
<PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="all" />
|
<PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="all" />
|
||||||
<PackageReference Include="goatcorp.Reloaded.Hooks" />
|
<PackageReference Include="goatcorp.Reloaded.Hooks" />
|
||||||
<PackageReference Include="goatcorp.Reloaded.Assembler" />
|
|
||||||
<PackageReference Include="JetBrains.Annotations" />
|
<PackageReference Include="JetBrains.Annotations" />
|
||||||
<PackageReference Include="Lumina" />
|
<PackageReference Include="Lumina" />
|
||||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
|
||||||
|
|
@ -88,6 +87,15 @@
|
||||||
<PackageReference Include="TerraFX.Interop.Windows" />
|
<PackageReference Include="TerraFX.Interop.Windows" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\generators\Dalamud.EnumGenerator\Dalamud.EnumGenerator\Dalamud.EnumGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="EnumCloneMap.txt"/>
|
||||||
|
<AdditionalFiles Include="EnumCloneMap.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
|
<EmbeddedResource Include="Interface\ImGuiBackend\Renderers\imgui-frag.hlsl.bytes">
|
||||||
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
|
<LogicalName>imgui-frag.hlsl.bytes</LogicalName>
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Timing;
|
using Dalamud.Utility.Timing;
|
||||||
|
|
||||||
using Lumina;
|
using Lumina;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Data;
|
namespace Dalamud.Data;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
|
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
|
||||||
|
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
namespace Dalamud.Data;
|
namespace Dalamud.Data;
|
||||||
|
|
@ -13,7 +15,7 @@ namespace Dalamud.Data;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed unsafe class RsvResolver : IDisposable
|
internal sealed unsafe class RsvResolver : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("RsvProvider");
|
private static readonly ModuleLog Log = ModuleLog.Create<RsvResolver>();
|
||||||
|
|
||||||
private readonly Hook<LayoutWorld.Delegates.AddRsvString> addRsvStringHook;
|
private readonly Hook<LayoutWorld.Delegates.AddRsvString> addRsvStringHook;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -16,10 +14,13 @@ using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Storage;
|
using Dalamud.Storage;
|
||||||
using Dalamud.Support;
|
using Dalamud.Support;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
using Windows.Win32.Foundation;
|
using Windows.Win32.Foundation;
|
||||||
using Windows.Win32.UI.WindowsAndMessaging;
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
|
|
|
||||||
3
Dalamud/EnumCloneMap.txt
Normal file
3
Dalamud/EnumCloneMap.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Format: Target.Full.TypeName = Source.Full.EnumTypeName
|
||||||
|
# Example: Generate a local enum MyGeneratedEnum in namespace Sample.Gen mapped to SourceEnums.SampleSourceEnum
|
||||||
|
Dalamud.Game.Agent.AgentId = FFXIVClientStructs.FFXIV.Client.UI.Agent.AgentId
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
|
public static readonly Guid DalamudInternalKey = Guid.NewGuid();
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,11 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InputBaseInputReceived = 15,
|
InputBaseInputReceived = 15,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired at the very beginning of AtkInputManager.HandleInput on AtkStage.ViewportEventManager. Used in LovmMiniMap.
|
||||||
|
/// </summary>
|
||||||
|
RawInputData = 16,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Focus Start.
|
/// Focus Start.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -107,7 +112,12 @@ public enum AddonEventType : byte
|
||||||
SliderReleased = 30,
|
SliderReleased = 30,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentList RollOver.
|
/// AtkComponentList Button Press.
|
||||||
|
/// </summary>
|
||||||
|
ListButtonPress = 31,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Roll Over.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ListItemRollOver = 33,
|
ListItemRollOver = 33,
|
||||||
|
|
||||||
|
|
@ -126,11 +136,31 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ListItemDoubleClick = 36,
|
ListItemDoubleClick = 36,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Highlight.
|
||||||
|
/// </summary>
|
||||||
|
ListItemHighlight = 37,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentList Select.
|
/// AtkComponentList Select.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ListItemSelect = 38,
|
ListItemSelect = 38,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Pad Drag Drop Begin.
|
||||||
|
/// </summary>
|
||||||
|
ListItemPadDragDropBegin = 40,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Pad Drag Drop End.
|
||||||
|
/// </summary>
|
||||||
|
ListItemPadDragDropEnd = 41,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentList Pad Drag Drop Insert.
|
||||||
|
/// </summary>
|
||||||
|
ListItemPadDragDropInsert = 42,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Begin.
|
/// AtkComponentDragDrop Begin.
|
||||||
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
/// Sent on MouseDown over a draggable icon (will NOT send for a locked icon).
|
||||||
|
|
@ -142,12 +172,22 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DragDropEnd = 51,
|
DragDropEnd = 51,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentDragDrop Insert Attempt.
|
||||||
|
/// </summary>
|
||||||
|
DragDropInsertAttempt = 52,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Insert.
|
/// AtkComponentDragDrop Insert.
|
||||||
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
/// Sent when dropping an icon into a hotbar/inventory slot or similar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DragDropInsert = 53,
|
DragDropInsert = 53,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkComponentDragDrop Can Accept Check.
|
||||||
|
/// </summary>
|
||||||
|
DragDropCanAcceptCheck = 54,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Roll Over.
|
/// AtkComponentDragDrop Roll Over.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -165,23 +205,18 @@ public enum AddonEventType : byte
|
||||||
DragDropDiscard = 57,
|
DragDropDiscard = 57,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drag Drop Unknown.
|
/// AtkComponentDragDrop Click.
|
||||||
|
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Use DragDropDiscard", true)]
|
DragDropClick = 58,
|
||||||
DragDropUnk54 = 54,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentDragDrop Cancel.
|
/// AtkComponentDragDrop Cancel.
|
||||||
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
/// Sent on MouseUp if the cursor has not moved since DragDropBegin, OR on MouseDown over a locked icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Renamed to DragDropClick")]
|
||||||
DragDropCancel = 58,
|
DragDropCancel = 58,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Drag Drop Unknown.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use DragDropCancel", true)]
|
|
||||||
DragDropUnk55 = 55,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkComponentIconText Roll Over.
|
/// AtkComponentIconText Roll Over.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -217,6 +252,11 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TimerEnd = 65,
|
TimerEnd = 65,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkTimer Start.
|
||||||
|
/// </summary>
|
||||||
|
TimerStart = 66,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkSimpleTween Progress.
|
/// AtkSimpleTween Progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -247,6 +287,11 @@ public enum AddonEventType : byte
|
||||||
/// </summary>
|
/// </summary>
|
||||||
WindowChangeScale = 72,
|
WindowChangeScale = 72,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AtkTimeline Active Label Changed.
|
||||||
|
/// </summary>
|
||||||
|
TimelineActiveLabelChanged = 75,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AtkTextNode Link Mouse Click.
|
/// AtkTextNode Link Mouse Click.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using Dalamud.Game.Addon.Events.EventDataTypes;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
|
@ -16,7 +15,7 @@ namespace Dalamud.Game.Addon.Events;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal unsafe class PluginEventController : IDisposable
|
internal unsafe class PluginEventController : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("AddonEventManager");
|
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
||||||
|
|
@ -28,7 +27,7 @@ internal unsafe class PluginEventController : IDisposable
|
||||||
|
|
||||||
private AddonEventListener EventListener { get; init; }
|
private AddonEventListener EventListener { get; init; }
|
||||||
|
|
||||||
private List<AddonEventEntry> Events { get; } = new();
|
private List<AddonEventEntry> Events { get; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a tracked event.
|
/// Adds a tracked event.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Addon argument data for OnFocusChanged events.
|
||||||
|
/// </summary>
|
||||||
|
public class AddonFocusChangedArgs : AddonArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddonFocusChangedArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AddonFocusChangedArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AddonArgsType Type => AddonArgsType.FocusChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the window is being focused or unfocused.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShouldFocus { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -44,4 +44,9 @@ public enum AddonArgsType
|
||||||
/// Contains argument data for Close.
|
/// Contains argument data for Close.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Close,
|
Close,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for OnFocusChanged.
|
||||||
|
/// </summary>
|
||||||
|
FocusChanged,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,4 +203,14 @@ public enum AddonEvent
|
||||||
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
/// Be aware this is only called for certain popup windows, it is not triggered when clicking on windows.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
PostFocus,
|
PostFocus,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before an addon processes its FocusChanged method.
|
||||||
|
/// </summary>
|
||||||
|
PreFocusChanged,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after a addon processes its FocusChanged method.
|
||||||
|
/// </summary>
|
||||||
|
PostFocusChanged,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,13 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly List<AddonVirtualTable> AllocatedTables = [];
|
public static readonly List<AddonVirtualTable> AllocatedTables = [];
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
private static readonly ModuleLog Log = ModuleLog.Create<AddonLifecycle>();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
|
||||||
|
private bool isInvokingListeners;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private AddonLifecycle()
|
private AddonLifecycle()
|
||||||
|
|
@ -52,26 +56,36 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
AllocatedTables.Clear();
|
AllocatedTables.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves a virtual table address to the original virtual table address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableAddress">The modified address to resolve.</param>
|
||||||
|
/// <returns>The original address.</returns>
|
||||||
|
internal static AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
|
||||||
|
{
|
||||||
|
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||||
|
if (matchedTable == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedTable.OriginalVirtualTable;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register a listener for the target event and addon.
|
/// Register a listener for the target event and addon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="listener">The listener to register.</param>
|
/// <param name="listener">The listener to register.</param>
|
||||||
internal void RegisterListener(AddonLifecycleEventListener listener)
|
internal void RegisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
if (!this.EventListeners.ContainsKey(listener.EventType))
|
if (this.isInvokingListeners)
|
||||||
{
|
{
|
||||||
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
|
||||||
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
|
||||||
{
|
{
|
||||||
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -80,12 +94,13 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="listener">The listener to unregister.</param>
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
internal void UnregisterListener(AddonLifecycleEventListener listener)
|
||||||
{
|
{
|
||||||
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
if (this.isInvokingListeners)
|
||||||
{
|
{
|
||||||
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
|
||||||
{
|
}
|
||||||
addonListener.Remove(listener);
|
else
|
||||||
}
|
{
|
||||||
|
this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,6 +112,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
/// <param name="blame">What to blame on errors.</param>
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||||
{
|
{
|
||||||
|
this.isInvokingListeners = true;
|
||||||
|
|
||||||
// Early return if we don't have any listeners of this type
|
// Early return if we don't have any listeners of this type
|
||||||
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return;
|
||||||
|
|
||||||
|
|
@ -131,19 +148,41 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isInvokingListeners = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void RegisterListenerMethod(AddonLifecycleEventListener listener)
|
||||||
/// Resolves a virtual table address to the original virtual table address.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tableAddress">The modified address to resolve.</param>
|
|
||||||
/// <returns>The original address.</returns>
|
|
||||||
internal AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
|
|
||||||
{
|
{
|
||||||
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||||
if (matchedTable == null) return null;
|
{
|
||||||
|
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return matchedTable.OriginalVirtualTable;
|
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
|
||||||
|
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
|
||||||
|
{
|
||||||
|
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnregisterListenerMethod(AddonLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
|
||||||
|
{
|
||||||
|
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
|
||||||
|
{
|
||||||
|
addonListener.Remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAddonInitialize(AtkUnitBase* addon)
|
private void OnAddonInitialize(AtkUnitBase* addon)
|
||||||
|
|
@ -263,5 +302,5 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
||||||
=> (nint)this.addonLifecycleService.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress);
|
=> (nint)AddonLifecycle.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ internal unsafe class AddonVirtualTable : IDisposable
|
||||||
private readonly AddonArgs onMouseOverArgs = new();
|
private readonly AddonArgs onMouseOverArgs = new();
|
||||||
private readonly AddonArgs onMouseOutArgs = new();
|
private readonly AddonArgs onMouseOutArgs = new();
|
||||||
private readonly AddonArgs focusArgs = new();
|
private readonly AddonArgs focusArgs = new();
|
||||||
|
private readonly AddonFocusChangedArgs focusChangedArgs = new();
|
||||||
|
|
||||||
private readonly AtkUnitBase* atkUnitBase;
|
private readonly AtkUnitBase* atkUnitBase;
|
||||||
|
|
||||||
|
|
@ -63,6 +64,7 @@ internal unsafe class AddonVirtualTable : IDisposable
|
||||||
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
|
private readonly AtkUnitBase.Delegates.OnMouseOver onMouseOverFunction;
|
||||||
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
|
private readonly AtkUnitBase.Delegates.OnMouseOut onMouseOutFunction;
|
||||||
private readonly AtkUnitBase.Delegates.Focus focusFunction;
|
private readonly AtkUnitBase.Delegates.Focus focusFunction;
|
||||||
|
private readonly AtkUnitBase.Delegates.OnFocusChange onFocusChangeFunction;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
|
/// Initializes a new instance of the <see cref="AddonVirtualTable"/> class.
|
||||||
|
|
@ -103,6 +105,7 @@ internal unsafe class AddonVirtualTable : IDisposable
|
||||||
this.onMouseOverFunction = this.OnAddonMouseOver;
|
this.onMouseOverFunction = this.OnAddonMouseOver;
|
||||||
this.onMouseOutFunction = this.OnAddonMouseOut;
|
this.onMouseOutFunction = this.OnAddonMouseOut;
|
||||||
this.focusFunction = this.OnAddonFocus;
|
this.focusFunction = this.OnAddonFocus;
|
||||||
|
this.onFocusChangeFunction = this.OnAddonFocusChange;
|
||||||
|
|
||||||
// Overwrite specific virtual table entries
|
// Overwrite specific virtual table entries
|
||||||
this.ModifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
|
this.ModifiedVirtualTable->Dtor = (delegate* unmanaged<AtkUnitBase*, byte, AtkEventListener*>)Marshal.GetFunctionPointerForDelegate(this.destructorFunction);
|
||||||
|
|
@ -121,6 +124,7 @@ internal unsafe class AddonVirtualTable : IDisposable
|
||||||
this.ModifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
|
this.ModifiedVirtualTable->OnMouseOver = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOverFunction);
|
||||||
this.ModifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
|
this.ModifiedVirtualTable->OnMouseOut = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.onMouseOutFunction);
|
||||||
this.ModifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
|
this.ModifiedVirtualTable->Focus = (delegate* unmanaged<AtkUnitBase*, void>)Marshal.GetFunctionPointerForDelegate(this.focusFunction);
|
||||||
|
this.ModifiedVirtualTable->OnFocusChange = (delegate* unmanaged<AtkUnitBase*, bool, void>)Marshal.GetFunctionPointerForDelegate(this.onFocusChangeFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -630,6 +634,36 @@ internal unsafe class AddonVirtualTable : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAddonFocusChange(AtkUnitBase* thisPtr, bool isFocused)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.focusChangedArgs.Addon = thisPtr;
|
||||||
|
this.focusChangedArgs.ShouldFocus = isFocused;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PreFocusChanged, this.focusChangedArgs);
|
||||||
|
|
||||||
|
isFocused = this.focusChangedArgs.ShouldFocus;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnFocusChange(thisPtr, isFocused);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnFocusChanged. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AddonEvent.PostFocusChanged, this.focusChangedArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAddonFocusChange.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
39
Dalamud/Game/Agent/AgentArgTypes/AgentArgs.cs
Normal file
39
Dalamud/Game/Agent/AgentArgTypes/AgentArgs.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Dalamud.Game.NativeWrapper;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for AgentLifecycle AgentArgTypes.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe class AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pointer to the Agents AgentInterface*.
|
||||||
|
/// </summary>
|
||||||
|
public AgentInterfacePtr Agent { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the agent id.
|
||||||
|
/// </summary>
|
||||||
|
public AgentId AgentId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of these args.
|
||||||
|
/// </summary>
|
||||||
|
public virtual AgentArgsType Type => AgentArgsType.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the typed pointer to the Agents AgentInterface*.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">AgentInterface.</typeparam>
|
||||||
|
/// <returns>Typed pointer to contained Agents AgentInterface.</returns>
|
||||||
|
public T* GetAgentPointer<T>() where T : unmanaged
|
||||||
|
=> (T*)this.Agent.Address;
|
||||||
|
}
|
||||||
22
Dalamud/Game/Agent/AgentArgTypes/AgentClassJobChangeArgs.cs
Normal file
22
Dalamud/Game/Agent/AgentArgTypes/AgentClassJobChangeArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for game events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentClassJobChangeArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentClassJobChangeArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentClassJobChangeArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.ClassJobChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating what the new ClassJob is.
|
||||||
|
/// </summary>
|
||||||
|
public byte ClassJobId { get; set; }
|
||||||
|
}
|
||||||
22
Dalamud/Game/Agent/AgentArgTypes/AgentGameEventArgs.cs
Normal file
22
Dalamud/Game/Agent/AgentArgTypes/AgentGameEventArgs.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for game events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentGameEventArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentGameEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentGameEventArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.GameEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value representing which gameEvent was triggered.
|
||||||
|
/// </summary>
|
||||||
|
public int GameEvent { get; set; }
|
||||||
|
}
|
||||||
27
Dalamud/Game/Agent/AgentArgTypes/AgentLevelChangeArgs.cs
Normal file
27
Dalamud/Game/Agent/AgentArgTypes/AgentLevelChangeArgs.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for game events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentLevelChangeArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentLevelChangeArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentLevelChangeArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.LevelChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating which ClassJob was switched to.
|
||||||
|
/// </summary>
|
||||||
|
public byte ClassJobId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating what the new level is.
|
||||||
|
/// </summary>
|
||||||
|
public ushort Level { get; set; }
|
||||||
|
}
|
||||||
37
Dalamud/Game/Agent/AgentArgTypes/AgentReceiveEventArgs.cs
Normal file
37
Dalamud/Game/Agent/AgentArgTypes/AgentReceiveEventArgs.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent argument data for ReceiveEvent events.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentReceiveEventArgs : AgentArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentReceiveEventArgs"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentReceiveEventArgs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override AgentArgsType Type => AgentArgsType.ReceiveEvent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkValue return value for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public nint ReturnValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkValue array for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public nint AtkValues { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the AtkValue count for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public uint ValueCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the event kind for this event message.
|
||||||
|
/// </summary>
|
||||||
|
public ulong EventKind { get; set; }
|
||||||
|
}
|
||||||
32
Dalamud/Game/Agent/AgentArgsType.cs
Normal file
32
Dalamud/Game/Agent/AgentArgsType.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for available AgentLifecycle arg data.
|
||||||
|
/// </summary>
|
||||||
|
public enum AgentArgsType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic arg type that contains no meaningful data.
|
||||||
|
/// </summary>
|
||||||
|
Generic,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for ReceiveEvent.
|
||||||
|
/// </summary>
|
||||||
|
ReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for GameEvent.
|
||||||
|
/// </summary>
|
||||||
|
GameEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for LevelChange.
|
||||||
|
/// </summary>
|
||||||
|
LevelChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains argument data for ClassJobChange.
|
||||||
|
/// </summary>
|
||||||
|
ClassJobChange,
|
||||||
|
}
|
||||||
87
Dalamud/Game/Agent/AgentEvent.cs
Normal file
87
Dalamud/Game/Agent/AgentEvent.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration for available AgentLifecycle events.
|
||||||
|
/// </summary>
|
||||||
|
public enum AgentEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PostReceiveEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Filtered Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreReceiveEventWithResult,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Filtered Receive Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PostReceiveEventWithResult,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Show Function.
|
||||||
|
/// </summary>
|
||||||
|
PreShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Show Function.
|
||||||
|
/// </summary>
|
||||||
|
PostShow,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Hide Function.
|
||||||
|
/// </summary>
|
||||||
|
PreHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Hide Function.
|
||||||
|
/// </summary>
|
||||||
|
PostHide,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Update Function.
|
||||||
|
/// </summary>
|
||||||
|
PreUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Update Function.
|
||||||
|
/// </summary>
|
||||||
|
PostUpdate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Game Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreGameEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Game Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PostGameEvent,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its Game Event Function.
|
||||||
|
/// </summary>
|
||||||
|
PreLevelChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its Level Change Function.
|
||||||
|
/// </summary>
|
||||||
|
PostLevelChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired before the agent processes its ClassJob Change Function.
|
||||||
|
/// </summary>
|
||||||
|
PreClassJobChange,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event that is fired after the agent has processed its ClassJob Change Function.
|
||||||
|
/// </summary>
|
||||||
|
PostClassJobChange,
|
||||||
|
}
|
||||||
338
Dalamud/Game/Agent/AgentLifecycle.cs
Normal file
338
Dalamud/Game/Agent/AgentLifecycle.cs
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
using Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides events for in-game agent lifecycles.
|
||||||
|
/// </summary>
|
||||||
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal unsafe class AgentLifecycle : IInternalDisposableService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all allocated agent virtual tables.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly List<AgentVirtualTable> AllocatedTables = [];
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("AgentLifecycle");
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
private Hook<AgentModule.Delegates.Ctor>? onInitializeAgentsHook;
|
||||||
|
private bool isInvokingListeners;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private AgentLifecycle()
|
||||||
|
{
|
||||||
|
var agentModuleInstance = AgentModule.Instance();
|
||||||
|
|
||||||
|
// Hook is only used to determine appropriate timing for replacing Agent Virtual Tables
|
||||||
|
// If the agent module is already initialized, then we can replace the tables safely.
|
||||||
|
if (agentModuleInstance is null)
|
||||||
|
{
|
||||||
|
this.onInitializeAgentsHook = Hook<AgentModule.Delegates.Ctor>.FromAddress((nint)AgentModule.MemberFunctionPointers.Ctor, this.OnAgentModuleInitialize);
|
||||||
|
this.onInitializeAgentsHook.Enable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For safety because this might be injected async, we will make sure we are on the main thread first.
|
||||||
|
this.framework.RunOnFrameworkThread(() => this.ReplaceVirtualTables(agentModuleInstance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of all AgentLifecycle Event Listeners.
|
||||||
|
/// </summary> <br/>
|
||||||
|
/// Mapping is: EventType -> ListenerList
|
||||||
|
internal Dictionary<AgentEvent, Dictionary<AgentId, HashSet<AgentLifecycleEventListener>>> EventListeners { get; } = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
this.onInitializeAgentsHook?.Dispose();
|
||||||
|
this.onInitializeAgentsHook = null;
|
||||||
|
|
||||||
|
AllocatedTables.ForEach(entry => entry.Dispose());
|
||||||
|
AllocatedTables.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves a virtual table address to the original virtual table address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableAddress">The modified address to resolve.</param>
|
||||||
|
/// <returns>The original address.</returns>
|
||||||
|
internal static AgentInterface.AgentInterfaceVirtualTable* GetOriginalVirtualTable(AgentInterface.AgentInterfaceVirtualTable* tableAddress)
|
||||||
|
{
|
||||||
|
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
|
||||||
|
if (matchedTable == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedTable.OriginalVirtualTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a listener for the target event and agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The listener to register.</param>
|
||||||
|
internal void RegisterListener(AgentLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
if (this.isInvokingListeners)
|
||||||
|
{
|
||||||
|
this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters the listener from events.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The listener to unregister.</param>
|
||||||
|
internal void UnregisterListener(AgentLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
if (this.isInvokingListeners)
|
||||||
|
{
|
||||||
|
this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke listeners for the specified event type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event Type.</param>
|
||||||
|
/// <param name="args">AgentARgs.</param>
|
||||||
|
/// <param name="blame">What to blame on errors.</param>
|
||||||
|
internal void InvokeListenersSafely(AgentEvent eventType, AgentArgs args, [CallerMemberName] string blame = "")
|
||||||
|
{
|
||||||
|
this.isInvokingListeners = true;
|
||||||
|
|
||||||
|
// Early return if we don't have any listeners of this type
|
||||||
|
if (!this.EventListeners.TryGetValue(eventType, out var agentListeners)) return;
|
||||||
|
|
||||||
|
// Handle listeners for this event type that don't care which agent is triggering it
|
||||||
|
if (agentListeners.TryGetValue((AgentId)uint.MaxValue, out var globalListeners))
|
||||||
|
{
|
||||||
|
foreach (var listener in globalListeners)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global agent event listener.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle listeners that are listening for this agent and event type specifically
|
||||||
|
if (agentListeners.TryGetValue(args.AgentId, out var agentListener))
|
||||||
|
{
|
||||||
|
foreach (var listener in agentListener)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.FunctionDelegate.Invoke(eventType, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific agent {args.AgentId}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInvokingListeners = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentModuleInitialize(AgentModule* thisPtr, UIModule* uiModule)
|
||||||
|
{
|
||||||
|
this.onInitializeAgentsHook!.Original(thisPtr, uiModule);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.ReplaceVirtualTables(thisPtr);
|
||||||
|
|
||||||
|
// We don't need this hook anymore, it did its job!
|
||||||
|
this.onInitializeAgentsHook!.Dispose();
|
||||||
|
this.onInitializeAgentsHook = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in AgentLifecycle during AgentModule Ctor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterListenerMethod(AgentLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
if (!this.EventListeners.ContainsKey(listener.EventType))
|
||||||
|
{
|
||||||
|
if (!this.EventListeners.TryAdd(listener.EventType, []))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: uint.MaxValue is a valid agent id, as that will trigger on any agent for this event type
|
||||||
|
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AgentId))
|
||||||
|
{
|
||||||
|
if (!this.EventListeners[listener.EventType].TryAdd(listener.AgentId, []))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.EventListeners[listener.EventType][listener.AgentId].Add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnregisterListenerMethod(AgentLifecycleEventListener listener)
|
||||||
|
{
|
||||||
|
if (this.EventListeners.TryGetValue(listener.EventType, out var agentListeners))
|
||||||
|
{
|
||||||
|
if (agentListeners.TryGetValue(listener.AgentId, out var agentListener))
|
||||||
|
{
|
||||||
|
agentListener.Remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplaceVirtualTables(AgentModule* agentModule)
|
||||||
|
{
|
||||||
|
foreach (uint index in Enumerable.Range(0, agentModule->Agents.Length))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var agentPointer = agentModule->Agents.GetPointer((int)index);
|
||||||
|
|
||||||
|
if (agentPointer is null)
|
||||||
|
{
|
||||||
|
Log.Warning("Null Agent Found?");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions
|
||||||
|
AllocatedTables.Add(new AgentVirtualTable(agentPointer->Value, (AgentId)index, this));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Exception in AgentLifecycle during ReplaceVirtualTables.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin-scoped version of a AgentLifecycle service.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[ServiceManager.ScopedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IAgentLifecycle>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal class AgentLifecyclePluginScoped : IInternalDisposableService, IAgentLifecycle
|
||||||
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly AgentLifecycle agentLifecycleService = Service<AgentLifecycle>.Get();
|
||||||
|
|
||||||
|
private readonly List<AgentLifecycleEventListener> eventListeners = [];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IInternalDisposableService.DisposeService()
|
||||||
|
{
|
||||||
|
foreach (var listener in this.eventListeners)
|
||||||
|
{
|
||||||
|
this.agentLifecycleService.UnregisterListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate handler)
|
||||||
|
{
|
||||||
|
foreach (var agentId in agentIds)
|
||||||
|
{
|
||||||
|
this.RegisterListener(eventType, agentId, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate handler)
|
||||||
|
{
|
||||||
|
var listener = new AgentLifecycleEventListener(eventType, agentId, handler);
|
||||||
|
this.eventListeners.Add(listener);
|
||||||
|
this.agentLifecycleService.RegisterListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RegisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate handler)
|
||||||
|
{
|
||||||
|
this.RegisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AgentEvent eventType, IEnumerable<AgentId> agentIds, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
foreach (var agentId in agentIds)
|
||||||
|
{
|
||||||
|
this.UnregisterListener(eventType, agentId, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
this.eventListeners.RemoveAll(entry =>
|
||||||
|
{
|
||||||
|
if (entry.EventType != eventType) return false;
|
||||||
|
if (entry.AgentId != agentId) return false;
|
||||||
|
if (handler is not null && entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
|
this.agentLifecycleService.UnregisterListener(entry);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(AgentEvent eventType, IAgentLifecycle.AgentEventDelegate? handler = null)
|
||||||
|
{
|
||||||
|
this.UnregisterListener(eventType, (AgentId)uint.MaxValue, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void UnregisterListener(params IAgentLifecycle.AgentEventDelegate[] handlers)
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers)
|
||||||
|
{
|
||||||
|
this.eventListeners.RemoveAll(entry =>
|
||||||
|
{
|
||||||
|
if (entry.FunctionDelegate != handler) return false;
|
||||||
|
|
||||||
|
this.agentLifecycleService.UnregisterListener(entry);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
|
||||||
|
=> (nint)AgentLifecycle.GetOriginalVirtualTable((AgentInterface.AgentInterfaceVirtualTable*)virtualTableAddress);
|
||||||
|
}
|
||||||
38
Dalamud/Game/Agent/AgentLifecycleEventListener.cs
Normal file
38
Dalamud/Game/Agent/AgentLifecycleEventListener.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a helper for tracking and invoking listener delegates.
|
||||||
|
/// </summary>
|
||||||
|
public class AgentLifecycleEventListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentLifecycleEventListener"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventType">Event type to listen for.</param>
|
||||||
|
/// <param name="agentId">Agent id to listen for.</param>
|
||||||
|
/// <param name="functionDelegate">Delegate to invoke.</param>
|
||||||
|
internal AgentLifecycleEventListener(AgentEvent eventType, AgentId agentId, IAgentLifecycle.AgentEventDelegate functionDelegate)
|
||||||
|
{
|
||||||
|
this.EventType = eventType;
|
||||||
|
this.AgentId = agentId;
|
||||||
|
this.FunctionDelegate = functionDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the agentId of the agent this listener is looking for.
|
||||||
|
/// uint.MaxValue if it wants to be called for any agent.
|
||||||
|
/// </summary>
|
||||||
|
public AgentId AgentId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the event type this listener is looking for.
|
||||||
|
/// </summary>
|
||||||
|
public AgentEvent EventType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the delegate this listener invokes.
|
||||||
|
/// </summary>
|
||||||
|
public IAgentLifecycle.AgentEventDelegate FunctionDelegate { get; init; }
|
||||||
|
}
|
||||||
391
Dalamud/Game/Agent/AgentVirtualTable.cs
Normal file
391
Dalamud/Game/Agent/AgentVirtualTable.cs
Normal file
|
|
@ -0,0 +1,391 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Dalamud.Game.Agent.AgentArgTypes;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Agent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class that holds references to an agents original and modified virtual table entries.
|
||||||
|
/// </summary>
|
||||||
|
internal unsafe class AgentVirtualTable : IDisposable
|
||||||
|
{
|
||||||
|
// This need to be at minimum the largest virtual table size of all agents
|
||||||
|
// Copying extra entries is not problematic, and is considered safe.
|
||||||
|
private const int VirtualTableEntryCount = 60;
|
||||||
|
|
||||||
|
private const bool EnableLogging = false;
|
||||||
|
|
||||||
|
private static readonly ModuleLog Log = new("AgentVT");
|
||||||
|
|
||||||
|
private readonly AgentLifecycle lifecycleService;
|
||||||
|
|
||||||
|
private readonly AgentId agentId;
|
||||||
|
|
||||||
|
// Each agent gets its own set of args that are used to mutate the original call when used in pre-calls
|
||||||
|
private readonly AgentReceiveEventArgs receiveEventArgs = new();
|
||||||
|
private readonly AgentReceiveEventArgs filteredReceiveEventArgs = new();
|
||||||
|
private readonly AgentArgs showArgs = new();
|
||||||
|
private readonly AgentArgs hideArgs = new();
|
||||||
|
private readonly AgentArgs updateArgs = new();
|
||||||
|
private readonly AgentGameEventArgs gameEventArgs = new();
|
||||||
|
private readonly AgentLevelChangeArgs levelChangeArgs = new();
|
||||||
|
private readonly AgentClassJobChangeArgs classJobChangeArgs = new();
|
||||||
|
|
||||||
|
private readonly AgentInterface* agentInterface;
|
||||||
|
|
||||||
|
// Pinned Function Delegates, as these functions get assigned to an unmanaged virtual table,
|
||||||
|
// the CLR needs to know they are in use, or it will invalidate them causing random crashing.
|
||||||
|
private readonly AgentInterface.Delegates.ReceiveEvent receiveEventFunction;
|
||||||
|
private readonly AgentInterface.Delegates.ReceiveEventWithResult receiveEventWithResultFunction;
|
||||||
|
private readonly AgentInterface.Delegates.Show showFunction;
|
||||||
|
private readonly AgentInterface.Delegates.Hide hideFunction;
|
||||||
|
private readonly AgentInterface.Delegates.Update updateFunction;
|
||||||
|
private readonly AgentInterface.Delegates.OnGameEvent gameEventFunction;
|
||||||
|
private readonly AgentInterface.Delegates.OnLevelChange levelChangeFunction;
|
||||||
|
private readonly AgentInterface.Delegates.OnClassJobChange classJobChangeFunction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AgentVirtualTable"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="agent">AgentInterface* for the agent to replace the table of.</param>
|
||||||
|
/// <param name="agentId">Agent ID.</param>
|
||||||
|
/// <param name="lifecycleService">Reference to AgentLifecycle service to callback and invoke listeners.</param>
|
||||||
|
internal AgentVirtualTable(AgentInterface* agent, AgentId agentId, AgentLifecycle lifecycleService)
|
||||||
|
{
|
||||||
|
this.agentInterface = agent;
|
||||||
|
this.agentId = agentId;
|
||||||
|
this.lifecycleService = lifecycleService;
|
||||||
|
|
||||||
|
// Save original virtual table
|
||||||
|
this.OriginalVirtualTable = agent->VirtualTable;
|
||||||
|
|
||||||
|
// Create copy of original table
|
||||||
|
// Note this will copy any derived/overriden functions that this specific agent has.
|
||||||
|
// Note: currently there are 16 virtual functions, but there's no harm in copying more for when they add new virtual functions to the game
|
||||||
|
this.ModifiedVirtualTable = (AgentInterface.AgentInterfaceVirtualTable*)IMemorySpace.GetUISpace()->Malloc(0x8 * VirtualTableEntryCount, 8);
|
||||||
|
NativeMemory.Copy(agent->VirtualTable, this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
|
||||||
|
// Overwrite the agents existing virtual table with our own
|
||||||
|
agent->VirtualTable = this.ModifiedVirtualTable;
|
||||||
|
|
||||||
|
// Pin each of our listener functions
|
||||||
|
this.receiveEventFunction = this.OnAgentReceiveEvent;
|
||||||
|
this.receiveEventWithResultFunction = this.OnAgentReceiveEventWithResult;
|
||||||
|
this.showFunction = this.OnAgentShow;
|
||||||
|
this.hideFunction = this.OnAgentHide;
|
||||||
|
this.updateFunction = this.OnAgentUpdate;
|
||||||
|
this.gameEventFunction = this.OnAgentGameEvent;
|
||||||
|
this.levelChangeFunction = this.OnAgentLevelChange;
|
||||||
|
this.classJobChangeFunction = this.OnClassJobChange;
|
||||||
|
|
||||||
|
// Overwrite specific virtual table entries
|
||||||
|
this.ModifiedVirtualTable->ReceiveEvent = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.receiveEventFunction);
|
||||||
|
this.ModifiedVirtualTable->ReceiveEventWithResult = (delegate* unmanaged<AgentInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(this.receiveEventWithResultFunction);
|
||||||
|
this.ModifiedVirtualTable->Show = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.showFunction);
|
||||||
|
this.ModifiedVirtualTable->Hide = (delegate* unmanaged<AgentInterface*, void>)Marshal.GetFunctionPointerForDelegate(this.hideFunction);
|
||||||
|
this.ModifiedVirtualTable->Update = (delegate* unmanaged<AgentInterface*, uint, void>)Marshal.GetFunctionPointerForDelegate(this.updateFunction);
|
||||||
|
this.ModifiedVirtualTable->OnGameEvent = (delegate* unmanaged<AgentInterface*, AgentInterface.GameEvent, void>)Marshal.GetFunctionPointerForDelegate(this.gameEventFunction);
|
||||||
|
this.ModifiedVirtualTable->OnLevelChange = (delegate* unmanaged<AgentInterface*, byte, ushort, void>)Marshal.GetFunctionPointerForDelegate(this.levelChangeFunction);
|
||||||
|
this.ModifiedVirtualTable->OnClassJobChange = (delegate* unmanaged<AgentInterface*, byte, void>)Marshal.GetFunctionPointerForDelegate(this.classJobChangeFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the original virtual table address for this agent.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentInterface.AgentInterfaceVirtualTable* OriginalVirtualTable { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the modified virtual address for this agent.
|
||||||
|
/// </summary>
|
||||||
|
internal AgentInterface.AgentInterfaceVirtualTable* ModifiedVirtualTable { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Ensure restoration is done atomically.
|
||||||
|
Interlocked.Exchange(ref *(nint*)&this.agentInterface->VirtualTable, (nint)this.OriginalVirtualTable);
|
||||||
|
IMemorySpace.Free(this.ModifiedVirtualTable, 0x8 * VirtualTableEntryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkValue* OnAgentReceiveEvent(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||||
|
{
|
||||||
|
AtkValue* result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.receiveEventArgs.Agent = thisPtr;
|
||||||
|
this.receiveEventArgs.AgentId = this.agentId;
|
||||||
|
this.receiveEventArgs.ReturnValue = (nint)returnValue;
|
||||||
|
this.receiveEventArgs.AtkValues = (nint)values;
|
||||||
|
this.receiveEventArgs.ValueCount = valueCount;
|
||||||
|
this.receiveEventArgs.EventKind = eventKind;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveEvent, this.receiveEventArgs);
|
||||||
|
|
||||||
|
returnValue = (AtkValue*)this.receiveEventArgs.ReturnValue;
|
||||||
|
values = (AtkValue*)this.receiveEventArgs.AtkValues;
|
||||||
|
valueCount = this.receiveEventArgs.ValueCount;
|
||||||
|
eventKind = this.receiveEventArgs.EventKind;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.OriginalVirtualTable->ReceiveEvent(thisPtr, returnValue, values, valueCount, eventKind);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Agent ReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveEvent, this.receiveEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentReceiveEvent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtkValue* OnAgentReceiveEventWithResult(AgentInterface* thisPtr, AtkValue* returnValue, AtkValue* values, uint valueCount, ulong eventKind)
|
||||||
|
{
|
||||||
|
AtkValue* result = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.filteredReceiveEventArgs.Agent = thisPtr;
|
||||||
|
this.filteredReceiveEventArgs.AgentId = this.agentId;
|
||||||
|
this.filteredReceiveEventArgs.ReturnValue = (nint)returnValue;
|
||||||
|
this.filteredReceiveEventArgs.AtkValues = (nint)values;
|
||||||
|
this.filteredReceiveEventArgs.ValueCount = valueCount;
|
||||||
|
this.filteredReceiveEventArgs.EventKind = eventKind;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreReceiveEventWithResult, this.filteredReceiveEventArgs);
|
||||||
|
|
||||||
|
returnValue = (AtkValue*)this.filteredReceiveEventArgs.ReturnValue;
|
||||||
|
values = (AtkValue*)this.filteredReceiveEventArgs.AtkValues;
|
||||||
|
valueCount = this.filteredReceiveEventArgs.ValueCount;
|
||||||
|
eventKind = this.filteredReceiveEventArgs.EventKind;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = this.OriginalVirtualTable->ReceiveEventWithResult(thisPtr, returnValue, values, valueCount, eventKind);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Agent FilteredReceiveEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostReceiveEventWithResult, this.filteredReceiveEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentReceiveEventWithResult.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentShow(AgentInterface* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.showArgs.Agent = thisPtr;
|
||||||
|
this.showArgs.AgentId = this.agentId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreShow, this.showArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->Show(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Show. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostShow, this.showArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentShow.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentHide(AgentInterface* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.hideArgs.Agent = thisPtr;
|
||||||
|
this.hideArgs.AgentId = this.agentId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreHide, this.hideArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->Hide(thisPtr);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Hide. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostHide, this.hideArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentHide.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentUpdate(AgentInterface* thisPtr, uint frameCount)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.updateArgs.Agent = thisPtr;
|
||||||
|
this.updateArgs.AgentId = this.agentId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreUpdate, this.updateArgs);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->Update(thisPtr, frameCount);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon Update. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostUpdate, this.updateArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentUpdate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentGameEvent(AgentInterface* thisPtr, AgentInterface.GameEvent gameEvent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.gameEventArgs.Agent = thisPtr;
|
||||||
|
this.gameEventArgs.AgentId = this.agentId;
|
||||||
|
this.gameEventArgs.GameEvent = (int)gameEvent;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreGameEvent, this.gameEventArgs);
|
||||||
|
|
||||||
|
gameEvent = (AgentInterface.GameEvent)this.gameEventArgs.GameEvent;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnGameEvent(thisPtr, gameEvent);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnGameEvent. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostGameEvent, this.gameEventArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentGameEvent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAgentLevelChange(AgentInterface* thisPtr, byte classJobId, ushort level)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.levelChangeArgs.Agent = thisPtr;
|
||||||
|
this.levelChangeArgs.AgentId = this.agentId;
|
||||||
|
this.levelChangeArgs.ClassJobId = classJobId;
|
||||||
|
this.levelChangeArgs.Level = level;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreLevelChange, this.levelChangeArgs);
|
||||||
|
|
||||||
|
classJobId = this.levelChangeArgs.ClassJobId;
|
||||||
|
level = this.levelChangeArgs.Level;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnLevelChange(thisPtr, classJobId, level);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnLevelChange. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostLevelChange, this.levelChangeArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnAgentLevelChange.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClassJobChange(AgentInterface* thisPtr, byte classJobId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogEvent(EnableLogging);
|
||||||
|
|
||||||
|
this.classJobChangeArgs.Agent = thisPtr;
|
||||||
|
this.classJobChangeArgs.AgentId = this.agentId;
|
||||||
|
this.classJobChangeArgs.ClassJobId = classJobId;
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PreClassJobChange, this.classJobChangeArgs);
|
||||||
|
|
||||||
|
classJobId = this.classJobChangeArgs.ClassJobId;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.OriginalVirtualTable->OnClassJobChange(thisPtr, classJobId);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception when calling original Addon OnClassJobChange. This may be a bug in the game or another plugin hooking this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lifecycleService.InvokeListenersSafely(AgentEvent.PostClassJobChange, this.classJobChangeArgs);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Caught exception from Dalamud when attempting to process OnClassJobChange.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void LogEvent(bool loggingEnabled, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
if (loggingEnabled)
|
||||||
|
{
|
||||||
|
// Manually disable the really spammy log events, you can comment this out if you need to debug them.
|
||||||
|
if (caller is "OnAgentUpdate" || this.agentId is AgentId.PadMouseMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.Debug($"[{caller}]: {this.agentId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ public abstract class BaseAddressResolver
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of memory addresses that were found, to list in /xldata.
|
/// Gets a list of memory addresses that were found, to list in /xldata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = new();
|
public static Dictionary<string, List<(string ClassName, IntPtr Address)>> DebugScannedValues { get; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(ISigScanner)"/> or <see cref="Setup64Bit(ISigScanner)"/>.
|
/// Gets or sets a value indicating whether the resolver has successfully run <see cref="Setup32Bit(ISigScanner)"/> or <see cref="Setup64Bit(ISigScanner)"/>.
|
||||||
|
|
|
||||||
221
Dalamud/Game/Chat/LogMessage.cs
Normal file
221
Dalamud/Game/Chat/LogMessage.cs
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.Text;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
|
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Chat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing a log message.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILogMessage : IEquatable<ILogMessage>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of the log message in memory.
|
||||||
|
/// </summary>
|
||||||
|
nint Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of this log message.
|
||||||
|
/// </summary>
|
||||||
|
uint LogMessageId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the GameData associated with this log message.
|
||||||
|
/// </summary>
|
||||||
|
RowRef<Lumina.Excel.Sheets.LogMessage> GameData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity that is the source of this log message, if any.
|
||||||
|
/// </summary>
|
||||||
|
ILogMessageEntity? SourceEntity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entity that is the target of this log message, if any.
|
||||||
|
/// </summary>
|
||||||
|
ILogMessageEntity? TargetEntity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of parameters.
|
||||||
|
/// </summary>
|
||||||
|
int ParameterCount { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of a parameter for the log message if it is an int.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the parameter to retrieve.</param>
|
||||||
|
/// <param name="value">The value of the parameter.</param>
|
||||||
|
/// <returns><see langword="true"/> if the parameter was retrieved successfully.</returns>
|
||||||
|
bool TryGetIntParameter(int index, out int value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of a parameter for the log message if it is a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the parameter to retrieve.</param>
|
||||||
|
/// <param name="value">The value of the parameter.</param>
|
||||||
|
/// <returns><see langword="true"/> if the parameter was retrieved successfully.</returns>
|
||||||
|
bool TryGetStringParameter(int index, out ReadOnlySeString value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats this log message into an approximation of the string that will eventually be shown in the log.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This can cause side effects such as playing sound effects and thus should only be used for debugging.</remarks>
|
||||||
|
/// <returns>The formatted string.</returns>
|
||||||
|
ReadOnlySeString FormatLogMessageForDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This struct represents log message in the queue to be added to the chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">A pointer to the log message.</param>
|
||||||
|
internal unsafe readonly struct LogMessage(LogMessageQueueItem* ptr) : ILogMessage
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public nint Address => (nint)ptr;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint LogMessageId => ptr->LogMessageId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public RowRef<Lumina.Excel.Sheets.LogMessage> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.LogMessage>(ptr->LogMessageId);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
ILogMessageEntity? ILogMessage.SourceEntity => ptr->SourceKind == EntityRelationKind.None ? null : this.SourceEntity;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
ILogMessageEntity? ILogMessage.TargetEntity => ptr->TargetKind == EntityRelationKind.None ? null : this.TargetEntity;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int ParameterCount => ptr->Parameters.Count;
|
||||||
|
|
||||||
|
private LogMessageEntity SourceEntity => new(ptr, true);
|
||||||
|
|
||||||
|
private LogMessageEntity TargetEntity => new(ptr, false);
|
||||||
|
|
||||||
|
public static bool operator ==(LogMessage x, LogMessage y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(LogMessage x, LogMessage y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(ILogMessage? other)
|
||||||
|
{
|
||||||
|
return other is LogMessage logMessage && this.Equals(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is LogMessage logMessage && this.Equals(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(this.LogMessageId, this.SourceEntity, this.TargetEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetIntParameter(int index, out int value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
if (!this.TryGetParameter(index, out var parameter)) return false;
|
||||||
|
if (parameter.Type != TextParameterType.Integer) return false;
|
||||||
|
value = parameter.IntValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool TryGetStringParameter(int index, out ReadOnlySeString value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (!this.TryGetParameter(index, out var parameter)) return false;
|
||||||
|
if (parameter.Type == TextParameterType.String)
|
||||||
|
{
|
||||||
|
value = new(parameter.StringValue.AsSpan());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.Type == TextParameterType.ReferencedUtf8String)
|
||||||
|
{
|
||||||
|
value = new(parameter.ReferencedUtf8StringValue->Utf8String.AsSpan());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ReadOnlySeString FormatLogMessageForDebugging()
|
||||||
|
{
|
||||||
|
var logModule = RaptureLogModule.Instance();
|
||||||
|
|
||||||
|
// the formatting logic is taken from RaptureLogModule_Update
|
||||||
|
|
||||||
|
using var utf8 = new Utf8String();
|
||||||
|
SetName(logModule, this.SourceEntity);
|
||||||
|
SetName(logModule, this.TargetEntity);
|
||||||
|
|
||||||
|
using var rssb = new RentedSeStringBuilder();
|
||||||
|
logModule->RaptureTextModule->FormatString(rssb.Builder.Append(this.GameData.Value.Text).GetViewAsSpan(), &ptr->Parameters, &utf8);
|
||||||
|
|
||||||
|
return new ReadOnlySeString(utf8.AsSpan());
|
||||||
|
|
||||||
|
static void SetName(RaptureLogModule* self, LogMessageEntity item)
|
||||||
|
{
|
||||||
|
var name = item.NameSpan.GetPointer(0);
|
||||||
|
|
||||||
|
if (item.IsPlayer)
|
||||||
|
{
|
||||||
|
var str = self->TempParseMessage.GetPointer(item.IsSourceEntity ? 8 : 9);
|
||||||
|
self->FormatPlayerLink(name, str, null, 0, item.Kind != 1 /* LocalPlayer */, item.HomeWorldId, false, null, false);
|
||||||
|
|
||||||
|
if (item.HomeWorldId != 0 && item.HomeWorldId != AgentLobby.Instance()->LobbyData.HomeWorldId)
|
||||||
|
{
|
||||||
|
var crossWorldSymbol = self->RaptureTextModule->UnkStrings0.GetPointer(3);
|
||||||
|
if (!crossWorldSymbol->StringPtr.HasValue)
|
||||||
|
self->RaptureTextModule->ProcessMacroCode(crossWorldSymbol, "<icon(88)>\0"u8);
|
||||||
|
str->Append(crossWorldSymbol);
|
||||||
|
if (self->UIModule->GetWorldHelper()->AllWorlds.TryGetValuePointer(item.HomeWorldId, out var world))
|
||||||
|
str->ConcatCStr(world->Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = str->StringPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.IsSourceEntity)
|
||||||
|
{
|
||||||
|
self->RaptureTextModule->SetGlobalTempEntity1(name, item.Sex, item.ObjStrId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self->RaptureTextModule->SetGlobalTempEntity2(name, item.Sex, item.ObjStrId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetParameter(int index, out TextParameter value)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= ptr->Parameters.Count)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = ptr->Parameters[index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(LogMessage other)
|
||||||
|
{
|
||||||
|
return this.LogMessageId == other.LogMessageId && this.SourceEntity == other.SourceEntity && this.TargetEntity == other.TargetEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
Dalamud/Game/Chat/LogMessageEntity.cs
Normal file
113
Dalamud/Game/Chat/LogMessageEntity.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.Chat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing an entity related to a log message.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILogMessageEntity : IEquatable<ILogMessageEntity>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of this entity.
|
||||||
|
/// </summary>
|
||||||
|
ReadOnlySeString Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the homeworld of this entity, if it is a player.
|
||||||
|
/// </summary>
|
||||||
|
ushort HomeWorldId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the homeworld of this entity, if it is a player.
|
||||||
|
/// </summary>
|
||||||
|
RowRef<World> HomeWorld { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ObjStr ID of this entity, if not a player. See <seealso cref="ISeStringEvaluator.EvaluateObjStr"/>.
|
||||||
|
/// </summary>
|
||||||
|
uint ObjStrId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this entity is a player.
|
||||||
|
/// </summary>
|
||||||
|
bool IsPlayer { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This struct represents an entity related to a log message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">A pointer to the log message item.</param>
|
||||||
|
/// <param name="source">If <see langword="true"/> represents the source entity of the log message, otherwise represents the target entity.</param>
|
||||||
|
internal unsafe readonly struct LogMessageEntity(LogMessageQueueItem* ptr, bool source) : ILogMessageEntity
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ReadOnlySeString Name => new(this.NameSpan[..this.NameSpan.IndexOf((byte)0)]);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ushort HomeWorldId => source ? ptr->SourceHomeWorld : ptr->TargetHomeWorld;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public RowRef<World> HomeWorld => LuminaUtils.CreateRef<World>(this.HomeWorldId);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public uint ObjStrId => source ? ptr->SourceObjStrId : ptr->TargetObjStrId;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsPlayer => source ? ptr->SourceIsPlayer : ptr->TargetIsPlayer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Span containing the raw name of this entity.
|
||||||
|
/// </summary>
|
||||||
|
internal Span<byte> NameSpan => source ? ptr->SourceName : ptr->TargetName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the kind of the entity.
|
||||||
|
/// </summary>
|
||||||
|
internal byte Kind => source ? (byte)ptr->SourceKind : (byte)ptr->TargetKind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Sex of this entity.
|
||||||
|
/// </summary>
|
||||||
|
internal byte Sex => source ? ptr->SourceSex : ptr->TargetSex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this entity is the source entity of a log message.
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsSourceEntity => source;
|
||||||
|
|
||||||
|
public static bool operator ==(LogMessageEntity x, LogMessageEntity y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(LogMessageEntity x, LogMessageEntity y) => !(x == y);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(ILogMessageEntity other)
|
||||||
|
{
|
||||||
|
return other is LogMessageEntity entity && this.Equals(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||||
|
{
|
||||||
|
return obj is LogMessageEntity entity && this.Equals(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(this.Name, this.HomeWorldId, this.ObjStrId, this.Sex, this.IsPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(LogMessageEntity other)
|
||||||
|
{
|
||||||
|
return this.Name == other.Name && this.HomeWorldId == other.HomeWorldId && this.ObjStrId == other.ObjStrId && this.Kind == other.Kind && this.Sex == other.Sex && this.IsPlayer == other.IsPlayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -23,7 +22,7 @@ namespace Dalamud.Game;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal partial class ChatHandlers : IServiceType
|
internal partial class ChatHandlers : IServiceType
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("ChatHandlers");
|
private static readonly ModuleLog Log = ModuleLog.Create<ChatHandlers>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Aetherytes;
|
namespace Dalamud.Game.ClientState.Aetherytes;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Dalamud.Game.ClientState;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed class ClientState : IInternalDisposableService, IClientState
|
internal sealed class ClientState : IInternalDisposableService, IClientState
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("ClientState");
|
private static readonly ModuleLog Log = ModuleLog.Create<ClientState>();
|
||||||
|
|
||||||
private readonly GameLifecycle lifecycle;
|
private readonly GameLifecycle lifecycle;
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
|
||||||
311
Dalamud/Game/ClientState/Customize/CustomizeData.cs
Normal file
311
Dalamud/Game/ClientState/Customize/CustomizeData.cs
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
namespace Dalamud.Game.ClientState.Customize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents customization data a <see cref="ICharacter"/> has.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICustomizeData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current race.
|
||||||
|
/// E.g., Miqo'te, Aura.
|
||||||
|
/// </summary>
|
||||||
|
public byte Race { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current sex.
|
||||||
|
/// </summary>
|
||||||
|
public byte Sex { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current body type.
|
||||||
|
/// </summary>
|
||||||
|
public byte BodyType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current height (0 to 100).
|
||||||
|
/// </summary>
|
||||||
|
public byte Height { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current tribe.
|
||||||
|
/// E.g., Seeker of the Sun, Keeper of the Moon.
|
||||||
|
/// </summary>
|
||||||
|
public byte Tribe { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current face (1 to 4).
|
||||||
|
/// </summary>
|
||||||
|
public byte Face { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current hairstyle.
|
||||||
|
/// </summary>
|
||||||
|
public byte Hairstyle { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current skin color.
|
||||||
|
/// </summary>
|
||||||
|
public byte SkinColor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current color of the left eye.
|
||||||
|
/// </summary>
|
||||||
|
public byte EyeColorLeft { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current color of the right eye.
|
||||||
|
/// </summary>
|
||||||
|
public byte EyeColorRight { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current main hair color.
|
||||||
|
/// </summary>
|
||||||
|
public byte HairColor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current highlight hair color.
|
||||||
|
/// </summary>
|
||||||
|
public byte HighlightsColor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current tattoo color.
|
||||||
|
/// </summary>
|
||||||
|
public byte TattooColor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current eyebrow type.
|
||||||
|
/// </summary>
|
||||||
|
public byte Eyebrows { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current nose type.
|
||||||
|
/// </summary>
|
||||||
|
public byte Nose { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current jaw type.
|
||||||
|
/// </summary>
|
||||||
|
public byte Jaw { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current lip color fur pattern.
|
||||||
|
/// </summary>
|
||||||
|
public byte LipColorFurPattern { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current muscle mass value.
|
||||||
|
/// </summary>
|
||||||
|
public byte MuscleMass { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current tail type (1 to 4).
|
||||||
|
/// </summary>
|
||||||
|
public byte TailShape { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current bust size (0 to 100).
|
||||||
|
/// </summary>
|
||||||
|
public byte BustSize { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current color of the face paint.
|
||||||
|
/// </summary>
|
||||||
|
public byte FacePaintColor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether highlight color is used.
|
||||||
|
/// </summary>
|
||||||
|
public bool Highlights { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this facial feature is used.
|
||||||
|
/// </summary>
|
||||||
|
public bool FacialFeature1 { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="FacialFeature1"/>
|
||||||
|
public bool FacialFeature2 { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="FacialFeature1"/>
|
||||||
|
public bool FacialFeature3 { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="FacialFeature1"/>
|
||||||
|
public bool FacialFeature4 { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="FacialFeature1"/>
|
||||||
|
public bool FacialFeature5 { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="FacialFeature1"/>
|
||||||
|
public bool FacialFeature6 { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="FacialFeature1"/>
|
||||||
|
public bool FacialFeature7 { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the legacy tattoo is used.
|
||||||
|
/// </summary>
|
||||||
|
public bool LegacyTattoo { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current eye shape type.
|
||||||
|
/// </summary>
|
||||||
|
public byte EyeShape { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether small iris is used.
|
||||||
|
/// </summary>
|
||||||
|
public bool SmallIris { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current mouth type.
|
||||||
|
/// </summary>
|
||||||
|
public byte Mouth { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether lipstick is used.
|
||||||
|
/// </summary>
|
||||||
|
public bool Lipstick { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current face paint type.
|
||||||
|
/// </summary>
|
||||||
|
public byte FacePaint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether face paint reversed is used.
|
||||||
|
/// </summary>
|
||||||
|
public bool FacePaintReversed { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
internal readonly unsafe struct CustomizeData : ICustomizeData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the address of the customize data struct in memory.
|
||||||
|
/// </summary>
|
||||||
|
public readonly nint Address;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CustomizeData"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address of the status list.</param>
|
||||||
|
internal CustomizeData(nint address)
|
||||||
|
{
|
||||||
|
this.Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Race => this.Struct->Race;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Sex => this.Struct->Sex;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte BodyType => this.Struct->BodyType;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Height => this.Struct->Height;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Tribe => this.Struct->Tribe;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Face => this.Struct->Face;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Hairstyle => this.Struct->Hairstyle;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte SkinColor => this.Struct->SkinColor;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte EyeColorLeft => this.Struct->EyeColorLeft;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte EyeColorRight => this.Struct->EyeColorRight;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte HairColor => this.Struct->HairColor;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte HighlightsColor => this.Struct->HighlightsColor;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte TattooColor => this.Struct->TattooColor;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Eyebrows => this.Struct->Eyebrows;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Nose => this.Struct->Nose;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Jaw => this.Struct->Jaw;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte LipColorFurPattern => this.Struct->LipColorFurPattern;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte MuscleMass => this.Struct->MuscleMass;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte TailShape => this.Struct->TailShape;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte BustSize => this.Struct->BustSize;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte FacePaintColor => this.Struct->FacePaintColor;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Highlights => this.Struct->Highlights;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature1 => this.Struct->FacialFeature1;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature2 => this.Struct->FacialFeature2;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature3 => this.Struct->FacialFeature3;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature4 => this.Struct->FacialFeature4;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature5 => this.Struct->FacialFeature5;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature6 => this.Struct->FacialFeature6;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacialFeature7 => this.Struct->FacialFeature7;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool LegacyTattoo => this.Struct->LegacyTattoo;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte EyeShape => this.Struct->EyeShape;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool SmallIris => this.Struct->SmallIris;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte Mouth => this.Struct->Mouth;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Lipstick => this.Struct->Lipstick;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte FacePaint => this.Struct->FacePaint;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FacePaintReversed => this.Struct->FacePaintReversed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the underlying structure.
|
||||||
|
/// </summary>
|
||||||
|
internal FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData* Struct =>
|
||||||
|
(FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData*)this.Address;
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,9 @@ using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
using FFXIVClientStructs.FFXIV.Client.System.Input;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.GamePad;
|
namespace Dalamud.Game.ClientState.GamePad;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ internal class JobGauges : IServiceType, IJobGauges
|
||||||
// Since the gauge itself reads from live memory, there isn't much downside to doing this.
|
// Since the gauge itself reads from live memory, there isn't much downside to doing this.
|
||||||
if (!this.cache.TryGetValue(typeof(T), out var gauge))
|
if (!this.cache.TryGetValue(typeof(T), out var gauge))
|
||||||
{
|
{
|
||||||
gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null);
|
gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, [this.Address], null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (T)gauge;
|
return (T)gauge;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
@ -82,12 +83,12 @@ public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new[]
|
return
|
||||||
{
|
[
|
||||||
this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.Mage : Song.None,
|
this.Struct->SongFlags.HasFlag(SongFlags.MagesBalladCoda) ? Song.Mage : Song.None,
|
||||||
this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.Army : Song.None,
|
this.Struct->SongFlags.HasFlag(SongFlags.ArmysPaeonCoda) ? Song.Army : Song.None,
|
||||||
this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.Wanderer : Song.None,
|
this.Struct->SongFlags.HasFlag(SongFlags.WanderersMinuetCoda) ? Song.Wanderer : Song.None,
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
using CanvasFlags = Dalamud.Game.ClientState.JobGauge.Enums.CanvasFlags;
|
using CanvasFlags = Dalamud.Game.ClientState.JobGauge.Enums.CanvasFlags;
|
||||||
using CreatureFlags = Dalamud.Game.ClientState.JobGauge.Enums.CreatureFlags;
|
using CreatureFlags = Dalamud.Game.ClientState.JobGauge.Enums.CreatureFlags;
|
||||||
|
|
@ -22,45 +22,45 @@ public unsafe class PCTGauge : JobGaugeBase<PictomancerGauge>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the use of subjective pallete.
|
/// Gets the use of subjective pallete.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte PalleteGauge => Struct->PalleteGauge;
|
public byte PalleteGauge => this.Struct->PalleteGauge;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of paint the player has.
|
/// Gets the amount of paint the player has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte Paint => Struct->Paint;
|
public byte Paint => this.Struct->Paint;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether a creature motif is drawn.
|
/// Gets a value indicating whether a creature motif is drawn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CreatureMotifDrawn => Struct->CreatureMotifDrawn;
|
public bool CreatureMotifDrawn => this.Struct->CreatureMotifDrawn;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether a weapon motif is drawn.
|
/// Gets a value indicating whether a weapon motif is drawn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool WeaponMotifDrawn => Struct->WeaponMotifDrawn;
|
public bool WeaponMotifDrawn => this.Struct->WeaponMotifDrawn;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether a landscape motif is drawn.
|
/// Gets a value indicating whether a landscape motif is drawn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LandscapeMotifDrawn => Struct->LandscapeMotifDrawn;
|
public bool LandscapeMotifDrawn => this.Struct->LandscapeMotifDrawn;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether a moogle portrait is ready.
|
/// Gets a value indicating whether a moogle portrait is ready.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MooglePortraitReady => Struct->MooglePortraitReady;
|
public bool MooglePortraitReady => this.Struct->MooglePortraitReady;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether a madeen portrait is ready.
|
/// Gets a value indicating whether a madeen portrait is ready.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MadeenPortraitReady => Struct->MadeenPortraitReady;
|
public bool MadeenPortraitReady => this.Struct->MadeenPortraitReady;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets which creature flags are present.
|
/// Gets which creature flags are present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CreatureFlags CreatureFlags => (CreatureFlags)Struct->CreatureFlags;
|
public CreatureFlags CreatureFlags => (CreatureFlags)this.Struct->CreatureFlags;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets which canvas flags are present.
|
/// Gets which canvas flags are present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CanvasFlags CanvasFlags => (CanvasFlags)Struct->CanvasFlags;
|
public CanvasFlags CanvasFlags => (CanvasFlags)this.Struct->CanvasFlags;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Game.ClientState.JobGauge.Enums;
|
using Dalamud.Game.ClientState.JobGauge.Enums;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
namespace Dalamud.Game.ClientState.JobGauge.Types;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
using FFXIVClientStructs.FFXIV.Client.Game.Gauge;
|
||||||
|
|
||||||
using Reloaded.Memory;
|
|
||||||
|
|
||||||
using DreadCombo = Dalamud.Game.ClientState.JobGauge.Enums.DreadCombo;
|
using DreadCombo = Dalamud.Game.ClientState.JobGauge.Enums.DreadCombo;
|
||||||
using SerpentCombo = Dalamud.Game.ClientState.JobGauge.Enums.SerpentCombo;
|
using SerpentCombo = Dalamud.Game.ClientState.JobGauge.Enums.SerpentCombo;
|
||||||
|
|
||||||
|
|
@ -24,25 +22,25 @@ public unsafe class VPRGauge : JobGaugeBase<ViperGauge>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets how many uses of uncoiled fury the player has.
|
/// Gets how many uses of uncoiled fury the player has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte RattlingCoilStacks => Struct->RattlingCoilStacks;
|
public byte RattlingCoilStacks => this.Struct->RattlingCoilStacks;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets Serpent Offering stacks and gauge.
|
/// Gets Serpent Offering stacks and gauge.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte SerpentOffering => Struct->SerpentOffering;
|
public byte SerpentOffering => this.Struct->SerpentOffering;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets value indicating the use of 1st, 2nd, 3rd, 4th generation and Ouroboros.
|
/// Gets value indicating the use of 1st, 2nd, 3rd, 4th generation and Ouroboros.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte AnguineTribute => Struct->AnguineTribute;
|
public byte AnguineTribute => this.Struct->AnguineTribute;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the last Weaponskill used in DreadWinder/Pit of Dread combo.
|
/// Gets the last Weaponskill used in DreadWinder/Pit of Dread combo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DreadCombo DreadCombo => (DreadCombo)Struct->DreadCombo;
|
public DreadCombo DreadCombo => (DreadCombo)this.Struct->DreadCombo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets current ability for Serpent's Tail.
|
/// Gets current ability for Serpent's Tail.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SerpentCombo SerpentCombo => (SerpentCombo)Struct->SerpentCombo;
|
public SerpentCombo SerpentCombo => (SerpentCombo)this.Struct->SerpentCombo;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,50 +30,50 @@ internal sealed unsafe class TargetManager : IServiceType, ITargetManager
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? Target
|
public IGameObject? Target
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GetHardTarget());
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->GetHardTarget());
|
||||||
set => Struct->SetHardTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
set => this.Struct->SetHardTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? MouseOverTarget
|
public IGameObject? MouseOverTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->MouseOverTarget);
|
||||||
set => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
set => this.Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? FocusTarget
|
public IGameObject? FocusTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->FocusTarget);
|
||||||
set => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
set => this.Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? PreviousTarget
|
public IGameObject? PreviousTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->PreviousTarget);
|
||||||
set => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
set => this.Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? SoftTarget
|
public IGameObject? SoftTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GetSoftTarget());
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->GetSoftTarget());
|
||||||
set => Struct->SetSoftTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
set => this.Struct->SetSoftTarget((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? GPoseTarget
|
public IGameObject? GPoseTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->GPoseTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->GPoseTarget);
|
||||||
set => Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
set => this.Struct->GPoseTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IGameObject? MouseOverNameplateTarget
|
public IGameObject? MouseOverNameplateTarget
|
||||||
{
|
{
|
||||||
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverNameplateTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)this.Struct->MouseOverNameplateTarget);
|
||||||
set => Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
set => this.Struct->MouseOverNameplateTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)value?.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TargetSystem* Struct => TargetSystem.Instance();
|
private TargetSystem* Struct => TargetSystem.Instance();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Dalamud.Game.ClientState.Statuses;
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game.ClientState.Customize;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
@ -16,68 +15,73 @@ namespace Dalamud.Game.ClientState.Objects.Types;
|
||||||
public interface ICharacter : IGameObject
|
public interface ICharacter : IGameObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current HP of this Chara.
|
/// Gets the current HP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint CurrentHp { get; }
|
public uint CurrentHp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum HP of this Chara.
|
/// Gets the maximum HP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint MaxHp { get; }
|
public uint MaxHp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current MP of this Chara.
|
/// Gets the current MP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint CurrentMp { get; }
|
public uint CurrentMp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum MP of this Chara.
|
/// Gets the maximum MP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint MaxMp { get; }
|
public uint MaxMp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current GP of this Chara.
|
/// Gets the current GP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint CurrentGp { get; }
|
public uint CurrentGp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum GP of this Chara.
|
/// Gets the maximum GP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint MaxGp { get; }
|
public uint MaxGp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current CP of this Chara.
|
/// Gets the current CP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint CurrentCp { get; }
|
public uint CurrentCp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum CP of this Chara.
|
/// Gets the maximum CP of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint MaxCp { get; }
|
public uint MaxCp { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the shield percentage of this Chara.
|
/// Gets the shield percentage of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte ShieldPercentage { get; }
|
public byte ShieldPercentage { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ClassJob of this Chara.
|
/// Gets the ClassJob of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RowRef<ClassJob> ClassJob { get; }
|
public RowRef<ClassJob> ClassJob { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the level of this Chara.
|
/// Gets the level of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte Level { get; }
|
public byte Level { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a byte array describing the visual appearance of this Chara.
|
/// Gets a byte array describing the visual appearance of this character.
|
||||||
/// Indexed by <see cref="CustomizeIndex"/>.
|
/// Indexed by <see cref="CustomizeIndex"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] Customize { get; }
|
public byte[] Customize { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Free Company tag of this chara.
|
/// Gets the underlying CustomizeData struct for this character.
|
||||||
|
/// </summary>
|
||||||
|
public ICustomizeData CustomizeData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Free Company tag of this character.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SeString CompanyTag { get; }
|
public SeString CompanyTag { get; }
|
||||||
|
|
||||||
|
|
@ -119,7 +123,7 @@ internal unsafe class Character : GameObject, ICharacter
|
||||||
/// This represents a non-static entity.
|
/// This represents a non-static entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The address of this character in memory.</param>
|
/// <param name="address">The address of this character in memory.</param>
|
||||||
internal Character(IntPtr address)
|
internal Character(nint address)
|
||||||
: base(address)
|
: base(address)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -158,8 +162,12 @@ internal unsafe class Character : GameObject, ICharacter
|
||||||
public byte Level => this.Struct->CharacterData.Level;
|
public byte Level => this.Struct->CharacterData.Level;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
[Api15ToDo("Do not allocate on each call, use the CS Span and let consumers do allocation if necessary")]
|
||||||
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
|
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ICustomizeData CustomizeData => new CustomizeData((nint)(&this.Struct->DrawData.CustomizeData));
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
|
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
||||||
public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
|
public unsafe nint GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
public nint GroupListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
public nint AllianceListAddress => (nint)Unsafe.AsPointer(ref this.GroupManagerStruct->MainGroup.AllianceMembers[0]);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.ClientState.Statuses;
|
using Dalamud.Game.ClientState.Statuses;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public sealed unsafe partial class StatusList
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of status effect slots the actor has.
|
/// Gets the amount of status effect slots the actor has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Length => Struct->NumValidStatuses;
|
public int Length => this.Struct->NumValidStatuses;
|
||||||
|
|
||||||
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Dalamud.Game.Command;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed unsafe class CommandManager : IInternalDisposableService, ICommandManager
|
internal sealed unsafe class CommandManager : IInternalDisposableService, ICommandManager
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("Command");
|
private static readonly ModuleLog Log = ModuleLog.Create<CommandManager>();
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, IReadOnlyCommandInfo> commandMap = new();
|
private readonly ConcurrentDictionary<string, IReadOnlyCommandInfo> commandMap = new();
|
||||||
private readonly ConcurrentDictionary<(string, IReadOnlyCommandInfo), string> commandAssemblyNameMap = new();
|
private readonly ConcurrentDictionary<(string, IReadOnlyCommandInfo), string> commandAssemblyNameMap = new();
|
||||||
|
|
@ -71,7 +71,7 @@ internal sealed unsafe class CommandManager : IInternalDisposableService, IComma
|
||||||
if (separatorPosition + 1 >= content.Length)
|
if (separatorPosition + 1 >= content.Length)
|
||||||
{
|
{
|
||||||
// Remove the trailing space
|
// Remove the trailing space
|
||||||
command = content.Substring(0, separatorPosition);
|
command = content[..separatorPosition];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -262,12 +262,12 @@ internal sealed unsafe class CommandManager : IInternalDisposableService, IComma
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager
|
internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("Command");
|
private static readonly ModuleLog Log = ModuleLog.Create<CommandManager>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly CommandManager commandManagerService = Service<CommandManager>.Get();
|
private readonly CommandManager commandManagerService = Service<CommandManager>.Get();
|
||||||
|
|
||||||
private readonly List<string> pluginRegisteredCommands = new();
|
private readonly List<string> pluginRegisteredCommands = [];
|
||||||
private readonly LocalPlugin pluginInfo;
|
private readonly LocalPlugin pluginInfo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Common.Configuration;
|
using FFXIVClientStructs.FFXIV.Common.Configuration;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Config;
|
namespace Dalamud.Game.Config;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Common.Configuration;
|
using FFXIVClientStructs.FFXIV.Common.Configuration;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Config;
|
namespace Dalamud.Game.Config;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Dalamud.Game;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed class Framework : IInternalDisposableService, IFramework
|
internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("Framework");
|
private static readonly ModuleLog Log = ModuleLog.Create<Framework>();
|
||||||
|
|
||||||
private static readonly Stopwatch StatsStopwatch = new();
|
private static readonly Stopwatch StatsStopwatch = new();
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the stats history mapping.
|
/// Gets the stats history mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, List<double>> StatsHistory { get; } = new();
|
public static Dictionary<string, List<double>> StatsHistory { get; } = [];
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
public DateTime LastUpdate { get; private set; } = DateTime.MinValue;
|
||||||
|
|
@ -106,7 +106,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the list of update sub-delegates that didn't get updated this frame.
|
/// Gets the list of update sub-delegates that didn't get updated this frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal List<string> NonUpdatedSubDelegates { get; private set; } = new();
|
internal List<string> NonUpdatedSubDelegates { get; private set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to dispatch update events.
|
/// Gets or sets a value indicating whether to dispatch update events.
|
||||||
|
|
@ -121,9 +121,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Task DelayTicks(long numTicks, CancellationToken cancellationToken = default)
|
public Task DelayTicks(long numTicks, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (this.frameworkDestroy.IsCancellationRequested)
|
if (this.frameworkDestroy.IsCancellationRequested) // Going away
|
||||||
return Task.FromCanceled(this.frameworkDestroy.Token);
|
return Task.FromCanceled(this.frameworkDestroy.Token);
|
||||||
if (numTicks <= 0)
|
if (numTicks <= 0 || this.frameworkThreadTaskScheduler.BoundThread == null) // Nonsense or before first tick
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
@ -212,11 +212,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
if (cancellationToken == default)
|
if (cancellationToken == default)
|
||||||
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
||||||
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
Task.Delay(delay, cancellationToken),
|
Task.Delay(delay, cancellationToken),
|
||||||
this.DelayTicks(delayTicks, cancellationToken),
|
this.DelayTicks(delayTicks, cancellationToken),
|
||||||
},
|
],
|
||||||
_ => func(),
|
_ => func(),
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
TaskContinuationOptions.HideScheduler,
|
TaskContinuationOptions.HideScheduler,
|
||||||
|
|
@ -239,11 +238,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
if (cancellationToken == default)
|
if (cancellationToken == default)
|
||||||
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
||||||
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
Task.Delay(delay, cancellationToken),
|
Task.Delay(delay, cancellationToken),
|
||||||
this.DelayTicks(delayTicks, cancellationToken),
|
this.DelayTicks(delayTicks, cancellationToken),
|
||||||
},
|
],
|
||||||
_ => action(),
|
_ => action(),
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
TaskContinuationOptions.HideScheduler,
|
TaskContinuationOptions.HideScheduler,
|
||||||
|
|
@ -266,11 +264,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
if (cancellationToken == default)
|
if (cancellationToken == default)
|
||||||
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
||||||
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
Task.Delay(delay, cancellationToken),
|
Task.Delay(delay, cancellationToken),
|
||||||
this.DelayTicks(delayTicks, cancellationToken),
|
this.DelayTicks(delayTicks, cancellationToken),
|
||||||
},
|
],
|
||||||
_ => func(),
|
_ => func(),
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
TaskContinuationOptions.HideScheduler,
|
TaskContinuationOptions.HideScheduler,
|
||||||
|
|
@ -293,11 +290,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
if (cancellationToken == default)
|
if (cancellationToken == default)
|
||||||
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
cancellationToken = this.FrameworkThreadTaskFactory.CancellationToken;
|
||||||
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
return this.FrameworkThreadTaskFactory.ContinueWhenAll(
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
Task.Delay(delay, cancellationToken),
|
Task.Delay(delay, cancellationToken),
|
||||||
this.DelayTicks(delayTicks, cancellationToken),
|
this.DelayTicks(delayTicks, cancellationToken),
|
||||||
},
|
],
|
||||||
_ => func(),
|
_ => func(),
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
TaskContinuationOptions.HideScheduler,
|
TaskContinuationOptions.HideScheduler,
|
||||||
|
|
@ -333,7 +329,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
||||||
internal static void AddToStats(string key, double ms)
|
internal static void AddToStats(string key, double ms)
|
||||||
{
|
{
|
||||||
if (!StatsHistory.ContainsKey(key))
|
if (!StatsHistory.ContainsKey(key))
|
||||||
StatsHistory.Add(key, new List<double>());
|
StatsHistory.Add(key, []);
|
||||||
|
|
||||||
StatsHistory[key].Add(ms);
|
StatsHistory[key].Add(ms);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
|
|
@ -37,14 +38,16 @@ namespace Dalamud.Game.Gui;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("ChatGui");
|
private static readonly ModuleLog Log = ModuleLog.Create<ChatGui>();
|
||||||
|
|
||||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = [];
|
||||||
|
private readonly List<nint> seenLogMessageObjects = [];
|
||||||
|
|
||||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||||
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
|
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
|
||||||
private readonly Hook<LogViewer.Delegates.HandleLinkClick> handleLinkClickHook;
|
private readonly Hook<LogViewer.Delegates.HandleLinkClick> handleLinkClickHook;
|
||||||
|
private readonly Hook<RaptureLogModule.Delegates.Update> handleLogModuleUpdate;
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
@ -58,10 +61,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
||||||
this.inventoryItemCopyHook = Hook<InventoryItem.Delegates.Copy>.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour);
|
this.inventoryItemCopyHook = Hook<InventoryItem.Delegates.Copy>.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour);
|
||||||
this.handleLinkClickHook = Hook<LogViewer.Delegates.HandleLinkClick>.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour);
|
this.handleLinkClickHook = Hook<LogViewer.Delegates.HandleLinkClick>.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour);
|
||||||
|
this.handleLogModuleUpdate = Hook<RaptureLogModule.Delegates.Update>.FromAddress(RaptureLogModule.Addresses.Update.Value, this.UpdateDetour);
|
||||||
|
|
||||||
this.printMessageHook.Enable();
|
this.printMessageHook.Enable();
|
||||||
this.inventoryItemCopyHook.Enable();
|
this.inventoryItemCopyHook.Enable();
|
||||||
this.handleLinkClickHook.Enable();
|
this.handleLinkClickHook.Enable();
|
||||||
|
this.handleLogModuleUpdate.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
|
@ -79,6 +84,9 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IChatGui.OnLogMessageDelegate? LogMessage;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint LastLinkedItemId { get; private set; }
|
public uint LastLinkedItemId { get; private set; }
|
||||||
|
|
||||||
|
|
@ -110,6 +118,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
this.printMessageHook.Dispose();
|
this.printMessageHook.Dispose();
|
||||||
this.inventoryItemCopyHook.Dispose();
|
this.inventoryItemCopyHook.Dispose();
|
||||||
this.handleLinkClickHook.Dispose();
|
this.handleLinkClickHook.Dispose();
|
||||||
|
this.handleLogModuleUpdate.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region DalamudSeString
|
#region DalamudSeString
|
||||||
|
|
@ -493,6 +502,46 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateDetour(RaptureLogModule* thisPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (ref var item in thisPtr->LogMessageQueue)
|
||||||
|
{
|
||||||
|
var logMessage = new Chat.LogMessage((LogMessageQueueItem*)Unsafe.AsPointer(ref item));
|
||||||
|
|
||||||
|
// skip any entries that survived the previous Update call as the event was already called for them
|
||||||
|
if (this.seenLogMessageObjects.Contains(logMessage.Address))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var action in Delegate.EnumerateInvocationList(this.LogMessage))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(logMessage);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "Could not invoke registered OnLogMessageDelegate for {Name}", action.Method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleLogModuleUpdate.Original(thisPtr);
|
||||||
|
|
||||||
|
// record the log messages for that we already called the event, but are still in the queue
|
||||||
|
this.seenLogMessageObjects.Clear();
|
||||||
|
foreach (ref var item in thisPtr->LogMessageQueue)
|
||||||
|
{
|
||||||
|
this.seenLogMessageObjects.Add((nint)Unsafe.AsPointer(ref item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Exception in UpdateDetour");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -521,6 +570,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward;
|
this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward;
|
||||||
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
|
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
|
||||||
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
|
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
|
||||||
|
this.chatGuiService.LogMessage += this.OnLogMessageForward;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -535,6 +585,9 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event IChatGui.OnLogMessageDelegate? LogMessage;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||||
|
|
||||||
|
|
@ -551,11 +604,13 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;
|
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;
|
||||||
this.chatGuiService.ChatMessageHandled -= this.OnMessageHandledForward;
|
this.chatGuiService.ChatMessageHandled -= this.OnMessageHandledForward;
|
||||||
this.chatGuiService.ChatMessageUnhandled -= this.OnMessageUnhandledForward;
|
this.chatGuiService.ChatMessageUnhandled -= this.OnMessageUnhandledForward;
|
||||||
|
this.chatGuiService.LogMessage -= this.OnLogMessageForward;
|
||||||
|
|
||||||
this.ChatMessage = null;
|
this.ChatMessage = null;
|
||||||
this.CheckMessageHandled = null;
|
this.CheckMessageHandled = null;
|
||||||
this.ChatMessageHandled = null;
|
this.ChatMessageHandled = null;
|
||||||
this.ChatMessageUnhandled = null;
|
this.ChatMessageUnhandled = null;
|
||||||
|
this.LogMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -609,4 +664,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
private void OnMessageUnhandledForward(XivChatType type, int timestamp, SeString sender, SeString message)
|
private void OnMessageUnhandledForward(XivChatType type, int timestamp, SeString sender, SeString message)
|
||||||
=> this.ChatMessageUnhandled?.Invoke(type, timestamp, sender, message);
|
=> this.ChatMessageUnhandled?.Invoke(type, timestamp, sender, message);
|
||||||
|
|
||||||
|
private void OnLogMessageForward(Chat.ILogMessage message)
|
||||||
|
=> this.LogMessage?.Invoke(message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
@ -28,7 +29,7 @@ namespace Dalamud.Game.Gui.ContextMenu;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextMenu
|
internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextMenu
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("ContextMenu");
|
private static readonly ModuleLog Log = ModuleLog.Create<ContextMenu>();
|
||||||
|
|
||||||
private readonly Hook<AtkModuleVf22OpenAddonByAgentDelegate> atkModuleVf22OpenAddonByAgentHook;
|
private readonly Hook<AtkModuleVf22OpenAddonByAgentDelegate> atkModuleVf22OpenAddonByAgentHook;
|
||||||
private readonly Hook<AddonContextMenu.Delegates.OnMenuSelected> addonContextMenuOnMenuSelectedHook;
|
private readonly Hook<AddonContextMenu.Delegates.OnMenuSelected> addonContextMenuOnMenuSelectedHook;
|
||||||
|
|
@ -53,7 +54,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
|
|
||||||
private Dictionary<ContextMenuType, List<IMenuItem>> MenuItems { get; } = [];
|
private Dictionary<ContextMenuType, List<IMenuItem>> MenuItems { get; } = [];
|
||||||
|
|
||||||
private object MenuItemsLock { get; } = new();
|
private Lock MenuItemsLock { get; } = new();
|
||||||
|
|
||||||
private AgentInterface* SelectedAgent { get; set; }
|
private AgentInterface* SelectedAgent { get; set; }
|
||||||
|
|
||||||
|
|
@ -335,7 +336,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
|
||||||
this.MenuCallbackIds.Clear();
|
this.MenuCallbackIds.Clear();
|
||||||
this.SelectedAgent = agent;
|
this.SelectedAgent = agent;
|
||||||
var unitManager = RaptureAtkUnitManager.Instance();
|
var unitManager = RaptureAtkUnitManager.Instance();
|
||||||
this.SelectedParentAddon = unitManager->GetAddonById(unitManager->GetAddonByName(addonName)->ContextMenuParentId);
|
this.SelectedParentAddon = unitManager->GetAddonById(unitManager->GetAddonByName(addonName)->BlockedParentId);
|
||||||
this.SelectedEventInterfaces.Clear();
|
this.SelectedEventInterfaces.Clear();
|
||||||
if (this.SelectedAgent == AgentInventoryContext.Instance())
|
if (this.SelectedAgent == AgentInventoryContext.Instance())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Dalamud.Memory;
|
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
|
||||||
{
|
{
|
||||||
private const uint BaseNodeId = 1000;
|
private const uint BaseNodeId = 1000;
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("DtrBar");
|
private static readonly ModuleLog Log = ModuleLog.Create<DtrBar>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
@ -54,7 +54,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
|
||||||
private readonly ReaderWriterLockSlim entriesLock = new();
|
private readonly ReaderWriterLockSlim entriesLock = new();
|
||||||
private readonly List<DtrBarEntry> entries = [];
|
private readonly List<DtrBarEntry> entries = [];
|
||||||
|
|
||||||
private readonly Dictionary<uint, List<IAddonEventHandle>> eventHandles = new();
|
private readonly Dictionary<uint, List<IAddonEventHandle>> eventHandles = [];
|
||||||
|
|
||||||
private ImmutableList<IReadOnlyDtrBarEntry>? entriesReadOnlyCopy;
|
private ImmutableList<IReadOnlyDtrBarEntry>? entriesReadOnlyCopy;
|
||||||
|
|
||||||
|
|
@ -397,7 +397,15 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
|
||||||
|
|
||||||
ushort w = 0, h = 0;
|
ushort w = 0, h = 0;
|
||||||
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
node->GetTextDrawSize(&w, &h, node->NodeText.StringPtr);
|
||||||
node->SetWidth(w);
|
|
||||||
|
if (data.MinimumWidth > 0)
|
||||||
|
{
|
||||||
|
node->SetWidth(Math.Max(data.MinimumWidth, w));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->SetWidth(w);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var elementWidth = data.TextNode->Width + this.configuration.DtrSpacing;
|
var elementWidth = data.TextNode->Width + this.configuration.DtrSpacing;
|
||||||
|
|
@ -516,7 +524,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
|
||||||
|
|
||||||
var node = data.TextNode = this.MakeNode(++this.runningNodeIds);
|
var node = data.TextNode = this.MakeNode(++this.runningNodeIds);
|
||||||
|
|
||||||
this.eventHandles.TryAdd(node->NodeId, new List<IAddonEventHandle>());
|
this.eventHandles.TryAdd(node->NodeId, []);
|
||||||
this.eventHandles[node->NodeId].AddRange(new List<IAddonEventHandle>
|
this.eventHandles[node->NodeId].AddRange(new List<IAddonEventHandle>
|
||||||
{
|
{
|
||||||
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler),
|
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game.Addon.Events.EventDataTypes;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
@ -41,6 +40,11 @@ public interface IReadOnlyDtrBarEntry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Shown { get; }
|
public bool Shown { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating this entry's minimum width.
|
||||||
|
/// </summary>
|
||||||
|
public ushort MinimumWidth { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the user has hidden this entry from view through the Dalamud settings.
|
/// Gets a value indicating whether the user has hidden this entry from view through the Dalamud settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -77,6 +81,11 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new bool Shown { get; set; }
|
public new bool Shown { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value specifying the requested minimum width to make this entry.
|
||||||
|
/// </summary>
|
||||||
|
public new ushort MinimumWidth { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action to be invoked when the user clicks on the dtr entry.
|
/// Gets or sets an action to be invoked when the user clicks on the dtr entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -129,6 +138,25 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
||||||
/// <inheritdoc cref="IDtrBarEntry.Tooltip" />
|
/// <inheritdoc cref="IDtrBarEntry.Tooltip" />
|
||||||
public SeString? Tooltip { get; set; }
|
public SeString? Tooltip { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MinimumWidth" />
|
||||||
|
public ushort MinimumWidth
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
if (this.TextNode is not null)
|
||||||
|
{
|
||||||
|
if (this.TextNode->GetWidth() < value)
|
||||||
|
{
|
||||||
|
this.TextNode->SetWidth(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Action<DtrInteractionEvent>? OnClick { get; set; }
|
public Action<DtrInteractionEvent>? OnClick { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Dalamud.Game.Gui;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("GameGui");
|
private static readonly ModuleLog Log = ModuleLog.Create<GameGui>();
|
||||||
|
|
||||||
private readonly GameGuiAddressResolver address;
|
private readonly GameGuiAddressResolver address;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -427,8 +427,8 @@ internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int VisibilityFlags
|
public int VisibilityFlags
|
||||||
{
|
{
|
||||||
get => ObjectData->VisibilityFlags;
|
get => this.ObjectData->VisibilityFlags;
|
||||||
set => ObjectData->VisibilityFlags = value;
|
set => this.ObjectData->VisibilityFlags = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||||
|
|
|
||||||
|
|
@ -23,23 +23,7 @@ public class PartyFinderSlot
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of jobs that this slot is accepting.
|
/// Gets a list of jobs that this slot is accepting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<JobFlags> Accepting
|
public IReadOnlyCollection<JobFlags> Accepting => this.listAccepting ??= Enum.GetValues<JobFlags>().Where(flag => this[flag]).ToArray();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.listAccepting != null)
|
|
||||||
{
|
|
||||||
return this.listAccepting;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listAccepting = Enum.GetValues(typeof(JobFlags))
|
|
||||||
.Cast<JobFlags>()
|
|
||||||
.Where(flag => this[flag])
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return this.listAccepting;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests if this slot is accepting a job.
|
/// Tests if this slot is accepting a job.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Dalamud.Game.Internal;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("DalamudAtkTweaks");
|
private static readonly ModuleLog Log = ModuleLog.Create<DalamudAtkTweaks>();
|
||||||
|
|
||||||
private readonly Hook<AgentHUD.Delegates.OpenSystemMenu> hookAgentHudOpenSystemMenu;
|
private readonly Hook<AgentHUD.Delegates.OpenSystemMenu> hookAgentHudOpenSystemMenu;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ namespace Dalamud.Game.Inventory;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal class GameInventory : IInternalDisposableService
|
internal class GameInventory : IInternalDisposableService
|
||||||
{
|
{
|
||||||
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new();
|
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = [];
|
||||||
private readonly List<GameInventoryPluginScoped> subscribers = new();
|
private readonly List<GameInventoryPluginScoped> subscribers = [];
|
||||||
|
|
||||||
private readonly List<InventoryItemAddedArgs> addedEvents = new();
|
private readonly List<InventoryItemAddedArgs> addedEvents = [];
|
||||||
private readonly List<InventoryItemRemovedArgs> removedEvents = new();
|
private readonly List<InventoryItemRemovedArgs> removedEvents = [];
|
||||||
private readonly List<InventoryItemChangedArgs> changedEvents = new();
|
private readonly List<InventoryItemChangedArgs> changedEvents = [];
|
||||||
private readonly List<InventoryItemMovedArgs> movedEvents = new();
|
private readonly List<InventoryItemMovedArgs> movedEvents = [];
|
||||||
private readonly List<InventoryItemSplitArgs> splitEvents = new();
|
private readonly List<InventoryItemSplitArgs> splitEvents = [];
|
||||||
private readonly List<InventoryItemMergedArgs> mergedEvents = new();
|
private readonly List<InventoryItemMergedArgs> mergedEvents = [];
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
@ -151,7 +151,7 @@ internal class GameInventory : IInternalDisposableService
|
||||||
bool isNew;
|
bool isNew;
|
||||||
lock (this.subscribersPendingChange)
|
lock (this.subscribersPendingChange)
|
||||||
{
|
{
|
||||||
isNew = this.subscribersPendingChange.Any() && !this.subscribers.Any();
|
isNew = this.subscribersPendingChange.Count != 0 && this.subscribers.Count == 0;
|
||||||
this.subscribers.Clear();
|
this.subscribers.Clear();
|
||||||
this.subscribers.AddRange(this.subscribersPendingChange);
|
this.subscribers.AddRange(this.subscribersPendingChange);
|
||||||
this.subscribersChanged = false;
|
this.subscribersChanged = false;
|
||||||
|
|
@ -348,7 +348,7 @@ internal class GameInventory : IInternalDisposableService
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInventory
|
internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInventory
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped));
|
private static readonly ModuleLog Log = ModuleLog.Create<GameInventoryPluginScoped>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly GameInventory gameInventoryService = Service<GameInventory>.Get();
|
private readonly GameInventory gameInventoryService = Service<GameInventory>.Get();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
using Dalamud.Game.Network.Internal;
|
using Dalamud.Game.Network.Internal;
|
||||||
using Dalamud.Game.Network.Structures;
|
using Dalamud.Game.Network.Structures;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
|
|
@ -95,7 +93,7 @@ internal class MarketBoard : IInternalDisposableService, IMarketBoard
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoard
|
internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoard
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new(nameof(MarketBoardPluginScoped));
|
private static readonly ModuleLog Log = ModuleLog.Create<MarketBoardPluginScoped>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly MarketBoard marketBoardService = Service<MarketBoard>.Get();
|
private readonly MarketBoard marketBoardService = Service<MarketBoard>.Get();
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ public readonly unsafe struct AgentInterfacePtr(nint address) : IEquatable<Agent
|
||||||
/// Focuses the AtkUnitBase.
|
/// Focuses the AtkUnitBase.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns> <c>true</c> when the addon was focused, <c>false</c> otherwise. </returns>
|
/// <returns> <c>true</c> when the addon was focused, <c>false</c> otherwise. </returns>
|
||||||
public readonly bool FocusAddon() => this.IsNull && this.Struct->FocusAddon();
|
public readonly bool FocusAddon() => !this.IsNull && this.Struct->FocusAddon();
|
||||||
|
|
||||||
/// <summary>Determines whether the specified AgentInterfacePtr is equal to the current AgentInterfacePtr.</summary>
|
/// <summary>Determines whether the specified AgentInterfacePtr is equal to the current AgentInterfacePtr.</summary>
|
||||||
/// <param name="other">The AgentInterfacePtr to compare with the current AgentInterfacePtr.</param>
|
/// <param name="other">The AgentInterfacePtr to compare with the current AgentInterfacePtr.</param>
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ public readonly unsafe struct AtkValuePtr(nint address) : IEquatable<AtkValuePtr
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public unsafe bool TryGet<T>([NotNullWhen(true)] out T? result) where T : struct
|
public unsafe bool TryGet<T>([NotNullWhen(true)] out T? result) where T : struct
|
||||||
{
|
{
|
||||||
object? value = this.GetValue();
|
var value = this.GetValue();
|
||||||
if (value is T typed)
|
if (value is T typed)
|
||||||
{
|
{
|
||||||
result = typed;
|
result = typed;
|
||||||
|
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.IoC;
|
|
||||||
using Dalamud.IoC.Internal;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Network;
|
|
||||||
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This class handles interacting with game network events.
|
|
||||||
/// </summary>
|
|
||||||
[ServiceManager.EarlyLoadedService]
|
|
||||||
internal sealed unsafe class GameNetwork : IInternalDisposableService
|
|
||||||
{
|
|
||||||
private readonly GameNetworkAddressResolver address;
|
|
||||||
private readonly Hook<PacketDispatcher.Delegates.OnReceivePacket> processZonePacketDownHook;
|
|
||||||
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
|
|
||||||
|
|
||||||
private readonly HitchDetector hitchDetectorUp;
|
|
||||||
private readonly HitchDetector hitchDetectorDown;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private unsafe GameNetwork(TargetSigScanner sigScanner)
|
|
||||||
{
|
|
||||||
this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch);
|
|
||||||
this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch);
|
|
||||||
|
|
||||||
this.address = new GameNetworkAddressResolver();
|
|
||||||
this.address.Setup(sigScanner);
|
|
||||||
|
|
||||||
var onReceivePacketAddress = (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket;
|
|
||||||
|
|
||||||
Log.Verbose("===== G A M E N E T W O R K =====");
|
|
||||||
Log.Verbose($"OnReceivePacket address {Util.DescribeAddress(onReceivePacketAddress)}");
|
|
||||||
Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}");
|
|
||||||
|
|
||||||
this.processZonePacketDownHook = Hook<PacketDispatcher.Delegates.OnReceivePacket>.FromAddress(onReceivePacketAddress, this.ProcessZonePacketDownDetour);
|
|
||||||
this.processZonePacketUpHook = Hook<ProcessZonePacketUpDelegate>.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
|
|
||||||
|
|
||||||
this.processZonePacketDownHook.Enable();
|
|
||||||
this.processZonePacketUpHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The delegate type of a network message event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataPtr">The pointer to the raw data.</param>
|
|
||||||
/// <param name="opCode">The operation ID code.</param>
|
|
||||||
/// <param name="sourceActorId">The source actor ID.</param>
|
|
||||||
/// <param name="targetActorId">The taret actor ID.</param>
|
|
||||||
/// <param name="direction">The direction of the packed.</param>
|
|
||||||
public delegate void OnNetworkMessageDelegate(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that is called when a network message is sent/received.
|
|
||||||
/// </summary>
|
|
||||||
public event OnNetworkMessageDelegate? NetworkMessage;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
void IInternalDisposableService.DisposeService()
|
|
||||||
{
|
|
||||||
this.processZonePacketDownHook.Dispose();
|
|
||||||
this.processZonePacketUpHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessZonePacketDownDetour(PacketDispatcher* dispatcher, uint targetId, IntPtr dataPtr)
|
|
||||||
{
|
|
||||||
this.hitchDetectorDown.Start();
|
|
||||||
|
|
||||||
// Go back 0x10 to get back to the start of the packet header
|
|
||||||
dataPtr -= 0x10;
|
|
||||||
|
|
||||||
foreach (var d in Delegate.EnumerateInvocationList(this.NetworkMessage))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
d.Invoke(
|
|
||||||
dataPtr + 0x20,
|
|
||||||
(ushort)Marshal.ReadInt16(dataPtr, 0x12),
|
|
||||||
0,
|
|
||||||
targetId,
|
|
||||||
NetworkMessageDirection.ZoneDown);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string header;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var data = new byte[32];
|
|
||||||
Marshal.Copy(dataPtr, data, 0, 32);
|
|
||||||
header = BitConverter.ToString(data);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
header = "failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10);
|
|
||||||
this.hitchDetectorDown.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4)
|
|
||||||
{
|
|
||||||
this.hitchDetectorUp.Start();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Call events
|
|
||||||
// TODO: Implement actor IDs
|
|
||||||
this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string header;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var data = new byte[32];
|
|
||||||
Marshal.Copy(dataPtr, data, 0, 32);
|
|
||||||
header = BitConverter.ToString(data);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
header = "failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hitchDetectorUp.Stop();
|
|
||||||
|
|
||||||
return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The address resolver for the <see cref="GameNetwork"/> class.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class GameNetworkAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of the ProcessZonePacketUp method.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ProcessZonePacketUp { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Setup64Bit(ISigScanner sig)
|
|
||||||
{
|
|
||||||
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); // unnamed in cs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -8,6 +7,7 @@ using Dalamud.Game.Network.Structures;
|
||||||
using Dalamud.Networking.Http;
|
using Dalamud.Networking.Http;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
|
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
|
||||||
|
|
@ -64,7 +64,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
||||||
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
||||||
Quantity = marketBoardItemListing.ItemQuantity,
|
Quantity = marketBoardItemListing.ItemQuantity,
|
||||||
RetainerCity = marketBoardItemListing.RetainerCityId,
|
RetainerCity = marketBoardItemListing.RetainerCityId,
|
||||||
Materia = new List<UniversalisItemMateria>(),
|
Materia = [],
|
||||||
};
|
};
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Dalamud.Game.Network.Internal;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal unsafe class NetworkHandlers : IInternalDisposableService
|
internal unsafe class NetworkHandlers : IInternalDisposableService
|
||||||
{
|
{
|
||||||
private readonly IMarketBoardUploader uploader;
|
private readonly UniversalisMarketBoardUploader uploader;
|
||||||
|
|
||||||
private readonly IDisposable handleMarketBoardItemRequest;
|
private readonly IDisposable handleMarketBoardItemRequest;
|
||||||
private readonly IDisposable handleMarketTaxRates;
|
private readonly IDisposable handleMarketTaxRates;
|
||||||
|
|
@ -55,10 +55,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
||||||
private bool disposing;
|
private bool disposing;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private NetworkHandlers(
|
private NetworkHandlers(TargetSigScanner sigScanner, HappyHttpClient happyHttpClient)
|
||||||
GameNetwork gameNetwork,
|
|
||||||
TargetSigScanner sigScanner,
|
|
||||||
HappyHttpClient happyHttpClient)
|
|
||||||
{
|
{
|
||||||
this.uploader = new UniversalisMarketBoardUploader(happyHttpClient);
|
this.uploader = new UniversalisMarketBoardUploader(happyHttpClient);
|
||||||
|
|
||||||
|
|
@ -419,7 +416,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
||||||
|
|
||||||
private IDisposable HandleMarketBoardItemRequest()
|
private IDisposable HandleMarketBoardItemRequest()
|
||||||
{
|
{
|
||||||
void LogStartObserved(MarketBoardItemRequest request)
|
static void LogStartObserved(MarketBoardItemRequest request)
|
||||||
{
|
{
|
||||||
Log.Verbose("Observed start of request for item with {NumListings} expected listings", request.AmountToArrive);
|
Log.Verbose("Observed start of request for item with {NumListings} expected listings", request.AmountToArrive);
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +445,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
||||||
private void UploadMarketBoardData(
|
private void UploadMarketBoardData(
|
||||||
MarketBoardItemRequest request,
|
MarketBoardItemRequest request,
|
||||||
(uint CatalogId, ICollection<MarketBoardHistory.MarketBoardHistoryListing> Sales) sales,
|
(uint CatalogId, ICollection<MarketBoardHistory.MarketBoardHistoryListing> Sales) sales,
|
||||||
ICollection<MarketBoardCurrentOfferings.MarketBoardItemListing> listings,
|
List<MarketBoardCurrentOfferings.MarketBoardItemListing> listings,
|
||||||
ulong uploaderId,
|
ulong uploaderId,
|
||||||
uint worldId)
|
uint worldId)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ namespace Dalamud.Game.Network;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This represents the direction of a network message.
|
/// This represents the direction of a network message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("No longer part of public API", true)]
|
||||||
public enum NetworkMessageDirection
|
public enum NetworkMessageDirection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@ using System.Threading;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
|
||||||
using Iced.Intel;
|
using Iced.Intel;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game;
|
namespace Dalamud.Game;
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Dalamud.Game.Text.Evaluator;
|
||||||
[ResolveVia<ISeStringEvaluator>]
|
[ResolveVia<ISeStringEvaluator>]
|
||||||
internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("SeStringEvaluator");
|
private static readonly ModuleLog Log = ModuleLog.Create<SeStringEvaluator>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly ClientState.ClientState clientState = Service<ClientState.ClientState>.Get();
|
private readonly ClientState.ClientState clientState = Service<ClientState.ClientState>.Get();
|
||||||
|
|
@ -244,154 +244,67 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
// if (context.HandlePayload(payload, in context))
|
// if (context.HandlePayload(payload, in context))
|
||||||
// return true;
|
// return true;
|
||||||
|
|
||||||
switch (payload.MacroCode)
|
return payload.MacroCode switch
|
||||||
{
|
{
|
||||||
case MacroCode.SetResetTime:
|
MacroCode.SetResetTime => this.TryResolveSetResetTime(in context, payload),
|
||||||
return this.TryResolveSetResetTime(in context, payload);
|
MacroCode.SetTime => this.TryResolveSetTime(in context, payload),
|
||||||
|
MacroCode.If => this.TryResolveIf(in context, payload),
|
||||||
case MacroCode.SetTime:
|
MacroCode.Switch => this.TryResolveSwitch(in context, payload),
|
||||||
return this.TryResolveSetTime(in context, payload);
|
MacroCode.SwitchPlatform => this.TryResolveSwitchPlatform(in context, payload),
|
||||||
|
MacroCode.PcName => this.TryResolvePcName(in context, payload),
|
||||||
case MacroCode.If:
|
MacroCode.IfPcGender => this.TryResolveIfPcGender(in context, payload),
|
||||||
return this.TryResolveIf(in context, payload);
|
MacroCode.IfPcName => this.TryResolveIfPcName(in context, payload),
|
||||||
|
// MacroCode.Josa
|
||||||
case MacroCode.Switch:
|
// MacroCode.Josaro
|
||||||
return this.TryResolveSwitch(in context, payload);
|
MacroCode.IfSelf => this.TryResolveIfSelf(in context, payload),
|
||||||
|
// MacroCode.NewLine (pass through)
|
||||||
case MacroCode.SwitchPlatform:
|
// MacroCode.Wait (pass through)
|
||||||
return this.TryResolveSwitchPlatform(in context, payload);
|
// MacroCode.Icon (pass through)
|
||||||
|
MacroCode.Color => this.TryResolveColor(in context, payload),
|
||||||
case MacroCode.PcName:
|
MacroCode.EdgeColor => this.TryResolveEdgeColor(in context, payload),
|
||||||
return this.TryResolvePcName(in context, payload);
|
MacroCode.ShadowColor => this.TryResolveShadowColor(in context, payload),
|
||||||
|
// MacroCode.SoftHyphen (pass through)
|
||||||
case MacroCode.IfPcGender:
|
// MacroCode.Key
|
||||||
return this.TryResolveIfPcGender(in context, payload);
|
// MacroCode.Scale
|
||||||
|
MacroCode.Bold => this.TryResolveBold(in context, payload),
|
||||||
case MacroCode.IfPcName:
|
MacroCode.Italic => this.TryResolveItalic(in context, payload),
|
||||||
return this.TryResolveIfPcName(in context, payload);
|
// MacroCode.Edge
|
||||||
|
// MacroCode.Shadow
|
||||||
// case MacroCode.Josa:
|
// MacroCode.NonBreakingSpace (pass through)
|
||||||
// case MacroCode.Josaro:
|
// MacroCode.Icon2 (pass through)
|
||||||
|
// MacroCode.Hyphen (pass through)
|
||||||
case MacroCode.IfSelf:
|
MacroCode.Num => this.TryResolveNum(in context, payload),
|
||||||
return this.TryResolveIfSelf(in context, payload);
|
MacroCode.Hex => this.TryResolveHex(in context, payload),
|
||||||
|
MacroCode.Kilo => this.TryResolveKilo(in context, payload),
|
||||||
// case MacroCode.NewLine: // pass through
|
// MacroCode.Byte
|
||||||
// case MacroCode.Wait: // pass through
|
MacroCode.Sec => this.TryResolveSec(in context, payload),
|
||||||
// case MacroCode.Icon: // pass through
|
// MacroCode.Time
|
||||||
|
MacroCode.Float => this.TryResolveFloat(in context, payload),
|
||||||
case MacroCode.Color:
|
// MacroCode.Link (pass through)
|
||||||
return this.TryResolveColor(in context, payload);
|
MacroCode.Sheet => this.TryResolveSheet(in context, payload),
|
||||||
|
MacroCode.SheetSub => this.TryResolveSheetSub(in context, payload),
|
||||||
case MacroCode.EdgeColor:
|
MacroCode.String => this.TryResolveString(in context, payload),
|
||||||
return this.TryResolveEdgeColor(in context, payload);
|
MacroCode.Caps => this.TryResolveCaps(in context, payload),
|
||||||
|
MacroCode.Head => this.TryResolveHead(in context, payload),
|
||||||
case MacroCode.ShadowColor:
|
MacroCode.Split => this.TryResolveSplit(in context, payload),
|
||||||
return this.TryResolveShadowColor(in context, payload);
|
MacroCode.HeadAll => this.TryResolveHeadAll(in context, payload),
|
||||||
|
MacroCode.Fixed => this.TryResolveFixed(in context, payload),
|
||||||
// case MacroCode.SoftHyphen: // pass through
|
MacroCode.Lower => this.TryResolveLower(in context, payload),
|
||||||
// case MacroCode.Key:
|
MacroCode.JaNoun => this.TryResolveNoun(ClientLanguage.Japanese, in context, payload),
|
||||||
// case MacroCode.Scale:
|
MacroCode.EnNoun => this.TryResolveNoun(ClientLanguage.English, in context, payload),
|
||||||
|
MacroCode.DeNoun => this.TryResolveNoun(ClientLanguage.German, in context, payload),
|
||||||
case MacroCode.Bold:
|
MacroCode.FrNoun => this.TryResolveNoun(ClientLanguage.French, in context, payload),
|
||||||
return this.TryResolveBold(in context, payload);
|
// MacroCode.ChNoun
|
||||||
|
MacroCode.LowerHead => this.TryResolveLowerHead(in context, payload),
|
||||||
case MacroCode.Italic:
|
MacroCode.ColorType => this.TryResolveColorType(in context, payload),
|
||||||
return this.TryResolveItalic(in context, payload);
|
MacroCode.EdgeColorType => this.TryResolveEdgeColorType(in context, payload),
|
||||||
|
// MacroCode.Ruby
|
||||||
// case MacroCode.Edge:
|
MacroCode.Digit => this.TryResolveDigit(in context, payload),
|
||||||
// case MacroCode.Shadow:
|
MacroCode.Ordinal => this.TryResolveOrdinal(in context, payload),
|
||||||
// case MacroCode.NonBreakingSpace: // pass through
|
// MacroCode.Sound (pass through)
|
||||||
// case MacroCode.Icon2: // pass through
|
MacroCode.LevelPos => this.TryResolveLevelPos(in context, payload),
|
||||||
// case MacroCode.Hyphen: // pass through
|
_ => false,
|
||||||
|
};
|
||||||
case MacroCode.Num:
|
|
||||||
return this.TryResolveNum(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Hex:
|
|
||||||
return this.TryResolveHex(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Kilo:
|
|
||||||
return this.TryResolveKilo(in context, payload);
|
|
||||||
|
|
||||||
// case MacroCode.Byte:
|
|
||||||
|
|
||||||
case MacroCode.Sec:
|
|
||||||
return this.TryResolveSec(in context, payload);
|
|
||||||
|
|
||||||
// case MacroCode.Time:
|
|
||||||
|
|
||||||
case MacroCode.Float:
|
|
||||||
return this.TryResolveFloat(in context, payload);
|
|
||||||
|
|
||||||
// case MacroCode.Link: // pass through
|
|
||||||
|
|
||||||
case MacroCode.Sheet:
|
|
||||||
return this.TryResolveSheet(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.SheetSub:
|
|
||||||
return this.TryResolveSheetSub(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.String:
|
|
||||||
return this.TryResolveString(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Caps:
|
|
||||||
return this.TryResolveCaps(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Head:
|
|
||||||
return this.TryResolveHead(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Split:
|
|
||||||
return this.TryResolveSplit(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.HeadAll:
|
|
||||||
return this.TryResolveHeadAll(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Fixed:
|
|
||||||
return this.TryResolveFixed(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Lower:
|
|
||||||
return this.TryResolveLower(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.JaNoun:
|
|
||||||
return this.TryResolveNoun(ClientLanguage.Japanese, in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.EnNoun:
|
|
||||||
return this.TryResolveNoun(ClientLanguage.English, in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.DeNoun:
|
|
||||||
return this.TryResolveNoun(ClientLanguage.German, in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.FrNoun:
|
|
||||||
return this.TryResolveNoun(ClientLanguage.French, in context, payload);
|
|
||||||
|
|
||||||
// case MacroCode.ChNoun:
|
|
||||||
|
|
||||||
case MacroCode.LowerHead:
|
|
||||||
return this.TryResolveLowerHead(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.ColorType:
|
|
||||||
return this.TryResolveColorType(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.EdgeColorType:
|
|
||||||
return this.TryResolveEdgeColorType(in context, payload);
|
|
||||||
|
|
||||||
// case MacroCode.Ruby:
|
|
||||||
|
|
||||||
case MacroCode.Digit:
|
|
||||||
return this.TryResolveDigit(in context, payload);
|
|
||||||
|
|
||||||
case MacroCode.Ordinal:
|
|
||||||
return this.TryResolveOrdinal(in context, payload);
|
|
||||||
|
|
||||||
// case MacroCode.Sound: // pass through
|
|
||||||
|
|
||||||
case MacroCode.LevelPos:
|
|
||||||
return this.TryResolveLevelPos(in context, payload);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool TryResolveSetResetTime(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
private unsafe bool TryResolveSetResetTime(in SeStringContext context, in ReadOnlySePayloadSpan payload)
|
||||||
|
|
@ -932,7 +845,7 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
using var rssb = new RentedSeStringBuilder();
|
using var rssb = new RentedSeStringBuilder();
|
||||||
var sb = rssb.Builder;
|
var sb = rssb.Builder;
|
||||||
|
|
||||||
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language));
|
sb.Append(this.EvaluateFromAddon(6, [rarity], context.Language)); // appends colortype and edgecolortype
|
||||||
|
|
||||||
if (!skipLink)
|
if (!skipLink)
|
||||||
sb.PushLink(LinkMacroPayloadType.Item, itemId, rarity, 0u); // arg3 = some LogMessage flag based on LogKind RowId? => "89 5C 24 20 E8 ?? ?? ?? ?? 48 8B 1F"
|
sb.PushLink(LinkMacroPayloadType.Item, itemId, rarity, 0u); // arg3 = some LogMessage flag based on LogKind RowId? => "89 5C 24 20 E8 ?? ?? ?? ?? 48 8B 1F"
|
||||||
|
|
@ -955,6 +868,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
|
||||||
if (!skipLink)
|
if (!skipLink)
|
||||||
sb.PopLink();
|
sb.PopLink();
|
||||||
|
|
||||||
|
sb.PopEdgeColorType();
|
||||||
|
sb.PopColorType();
|
||||||
|
|
||||||
text = sb.ToReadOnlySeString();
|
text = sb.ToReadOnlySeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ internal class NounProcessor : IServiceType
|
||||||
private const int PronounColumnIdx = 6;
|
private const int PronounColumnIdx = 6;
|
||||||
private const int ArticleColumnIdx = 7;
|
private const int ArticleColumnIdx = 7;
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("NounProcessor");
|
private static readonly ModuleLog Log = ModuleLog.Create<NounProcessor>();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||||
|
|
|
||||||
|
|
@ -213,11 +213,10 @@ public abstract partial class Payload
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Payload DecodeText(BinaryReader reader)
|
private static TextPayload DecodeText(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var payload = new TextPayload();
|
var payload = new TextPayload();
|
||||||
payload.DecodeImpl(reader, reader.BaseStream.Length);
|
payload.DecodeImpl(reader, reader.BaseStream.Length);
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -382,7 +381,7 @@ public abstract partial class Payload
|
||||||
{
|
{
|
||||||
if (value < 0xCF)
|
if (value < 0xCF)
|
||||||
{
|
{
|
||||||
return new byte[] { (byte)(value + 1) };
|
return [(byte)(value + 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = BitConverter.GetBytes(value);
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,10 @@ public class IconPayload : Payload
|
||||||
{
|
{
|
||||||
var indexBytes = MakeInteger((uint)this.Icon);
|
var indexBytes = MakeInteger((uint)this.Icon);
|
||||||
var chunkLen = indexBytes.Length + 1;
|
var chunkLen = indexBytes.Length + 1;
|
||||||
var bytes = new List<byte>(new byte[]
|
var bytes = new List<byte>(
|
||||||
{
|
[
|
||||||
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen,
|
START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen,
|
||||||
});
|
]);
|
||||||
bytes.AddRange(indexBytes);
|
bytes.AddRange(indexBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Dalamud.Utility;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -172,7 +173,7 @@ public class ItemPayload : Payload
|
||||||
};
|
};
|
||||||
bytes.AddRange(idBytes);
|
bytes.AddRange(idBytes);
|
||||||
// unk
|
// unk
|
||||||
bytes.AddRange(new byte[] { 0x02, 0x01 });
|
bytes.AddRange([0x02, 0x01]);
|
||||||
|
|
||||||
// Links don't have to include the name, but if they do, it requires additional work
|
// Links don't have to include the name, but if they do, it requires additional work
|
||||||
if (hasName)
|
if (hasName)
|
||||||
|
|
@ -183,17 +184,17 @@ public class ItemPayload : Payload
|
||||||
nameLen += 4; // space plus 3 bytes for HQ symbol
|
nameLen += 4; // space plus 3 bytes for HQ symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes.AddRange(new byte[]
|
bytes.AddRange(
|
||||||
{
|
[
|
||||||
0xFF, // unk
|
0xFF, // unk
|
||||||
(byte)nameLen,
|
(byte)nameLen,
|
||||||
});
|
]);
|
||||||
bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName));
|
bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName));
|
||||||
|
|
||||||
if (this.IsHQ)
|
if (this.IsHQ)
|
||||||
{
|
{
|
||||||
// space and HQ symbol
|
// space and HQ symbol
|
||||||
bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC });
|
bytes.AddRange([0x20, 0xEE, 0x80, 0xBC]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Data;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -174,7 +175,7 @@ public class MapLinkPayload : Payload
|
||||||
bytes.AddRange(yBytes);
|
bytes.AddRange(yBytes);
|
||||||
|
|
||||||
// unk
|
// unk
|
||||||
bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE });
|
bytes.AddRange([0xFF, 0x01, END_BYTE]);
|
||||||
|
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NewLinePayload : Payload, ITextProvider
|
public class NewLinePayload : Payload, ITextProvider
|
||||||
{
|
{
|
||||||
private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE };
|
private readonly byte[] bytes = [START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance of NewLinePayload.
|
/// Gets an instance of NewLinePayload.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Lumina.Extensions;
|
using Lumina.Extensions;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
|
|
@ -97,7 +98,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
reader.ReadByte();
|
reader.ReadByte();
|
||||||
|
|
||||||
// if the next byte is 0xF3 then this listing is limited to home world
|
// if the next byte is 0xF3 then this listing is limited to home world
|
||||||
byte nextByte = reader.ReadByte();
|
var nextByte = reader.ReadByte();
|
||||||
switch (nextByte)
|
switch (nextByte)
|
||||||
{
|
{
|
||||||
case (byte)PartyFinderLinkType.LimitedToHomeWorld:
|
case (byte)PartyFinderLinkType.LimitedToHomeWorld:
|
||||||
|
|
@ -121,11 +122,11 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
|
||||||
// if the link type is notification, just use premade payload data since it's always the same.
|
// if the link type is notification, just use premade payload data since it's always the same.
|
||||||
// i have no idea why it is formatted like this, but it is how it is.
|
// i have no idea why it is formatted like this, but it is how it is.
|
||||||
// note it is identical to the link terminator payload except the embedded info type is 0x08
|
// note it is identical to the link terminator payload except the embedded info type is 0x08
|
||||||
if (this.LinkType == PartyFinderLinkType.PartyFinderNotification) return new byte[] { 0x02, 0x27, 0x07, 0x08, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03, };
|
if (this.LinkType == PartyFinderLinkType.PartyFinderNotification) return [0x02, 0x27, 0x07, 0x08, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03,];
|
||||||
|
|
||||||
// back to our regularly scheduled programming...
|
// back to our regularly scheduled programming...
|
||||||
var listingIDBytes = MakeInteger(this.ListingId);
|
var listingIDBytes = MakeInteger(this.ListingId);
|
||||||
bool isFlagSpecified = this.LinkType != PartyFinderLinkType.NotSpecified;
|
var isFlagSpecified = this.LinkType != PartyFinderLinkType.NotSpecified;
|
||||||
|
|
||||||
var chunkLen = listingIDBytes.Length + 4;
|
var chunkLen = listingIDBytes.Length + 4;
|
||||||
// 1 more byte for the type flag if it is specified
|
// 1 more byte for the type flag if it is specified
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Data;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -62,7 +63,7 @@ public class QuestPayload : Payload
|
||||||
};
|
};
|
||||||
|
|
||||||
bytes.AddRange(idBytes);
|
bytes.AddRange(idBytes);
|
||||||
bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE });
|
bytes.AddRange([0x01, 0x01, END_BYTE]);
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public class RawPayload : Payload
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains.
|
/// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 });
|
public static RawPayload LinkTerminator => new([0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override PayloadType Type => PayloadType.Unknown;
|
public override PayloadType Type => PayloadType.Unknown;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SeHyphenPayload : Payload, ITextProvider
|
public class SeHyphenPayload : Payload, ITextProvider
|
||||||
{
|
{
|
||||||
private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE };
|
private readonly byte[] bytes = [START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance of SeHyphenPayload.
|
/// Gets an instance of SeHyphenPayload.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Data;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -63,7 +64,7 @@ public class StatusPayload : Payload
|
||||||
|
|
||||||
bytes.AddRange(idBytes);
|
bytes.AddRange(idBytes);
|
||||||
// unk
|
// unk
|
||||||
bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE });
|
bytes.AddRange([0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE]);
|
||||||
|
|
||||||
return bytes.ToArray();
|
return bytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Data;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -95,10 +96,10 @@ public class UIForegroundPayload : Payload
|
||||||
var colorBytes = MakeInteger(this.colorKey);
|
var colorBytes = MakeInteger(this.colorKey);
|
||||||
var chunkLen = colorBytes.Length + 1;
|
var chunkLen = colorBytes.Length + 1;
|
||||||
|
|
||||||
var bytes = new List<byte>(new byte[]
|
var bytes = new List<byte>(
|
||||||
{
|
[
|
||||||
START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen,
|
START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen,
|
||||||
});
|
]);
|
||||||
|
|
||||||
bytes.AddRange(colorBytes);
|
bytes.AddRange(colorBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Data;
|
||||||
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -98,10 +99,10 @@ public class UIGlowPayload : Payload
|
||||||
var colorBytes = MakeInteger(this.colorKey);
|
var colorBytes = MakeInteger(this.colorKey);
|
||||||
var chunkLen = colorBytes.Length + 1;
|
var chunkLen = colorBytes.Length + 1;
|
||||||
|
|
||||||
var bytes = new List<byte>(new byte[]
|
var bytes = new List<byte>(
|
||||||
{
|
[
|
||||||
START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen,
|
START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen,
|
||||||
});
|
]);
|
||||||
|
|
||||||
bytes.AddRange(colorBytes);
|
bytes.AddRange(colorBytes);
|
||||||
bytes.Add(END_BYTE);
|
bytes.Add(END_BYTE);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class SeString
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SeString()
|
public SeString()
|
||||||
{
|
{
|
||||||
this.Payloads = new List<Payload>();
|
this.Payloads = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
|
@ -16,14 +17,14 @@ using FFXIVClientStructs.FFXIV.Component.Exd;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
|
||||||
|
using AchievementSheet = Lumina.Excel.Sheets.Achievement;
|
||||||
using ActionSheet = Lumina.Excel.Sheets.Action;
|
using ActionSheet = Lumina.Excel.Sheets.Action;
|
||||||
|
using CSAchievement = FFXIVClientStructs.FFXIV.Client.Game.UI.Achievement;
|
||||||
using InstanceContentSheet = Lumina.Excel.Sheets.InstanceContent;
|
using InstanceContentSheet = Lumina.Excel.Sheets.InstanceContent;
|
||||||
using PublicContentSheet = Lumina.Excel.Sheets.PublicContent;
|
using PublicContentSheet = Lumina.Excel.Sheets.PublicContent;
|
||||||
|
|
||||||
namespace Dalamud.Game.UnlockState;
|
namespace Dalamud.Game.UnlockState;
|
||||||
|
|
||||||
#pragma warning disable Dalamud001
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class provides unlock state of various content in the game.
|
/// This class provides unlock state of various content in the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -32,8 +33,6 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new(nameof(UnlockState));
|
private static readonly ModuleLog Log = new(nameof(UnlockState));
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Type, HashSet<uint>> cachedUnlockedRowIds = [];
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DataManager dataManager = Service<DataManager>.Get();
|
private readonly DataManager dataManager = Service<DataManager>.Get();
|
||||||
|
|
||||||
|
|
@ -46,17 +45,38 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly RecipeData recipeData = Service<RecipeData>.Get();
|
private readonly RecipeData recipeData = Service<RecipeData>.Get();
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<Type, HashSet<uint>> cachedUnlockedRowIds = [];
|
||||||
|
private readonly Hook<CSAchievement.Delegates.SetAchievementCompleted> setAchievementCompletedHook;
|
||||||
|
private readonly Hook<TitleList.Delegates.SetTitleUnlocked> setTitleUnlockedHook;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private UnlockState()
|
private UnlockState()
|
||||||
{
|
{
|
||||||
this.clientState.Login += this.OnLogin;
|
this.clientState.Login += this.OnLogin;
|
||||||
this.clientState.Logout += this.OnLogout;
|
this.clientState.Logout += this.OnLogout;
|
||||||
this.gameGui.AgentUpdate += this.OnAgentUpdate;
|
this.gameGui.AgentUpdate += this.OnAgentUpdate;
|
||||||
|
|
||||||
|
this.setAchievementCompletedHook = Hook<CSAchievement.Delegates.SetAchievementCompleted>.FromAddress(
|
||||||
|
(nint)CSAchievement.MemberFunctionPointers.SetAchievementCompleted,
|
||||||
|
this.SetAchievementCompletedDetour);
|
||||||
|
|
||||||
|
this.setTitleUnlockedHook = Hook<TitleList.Delegates.SetTitleUnlocked>.FromAddress(
|
||||||
|
(nint)TitleList.MemberFunctionPointers.SetTitleUnlocked,
|
||||||
|
this.SetTitleUnlockedDetour);
|
||||||
|
|
||||||
|
this.setAchievementCompletedHook.Enable();
|
||||||
|
this.setTitleUnlockedHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IUnlockState.UnlockDelegate Unlock;
|
public event IUnlockState.UnlockDelegate Unlock;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementListLoaded => CSAchievement.Instance()->IsLoaded();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsTitleListLoaded => UIState.Instance()->TitleList.DataReceived;
|
||||||
|
|
||||||
private bool IsLoaded => PlayerState.Instance()->IsLoaded;
|
private bool IsLoaded => PlayerState.Instance()->IsLoaded;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -65,6 +85,21 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
this.clientState.Login -= this.OnLogin;
|
this.clientState.Login -= this.OnLogin;
|
||||||
this.clientState.Logout -= this.OnLogout;
|
this.clientState.Logout -= this.OnLogout;
|
||||||
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
|
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
|
||||||
|
|
||||||
|
this.setAchievementCompletedHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementComplete(AchievementSheet row)
|
||||||
|
{
|
||||||
|
// Only check for login state here as individual Achievements
|
||||||
|
// may be flagged as complete when you unlock them, regardless
|
||||||
|
// of whether the full Achievements list was loaded or not.
|
||||||
|
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return CSAchievement.Instance()->IsComplete((int)row.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -73,6 +108,15 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
return this.IsUnlockLinkUnlocked(row.UnlockLink.RowId);
|
return this.IsUnlockLinkUnlocked(row.UnlockLink.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAdventureComplete(Adventure row)
|
||||||
|
{
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return PlayerState.Instance()->IsAdventureComplete(row.RowId - 0x210000);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsAetherCurrentUnlocked(AetherCurrent row)
|
public bool IsAetherCurrentUnlocked(AetherCurrent row)
|
||||||
{
|
{
|
||||||
|
|
@ -313,9 +357,12 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMcGuffinUnlocked(McGuffin row)
|
public bool IsLeveCompleted(Leve row)
|
||||||
{
|
{
|
||||||
return PlayerState.Instance()->IsMcGuffinUnlocked(row.RowId);
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return QuestManager.Instance()->IsLevequestComplete((ushort)row.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -330,6 +377,15 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
return this.IsUnlockLinkUnlocked(row.UnlockLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsMcGuffinUnlocked(McGuffin row)
|
||||||
|
{
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return PlayerState.Instance()->IsMcGuffinUnlocked(row.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMountUnlocked(Mount row)
|
public bool IsMountUnlocked(Mount row)
|
||||||
{
|
{
|
||||||
|
|
@ -378,9 +434,21 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
return UIState.IsPublicContentUnlocked(row.RowId);
|
return UIState.IsPublicContentUnlocked(row.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsQuestCompleted(Quest row)
|
||||||
|
{
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return QuestManager.IsQuestComplete(row.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsRecipeUnlocked(Recipe row)
|
public bool IsRecipeUnlocked(Recipe row)
|
||||||
{
|
{
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
return this.recipeData.IsRecipeUnlocked(row);
|
return this.recipeData.IsRecipeUnlocked(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,6 +461,19 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.RowId);
|
return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsTitleUnlocked(Title row)
|
||||||
|
{
|
||||||
|
// Only check for login state here as individual Titles
|
||||||
|
// may be flagged as complete when you unlock them, regardless
|
||||||
|
// of whether the full Titles list was loaded or not.
|
||||||
|
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return UIState.Instance()->TitleList.IsTitleUnlocked((ushort)row.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsTraitUnlocked(Trait row)
|
public bool IsTraitUnlocked(Trait row)
|
||||||
{
|
{
|
||||||
|
|
@ -442,9 +523,15 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
if (!this.IsLoaded || rowRef.IsUntyped)
|
if (!this.IsLoaded || rowRef.IsUntyped)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (rowRef.TryGetValue<AchievementSheet>(out var achievementRow))
|
||||||
|
return this.IsAchievementComplete(achievementRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<ActionSheet>(out var actionRow))
|
if (rowRef.TryGetValue<ActionSheet>(out var actionRow))
|
||||||
return this.IsActionUnlocked(actionRow);
|
return this.IsActionUnlocked(actionRow);
|
||||||
|
|
||||||
|
if (rowRef.TryGetValue<Adventure>(out var adventureRow))
|
||||||
|
return this.IsAdventureComplete(adventureRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<AetherCurrent>(out var aetherCurrentRow))
|
if (rowRef.TryGetValue<AetherCurrent>(out var aetherCurrentRow))
|
||||||
return this.IsAetherCurrentUnlocked(aetherCurrentRow);
|
return this.IsAetherCurrentUnlocked(aetherCurrentRow);
|
||||||
|
|
||||||
|
|
@ -511,6 +598,9 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
if (rowRef.TryGetValue<Item>(out var itemRow))
|
if (rowRef.TryGetValue<Item>(out var itemRow))
|
||||||
return this.IsItemUnlocked(itemRow);
|
return this.IsItemUnlocked(itemRow);
|
||||||
|
|
||||||
|
if (rowRef.TryGetValue<Leve>(out var leveRow))
|
||||||
|
return this.IsLeveCompleted(leveRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<MJILandmark>(out var mjiLandmarkRow))
|
if (rowRef.TryGetValue<MJILandmark>(out var mjiLandmarkRow))
|
||||||
return this.IsMJILandmarkUnlocked(mjiLandmarkRow);
|
return this.IsMJILandmarkUnlocked(mjiLandmarkRow);
|
||||||
|
|
||||||
|
|
@ -538,12 +628,18 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
if (rowRef.TryGetValue<PublicContentSheet>(out var publicContentRow))
|
if (rowRef.TryGetValue<PublicContentSheet>(out var publicContentRow))
|
||||||
return this.IsPublicContentUnlocked(publicContentRow);
|
return this.IsPublicContentUnlocked(publicContentRow);
|
||||||
|
|
||||||
|
if (rowRef.TryGetValue<Quest>(out var questRow))
|
||||||
|
return this.IsQuestCompleted(questRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<Recipe>(out var recipeRow))
|
if (rowRef.TryGetValue<Recipe>(out var recipeRow))
|
||||||
return this.IsRecipeUnlocked(recipeRow);
|
return this.IsRecipeUnlocked(recipeRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<SecretRecipeBook>(out var secretRecipeBookRow))
|
if (rowRef.TryGetValue<SecretRecipeBook>(out var secretRecipeBookRow))
|
||||||
return this.IsSecretRecipeBookUnlocked(secretRecipeBookRow);
|
return this.IsSecretRecipeBookUnlocked(secretRecipeBookRow);
|
||||||
|
|
||||||
|
if (rowRef.TryGetValue<Title>(out var titleRow))
|
||||||
|
return this.IsTitleUnlocked(titleRow);
|
||||||
|
|
||||||
if (rowRef.TryGetValue<Trait>(out var traitRow))
|
if (rowRef.TryGetValue<Trait>(out var traitRow))
|
||||||
return this.IsTraitUnlocked(traitRow);
|
return this.IsTraitUnlocked(traitRow);
|
||||||
|
|
||||||
|
|
@ -593,12 +689,37 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
this.Update();
|
this.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetAchievementCompletedDetour(CSAchievement* thisPtr, uint id)
|
||||||
|
{
|
||||||
|
this.setAchievementCompletedHook.Original(thisPtr, id);
|
||||||
|
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.RaiseUnlockSafely((RowRef)LuminaUtils.CreateRef<AchievementSheet>(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTitleUnlockedDetour(TitleList* thisPtr, ushort id)
|
||||||
|
{
|
||||||
|
this.setTitleUnlockedHook.Original(thisPtr, id);
|
||||||
|
|
||||||
|
if (!this.IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.RaiseUnlockSafely((RowRef)LuminaUtils.CreateRef<Title>(id));
|
||||||
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (!this.IsLoaded)
|
if (!this.IsLoaded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Log.Verbose("Checking for new unlocks...");
|
||||||
|
|
||||||
|
// Do not check for Achievements or Titles here!
|
||||||
|
|
||||||
this.UpdateUnlocksForSheet<ActionSheet>();
|
this.UpdateUnlocksForSheet<ActionSheet>();
|
||||||
|
this.UpdateUnlocksForSheet<Adventure>();
|
||||||
this.UpdateUnlocksForSheet<AetherCurrent>();
|
this.UpdateUnlocksForSheet<AetherCurrent>();
|
||||||
this.UpdateUnlocksForSheet<AetherCurrentCompFlgSet>();
|
this.UpdateUnlocksForSheet<AetherCurrentCompFlgSet>();
|
||||||
this.UpdateUnlocksForSheet<AozAction>();
|
this.UpdateUnlocksForSheet<AozAction>();
|
||||||
|
|
@ -631,6 +752,7 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
this.UpdateUnlocksForSheet<Ornament>();
|
this.UpdateUnlocksForSheet<Ornament>();
|
||||||
this.UpdateUnlocksForSheet<Perform>();
|
this.UpdateUnlocksForSheet<Perform>();
|
||||||
this.UpdateUnlocksForSheet<PublicContentSheet>();
|
this.UpdateUnlocksForSheet<PublicContentSheet>();
|
||||||
|
this.UpdateUnlocksForSheet<Quest>();
|
||||||
this.UpdateUnlocksForSheet<Recipe>();
|
this.UpdateUnlocksForSheet<Recipe>();
|
||||||
this.UpdateUnlocksForSheet<SecretRecipeBook>();
|
this.UpdateUnlocksForSheet<SecretRecipeBook>();
|
||||||
this.UpdateUnlocksForSheet<Trait>();
|
this.UpdateUnlocksForSheet<Trait>();
|
||||||
|
|
@ -639,11 +761,11 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
// Not implemented:
|
// Not implemented:
|
||||||
// - DescriptionPage: quite complex
|
// - DescriptionPage: quite complex
|
||||||
// - QuestAcceptAdditionCondition: ignored
|
// - QuestAcceptAdditionCondition: ignored
|
||||||
|
// - Leve: AgentUpdateFlag.UnlocksUpdate is not set and the completed status can be unset again!
|
||||||
|
|
||||||
// For some other day:
|
// For some other day:
|
||||||
// - FishingSpot
|
// - FishingSpot
|
||||||
// - Spearfishing
|
// - Spearfishing
|
||||||
// - Adventure (Sightseeing)
|
|
||||||
// - MinerFolkloreTome
|
// - MinerFolkloreTome
|
||||||
// - BotanistFolkloreTome
|
// - BotanistFolkloreTome
|
||||||
// - FishingFolkloreTome
|
// - FishingFolkloreTome
|
||||||
|
|
@ -656,8 +778,6 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
// - EmjCostume
|
// - EmjCostume
|
||||||
|
|
||||||
// Probably not happening, because it requires fetching data from server:
|
// Probably not happening, because it requires fetching data from server:
|
||||||
// - Achievements
|
|
||||||
// - Titles
|
|
||||||
// - Bozjan Field Notes
|
// - Bozjan Field Notes
|
||||||
// - Support/Phantom Jobs, which require to be in Occult Crescent, because it checks the jobs level for != 0
|
// - Support/Phantom Jobs, which require to be in Occult Crescent, because it checks the jobs level for != 0
|
||||||
}
|
}
|
||||||
|
|
@ -678,18 +798,23 @@ internal unsafe class UnlockState : IInternalDisposableService, IUnlockState
|
||||||
|
|
||||||
unlockedRowIds.Add(row.RowId);
|
unlockedRowIds.Add(row.RowId);
|
||||||
|
|
||||||
Log.Verbose($"Unlock detected: {typeof(T).Name}#{row.RowId}");
|
// Log.Verbose($"Unlock detected: {typeof(T).Name}#{row.RowId}");
|
||||||
|
|
||||||
foreach (var action in Delegate.EnumerateInvocationList(this.Unlock))
|
this.RaiseUnlockSafely((RowRef)rowRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseUnlockSafely(RowRef rowRef)
|
||||||
|
{
|
||||||
|
foreach (var action in Delegate.EnumerateInvocationList(this.Unlock))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
action(rowRef);
|
||||||
{
|
}
|
||||||
action((RowRef)rowRef);
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -719,9 +844,21 @@ internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockStat
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public event IUnlockState.UnlockDelegate? Unlock;
|
public event IUnlockState.UnlockDelegate? Unlock;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementListLoaded => this.unlockStateService.IsAchievementListLoaded;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsTitleListLoaded => this.unlockStateService.IsTitleListLoaded;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAchievementComplete(AchievementSheet row) => this.unlockStateService.IsAchievementComplete(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsActionUnlocked(ActionSheet row) => this.unlockStateService.IsActionUnlocked(row);
|
public bool IsActionUnlocked(ActionSheet row) => this.unlockStateService.IsActionUnlocked(row);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsAdventureComplete(Adventure row) => this.unlockStateService.IsAdventureComplete(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row) => this.unlockStateService.IsAetherCurrentCompFlgSetUnlocked(row);
|
public bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row) => this.unlockStateService.IsAetherCurrentCompFlgSetUnlocked(row);
|
||||||
|
|
||||||
|
|
@ -798,7 +935,7 @@ internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockStat
|
||||||
public bool IsItemUnlocked(Item row) => this.unlockStateService.IsItemUnlocked(row);
|
public bool IsItemUnlocked(Item row) => this.unlockStateService.IsItemUnlocked(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMcGuffinUnlocked(McGuffin row) => this.unlockStateService.IsMcGuffinUnlocked(row);
|
public bool IsLeveCompleted(Leve row) => this.unlockStateService.IsLeveCompleted(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMJILandmarkUnlocked(MJILandmark row) => this.unlockStateService.IsMJILandmarkUnlocked(row);
|
public bool IsMJILandmarkUnlocked(MJILandmark row) => this.unlockStateService.IsMJILandmarkUnlocked(row);
|
||||||
|
|
@ -806,6 +943,9 @@ internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockStat
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMKDLoreUnlocked(MKDLore row) => this.unlockStateService.IsMKDLoreUnlocked(row);
|
public bool IsMKDLoreUnlocked(MKDLore row) => this.unlockStateService.IsMKDLoreUnlocked(row);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsMcGuffinUnlocked(McGuffin row) => this.unlockStateService.IsMcGuffinUnlocked(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsMountUnlocked(Mount row) => this.unlockStateService.IsMountUnlocked(row);
|
public bool IsMountUnlocked(Mount row) => this.unlockStateService.IsMountUnlocked(row);
|
||||||
|
|
||||||
|
|
@ -824,6 +964,9 @@ internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockStat
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsPublicContentUnlocked(PublicContentSheet row) => this.unlockStateService.IsPublicContentUnlocked(row);
|
public bool IsPublicContentUnlocked(PublicContentSheet row) => this.unlockStateService.IsPublicContentUnlocked(row);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsQuestCompleted(Quest row) => this.unlockStateService.IsQuestCompleted(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsRecipeUnlocked(Recipe row) => this.unlockStateService.IsRecipeUnlocked(row);
|
public bool IsRecipeUnlocked(Recipe row) => this.unlockStateService.IsRecipeUnlocked(row);
|
||||||
|
|
||||||
|
|
@ -836,6 +979,9 @@ internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockStat
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsSecretRecipeBookUnlocked(SecretRecipeBook row) => this.unlockStateService.IsSecretRecipeBookUnlocked(row);
|
public bool IsSecretRecipeBookUnlocked(SecretRecipeBook row) => this.unlockStateService.IsSecretRecipeBookUnlocked(row);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsTitleUnlocked(Title row) => this.unlockStateService.IsTitleUnlocked(row);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsTraitUnlocked(Trait row) => this.unlockStateService.IsTraitUnlocked(row);
|
public bool IsTraitUnlocked(Trait row) => this.unlockStateService.IsTraitUnlocked(row);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,9 +169,6 @@ public sealed class AsmHook : IDisposable, IDalamudHook
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void CheckDisposed()
|
private void CheckDisposed()
|
||||||
{
|
{
|
||||||
if (this.IsDisposed)
|
ObjectDisposedException.ThrowIf(this.IsDisposed, this);
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(message: "Hook is already disposed", null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Hooking.Internal;
|
using Dalamud.Hooking.Internal;
|
||||||
using Dalamud.Hooking.Internal.Verification;
|
using Dalamud.Hooking.Internal.Verification;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
namespace Dalamud.Hooking;
|
namespace Dalamud.Hooking;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -20,6 +22,8 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000;
|
private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000;
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000;
|
private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000;
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
private const int IMAGE_DIRECTORY_ENTRY_IMPORT = 1;
|
||||||
#pragma warning restore SA1310
|
#pragma warning restore SA1310
|
||||||
|
|
||||||
private readonly IntPtr address;
|
private readonly IntPtr address;
|
||||||
|
|
@ -124,25 +128,25 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
module ??= Process.GetCurrentProcess().MainModule;
|
module ??= Process.GetCurrentProcess().MainModule;
|
||||||
if (module == null)
|
if (module == null)
|
||||||
throw new InvalidOperationException("Current module is null?");
|
throw new InvalidOperationException("Current module is null?");
|
||||||
var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress;
|
var pDos = (IMAGE_DOS_HEADER*)module.BaseAddress;
|
||||||
var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4);
|
var pNt = (IMAGE_FILE_HEADER*)(module.BaseAddress + pDos->e_lfanew + 4);
|
||||||
var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<PeHeader.IMAGE_OPTIONAL_HEADER64>();
|
var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<IMAGE_OPTIONAL_HEADER64>();
|
||||||
PeHeader.IMAGE_DATA_DIRECTORY* pDataDirectory;
|
IMAGE_DATA_DIRECTORY* pDataDirectory;
|
||||||
if (isPe64)
|
if (isPe64)
|
||||||
{
|
{
|
||||||
var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf<PeHeader.IMAGE_FILE_HEADER>());
|
var pOpt = (IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + pDos->e_lfanew + 4 + Marshal.SizeOf<IMAGE_FILE_HEADER>());
|
||||||
pDataDirectory = &pOpt->ImportTable;
|
pDataDirectory = &pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf<PeHeader.IMAGE_FILE_HEADER>());
|
var pOpt = (IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + pDos->e_lfanew + 4 + Marshal.SizeOf<IMAGE_FILE_HEADER>());
|
||||||
pDataDirectory = &pOpt->ImportTable;
|
pDataDirectory = &pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||||
}
|
}
|
||||||
|
|
||||||
var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant();
|
var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant();
|
||||||
foreach (ref var importDescriptor in new Span<PeHeader.IMAGE_IMPORT_DESCRIPTOR>(
|
foreach (ref var importDescriptor in new Span<IMAGE_IMPORT_DESCRIPTOR>(
|
||||||
(PeHeader.IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress),
|
(IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress),
|
||||||
(int)(pDataDirectory->Size / Marshal.SizeOf<PeHeader.IMAGE_IMPORT_DESCRIPTOR>())))
|
(int)(pDataDirectory->Size / Marshal.SizeOf<IMAGE_IMPORT_DESCRIPTOR>())))
|
||||||
{
|
{
|
||||||
// Having all zero values signals the end of the table. We didn't find anything.
|
// Having all zero values signals the end of the table. We didn't find anything.
|
||||||
if (importDescriptor.Characteristics == 0)
|
if (importDescriptor.Characteristics == 0)
|
||||||
|
|
@ -160,7 +164,7 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
(int)Math.Min(pDataDirectory->Size + pDataDirectory->VirtualAddress - importDescriptor.Name, moduleNameLowerWithNullTerminator.Length));
|
(int)Math.Min(pDataDirectory->Size + pDataDirectory->VirtualAddress - importDescriptor.Name, moduleNameLowerWithNullTerminator.Length));
|
||||||
|
|
||||||
// Is this entry about the DLL that we're looking for? (Case insensitive)
|
// Is this entry about the DLL that we're looking for? (Case insensitive)
|
||||||
if (currentDllNameWithNullTerminator.ToLowerInvariant() != moduleNameLowerWithNullTerminator)
|
if (!currentDllNameWithNullTerminator.Equals(moduleNameLowerWithNullTerminator, StringComparison.InvariantCultureIgnoreCase))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (isPe64)
|
if (isPe64)
|
||||||
|
|
@ -245,13 +249,10 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void CheckDisposed()
|
protected void CheckDisposed()
|
||||||
{
|
{
|
||||||
if (this.IsDisposed)
|
ObjectDisposedException.ThrowIf(this.IsDisposed, this);
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(message: "Hook is already disposed", null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
|
private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref IMAGE_IMPORT_DESCRIPTOR desc, ref IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
|
||||||
{
|
{
|
||||||
var importLookupsOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<int>()));
|
var importLookupsOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<int>()));
|
||||||
var importAddressesOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<int>()));
|
var importAddressesOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<int>()));
|
||||||
|
|
@ -301,7 +302,7 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||||
throw new MissingMethodException("Specified method not found");
|
throw new MissingMethodException("Specified method not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
|
private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref IMAGE_IMPORT_DESCRIPTOR desc, ref IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
|
||||||
{
|
{
|
||||||
var importLookupsOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<ulong>()));
|
var importLookupsOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<ulong>()));
|
||||||
var importAddressesOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<ulong>()));
|
var importAddressesOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<ulong>()));
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
using Windows.Win32.System.Memory;
|
using Windows.Win32.System.Memory;
|
||||||
|
|
||||||
using Win32Exception = System.ComponentModel.Win32Exception;
|
using Win32Exception = System.ComponentModel.Win32Exception;
|
||||||
|
|
@ -45,7 +45,7 @@ internal unsafe class FunctionPointerVariableHook<T> : Hook<T>
|
||||||
|
|
||||||
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
||||||
{
|
{
|
||||||
indexList = HookManager.MultiHookTracker[this.Address] = new List<IDalamudHook>();
|
indexList = HookManager.MultiHookTracker[this.Address] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.detourDelegate = detour;
|
this.detourDelegate = detour;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
|
|
@ -8,6 +8,7 @@ using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Hooking.Internal;
|
namespace Dalamud.Hooking.Internal;
|
||||||
|
|
@ -25,7 +26,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternal
|
||||||
private readonly LocalPlugin plugin;
|
private readonly LocalPlugin plugin;
|
||||||
private readonly SigScanner scanner;
|
private readonly SigScanner scanner;
|
||||||
|
|
||||||
private readonly WeakConcurrentCollection<IDalamudHook> trackedHooks = new();
|
private readonly WeakConcurrentCollection<IDalamudHook> trackedHooks = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameInteropProviderPluginScoped"/> class.
|
/// Initializes a new instance of the <see cref="GameInteropProviderPluginScoped"/> class.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
@ -20,7 +21,7 @@ internal class HookManager : IInternalDisposableService
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logger shared with <see cref="Unhooker"/>.
|
/// Logger shared with <see cref="Unhooker"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static readonly ModuleLog Log = new("HM");
|
internal static readonly ModuleLog Log = ModuleLog.Create<HookManager>();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private HookManager()
|
private HookManager()
|
||||||
|
|
@ -30,7 +31,7 @@ internal class HookManager : IInternalDisposableService
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets sync root object for hook enabling/disabling.
|
/// Gets sync root object for hook enabling/disabling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static object HookEnableSyncRoot { get; } = new();
|
internal static Lock HookEnableSyncRoot { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a static list of tracked and registered hooks.
|
/// Gets a static list of tracked and registered hooks.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ internal class MinHookHook<T> : Hook<T> where T : Delegate
|
||||||
var unhooker = HookManager.RegisterUnhooker(this.Address);
|
var unhooker = HookManager.RegisterUnhooker(this.Address);
|
||||||
|
|
||||||
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
||||||
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
indexList = HookManager.MultiHookTracker[this.Address] = [];
|
||||||
|
|
||||||
var index = (ulong)indexList.Count;
|
var index = (ulong)indexList.Count;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,390 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
#pragma warning disable
|
|
||||||
namespace Dalamud.Hooking.Internal;
|
|
||||||
|
|
||||||
internal class PeHeader
|
|
||||||
{
|
|
||||||
public struct IMAGE_DOS_HEADER
|
|
||||||
{
|
|
||||||
public UInt16 e_magic;
|
|
||||||
public UInt16 e_cblp;
|
|
||||||
public UInt16 e_cp;
|
|
||||||
public UInt16 e_crlc;
|
|
||||||
public UInt16 e_cparhdr;
|
|
||||||
public UInt16 e_minalloc;
|
|
||||||
public UInt16 e_maxalloc;
|
|
||||||
public UInt16 e_ss;
|
|
||||||
public UInt16 e_sp;
|
|
||||||
public UInt16 e_csum;
|
|
||||||
public UInt16 e_ip;
|
|
||||||
public UInt16 e_cs;
|
|
||||||
public UInt16 e_lfarlc;
|
|
||||||
public UInt16 e_ovno;
|
|
||||||
public UInt16 e_res_0;
|
|
||||||
public UInt16 e_res_1;
|
|
||||||
public UInt16 e_res_2;
|
|
||||||
public UInt16 e_res_3;
|
|
||||||
public UInt16 e_oemid;
|
|
||||||
public UInt16 e_oeminfo;
|
|
||||||
public UInt16 e_res2_0;
|
|
||||||
public UInt16 e_res2_1;
|
|
||||||
public UInt16 e_res2_2;
|
|
||||||
public UInt16 e_res2_3;
|
|
||||||
public UInt16 e_res2_4;
|
|
||||||
public UInt16 e_res2_5;
|
|
||||||
public UInt16 e_res2_6;
|
|
||||||
public UInt16 e_res2_7;
|
|
||||||
public UInt16 e_res2_8;
|
|
||||||
public UInt16 e_res2_9;
|
|
||||||
public UInt32 e_lfanew;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct IMAGE_DATA_DIRECTORY
|
|
||||||
{
|
|
||||||
public UInt32 VirtualAddress;
|
|
||||||
public UInt32 Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct IMAGE_OPTIONAL_HEADER32
|
|
||||||
{
|
|
||||||
public UInt16 Magic;
|
|
||||||
public Byte MajorLinkerVersion;
|
|
||||||
public Byte MinorLinkerVersion;
|
|
||||||
public UInt32 SizeOfCode;
|
|
||||||
public UInt32 SizeOfInitializedData;
|
|
||||||
public UInt32 SizeOfUninitializedData;
|
|
||||||
public UInt32 AddressOfEntryPoint;
|
|
||||||
public UInt32 BaseOfCode;
|
|
||||||
public UInt32 BaseOfData;
|
|
||||||
public UInt32 ImageBase;
|
|
||||||
public UInt32 SectionAlignment;
|
|
||||||
public UInt32 FileAlignment;
|
|
||||||
public UInt16 MajorOperatingSystemVersion;
|
|
||||||
public UInt16 MinorOperatingSystemVersion;
|
|
||||||
public UInt16 MajorImageVersion;
|
|
||||||
public UInt16 MinorImageVersion;
|
|
||||||
public UInt16 MajorSubsystemVersion;
|
|
||||||
public UInt16 MinorSubsystemVersion;
|
|
||||||
public UInt32 Win32VersionValue;
|
|
||||||
public UInt32 SizeOfImage;
|
|
||||||
public UInt32 SizeOfHeaders;
|
|
||||||
public UInt32 CheckSum;
|
|
||||||
public UInt16 Subsystem;
|
|
||||||
public UInt16 DllCharacteristics;
|
|
||||||
public UInt32 SizeOfStackReserve;
|
|
||||||
public UInt32 SizeOfStackCommit;
|
|
||||||
public UInt32 SizeOfHeapReserve;
|
|
||||||
public UInt32 SizeOfHeapCommit;
|
|
||||||
public UInt32 LoaderFlags;
|
|
||||||
public UInt32 NumberOfRvaAndSizes;
|
|
||||||
|
|
||||||
public IMAGE_DATA_DIRECTORY ExportTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY ImportTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY ResourceTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY ExceptionTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY CertificateTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY BaseRelocationTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY Debug;
|
|
||||||
public IMAGE_DATA_DIRECTORY Architecture;
|
|
||||||
public IMAGE_DATA_DIRECTORY GlobalPtr;
|
|
||||||
public IMAGE_DATA_DIRECTORY TLSTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY LoadConfigTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY BoundImport;
|
|
||||||
public IMAGE_DATA_DIRECTORY IAT;
|
|
||||||
public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
|
|
||||||
public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
|
|
||||||
public IMAGE_DATA_DIRECTORY Reserved;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct IMAGE_OPTIONAL_HEADER64
|
|
||||||
{
|
|
||||||
public UInt16 Magic;
|
|
||||||
public Byte MajorLinkerVersion;
|
|
||||||
public Byte MinorLinkerVersion;
|
|
||||||
public UInt32 SizeOfCode;
|
|
||||||
public UInt32 SizeOfInitializedData;
|
|
||||||
public UInt32 SizeOfUninitializedData;
|
|
||||||
public UInt32 AddressOfEntryPoint;
|
|
||||||
public UInt32 BaseOfCode;
|
|
||||||
public UInt64 ImageBase;
|
|
||||||
public UInt32 SectionAlignment;
|
|
||||||
public UInt32 FileAlignment;
|
|
||||||
public UInt16 MajorOperatingSystemVersion;
|
|
||||||
public UInt16 MinorOperatingSystemVersion;
|
|
||||||
public UInt16 MajorImageVersion;
|
|
||||||
public UInt16 MinorImageVersion;
|
|
||||||
public UInt16 MajorSubsystemVersion;
|
|
||||||
public UInt16 MinorSubsystemVersion;
|
|
||||||
public UInt32 Win32VersionValue;
|
|
||||||
public UInt32 SizeOfImage;
|
|
||||||
public UInt32 SizeOfHeaders;
|
|
||||||
public UInt32 CheckSum;
|
|
||||||
public UInt16 Subsystem;
|
|
||||||
public UInt16 DllCharacteristics;
|
|
||||||
public UInt64 SizeOfStackReserve;
|
|
||||||
public UInt64 SizeOfStackCommit;
|
|
||||||
public UInt64 SizeOfHeapReserve;
|
|
||||||
public UInt64 SizeOfHeapCommit;
|
|
||||||
public UInt32 LoaderFlags;
|
|
||||||
public UInt32 NumberOfRvaAndSizes;
|
|
||||||
|
|
||||||
public IMAGE_DATA_DIRECTORY ExportTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY ImportTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY ResourceTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY ExceptionTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY CertificateTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY BaseRelocationTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY Debug;
|
|
||||||
public IMAGE_DATA_DIRECTORY Architecture;
|
|
||||||
public IMAGE_DATA_DIRECTORY GlobalPtr;
|
|
||||||
public IMAGE_DATA_DIRECTORY TLSTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY LoadConfigTable;
|
|
||||||
public IMAGE_DATA_DIRECTORY BoundImport;
|
|
||||||
public IMAGE_DATA_DIRECTORY IAT;
|
|
||||||
public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
|
|
||||||
public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
|
|
||||||
public IMAGE_DATA_DIRECTORY Reserved;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct IMAGE_FILE_HEADER
|
|
||||||
{
|
|
||||||
public UInt16 Machine;
|
|
||||||
public UInt16 NumberOfSections;
|
|
||||||
public UInt32 TimeDateStamp;
|
|
||||||
public UInt32 PointerToSymbolTable;
|
|
||||||
public UInt32 NumberOfSymbols;
|
|
||||||
public UInt16 SizeOfOptionalHeader;
|
|
||||||
public UInt16 Characteristics;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct IMAGE_SECTION_HEADER
|
|
||||||
{
|
|
||||||
[FieldOffset(0)]
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
||||||
public char[] Name;
|
|
||||||
[FieldOffset(8)]
|
|
||||||
public UInt32 VirtualSize;
|
|
||||||
[FieldOffset(12)]
|
|
||||||
public UInt32 VirtualAddress;
|
|
||||||
[FieldOffset(16)]
|
|
||||||
public UInt32 SizeOfRawData;
|
|
||||||
[FieldOffset(20)]
|
|
||||||
public UInt32 PointerToRawData;
|
|
||||||
[FieldOffset(24)]
|
|
||||||
public UInt32 PointerToRelocations;
|
|
||||||
[FieldOffset(28)]
|
|
||||||
public UInt32 PointerToLinenumbers;
|
|
||||||
[FieldOffset(32)]
|
|
||||||
public UInt16 NumberOfRelocations;
|
|
||||||
[FieldOffset(34)]
|
|
||||||
public UInt16 NumberOfLinenumbers;
|
|
||||||
[FieldOffset(36)]
|
|
||||||
public DataSectionFlags Characteristics;
|
|
||||||
|
|
||||||
public string Section
|
|
||||||
{
|
|
||||||
get { return new string(Name); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum DataSectionFlags : uint
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
TypeReg = 0x00000000,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
TypeDsect = 0x00000001,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
TypeNoLoad = 0x00000002,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
TypeGroup = 0x00000004,
|
|
||||||
/// <summary>
|
|
||||||
/// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
TypeNoPadded = 0x00000008,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
TypeCopy = 0x00000010,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains executable code.
|
|
||||||
/// </summary>
|
|
||||||
ContentCode = 0x00000020,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains initialized data.
|
|
||||||
/// </summary>
|
|
||||||
ContentInitializedData = 0x00000040,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains uninitialized data.
|
|
||||||
/// </summary>
|
|
||||||
ContentUninitializedData = 0x00000080,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
LinkOther = 0x00000100,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains comments or other information. The .drectve section has this type. This is valid for object files only.
|
|
||||||
/// </summary>
|
|
||||||
LinkInfo = 0x00000200,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
TypeOver = 0x00000400,
|
|
||||||
/// <summary>
|
|
||||||
/// The section will not become part of the image. This is valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
LinkRemove = 0x00000800,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
LinkComDat = 0x00001000,
|
|
||||||
/// <summary>
|
|
||||||
/// Reset speculative exceptions handling bits in the TLB entries for this section.
|
|
||||||
/// </summary>
|
|
||||||
NoDeferSpecExceptions = 0x00004000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains data referenced through the global pointer (GP).
|
|
||||||
/// </summary>
|
|
||||||
RelativeGP = 0x00008000,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
MemPurgeable = 0x00020000,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
Memory16Bit = 0x00020000,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
MemoryLocked = 0x00040000,
|
|
||||||
/// <summary>
|
|
||||||
/// Reserved for future use.
|
|
||||||
/// </summary>
|
|
||||||
MemoryPreload = 0x00080000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 1-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align1Bytes = 0x00100000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 2-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align2Bytes = 0x00200000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 4-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align4Bytes = 0x00300000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on an 8-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align8Bytes = 0x00400000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 16-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align16Bytes = 0x00500000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 32-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align32Bytes = 0x00600000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 64-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align64Bytes = 0x00700000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 128-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align128Bytes = 0x00800000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 256-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align256Bytes = 0x00900000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 512-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align512Bytes = 0x00A00000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 1024-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align1024Bytes = 0x00B00000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 2048-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align2048Bytes = 0x00C00000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on a 4096-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align4096Bytes = 0x00D00000,
|
|
||||||
/// <summary>
|
|
||||||
/// Align data on an 8192-byte boundary. Valid only for object files.
|
|
||||||
/// </summary>
|
|
||||||
Align8192Bytes = 0x00E00000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section contains extended relocations.
|
|
||||||
/// </summary>
|
|
||||||
LinkExtendedRelocationOverflow = 0x01000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section can be discarded as needed.
|
|
||||||
/// </summary>
|
|
||||||
MemoryDiscardable = 0x02000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section cannot be cached.
|
|
||||||
/// </summary>
|
|
||||||
MemoryNotCached = 0x04000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section is not pageable.
|
|
||||||
/// </summary>
|
|
||||||
MemoryNotPaged = 0x08000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section can be shared in memory.
|
|
||||||
/// </summary>
|
|
||||||
MemoryShared = 0x10000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section can be executed as code.
|
|
||||||
/// </summary>
|
|
||||||
MemoryExecute = 0x20000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section can be read.
|
|
||||||
/// </summary>
|
|
||||||
MemoryRead = 0x40000000,
|
|
||||||
/// <summary>
|
|
||||||
/// The section can be written to.
|
|
||||||
/// </summary>
|
|
||||||
MemoryWrite = 0x80000000
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct IMAGE_IMPORT_DESCRIPTOR
|
|
||||||
{
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public uint Characteristics;
|
|
||||||
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public uint OriginalFirstThunk;
|
|
||||||
|
|
||||||
[FieldOffset(4)]
|
|
||||||
public uint TimeDateStamp;
|
|
||||||
|
|
||||||
[FieldOffset(8)]
|
|
||||||
public uint ForwarderChain;
|
|
||||||
|
|
||||||
[FieldOffset(12)]
|
|
||||||
public uint Name;
|
|
||||||
|
|
||||||
[FieldOffset(16)]
|
|
||||||
public uint FirstThunk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
|
||||||
|
using InteropGenerator.Runtime;
|
||||||
|
|
||||||
namespace Dalamud.Hooking.Internal.Verification;
|
namespace Dalamud.Hooking.Internal.Verification;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -19,11 +24,13 @@ internal static class HookVerifier
|
||||||
new(
|
new(
|
||||||
"ActorControlSelf",
|
"ActorControlSelf",
|
||||||
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
||||||
typeof(ActorControlSelfDelegate),
|
typeof(ActorControlSelfDelegate), // TODO: change this to CS delegate
|
||||||
"Signature changed in Patch 7.4") // 7.4 (new parameters)
|
"Signature changed in Patch 7.4") // 7.4 (new parameters)
|
||||||
];
|
];
|
||||||
|
|
||||||
private delegate void ActorControlSelfDelegate(uint category, uint eventId, uint param1, uint param2, uint param3, uint param4, uint param5, uint param6, uint param7, uint param8, ulong targetId, byte param9);
|
private static readonly string ClientStructsInteropNamespacePrefix = string.Join(".", nameof(FFXIVClientStructs), nameof(FFXIVClientStructs.Interop));
|
||||||
|
|
||||||
|
private delegate void ActorControlSelfDelegate(uint category, uint eventId, uint param1, uint param2, uint param3, uint param4, uint param5, uint param6, uint param7, uint param8, ulong targetId, byte param9); // TODO: change this to CS delegate
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="HookVerifier"/> class.
|
/// Initializes a new instance of the <see cref="HookVerifier"/> class.
|
||||||
|
|
@ -71,7 +78,7 @@ internal static class HookVerifier
|
||||||
var enforcedInvoke = entry.TargetDelegateType.GetMethod("Invoke")!;
|
var enforcedInvoke = entry.TargetDelegateType.GetMethod("Invoke")!;
|
||||||
|
|
||||||
// Compare Return Type
|
// Compare Return Type
|
||||||
var mismatch = passedInvoke.ReturnType != enforcedInvoke.ReturnType;
|
var mismatch = !CheckParam(passedInvoke.ReturnType, enforcedInvoke.ReturnType);
|
||||||
|
|
||||||
// Compare Parameter Count
|
// Compare Parameter Count
|
||||||
var passedParams = passedInvoke.GetParameters();
|
var passedParams = passedInvoke.GetParameters();
|
||||||
|
|
@ -86,7 +93,7 @@ internal static class HookVerifier
|
||||||
// Compare Parameter Types
|
// Compare Parameter Types
|
||||||
for (var i = 0; i < passedParams.Length; i++)
|
for (var i = 0; i < passedParams.Length; i++)
|
||||||
{
|
{
|
||||||
if (passedParams[i].ParameterType != enforcedParams[i].ParameterType)
|
if (!CheckParam(passedParams[i].ParameterType, enforcedParams[i].ParameterType))
|
||||||
{
|
{
|
||||||
mismatch = true;
|
mismatch = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -100,6 +107,45 @@ internal static class HookVerifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CheckParam(Type paramLeft, Type paramRight)
|
||||||
|
{
|
||||||
|
var sameType = paramLeft == paramRight;
|
||||||
|
return sameType || SizeOf(paramLeft) == SizeOf(paramRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SizeOf(Type type)
|
||||||
|
{
|
||||||
|
return type switch {
|
||||||
|
_ when type == typeof(sbyte) || type == typeof(byte) || type == typeof(bool) => 1,
|
||||||
|
_ when type == typeof(char) || type == typeof(short) || type == typeof(ushort) || type == typeof(Half) => 2,
|
||||||
|
_ when type == typeof(int) || type == typeof(uint) || type == typeof(float) => 4,
|
||||||
|
_ when type == typeof(long) || type == typeof(ulong) || type == typeof(double) || type.IsPointer || type.IsFunctionPointer || type.IsUnmanagedFunctionPointer || (type.Name == "Pointer`1" && type.Namespace.AsSpan().SequenceEqual(ClientStructsInteropNamespacePrefix)) || type == typeof(CStringPointer) => 8,
|
||||||
|
_ when type.Name.StartsWith("FixedSizeArray") => SizeOf(type.GetGenericArguments()[0]) * int.Parse(type.Name[14..type.Name.IndexOf('`')]),
|
||||||
|
_ when type.GetCustomAttribute<InlineArrayAttribute>() is { Length: var length } => SizeOf(type.GetGenericArguments()[0]) * length,
|
||||||
|
_ when IsStruct(type) && !type.IsGenericType && (type.StructLayoutAttribute?.Value ?? LayoutKind.Sequential) != LayoutKind.Sequential => type.StructLayoutAttribute?.Size ?? (int?)typeof(Unsafe).GetMethod("SizeOf")?.MakeGenericMethod(type).Invoke(null, null) ?? 0,
|
||||||
|
_ when type.IsEnum => SizeOf(Enum.GetUnderlyingType(type)),
|
||||||
|
_ when type.IsGenericType => Marshal.SizeOf(Activator.CreateInstance(type)!),
|
||||||
|
_ => GetSizeOf(type),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetSizeOf(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Marshal.SizeOf(Activator.CreateInstance(type)!);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsStruct(Type type)
|
||||||
|
{
|
||||||
|
return type != typeof(decimal) && type is { IsValueType: true, IsPrimitive: false, IsEnum: false };
|
||||||
|
}
|
||||||
|
|
||||||
private record VerificationEntry(string Name, string Signature, Type TargetDelegateType, string Message)
|
private record VerificationEntry(string Name, string Signature, Type TargetDelegateType, string Message)
|
||||||
{
|
{
|
||||||
public nint Address { get; set; }
|
public nint Address { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ namespace Dalamud.Hooking.WndProcHook;
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal sealed class WndProcHookManager : IInternalDisposableService
|
internal sealed class WndProcHookManager : IInternalDisposableService
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new(nameof(WndProcHookManager));
|
private static readonly ModuleLog Log = ModuleLog.Create<WndProcHookManager>();
|
||||||
|
|
||||||
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||||
private readonly Dictionary<HWND, WndProcEventArgs> wndProcOverrides = new();
|
private readonly Dictionary<HWND, WndProcEventArgs> wndProcOverrides = [];
|
||||||
|
|
||||||
private HWND mainWindowHwnd;
|
private HWND mainWindowHwnd;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,10 @@ public static class ColorHelpers
|
||||||
var min = Math.Min(r, Math.Min(g, b));
|
var min = Math.Min(r, Math.Min(g, b));
|
||||||
|
|
||||||
var h = max;
|
var h = max;
|
||||||
var s = max;
|
|
||||||
var v = max;
|
var v = max;
|
||||||
|
|
||||||
var d = max - min;
|
var d = max - min;
|
||||||
s = max == 0 ? 0 : d / max;
|
var s = max == 0 ? 0 : d / max;
|
||||||
|
|
||||||
if (max == min)
|
if (max == min)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue