Update animation hooks.

This commit is contained in:
Ottermandias 2024-07-07 16:16:58 +02:00
parent 68135f3757
commit 4f0f3721a6
11 changed files with 83 additions and 61 deletions

View file

@ -1,3 +1,4 @@
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData; using Penumbra.GameData;
@ -18,26 +19,26 @@ public sealed unsafe class Dismount : FastHook<Dismount.Delegate>
Task = hooks.CreateHook<Delegate>("Dismount", Sigs.Dismount, Detour, HookSettings.VfxIdentificationHooks); Task = hooks.CreateHook<Delegate>("Dismount", Sigs.Dismount, Detour, HookSettings.VfxIdentificationHooks);
} }
public delegate void Delegate(nint a1, nint a2); public delegate void Delegate(MountContainer* a1, nint a2);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void Detour(nint a1, nint a2) private void Detour(MountContainer* a1, nint a2)
{ {
Penumbra.Log.Excessive($"[Dismount] Invoked on {a1:X} with {a2:X}."); Penumbra.Log.Excessive($"[Dismount] Invoked on 0x{(nint)a1:X} with {a2:X}.");
if (a1 == nint.Zero) if (a1 == null)
{ {
Task.Result.Original(a1, a2); Task.Result.Original(a1, a2);
return; return;
} }
var gameObject = *(GameObject**)(a1 + 8); var gameObject = a1->OwnerObject;
if (gameObject == null) if (gameObject == null)
{ {
Task.Result.Original(a1, a2); Task.Result.Original(a1, a2);
return; return;
} }
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection(gameObject, true)); var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*) gameObject, true));
Task.Result.Original(a1, a2); Task.Result.Original(a1, a2);
_state.RestoreAnimationData(last); _state.RestoreAnimationData(last);
} }

View file

@ -35,7 +35,7 @@ public sealed unsafe class LoadAreaVfx : FastHook<LoadAreaVfx.Delegate>
var last = _state.SetAnimationData(newData); var last = _state.SetAnimationData(newData);
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadAreaVfx); _crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadAreaVfx);
var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3); var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3);
Penumbra.Log.Excessive( Penumbra.Log.Information(
$"[Load Area VFX] Invoked with {vfxId}, [{pos[0]} {pos[1]} {pos[2]}], 0x{(nint)caster:X}, {unk1}, {unk2}, {unk3} -> 0x{ret:X}."); $"[Load Area VFX] Invoked with {vfxId}, [{pos[0]} {pos[1]} {pos[2]}], 0x{(nint)caster:X}, {unk1}, {unk2}, {unk3} -> 0x{ret:X}.");
_state.RestoreAnimationData(last); _state.RestoreAnimationData(last);
return ret; return ret;

View file

@ -19,20 +19,22 @@ public sealed unsafe class LoadCharacterSound : FastHook<LoadCharacterSound.Dele
_state = state; _state = state;
_collectionResolver = collectionResolver; _collectionResolver = collectionResolver;
_crashHandler = crashHandler; _crashHandler = crashHandler;
Task = hooks.CreateHook<Delegate>("Load Character Sound", (nint)VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour, HookSettings.VfxIdentificationHooks); Task = hooks.CreateHook<Delegate>("Load Character Sound", (nint)VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour,
HookSettings.VfxIdentificationHooks);
} }
public delegate nint Delegate(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7); public delegate nint Delegate(VfxContainer* container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private nint Detour(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7) private nint Detour(VfxContainer* container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7)
{ {
var character = *(GameObject**)(container + 8); var character = (GameObject*)container->OwnerObject;
var newData = _collectionResolver.IdentifyCollection(character, true); var newData = _collectionResolver.IdentifyCollection(character, true);
var last = _state.SetSoundData(newData); var last = _state.SetSoundData(newData);
_crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadCharacterSound); _crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadCharacterSound);
var ret = Task.Result.Original(container, unk1, unk2, unk3, unk4, unk5, unk6, unk7); var ret = Task.Result.Original(container, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
Penumbra.Log.Excessive($"[Load Character Sound] Invoked with {container:X} {unk1} {unk2} {unk3} {unk4} {unk5} {unk6} {unk7} -> {ret}."); Penumbra.Log.Excessive(
$"[Load Character Sound] Invoked with {(nint)container:X} {unk1} {unk2} {unk3} {unk4} {unk5} {unk6} {unk7} -> {ret}.");
_state.RestoreSoundData(last); _state.RestoreSoundData(last);
return ret; return ret;
} }

View file

