mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-18 22:07:44 +01:00
Compare commits
11 commits
52166e4b9e
...
c005bae265
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c005bae265 | ||
|
|
19fca721e9 | ||
|
|
a56d2cf40b | ||
|
|
3eb65c85c0 | ||
|
|
1b76aec89f | ||
|
|
b52024d927 | ||
|
|
56b0ae80b6 | ||
|
|
0b1a697d4d | ||
|
|
05beea003c | ||
|
|
3c8cef06dd | ||
|
|
0d533c18f8 |
16 changed files with 201 additions and 50 deletions
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Configuration;
|
|||
/// <summary>
|
||||
/// Configuration to store settings for a dalamud plugin.
|
||||
/// </summary>
|
||||
[Api14ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||
[Api15ToDo("Make this a service. We need to be able to dispose it reliably to write configs asynchronously. Maybe also let people write files with vfs.")]
|
||||
public sealed class PluginConfigurations
|
||||
{
|
||||
private readonly DirectoryInfo configDirectory;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Dalamud.Common;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Hooking.Internal.Verification;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Storage;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -73,6 +74,11 @@ internal sealed unsafe class Dalamud : IServiceType
|
|||
scanner,
|
||||
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
|
||||
|
||||
using (Timings.Start("HookVerifier Init"))
|
||||
{
|
||||
HookVerifier.Initialize(scanner);
|
||||
}
|
||||
|
||||
// Set up FFXIVClientStructs
|
||||
this.SetupClientStructsResolver(cacheDir);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ public class AddonRefreshArgs : AddonArgs
|
|||
AtkValuePtr ptr;
|
||||
unsafe
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
yield return ptr;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ public class AddonSetupArgs : AddonArgs
|
|||
AtkValuePtr ptr;
|
||||
unsafe
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
ptr = new AtkValuePtr((nint)this.AtkValueSpan.GetPointer(i));
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
yield return ptr;
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Api14ToDo("Maybe make this config scoped to internal name?")]
|
||||
[Api15ToDo("Maybe make this config scoped to internal name?")]
|
||||
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Hooking.Internal;
|
||||
using Dalamud.Hooking.Internal.Verification;
|
||||
|
||||
namespace Dalamud.Hooking;
|
||||
|
||||
|
|
@ -230,6 +231,8 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
|
|||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||
useMinHook = true;
|
||||
|
||||
HookVerifier.Verify<T>(procAddress);
|
||||
|
||||
procAddress = HookManager.FollowJmp(procAddress);
|
||||
if (useMinHook)
|
||||
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Hooking.Internal.Verification;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a provided delegate for a hook does not match a known delegate.
|
||||
/// </summary>
|
||||
public class HookVerificationException : Exception
|
||||
{
|
||||
private HookVerificationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="HookVerificationException"/> exception.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the function that is being hooked.</param>
|
||||
/// <param name="passed">The delegate passed by the user.</param>
|
||||
/// <param name="enforced">The delegate we think is correct.</param>
|
||||
/// <param name="message">Additional context to show to the user.</param>
|
||||
/// <returns>The created exception.</returns>
|
||||
internal static HookVerificationException Create(IntPtr address, Type passed, Type enforced, string message)
|
||||
{
|
||||
return new HookVerificationException(
|
||||
$"Hook verification failed for address 0x{address.ToInt64():X}\n\n" +
|
||||
$"Why: {message}\n" +
|
||||
$"Passed Delegate: {GetSignature(passed)}\n" +
|
||||
$"Correct Delegate: {GetSignature(enforced)}\n\n" +
|
||||
"The hook delegate must exactly match the provided signature to prevent memory corruption and wrong data passed to originals.");
|
||||
}
|
||||
|
||||
private static string GetSignature(Type delegateType)
|
||||
{
|
||||
var method = delegateType.GetMethod("Invoke");
|
||||
if (method == null) return delegateType.Name;
|
||||
|
||||
var parameters = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name));
|
||||
return $"{method.ReturnType.Name} ({parameters})";
|
||||
}
|
||||
}
|
||||
107
Dalamud/Hooking/Internal/Verification/HookVerifier.cs
Normal file
107
Dalamud/Hooking/Internal/Verification/HookVerifier.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using System.Linq;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
namespace Dalamud.Hooking.Internal.Verification;
|
||||
|
||||
/// <summary>
|
||||
/// Global utility that can verify whether hook delegates are correctly declared.
|
||||
/// Initialized out-of-band, since Hook is instantiated all over the place without a service, so this cannot be
|
||||
/// a service either.
|
||||
/// </summary>
|
||||
internal static class HookVerifier
|
||||
{
|
||||
private static readonly ModuleLog Log = new("HookVerifier");
|
||||
|
||||
private static readonly VerificationEntry[] ToVerify =
|
||||
[
|
||||
new(
|
||||
"ActorControlSelf",
|
||||
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
||||
typeof(ActorControlSelfDelegate),
|
||||
"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);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HookVerifier"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scanner">Process to scan in.</param>
|
||||
public static void Initialize(TargetSigScanner scanner)
|
||||
{
|
||||
foreach (var entry in ToVerify)
|
||||
{
|
||||
if (!scanner.TryScanText(entry.Signature, out var address))
|
||||
{
|
||||
Log.Error("Could not resolve signature for hook {Name} ({Sig})", entry.Name, entry.Signature);
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.Address = address;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify the hook with the provided address and exception.
|
||||
/// </summary>
|
||||
/// <param name="address">The address of the function we are hooking.</param>
|
||||
/// <typeparam name="T">The delegate type passed by the creator of the hook.</typeparam>
|
||||
/// <exception cref="HookVerificationException">Exception thrown when we think the hook is not correctly declared.</exception>
|
||||
public static void Verify<T>(IntPtr address) where T : Delegate
|
||||
{
|
||||
var entry = ToVerify.FirstOrDefault(x => x.Address == address);
|
||||
|
||||
// Nothing to verify for this hook?
|
||||
if (entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var passedType = typeof(T);
|
||||
|
||||
// Directly compare delegates
|
||||
if (passedType == entry.TargetDelegateType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var passedInvoke = passedType.GetMethod("Invoke")!;
|
||||
var enforcedInvoke = entry.TargetDelegateType.GetMethod("Invoke")!;
|
||||
|
||||
// Compare Return Type
|
||||
var mismatch = passedInvoke.ReturnType != enforcedInvoke.ReturnType;
|
||||
|
||||
// Compare Parameter Count
|
||||
var passedParams = passedInvoke.GetParameters();
|
||||
var enforcedParams = enforcedInvoke.GetParameters();
|
||||
|
||||
if (passedParams.Length != enforcedParams.Length)
|
||||
{
|
||||
mismatch = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compare Parameter Types
|
||||
for (var i = 0; i < passedParams.Length; i++)
|
||||
{
|
||||
if (passedParams[i].ParameterType != enforcedParams[i].ParameterType)
|
||||
{
|
||||
mismatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatch)
|
||||
{
|
||||
throw HookVerificationException.Create(address, passedType, entry.TargetDelegateType, entry.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private record VerificationEntry(string Name, string Signature, Type TargetDelegateType, string Message)
|
||||
{
|
||||
public nint Address { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ public abstract class Easing
|
|||
/// Gets the current value of the animation, following unclamped logic.
|
||||
/// </summary>
|
||||
[Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)]
|
||||
[Api14ToDo("Map this field to ValueClamped, probably.")]
|
||||
[Api15ToDo("Map this field to ValueClamped, probably.")]
|
||||
public double Value => this.ValueUnclamped;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -420,13 +420,13 @@ internal unsafe class UiDebug
|
|||
ImGui.SameLine();
|
||||
Service<SeStringRenderer>.Get().Draw(textInputComponent->AtkComponentInputBase.RawString);
|
||||
|
||||
ImGui.Text("Text1: "u8);
|
||||
ImGui.SameLine();
|
||||
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText01);
|
||||
|
||||
ImGui.Text("Text2: "u8);
|
||||
ImGui.SameLine();
|
||||
Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText02);
|
||||
// ImGui.Text("Text1: "u8);
|
||||
// ImGui.SameLine();
|
||||
// Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText01);
|
||||
//
|
||||
// ImGui.Text("Text2: "u8);
|
||||
// ImGui.SameLine();
|
||||
// Service<SeStringRenderer>.Get().Draw(textInputComponent->UnkText02);
|
||||
|
||||
ImGui.Text("AvailableLines: "u8);
|
||||
ImGui.SameLine();
|
||||
|
|
|
|||
|
|
@ -89,20 +89,14 @@ internal unsafe class ComponentNodeTree : ResNodeTree
|
|||
{
|
||||
case TextInput:
|
||||
var textInputComponent = (AtkComponentTextInput*)this.Component;
|
||||
ImGui.Text(
|
||||
$"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.EvaluatedString.StringPtr))}");
|
||||
ImGui.Text(
|
||||
$"InputBase Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.RawString.StringPtr))}");
|
||||
ImGui.Text(
|
||||
$"Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText01.StringPtr))}");
|
||||
ImGui.Text(
|
||||
$"Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText02.StringPtr))}");
|
||||
ImGui.Text(
|
||||
$"AvailableLines: {Marshal.PtrToStringAnsi(new(textInputComponent->AvailableLines.StringPtr))}");
|
||||
ImGui.Text(
|
||||
$"HighlightedAutoTranslateOptionColorPrefix: {Marshal.PtrToStringAnsi(new(textInputComponent->HighlightedAutoTranslateOptionColorPrefix.StringPtr))}");
|
||||
ImGui.Text(
|
||||
$"HighlightedAutoTranslateOptionColorSuffix: {Marshal.PtrToStringAnsi(new(textInputComponent->HighlightedAutoTranslateOptionColorSuffix.StringPtr))}");
|
||||
ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.EvaluatedString.StringPtr))}");
|
||||
ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->AtkComponentInputBase.RawString.StringPtr))}");
|
||||
// TODO: Reenable when unknowns have been unprivated / named
|
||||
// ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText01.StringPtr))}");
|
||||
// ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new(textInputComponent->UnkText02.StringPtr))}");
|
||||
ImGui.Text($"AvailableLines: {Marshal.PtrToStringAnsi(new(textInputComponent->AvailableLines.StringPtr))}");
|
||||
ImGui.Text($"HighlightedAutoTranslateOptionColorPrefix: {Marshal.PtrToStringAnsi(new(textInputComponent->HighlightedAutoTranslateOptionColorPrefix.StringPtr))}");
|
||||
ImGui.Text($"HighlightedAutoTranslateOptionColorSuffix: {Marshal.PtrToStringAnsi(new(textInputComponent->HighlightedAutoTranslateOptionColorSuffix.StringPtr))}");
|
||||
break;
|
||||
case List:
|
||||
case TreeList:
|
||||
|
|
|
|||
|
|
@ -149,16 +149,27 @@ internal class CallGateChannel
|
|||
return (TRet)result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the context for the invocations through this channel.
|
||||
/// </summary>
|
||||
/// <param name="ipcContext">The context to set.</param>
|
||||
internal void SetInvocationContext(IpcContext ipcContext)
|
||||
{
|
||||
this.ipcExecutionContext.Value = ipcContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the context for invocations through this channel.
|
||||
/// </summary>
|
||||
/// <returns>The context, if one was set.</returns>
|
||||
internal IpcContext? GetInvocationContext()
|
||||
{
|
||||
return this.ipcExecutionContext.IsValueCreated ? this.ipcExecutionContext.Value : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the context for this channel.
|
||||
/// </summary>
|
||||
internal void ClearInvocationContext()
|
||||
{
|
||||
this.ipcExecutionContext.Value = null;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Dalamud.Game;
|
|||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
|
|
@ -109,12 +110,14 @@ public interface IClientState : IDalamudService
|
|||
/// <summary>
|
||||
/// Gets the local player character, if one is present.
|
||||
/// </summary>
|
||||
[Api15ToDo("Remove")]
|
||||
[Obsolete($"Use {nameof(IPlayerState)} or {nameof(IObjectTable)}.{nameof(IObjectTable.LocalPlayer)} if necessary.")]
|
||||
public IPlayerCharacter? LocalPlayer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content ID of the local character.
|
||||
/// </summary>
|
||||
[Api15ToDo("Remove")]
|
||||
[Obsolete($"Use {nameof(IPlayerState)}.{nameof(IPlayerState.ContentId)}")]
|
||||
public ulong LocalContentId { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for marking something to be changed for API 13, for ease of lookup.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||
internal sealed class Api14ToDoAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks that this should be made internal.
|
||||
/// </summary>
|
||||
public const string MakeInternal = "Make internal.";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Api14ToDoAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="what">The explanation.</param>
|
||||
/// <param name="what2">The explanation 2.</param>
|
||||
public Api14ToDoAttribute(string what, string what2 = "")
|
||||
{
|
||||
_ = what;
|
||||
_ = what2;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
@ -125,10 +126,15 @@ public static class ItemUtil
|
|||
|
||||
if (IsEventItem(itemId))
|
||||
{
|
||||
// Only English, German, and French have a Name field.
|
||||
// For other languages, the Name is an empty string, and the Singular field should be used instead.
|
||||
language ??= dataManager.Language;
|
||||
var useSingular = language is not (ClientLanguage.English or ClientLanguage.German or ClientLanguage.French);
|
||||
|
||||
return dataManager
|
||||
.GetExcelSheet<EventItem>(language)
|
||||
.TryGetRow(itemId, out var eventItem)
|
||||
? eventItem.Name
|
||||
? (useSingular ? eventItem.Singular : eventItem.Name)
|
||||
: default;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 90168316b4c5e3af2746a1bdea52fb10f9113862
|
||||
Subproject commit a8827142678d35e62ab0c1bafe94d607271af010
|
||||
Loading…
Add table
Add a link
Reference in a new issue