From 4f0f3721a6296e0fad1c816c06e42d0ac3cf0de0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 7 Jul 2024 16:16:58 +0200 Subject: [PATCH] Update animation hooks. --- Penumbra/Interop/Hooks/Animation/Dismount.cs | 13 ++++---- .../Interop/Hooks/Animation/LoadAreaVfx.cs | 8 ++--- .../Hooks/Animation/LoadCharacterSound.cs | 16 ++++++---- .../Hooks/Animation/LoadTimelineResources.cs | 32 +++++++++---------- Penumbra/Interop/Hooks/HookSettings.cs | 2 +- .../Hooks/ResourceLoading/ResourceLoader.cs | 30 +++++++++-------- .../Hooks/ResourceLoading/TexMdlService.cs | 18 +++++++++-- Penumbra/Interop/ResourceTree/ResourceTree.cs | 2 +- Penumbra/Interop/Structs/ClipScheduler.cs | 4 ++- Penumbra/Interop/Structs/StructExtensions.cs | 12 +++---- Penumbra/UI/Tabs/Debug/DebugTab.cs | 7 ++-- 11 files changed, 83 insertions(+), 61 deletions(-) diff --git a/Penumbra/Interop/Hooks/Animation/Dismount.cs b/Penumbra/Interop/Hooks/Animation/Dismount.cs index 523e750c..034011e7 100644 --- a/Penumbra/Interop/Hooks/Animation/Dismount.cs +++ b/Penumbra/Interop/Hooks/Animation/Dismount.cs @@ -1,3 +1,4 @@ +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using OtterGui.Services; using Penumbra.GameData; @@ -18,26 +19,26 @@ public sealed unsafe class Dismount : FastHook Task = hooks.CreateHook("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)] - 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}."); - if (a1 == nint.Zero) + Penumbra.Log.Excessive($"[Dismount] Invoked on 0x{(nint)a1:X} with {a2:X}."); + if (a1 == null) { Task.Result.Original(a1, a2); return; } - var gameObject = *(GameObject**)(a1 + 8); + var gameObject = a1->OwnerObject; if (gameObject == null) { Task.Result.Original(a1, a2); return; } - var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection(gameObject, true)); + var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*) gameObject, true)); Task.Result.Original(a1, a2); _state.RestoreAnimationData(last); } diff --git a/Penumbra/Interop/Hooks/Animation/LoadAreaVfx.cs b/Penumbra/Interop/Hooks/Animation/LoadAreaVfx.cs index 0f51157c..cfab29d3 100644 --- a/Penumbra/Interop/Hooks/Animation/LoadAreaVfx.cs +++ b/Penumbra/Interop/Hooks/Animation/LoadAreaVfx.cs @@ -29,13 +29,13 @@ public sealed unsafe class LoadAreaVfx : FastHook private nint Detour(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3) { var newData = caster != null - ? _collectionResolver.IdentifyCollection(caster, true) - : ResolveData.Invalid; + ? _collectionResolver.IdentifyCollection(caster, true) + : ResolveData.Invalid; var last = _state.SetAnimationData(newData); _crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadAreaVfx); - var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3); - Penumbra.Log.Excessive( + var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3); + 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}."); _state.RestoreAnimationData(last); return ret; diff --git a/Penumbra/Interop/Hooks/Animation/LoadCharacterSound.cs b/Penumbra/Interop/Hooks/Animation/LoadCharacterSound.cs index ed04880e..8d1096d2 100644 --- a/Penumbra/Interop/Hooks/Animation/LoadCharacterSound.cs +++ b/Penumbra/Interop/Hooks/Animation/LoadCharacterSound.cs @@ -16,23 +16,25 @@ public sealed unsafe class LoadCharacterSound : FastHook("Load Character Sound", (nint)VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour, HookSettings.VfxIdentificationHooks); + _crashHandler = crashHandler; + Task = hooks.CreateHook("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)] - 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 last = _state.SetSoundData(newData); _crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadCharacterSound); 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); return ret; } diff --git a/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs b/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs index 4e9037bd..8bb14db6 100644 --- a/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs +++ b/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Base; using OtterGui.Services; using Penumbra.Collections; using Penumbra.GameData; @@ -25,45 +26,44 @@ public sealed unsafe class LoadTimelineResources : FastHook("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, HookSettings.VfxIdentificationHooks); + _conditions = conditions; + _objects = objects; + _crashHandler = crashHandler; + Task = hooks.CreateHook("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)] - 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. if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78]) return Task.Result.Original(timeline); var newData = GetDataFromTimeline(_objects, _collectionResolver, timeline); - var last = _state.SetAnimationData(newData); - + var last = _state.SetAnimationData(newData); + #if false // This is called far too often and spams the log too much. _crashHandler.LogAnimation(newData.AssociatedGameObject, newData.ModCollection, AnimationInvocationType.LoadTimelineResources); #endif - var ret = Task.Result.Original(timeline); + var ret = Task.Result.Original(timeline); _state.RestoreAnimationData(last); return ret; } /// Use timelines vfuncs to obtain the associated game object. - public static ResolveData GetDataFromTimeline(ObjectManager objects, CollectionResolver resolver, nint timeline) + public static ResolveData GetDataFromTimeline(ObjectManager objects, CollectionResolver resolver, SchedulerTimeline* timeline) { try { - if (timeline != nint.Zero) + if (timeline != null) { - var getGameObjectIdx = ((delegate* unmanaged**)timeline)[0][Offsets.GetGameObjectIdxVfunc]; - var idx = getGameObjectIdx(timeline); + var idx = timeline->GetOwningGameObjectIndex(); if (idx >= 0 && idx < objects.TotalCount) { var obj = objects[idx]; @@ -73,7 +73,7 @@ public sealed unsafe class LoadTimelineResources : FastHook Load a resource for a given path and a specific collection. @@ -80,10 +80,10 @@ public unsafe class ResourceLoader : IDisposable, IService public void Dispose() { - _resources.ResourceRequested -= ResourceHandler; + _resources.ResourceRequested -= ResourceHandler; _resources.ResourceHandleIncRef -= IncRefProtection; _resources.ResourceHandleDecRef -= DecRefProtection; - _fileReadService.ReadSqPack -= ReadSqPackDetour; + _fileReadService.ReadSqPack -= ReadSqPackDetour; } private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, @@ -94,6 +94,9 @@ public unsafe class ResourceLoader : IDisposable, IService 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. var (resolvedPath, data) = _incMode.Value ? (null, ResolveData.Invalid) @@ -112,7 +115,7 @@ public unsafe class ResourceLoader : IDisposable, IService // Replace the hash and path with the correct one for the replacement. hash = ComputeHash(resolvedPath.Value.InternalName, parameters); var oldPath = path; - path = p; + path = p; returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters); ResourceLoaded?.Invoke(returnValue, oldPath, resolvedPath.Value, data); } @@ -121,7 +124,8 @@ public unsafe class ResourceLoader : IDisposable, IService { 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; } @@ -140,12 +144,12 @@ public unsafe class ResourceLoader : IDisposable, IService } var path = ByteString.FromSpanUnsafe(actualPath, gamePath.Path.IsNullTerminated, gamePath.Path.IsAsciiLowerCase, gamePath.Path.IsAscii); - fileDescriptor->ResourceHandle->FileNameData = path.Path; + fileDescriptor->ResourceHandle->FileNameData = path.Path; fileDescriptor->ResourceHandle->FileNameLength = path.Length; MtrlForceSync(fileDescriptor, ref isSync); returnValue = DefaultLoadResource(path, fileDescriptor, priority, isSync, data); // Return original resource handle path so that they can be loaded separately. - fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; + fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; } @@ -165,7 +169,7 @@ public unsafe class ResourceLoader : IDisposable, IService // Ensure that the file descriptor has its wchar_t array on aligned boundary even if it has to be odd. var fd = stackalloc char[0x11 + 0x0B + 14]; fileDescriptor->FileDescriptor = (byte*)fd + 1; - CreateFileWHook.WritePtr(fd + 0x11, gamePath.Path, gamePath.Length); + CreateFileWHook.WritePtr(fd + 0x11, gamePath.Path, gamePath.Length); CreateFileWHook.WritePtr(&fileDescriptor->Utf16FileName, gamePath.Path, gamePath.Length); // Use the SE ReadFile function. @@ -206,7 +210,7 @@ public unsafe class ResourceLoader : IDisposable, IService return; _incMode.Value = true; - returnValue = _resources.IncRef(handle); + returnValue = _resources.IncRef(handle); _incMode.Value = false; } diff --git a/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs b/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs index 793d15af..80ba5cb9 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs @@ -87,6 +87,11 @@ public unsafe class TexMdlService : IDisposable, IRequiredService private readonly ThreadLocal _texReturnData = new(() => default); + private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle); + + [Signature(Sigs.TexHandleUpdateCategory)] + private readonly UpdateCategoryDelegate _updateCategory = null!; + /// /// 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. @@ -99,9 +104,14 @@ public unsafe class TexMdlService : IDisposable, IRequiredService return CustomFileFlag; if (_customTexCrc.Contains(crc64)) + { _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); @@ -118,7 +128,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService 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 _textureOnLoadHook = null!; 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. _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); diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 810c946d..96125df2 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -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. 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 genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType); diff --git a/Penumbra/Interop/Structs/ClipScheduler.cs b/Penumbra/Interop/Structs/ClipScheduler.cs index 44a905b8..8270e0f2 100644 --- a/Penumbra/Interop/Structs/ClipScheduler.cs +++ b/Penumbra/Interop/Structs/ClipScheduler.cs @@ -1,3 +1,5 @@ +using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Base; + namespace Penumbra.Interop.Structs; [StructLayout(LayoutKind.Explicit)] @@ -7,5 +9,5 @@ public unsafe struct ClipScheduler public nint* VTable; [FieldOffset(0x38)] - public nint SchedulerTimeline; + public SchedulerTimeline* SchedulerTimeline; } diff --git a/Penumbra/Interop/Structs/StructExtensions.cs b/Penumbra/Interop/Structs/StructExtensions.cs index 3cd87424..fc8b1c3d 100644 --- a/Penumbra/Interop/Structs/StructExtensions.cs +++ b/Penumbra/Interop/Structs/StructExtensions.cs @@ -9,37 +9,37 @@ internal static class StructExtensions public static unsafe ByteString AsByteString(in this StdString str) => ByteString.FromSpanUnsafe(str.AsSpan(), true); - public static ByteString ResolveEidPathAsByteString(in this CharacterBase character) + public static ByteString ResolveEidPathAsByteString(ref this CharacterBase character) { Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; 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 pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; 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 pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; 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]; 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 pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; 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 pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 9a03f384..180049bc 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -430,7 +430,7 @@ public class DebugTab : Window, ITab, IUiService 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(obj.Address == nint.Zero ? string.Empty @@ -482,14 +482,15 @@ public class DebugTab : Window, ITab, IUiService { var gameObject = (GameObject*)gameObjectPtr; ImGui.TableNextColumn(); - ImGui.TextUnformatted($"0x{drawObject:X}"); + + ImGuiUtil.CopyOnClickSelectable($"0x{drawObject:X}"); ImGui.TableNextColumn(); ImGui.TextUnformatted(gameObject->ObjectIndex.ToString()); ImGui.TableNextColumn(); ImGui.TextUnformatted(child ? "Child" : "Main"); ImGui.TableNextColumn(); var (address, name) = ($"0x{gameObjectPtr:X}", new ByteString(gameObject->Name).ToString()); - ImGui.TextUnformatted(address); + ImGuiUtil.CopyOnClickSelectable(address); ImGui.TableNextColumn(); ImGui.TextUnformatted(name); ImGui.TableNextColumn();