@ -1,6 +1,7 @@
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Base;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.GameData; using Penumbra.GameData;
@ -33,12 +34,12 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
Task = hooks.CreateHook<Delegate>("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, HookSettings.VfxIdentificationHooks); Task = hooks.CreateHook<Delegate>("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, HookSettings.VfxIdentificationHooks);
} }
public delegate ulong Delegate(nint timeline); public delegate ulong Delegate(SchedulerTimeline* timeline);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private ulong Detour(nint timeline) private ulong Detour(SchedulerTimeline* timeline)
{ {
Penumbra.Log.Excessive($"[Load Timeline Resources] Invoked on {timeline:X}."); Penumbra.Log.Excessive($"[Load Timeline Resources] Invoked on {(nint)timeline:X}.");
// Do not check timeline loading in cutscenes. // Do not check timeline loading in cutscenes.
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78]) if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
return Task.Result.Original(timeline); return Task.Result.Original(timeline);
@ -56,14 +57,13 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
} }
/// <summary> Use timelines vfuncs to obtain the associated game object. </summary> /// <summary> Use timelines vfuncs to obtain the associated game object. </summary>
public static ResolveData GetDataFromTimeline(ObjectManager objects, CollectionResolver resolver, nint timeline) public static ResolveData GetDataFromTimeline(ObjectManager objects, CollectionResolver resolver, SchedulerTimeline* timeline)
{ {
try try
{ {
if (timeline != nint.Zero) if (timeline != null)
{ {
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc]; var idx = timeline->GetOwningGameObjectIndex();
var idx = getGameObjectIdx(timeline);
if (idx >= 0 && idx < objects.TotalCount) if (idx >= 0 && idx < objects.TotalCount)
{ {
var obj = objects[idx]; var obj = objects[idx];
@ -73,7 +73,7 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
} }
catch (Exception e) catch (Exception e)
{ {
Penumbra.Log.Error($"Error getting timeline data for 0x{timeline:X}:\n{e}"); Penumbra.Log.Error($"Error getting timeline data for 0x{(nint)timeline:X}:\n{e}");
} }
return ResolveData.Invalid; return ResolveData.Invalid;

View file

@ -9,6 +9,6 @@ public static class HookSettings
public const bool ResourceHooks = true && AllHooks; public const bool ResourceHooks = true && AllHooks;
public const bool MetaEntryHooks = true && AllHooks; public const bool MetaEntryHooks = true && AllHooks;
public const bool MetaParentHooks = true && AllHooks; public const bool MetaParentHooks = true && AllHooks;
public const bool VfxIdentificationHooks = false && AllHooks; public const bool VfxIdentificationHooks = true && AllHooks;
public const bool PostProcessingHooks = true && AllHooks; public const bool PostProcessingHooks = true && AllHooks;
} }

View file

@ -94,6 +94,9 @@ public unsafe class ResourceLoader : IDisposable, IService
CompareHash(ComputeHash(path.Path, parameters), hash, path); CompareHash(ComputeHash(path.Path, parameters), hash, path);
if (path.ToString() == "vfx/common/eff/abi_cnj022g.avfx")
;
// If no replacements are being made, we still want to be able to trigger the event. // If no replacements are being made, we still want to be able to trigger the event.
var (resolvedPath, data) = _incMode.Value var (resolvedPath, data) = _incMode.Value
? (null, ResolveData.Invalid) ? (null, ResolveData.Invalid)
@ -121,7 +124,8 @@ public unsafe class ResourceLoader : IDisposable, IService
{ {
if (fileDescriptor->ResourceHandle == null) if (fileDescriptor->ResourceHandle == null)
{ {
Penumbra.Log.Error("[ResourceLoader] Failure to load file from SqPack: invalid File Descriptor."); Penumbra.Log.Verbose(
$"[ResourceLoader] Failure to load file from SqPack: invalid File Descriptor: {Marshal.PtrToStringUni((nint)(&fileDescriptor->Utf16FileName))}");
return; return;
} }

View file

@ -87,6 +87,11 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
private readonly ThreadLocal<bool> _texReturnData = new(() => default); private readonly ThreadLocal<bool> _texReturnData = new(() => default);
private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle);
[Signature(Sigs.TexHandleUpdateCategory)]
private readonly UpdateCategoryDelegate _updateCategory = null!;
/// <summary> /// <summary>
/// The function that checks a files CRC64 to determine whether it is 'protected'. /// The function that checks a files CRC64 to determine whether it is 'protected'.
/// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag for models. /// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag for models.
@ -99,9 +104,14 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
return CustomFileFlag; return CustomFileFlag;
if (_customTexCrc.Contains(crc64)) if (_customTexCrc.Contains(crc64))
{
_texReturnData.Value = true; _texReturnData.Value = true;
return nint.Zero;
}
return _checkFileStateHook.Original(ptr, crc64); var ret = _checkFileStateHook.Original(ptr, crc64);
Penumbra.Log.Excessive($"[CheckFileState] Called on 0x{ptr:X} with CRC {crc64:X16}, returned 0x{ret:X}.");
return ret;
} }
private delegate byte LoadTexFileLocalDelegate(TextureResourceHandle* handle, int unk1, SeFileDescriptor* unk2, bool unk3); private delegate byte LoadTexFileLocalDelegate(TextureResourceHandle* handle, int unk1, SeFileDescriptor* unk2, bool unk3);
@ -118,7 +128,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
private delegate byte TexResourceHandleOnLoadPrototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2); private delegate byte TexResourceHandleOnLoadPrototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2);
[Signature(Sigs.TexResourceHandleOnLoad, DetourName = nameof(OnLoadDetour))] [Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnLoadDetour))]
private readonly Hook<TexResourceHandleOnLoadPrototype> _textureOnLoadHook = null!; private readonly Hook<TexResourceHandleOnLoadPrototype> _textureOnLoadHook = null!;
private byte OnLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2) private byte OnLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
@ -129,7 +139,9 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
// Function failed on a replaced texture, call local. // Function failed on a replaced texture, call local.
_texReturnData.Value = false; _texReturnData.Value = false;
return _loadTexFileLocal(handle, _lodService.GetLod(handle), descriptor, unk2 != 0); ret = _loadTexFileLocal(handle, _lodService.GetLod(handle), descriptor, unk2 != 0);
_updateCategory(handle);
return ret;
} }
private delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3); private delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3);

