Reinstate the inlined ApricotSoundPlay hook one layer hup.

This commit is contained in:
Ottermandias 2024-07-21 00:13:43 +02:00
parent a4548bbf04
commit 258f7e9732
2 changed files with 29 additions and 15 deletions

View file

@ -1,4 +1,3 @@
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Services;
@ -11,33 +10,48 @@ using Penumbra.Services;
namespace Penumbra.Interop.Hooks.Animation;
/// <summary> Called for some sound effects caused by animations or VFX. </summary>
public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSoundPlay.Delegate>
/// <remarks> Actual function got inlined. </remarks>
public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook<ApricotListenerSoundPlayCaller.Delegate>
{
private readonly GameState _state;
private readonly CollectionResolver _collectionResolver;
private readonly CrashHandlerService _crashHandler;
// TODO because of inlining.
public ApricotListenerSoundPlay(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
public ApricotListenerSoundPlayCaller(HookManager hooks, GameState state, CollectionResolver collectionResolver,
CrashHandlerService crashHandler)
{
_state = state;
_collectionResolver = collectionResolver;
_crashHandler = crashHandler;
Task = hooks.CreateHook<Delegate>("Apricot Listener Sound Play", Sigs.ApricotListenerSoundPlay, Detour, HookSettings.VfxIdentificationHooks);
Task = hooks.CreateHook<Delegate>("Apricot Listener Sound Play Caller", Sigs.ApricotListenerSoundPlayCaller, Detour,
true); //HookSettings.VfxIdentificationHooks);
}
public delegate nint Delegate(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6);
public delegate nint Delegate(nint a1, nint a2, float a3);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private nint Detour(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6)
private nint Detour(nint a1, nint unused, float timeOffset)
{
Penumbra.Log.Excessive($"[Apricot Listener Sound Play] Invoked on 0x{a1:X} with {a2}, {a3}, {a4}, {a5}, {a6}.");
if (a6 == nint.Zero)
return Task.Result.Original(a1, a2, a3, a4, a5, a6);
// Short-circuiting and sanity checks done by game.
var playTime = a1 == nint.Zero ? -1 : *(float*)(a1 + 0x250);
if (playTime < 0)
return Task.Result.Original(a1, unused, timeOffset);
// a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1.
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6);
var someIntermediate = *(nint*)(a1 + 0x1F8);
var flags = someIntermediate == nint.Zero ? (ushort)0 : *(ushort*)(someIntermediate + 0x49C);
if (((flags >> 13) & 1) == 0)
return Task.Result.Original(a1, unused, timeOffset);
Penumbra.Log.Information(
$"[Apricot Listener Sound Play Caller] Invoked on 0x{a1:X} with {unused}, {timeOffset}.");
// Fetch the IInstanceListenner (sixth argument to inlined call of SoundPlay)
var apricotIInstanceListenner = *(nint*)(someIntermediate + 0x270);
if (apricotIInstanceListenner == nint.Zero)
return Task.Result.Original(a1, unused, timeOffset);
// In some cases we can obtain the associated caster via vfunc 1.
var newData = ResolveData.Invalid;
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)apricotIInstanceListenner)[1](apricotIInstanceListenner);
if (gameObject != null)
{
newData = _collectionResolver.IdentifyCollection(gameObject, true);
@ -47,14 +61,14 @@ public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSo
// for VfxListenner we can obtain the associated draw object as its first member,
// if the object has different type, drawObject will contain other values or garbage,
// but only be used in a dictionary pointer lookup, so this does not hurt.
var drawObject = ((DrawObject**)a6)[1];
var drawObject = ((DrawObject**)apricotIInstanceListenner)[1];
if (drawObject != null)
newData = _collectionResolver.IdentifyCollection(drawObject, true);
}
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.ApricotSoundPlay);
var last = _state.SetAnimationData(newData);
var ret = Task.Result.Original(a1, a2, a3, a4, a5, a6);
var ret = Task.Result.Original(a1, unused, timeOffset);
_state.RestoreAnimationData(last);
return ret;
}