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 OtterGui.Services;
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);
}
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);
}

View file

@ -30,12 +30,12 @@ public sealed unsafe class LoadAreaVfx : FastHook<LoadAreaVfx.Delegate>
{
var newData = caster != null
? _collectionResolver.IdentifyCollection(caster, true)
: ResolveData.Invalid;
: 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;

View file

@ -16,23 +16,25 @@ public sealed unsafe class LoadCharacterSound : FastHook<LoadCharacterSound.Dele
public LoadCharacterSound(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
{
_state = state;
_state = state;
_collectionResolver = collectionResolver;
_crashHandler = crashHandler;
Task = hooks.CreateHook<Delegate>("Load Character Sound", (nint)VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour, HookSettings.VfxIdentificationHooks);
_crashHandler = crashHandler;
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)]
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;
}

View file

@ -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,20 +26,20 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
public LoadTimelineResources(HookManager hooks, GameState state, CollectionResolver collectionResolver, ICondition conditions,
ObjectManager objects, CrashHandlerService crashHandler)
{
_state = state;
_state = state;
_collectionResolver = collectionResolver;
_conditions = conditions;
_objects = objects;
_crashHandler = crashHandler;
Task = hooks.CreateHook<Delegate>("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, HookSettings.VfxIdentificationHooks);
_conditions = conditions;
_objects = objects;
_crashHandler = crashHandler;
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)]
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);
@ -50,20 +51,19 @@ public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResource
// 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;
}
/// <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
{
if (timeline != nint.Zero)
if (timeline != null)
{
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)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<LoadTimelineResource
}
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;

View file

@ -9,6 +9,6 @@ public static class HookSettings
public const bool ResourceHooks = true && AllHooks;
public const bool MetaEntryHooks = 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;
}

View file

@ -15,21 +15,21 @@ public unsafe class ResourceLoader : IDisposable, IService
{
private readonly ResourceService _resources;
private readonly FileReadService _fileReadService;
private readonly TexMdlService _texMdlService;
private readonly TexMdlService _texMdlService;
private ResolveData _resolvedData = ResolveData.Invalid;
public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlService texMdlService)
{
_resources = resources;
_resources = resources;
_fileReadService = fileReadService;
_texMdlService = texMdlService;
_texMdlService = texMdlService;
ResetResolvePath();
_resources.ResourceRequested += ResourceHandler;
_resources.ResourceRequested += ResourceHandler;
_resources.ResourceHandleIncRef += IncRefProtection;
_resources.ResourceHandleDecRef += DecRefProtection;
_fileReadService.ReadSqPack += ReadSqPackDetour;
_fileReadService.ReadSqPack += ReadSqPackDetour;
}
/// <summary> Load a resource for a given path and a specific collection. </summary>
@ -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;
}

View file

@ -87,6 +87,11 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
private readonly ThreadLocal<bool> _texReturnData = new(() => default);
private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle);
[Signature(Sigs.TexHandleUpdateCategory)]
private readonly UpdateCategoryDelegate _updateCategory = null!;
/// <summary>
/// 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<TexResourceHandleOnLoadPrototype> _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);

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.
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);

View file

@ -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;
}

View file

@ -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<byte> 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<byte> 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<byte> 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<byte> 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<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex));

View file

@ -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();