View file

@ -124,7 +124,7 @@ public class ResourceTree
// This way to tell apart MainHand and OffHand is not always accurate, but seems good enough for what we're doing with it. // This way to tell apart MainHand and OffHand is not always accurate, but seems good enough for what we're doing with it.
var slot = weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand; var slot = weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand;
var equipment = new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, new StainIds(weapon->Stain1, weapon->Stain2)); var equipment = new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, new StainIds(weapon->Stain0, weapon->Stain1));
var weaponType = weapon->SecondaryId; var weaponType = weapon->SecondaryId;
var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType); var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType);

View file

@ -1,3 +1,5 @@
using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Base;
namespace Penumbra.Interop.Structs; namespace Penumbra.Interop.Structs;
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
@ -7,5 +9,5 @@ public unsafe struct ClipScheduler
public nint* VTable; public nint* VTable;
[FieldOffset(0x38)] [FieldOffset(0x38)]
public nint SchedulerTimeline; public SchedulerTimeline* SchedulerTimeline;
} }

View file

@ -9,37 +9,37 @@ internal static class StructExtensions
public static unsafe ByteString AsByteString(in this StdString str) public static unsafe ByteString AsByteString(in this StdString str)
=> ByteString.FromSpanUnsafe(str.AsSpan(), true); => ByteString.FromSpanUnsafe(str.AsSpan(), true);
public static ByteString ResolveEidPathAsByteString(in this CharacterBase character) public static ByteString ResolveEidPathAsByteString(ref this CharacterBase character)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveEidPath(pathBuffer)); return ToOwnedByteString(character.ResolveEidPath(pathBuffer));
} }
public static ByteString ResolveImcPathAsByteString(in this CharacterBase character, uint slotIndex) public static ByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex)); return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex));
} }
public static ByteString ResolveMdlPathAsByteString(in this CharacterBase character, uint slotIndex) public static ByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex)); return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex));
} }
public static unsafe ByteString ResolveMtrlPathAsByteString(in this CharacterBase character, uint slotIndex, byte* mtrlFileName) public static unsafe ByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName)
{ {
var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName));
} }
public static ByteString ResolveSklbPathAsByteString(in this CharacterBase character, uint partialSkeletonIndex) public static ByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex)); return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex));
} }
public static ByteString ResolveSkpPathAsByteString(in this CharacterBase character, uint partialSkeletonIndex) public static ByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex));

View file

@ -430,7 +430,7 @@ public class DebugTab : Window, ITab, IUiService
foreach (var obj in _objects) foreach (var obj in _objects)
{ {
ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL"); ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL");
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}"); ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");
ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero
? string.Empty ? string.Empty
@ -482,14 +482,15 @@ public class DebugTab : Window, ITab, IUiService
{ {
var gameObject = (GameObject*)gameObjectPtr; var gameObject = (GameObject*)gameObjectPtr;
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted($"0x{drawObject:X}");
ImGuiUtil.CopyOnClickSelectable($"0x{drawObject:X}");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(gameObject->ObjectIndex.ToString()); ImGui.TextUnformatted(gameObject->ObjectIndex.ToString());
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(child ? "Child" : "Main"); ImGui.TextUnformatted(child ? "Child" : "Main");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var (address, name) = ($"0x{gameObjectPtr:X}", new ByteString(gameObject->Name).ToString()); var (address, name) = ($"0x{gameObjectPtr:X}", new ByteString(gameObject->Name).ToString());
ImGui.TextUnformatted(address); ImGuiUtil.CopyOnClickSelectable(address);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(name); ImGui.TextUnformatted(name);
ImGui.TableNextColumn(); ImGui.TableNextColumn();