diff --git a/OtterGui b/OtterGui index 4c924791..09dcd012 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4c92479175161617161d8faf844c8f683aa2d5d2 +Subproject commit 09dcd012a3106862f20f045b9ff9e33d168047c4 diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 292d822c..2472b3e2 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -124,6 +124,9 @@ public interface IPenumbraApi : IPenumbraApiBase // Obtain the game object associated with a given draw object and the name of the collection associated with this game object. public (IntPtr, string) GetDrawObjectInfo( IntPtr drawObject ); + // Obtain the parent game object index for an unnamed cutscene actor by its index. + public int GetCutsceneParentIndex( int actor ); + // Obtain a list of all installed mods. The first string is their directory name, the second string is their mod name. public IList< (string, string) > GetModList(); diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index e311161a..81ef08a8 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -201,6 +201,7 @@ public class IpcTester : IDisposable private string _currentDrawObjectString = string.Empty; private string _currentReversePath = string.Empty; private IntPtr _currentDrawObject = IntPtr.Zero; + private int _currentCutsceneActor = 0; private string _lastCreatedGameObjectName = string.Empty; private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue; @@ -231,6 +232,8 @@ public class IpcTester : IDisposable : IntPtr.Zero; } + ImGui.InputInt( "Cutscene Actor", ref _currentCutsceneActor, 0 ); + using var table = ImRaii.Table( string.Empty, 3, ImGuiTableFlags.SizingFixedFit ); if( !table ) { @@ -263,6 +266,10 @@ public class IpcTester : IDisposable ImGui.TextUnformatted( ptr == IntPtr.Zero ? $"No Actor Associated, {collection}" : $"{ptr:X}, {collection}" ); } + DrawIntro( PenumbraIpc.LabelProviderGetDrawObjectInfo, "Cutscene Parent" ); + ImGui.TextUnformatted( _pi.GetIpcSubscriber< int, int >( PenumbraIpc.LabelProviderGetCutsceneParentIndex ) + .InvokeFunc( _currentCutsceneActor ).ToString() ); + DrawIntro( PenumbraIpc.LabelProviderReverseResolvePath, "Reversed Game Paths" ); if( _currentReversePath.Length > 0 ) { diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index b9b18c70..55bf0472 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Reflection; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; -using FFXIVClientStructs.FFXIV.Common.Configuration; using Lumina.Data; using Newtonsoft.Json; using OtterGui; @@ -16,14 +15,13 @@ using Penumbra.GameData.Enums; using Penumbra.Interop.Resolver; using Penumbra.Meta.Manipulations; using Penumbra.Mods; -using Penumbra.Util; namespace Penumbra.Api; public class PenumbraApi : IDisposable, IPenumbraApi { public (int, int) ApiVersion - => ( 4, 11 ); + => ( 4, 12 ); private Penumbra? _penumbra; private Lumina.GameData? _lumina; @@ -43,8 +41,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi public event CreatingCharacterBaseDelegate? CreatingCharacterBase { - add => _penumbra!.PathResolver.CreatingCharacterBase += value; - remove => _penumbra!.PathResolver.CreatingCharacterBase -= value; + add => PathResolver.DrawObjectState.CreatingCharacterBase += value; + remove => PathResolver.DrawObjectState.CreatingCharacterBase -= value; } public bool Valid @@ -54,8 +52,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi { _penumbra = penumbra; _lumina = ( Lumina.GameData? )Dalamud.GameData.GetType() - .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) - ?.GetValue( Dalamud.GameData ); + .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) + ?.GetValue( Dalamud.GameData ); foreach( var collection in Penumbra.CollectionManager ) { SubscribeToCollection( collection ); @@ -221,10 +219,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi public (IntPtr, string) GetDrawObjectInfo( IntPtr drawObject ) { CheckInitialized(); - var (obj, collection) = _penumbra!.PathResolver.IdentifyDrawObject( drawObject ); + var (obj, collection) = PathResolver.IdentifyDrawObject( drawObject ); return ( obj, collection.Name ); } + public int GetCutsceneParentIndex( int actor ) + { + CheckInitialized(); + return _penumbra!.PathResolver.CutsceneActor( actor ); + } + public IList< (string, string) > GetModList() { CheckInitialized(); @@ -429,7 +433,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi } if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character ) - || Penumbra.TempMods.Collections.ContainsKey( character ) ) + || Penumbra.TempMods.Collections.ContainsKey( character ) ) { return ( PenumbraApiEc.CharacterCollectionExists, string.Empty ); } @@ -475,7 +479,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection ) - && !Penumbra.CollectionManager.ByName( collectionName, out collection ) ) + && !Penumbra.CollectionManager.ByName( collectionName, out collection ) ) { return PenumbraApiEc.CollectionMissing; } @@ -512,7 +516,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection ) - && !Penumbra.CollectionManager.ByName( collectionName, out collection ) ) + && !Penumbra.CollectionManager.ByName( collectionName, out collection ) ) { return PenumbraApiEc.CollectionMissing; } diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index de179d66..63b71322 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -261,6 +261,7 @@ public partial class PenumbraIpc public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath"; public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo"; + public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex"; public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath"; public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath"; public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase"; @@ -269,6 +270,7 @@ public partial class PenumbraIpc internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; internal ICallGateProvider< string, string >? ProviderResolvePlayer; internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo; + internal ICallGateProvider< int, int >? ProviderGetCutsceneParentIndex; internal ICallGateProvider< string, string, string[] >? ProviderReverseResolvePath; internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer; internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase; @@ -315,6 +317,16 @@ public partial class PenumbraIpc PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetDrawObjectInfo}:\n{e}" ); } + try + { + ProviderGetCutsceneParentIndex = pi.GetIpcProvider( LabelProviderGetCutsceneParentIndex ); + ProviderGetCutsceneParentIndex.RegisterFunc( Api.GetCutsceneParentIndex ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetCutsceneParentIndex}:\n{e}" ); + } + try { ProviderReverseResolvePath = pi.GetIpcProvider< string, string, string[] >( LabelProviderReverseResolvePath ); @@ -350,6 +362,7 @@ public partial class PenumbraIpc private void DisposeResolveProviders() { ProviderGetDrawObjectInfo?.UnregisterFunc(); + ProviderGetCutsceneParentIndex?.UnregisterFunc(); ProviderResolveDefault?.UnregisterFunc(); ProviderResolveCharacter?.UnregisterFunc(); ProviderReverseResolvePath?.UnregisterFunc(); diff --git a/Penumbra/Interop/Resolver/CutsceneCharacters.cs b/Penumbra/Interop/Resolver/CutsceneCharacters.cs new file mode 100644 index 00000000..a701c293 --- /dev/null +++ b/Penumbra/Interop/Resolver/CutsceneCharacters.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Object; + +namespace Penumbra.Interop.Resolver; + +public class CutsceneCharacters : IDisposable +{ + public const int CutsceneStartIdx = 200; + public const int CutsceneSlots = 40; + public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots; + + private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, ObjectReloader.CutsceneSlots ).ToArray(); + + public IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > Actors + => Enumerable.Range( CutsceneStartIdx, CutsceneSlots ) + .Where( i => Dalamud.Objects[ i ] != null ) + .Select( i => KeyValuePair.Create( i, this[ i ] ?? Dalamud.Objects[ i ]! ) ); + + public CutsceneCharacters() + => SignatureHelper.Initialise( this ); + + // Get the related actor to a cutscene actor. + // Does not check for valid input index. + // Returns null if no connected actor is set or the actor does not exist anymore. + public global::Dalamud.Game.ClientState.Objects.Types.GameObject? this[ int idx ] + { + get + { + Debug.Assert( idx is >= CutsceneStartIdx and < CutsceneEndIdx ); + idx = _copiedCharacters[ idx - CutsceneStartIdx ]; + return idx < 0 ? null : Dalamud.Objects[ idx ]; + } + } + + // Return the currently set index of a parent or -1 if none is set or the index is invalid. + public int GetParentIndex( int idx ) + { + if( idx is >= CutsceneStartIdx and < CutsceneEndIdx ) + { + return _copiedCharacters[ idx - CutsceneStartIdx ]; + } + + return -1; + } + + public void Enable() + => _copyCharacterHook.Enable(); + + public void Disable() + => _copyCharacterHook.Disable(); + + public void Dispose() + => _copyCharacterHook.Dispose(); + + + private unsafe delegate ulong CopyCharacterDelegate( GameObject* target, GameObject* source, uint unk ); + + [Signature( "E8 ?? ?? ?? ?? 0F B6 9F ?? ?? ?? ?? 48 8D 8F", DetourName = nameof( CopyCharacterDetour ) )] + private readonly Hook< CopyCharacterDelegate > _copyCharacterHook = null!; + + private unsafe ulong CopyCharacterDetour( GameObject* target, GameObject* source, uint unk ) + { + try + { + if( target != null && target->ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx ) + { + var parent = source == null || source->ObjectIndex is < 0 or >= CutsceneStartIdx + ? -1 + : source->ObjectIndex; + _copiedCharacters[ target->ObjectIndex - CutsceneStartIdx ] = ( short )parent; + } + } + catch + { + // ignored + } + + return _copyCharacterHook.Original( target, source, unk ); + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Animation.cs b/Penumbra/Interop/Resolver/PathResolver.Animation.cs deleted file mode 100644 index 563b32ca..00000000 --- a/Penumbra/Interop/Resolver/PathResolver.Animation.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using Dalamud.Hooking; -using Dalamud.Utility.Signatures; -using Penumbra.Collections; -using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; - -namespace Penumbra.Interop.Resolver; - -public unsafe partial class PathResolver -{ - private ModCollection? _animationLoadCollection; - private ModCollection? _lastAvfxCollection = null; - - public delegate ulong LoadTimelineResourcesDelegate( IntPtr timeline ); - - // The timeline object loads the requested .tmb and .pap files. The .tmb files load the respective .avfx files. - // We can obtain the associated game object from the timelines 28'th vfunc and use that to apply the correct collection. - [Signature( "E8 ?? ?? ?? ?? 83 7F ?? ?? 75 ?? 0F B6 87", DetourName = nameof( LoadTimelineResourcesDetour ) )] - public Hook< LoadTimelineResourcesDelegate >? LoadTimelineResourcesHook; - - private ulong LoadTimelineResourcesDetour( IntPtr timeline ) - { - ulong ret; - var old = _animationLoadCollection; - try - { - var getGameObjectIdx = ( ( delegate* unmanaged < IntPtr, int>** )timeline )[ 0 ][ 28 ]; - var idx = getGameObjectIdx( timeline ); - if( idx >= 0 && idx < Dalamud.Objects.Length ) - { - var obj = Dalamud.Objects[ idx ]; - _animationLoadCollection = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : null; - } - else - { - _animationLoadCollection = null; - } - } - finally - { - ret = LoadTimelineResourcesHook!.Original( timeline ); - } - - _animationLoadCollection = old; - - return ret; - } - - // Probably used when the base idle animation gets loaded. - // Make it aware of the correct collection to load the correct pap files. - [Signature( "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8B CF 44 8B C2 E8 ?? ?? ?? ?? 48 8B 05", DetourName = "CharacterBaseLoadAnimationDetour" )] - public Hook< CharacterBaseDestructorDelegate >? CharacterBaseLoadAnimationHook; - - private void CharacterBaseLoadAnimationDetour( IntPtr drawObject ) - { - var last = _animationLoadCollection; - _animationLoadCollection = _lastCreatedCollection - ?? ( FindParent( drawObject, out var collection ) != null ? collection : Penumbra.CollectionManager.Default ); - CharacterBaseLoadAnimationHook!.Original( drawObject ); - _animationLoadCollection = last; - } - - public delegate ulong LoadSomeAvfx( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 ); - - [Signature( "E8 ?? ?? ?? ?? 45 0F B6 F7", DetourName = nameof( LoadSomeAvfxDetour ) )] - public Hook< LoadSomeAvfx >? LoadSomeAvfxHook; - - private ulong LoadSomeAvfxDetour( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 ) - { - var last = _animationLoadCollection; - _animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); - var ret = LoadSomeAvfxHook!.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 ); - _animationLoadCollection = last; - return ret; - } - - // Unknown what exactly this is but it seems to load a bunch of paps. - public delegate void LoadSomePap( IntPtr a1, int a2, IntPtr a3, int a4 ); - - [Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 41 8B D9 89 51" )] - public Hook< LoadSomePap >? LoadSomePapHook; - - private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 ) - { - var timelinePtr = a1 + 0x50; - var last = _animationLoadCollection; - if( timelinePtr != IntPtr.Zero ) - { - var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 ); - if( actorIdx >= 0 && actorIdx < Dalamud.Objects.Length ) - { - _animationLoadCollection = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) ); - } - } - - LoadSomePapHook!.Original( a1, a2, a3, a4 ); - _animationLoadCollection = last; - } - - // Seems to load character actions when zoning or changing class, maybe. - [Signature( "E8 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ?? 8B 8E", DetourName = nameof( SomeActionLoadDetour ) )] - public Hook< CharacterBaseDestructorDelegate >? SomeActionLoadHook; - - private void SomeActionLoadDetour( IntPtr gameObject ) - { - var last = _animationLoadCollection; - _animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); - SomeActionLoadHook!.Original( gameObject ); - _animationLoadCollection = last; - } - - [Signature( "E8 ?? ?? ?? ?? 44 84 BB", DetourName = nameof( SomeOtherAvfxDetour ) )] - public Hook< CharacterBaseDestructorDelegate >? SomeOtherAvfxHook; - - private void SomeOtherAvfxDetour( IntPtr unk ) - { - var last = _animationLoadCollection; - var gameObject = ( GameObject* )( unk - 0x8B0 ); - _animationLoadCollection = IdentifyCollection( gameObject ); - SomeOtherAvfxHook!.Original( unk ); - _animationLoadCollection = last; - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs new file mode 100644 index 00000000..b7ba3617 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs @@ -0,0 +1,215 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using Penumbra.Collections; +using Penumbra.GameData.ByteString; +using Penumbra.GameData.Enums; +using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; + +namespace Penumbra.Interop.Resolver; + +public unsafe partial class PathResolver +{ + public class AnimationState + { + private readonly DrawObjectState _drawObjectState; + + private ModCollection? _animationLoadCollection; + private ModCollection? _lastAvfxCollection; + + public AnimationState( DrawObjectState drawObjectState ) + { + _drawObjectState = drawObjectState; + SignatureHelper.Initialise( this ); + } + + public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out ModCollection? collection ) + { + switch( type ) + { + case ResourceType.Tmb: + case ResourceType.Pap: + case ResourceType.Scd: + if( _animationLoadCollection != null ) + { + collection = _animationLoadCollection; + return true; + } + + break; + case ResourceType.Avfx: + _lastAvfxCollection = _animationLoadCollection ?? Penumbra.CollectionManager.Default; + if( _animationLoadCollection != null ) + { + collection = _animationLoadCollection; + return true; + } + + break; + case ResourceType.Atex: + if( _lastAvfxCollection != null ) + { + collection = _lastAvfxCollection; + return true; + } + + if( _animationLoadCollection != null ) + { + collection = _animationLoadCollection; + return true; + } + + break; + } + + collection = null; + return false; + } + + public void Enable() + { + _loadTimelineResourcesHook.Enable(); + _characterBaseLoadAnimationHook.Enable(); + _loadSomeAvfxHook.Enable(); + _loadSomePapHook.Enable(); + _someActionLoadHook.Enable(); + _someOtherAvfxHook.Enable(); + } + + public void Disable() + { + _loadTimelineResourcesHook.Disable(); + _characterBaseLoadAnimationHook.Disable(); + _loadSomeAvfxHook.Disable(); + _loadSomePapHook.Disable(); + _someActionLoadHook.Disable(); + _someOtherAvfxHook.Disable(); + } + + public void Dispose() + { + _loadTimelineResourcesHook.Dispose(); + _characterBaseLoadAnimationHook.Dispose(); + _loadSomeAvfxHook.Dispose(); + _loadSomePapHook.Dispose(); + _someActionLoadHook.Dispose(); + _someOtherAvfxHook.Dispose(); + } + + // The timeline object loads the requested .tmb and .pap files. The .tmb files load the respective .avfx files. + // We can obtain the associated game object from the timelines 28'th vfunc and use that to apply the correct collection. + private delegate ulong LoadTimelineResourcesDelegate( IntPtr timeline ); + + [Signature( "E8 ?? ?? ?? ?? 83 7F ?? ?? 75 ?? 0F B6 87", DetourName = nameof( LoadTimelineResourcesDetour ) )] + private readonly Hook< LoadTimelineResourcesDelegate > _loadTimelineResourcesHook = null!; + + private ulong LoadTimelineResourcesDetour( IntPtr timeline ) + { + ulong ret; + var old = _animationLoadCollection; + try + { + var getGameObjectIdx = ( ( delegate* unmanaged< IntPtr, int >** )timeline )[ 0 ][ 28 ]; + var idx = getGameObjectIdx( timeline ); + if( idx >= 0 && idx < Dalamud.Objects.Length ) + { + var obj = Dalamud.Objects[ idx ]; + _animationLoadCollection = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : null; + } + else + { + _animationLoadCollection = null; + } + } + finally + { + ret = _loadTimelineResourcesHook!.Original( timeline ); + } + + _animationLoadCollection = old; + + return ret; + } + + // Probably used when the base idle animation gets loaded. + // Make it aware of the correct collection to load the correct pap files. + private delegate void CharacterBaseNoArgumentDelegate( IntPtr drawBase ); + + [Signature( "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8B CF 44 8B C2 E8 ?? ?? ?? ?? 48 8B 05", + DetourName = nameof( CharacterBaseLoadAnimationDetour ) )] + private readonly Hook< CharacterBaseNoArgumentDelegate > _characterBaseLoadAnimationHook = null!; + + private void CharacterBaseLoadAnimationDetour( IntPtr drawObject ) + { + var last = _animationLoadCollection; + _animationLoadCollection = _drawObjectState.LastCreatedCollection + ?? ( FindParent( drawObject, out var collection ) != null ? collection : Penumbra.CollectionManager.Default ); + _characterBaseLoadAnimationHook!.Original( drawObject ); + _animationLoadCollection = last; + } + + + public delegate ulong LoadSomeAvfx( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 ); + + [Signature( "E8 ?? ?? ?? ?? 45 0F B6 F7", DetourName = nameof( LoadSomeAvfxDetour ) )] + private readonly Hook< LoadSomeAvfx > _loadSomeAvfxHook = null!; + + private ulong LoadSomeAvfxDetour( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 ) + { + var last = _animationLoadCollection; + _animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); + var ret = _loadSomeAvfxHook!.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 ); + _animationLoadCollection = last; + return ret; + } + + // Unknown what exactly this is but it seems to load a bunch of paps. + private delegate void LoadSomePap( IntPtr a1, int a2, IntPtr a3, int a4 ); + + [Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 41 8B D9 89 51", + DetourName = nameof( LoadSomePapDetour ) )] + private readonly Hook< LoadSomePap > _loadSomePapHook = null!; + + private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 ) + { + var timelinePtr = a1 + 0x50; + var last = _animationLoadCollection; + if( timelinePtr != IntPtr.Zero ) + { + var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 ); + if( actorIdx >= 0 && actorIdx < Dalamud.Objects.Length ) + { + _animationLoadCollection = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) ); + } + } + + _loadSomePapHook!.Original( a1, a2, a3, a4 ); + _animationLoadCollection = last; + } + + // Seems to load character actions when zoning or changing class, maybe. + [Signature( "E8 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ?? 8B 8E", DetourName = nameof( SomeActionLoadDetour ) )] + private readonly Hook< CharacterBaseNoArgumentDelegate > _someActionLoadHook = null!; + + private void SomeActionLoadDetour( IntPtr gameObject ) + { + var last = _animationLoadCollection; + _animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); + _someActionLoadHook!.Original( gameObject ); + _animationLoadCollection = last; + } + + [Signature( "E8 ?? ?? ?? ?? 44 84 BB", DetourName = nameof( SomeOtherAvfxDetour ) )] + private readonly Hook< CharacterBaseNoArgumentDelegate > _someOtherAvfxHook = null!; + + private void SomeOtherAvfxDetour( IntPtr unk ) + { + var last = _animationLoadCollection; + var gameObject = ( GameObject* )( unk - 0x8B0 ); + _animationLoadCollection = IdentifyCollection( gameObject ); + _someOtherAvfxHook!.Original( unk ); + _animationLoadCollection = last; + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Demihuman.cs b/Penumbra/Interop/Resolver/PathResolver.Demihuman.cs deleted file mode 100644 index dbe57a7b..00000000 --- a/Penumbra/Interop/Resolver/PathResolver.Demihuman.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using Dalamud.Hooking; -using Dalamud.Utility.Signatures; - -namespace Penumbra.Interop.Resolver; - -public unsafe partial class PathResolver -{ - [Signature( "48 8D 05 ?? ?? ?? ?? 45 33 C0 48 89 03 BA", ScanType = ScanType.StaticAddress )] - public IntPtr* DrawObjectDemiVTable; - - public Hook< GeneralResolveDelegate >? ResolveDemiDecalPathHook; - public Hook< EidResolveDelegate >? ResolveDemiEidPathHook; - public Hook< GeneralResolveDelegate >? ResolveDemiImcPathHook; - public Hook< MPapResolveDelegate >? ResolveDemiMPapPathHook; - public Hook< GeneralResolveDelegate >? ResolveDemiMdlPathHook; - public Hook< MaterialResolveDetour >? ResolveDemiMtrlPathHook; - public Hook< MaterialResolveDetour >? ResolveDemiPapPathHook; - public Hook< GeneralResolveDelegate >? ResolveDemiPhybPathHook; - public Hook< GeneralResolveDelegate >? ResolveDemiSklbPathHook; - public Hook< GeneralResolveDelegate >? ResolveDemiSkpPathHook; - public Hook< EidResolveDelegate >? ResolveDemiTmbPathHook; - public Hook< MaterialResolveDetour >? ResolveDemiVfxPathHook; - - private void SetupDemiHooks() - { - ResolveDemiDecalPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveDecalIdx ], ResolveDemiDecalDetour ); - ResolveDemiEidPathHook = Hook< EidResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveEidIdx ], ResolveDemiEidDetour ); - ResolveDemiImcPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveImcIdx ], ResolveDemiImcDetour ); - ResolveDemiMPapPathHook = Hook< MPapResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveMPapIdx ], ResolveDemiMPapDetour ); - ResolveDemiMdlPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveMdlIdx ], ResolveDemiMdlDetour ); - ResolveDemiMtrlPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectDemiVTable[ ResolveMtrlIdx ], ResolveDemiMtrlDetour ); - ResolveDemiPapPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectDemiVTable[ ResolvePapIdx ], ResolveDemiPapDetour ); - ResolveDemiPhybPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolvePhybIdx ], ResolveDemiPhybDetour ); - ResolveDemiSklbPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveSklbIdx ], ResolveDemiSklbDetour ); - ResolveDemiSkpPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveSkpIdx ], ResolveDemiSkpDetour ); - ResolveDemiTmbPathHook = Hook< EidResolveDelegate >.FromAddress( DrawObjectDemiVTable[ ResolveTmbIdx ], ResolveDemiTmbDetour ); - ResolveDemiVfxPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectDemiVTable[ ResolveVfxIdx ], ResolveDemiVfxDetour ); - } - - private void EnableDemiHooks() - { - ResolveDemiDecalPathHook?.Enable(); - ResolveDemiEidPathHook?.Enable(); - ResolveDemiImcPathHook?.Enable(); - ResolveDemiMPapPathHook?.Enable(); - ResolveDemiMdlPathHook?.Enable(); - ResolveDemiMtrlPathHook?.Enable(); - ResolveDemiPapPathHook?.Enable(); - ResolveDemiPhybPathHook?.Enable(); - ResolveDemiSklbPathHook?.Enable(); - ResolveDemiSkpPathHook?.Enable(); - ResolveDemiTmbPathHook?.Enable(); - ResolveDemiVfxPathHook?.Enable(); - } - - private void DisableDemiHooks() - { - ResolveDemiDecalPathHook?.Disable(); - ResolveDemiEidPathHook?.Disable(); - ResolveDemiImcPathHook?.Disable(); - ResolveDemiMPapPathHook?.Disable(); - ResolveDemiMdlPathHook?.Disable(); - ResolveDemiMtrlPathHook?.Disable(); - ResolveDemiPapPathHook?.Disable(); - ResolveDemiPhybPathHook?.Disable(); - ResolveDemiSklbPathHook?.Disable(); - ResolveDemiSkpPathHook?.Disable(); - ResolveDemiTmbPathHook?.Disable(); - ResolveDemiVfxPathHook?.Disable(); - } - - private void DisposeDemiHooks() - { - ResolveDemiDecalPathHook?.Dispose(); - ResolveDemiEidPathHook?.Dispose(); - ResolveDemiImcPathHook?.Dispose(); - ResolveDemiMPapPathHook?.Dispose(); - ResolveDemiMdlPathHook?.Dispose(); - ResolveDemiMtrlPathHook?.Dispose(); - ResolveDemiPapPathHook?.Dispose(); - ResolveDemiPhybPathHook?.Dispose(); - ResolveDemiSklbPathHook?.Dispose(); - ResolveDemiSkpPathHook?.Dispose(); - ResolveDemiTmbPathHook?.Dispose(); - ResolveDemiVfxPathHook?.Dispose(); - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs new file mode 100644 index 00000000..41e55e2e --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -0,0 +1,234 @@ +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using Penumbra.Collections; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Penumbra.Api; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.GameData.ByteString; +using Penumbra.GameData.Enums; + +namespace Penumbra.Interop.Resolver; + +public unsafe partial class PathResolver +{ + public class DrawObjectState + { + public static event CreatingCharacterBaseDelegate? CreatingCharacterBase; + + public IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjects + => _drawObjectToObject; + + public int Count + => _drawObjectToObject.Count; + + public bool TryGetValue( IntPtr drawObject, out (ModCollection, int) value, out GameObject* gameObject ) + { + gameObject = null; + if( !_drawObjectToObject.TryGetValue( drawObject, out value ) ) + { + return false; + } + + var gameObjectIdx = value.Item2; + return VerifyEntry( drawObject, gameObjectIdx, out gameObject ); + } + + + // Set and update a parent object if it exists and a last game object is set. + public ModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject ) + { + if( parentObject == IntPtr.Zero && LastGameObject != null ) + { + var collection = IdentifyCollection( LastGameObject ); + _drawObjectToObject[ drawObject ] = ( collection, LastGameObject->ObjectIndex ); + return collection; + } + + return null; + } + + + public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out ModCollection? collection ) + { + if( type == ResourceType.Tex + && LastCreatedCollection != null + && gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) ) + { + collection = LastCreatedCollection!; + return true; + } + + collection = null; + return false; + } + + + public ModCollection? LastCreatedCollection + => _lastCreatedCollection; + + public GameObject* LastGameObject { get; private set; } + + public DrawObjectState() + { + SignatureHelper.Initialise( this ); + } + + public void Enable() + { + _characterBaseCreateHook.Enable(); + _characterBaseDestructorHook.Enable(); + _enableDrawHook.Enable(); + _weaponReloadHook.Enable(); + InitializeDrawObjects(); + Penumbra.CollectionManager.CollectionChanged += CheckCollections; + } + + public void Disable() + { + _characterBaseCreateHook.Disable(); + _characterBaseDestructorHook.Disable(); + _enableDrawHook.Disable(); + _weaponReloadHook.Disable(); + Penumbra.CollectionManager.CollectionChanged -= CheckCollections; + } + + public void Dispose() + { + Disable(); + _characterBaseCreateHook.Dispose(); + _characterBaseDestructorHook.Dispose(); + _enableDrawHook.Dispose(); + _weaponReloadHook.Dispose(); + } + + // Check that a linked DrawObject still corresponds to the correct actor and that it still exists, otherwise remove it. + private bool VerifyEntry( IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject ) + { + gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( gameObjectIdx ); + var draw = ( DrawObject* )drawObject; + if( gameObject != null + && ( gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject ) ) + { + return true; + } + + gameObject = null; + _drawObjectToObject.Remove( drawObject ); + return false; + } + + // This map links DrawObjects directly to Actors (by ObjectTable index) and their collections. + // It contains any DrawObjects that correspond to a human actor, even those without specific collections. + private readonly Dictionary< IntPtr, (ModCollection, int) > _drawObjectToObject = new(); + private ModCollection? _lastCreatedCollection; + + // Keep track of created DrawObjects that are CharacterBase, + // and use the last game object that called EnableDraw to link them. + private delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d ); + + [Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40", DetourName = nameof( CharacterBaseCreateDetour ) )] + private readonly Hook< CharacterBaseCreateDelegate > _characterBaseCreateHook = null!; + + private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) + { + using var cmp = MetaChanger.ChangeCmp( LastGameObject, out _lastCreatedCollection ); + + if( LastGameObject != null ) + { + var modelPtr = &a; + CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ( IntPtr )modelPtr, b, c ); + } + + var ret = _characterBaseCreateHook!.Original( a, b, c, d ); + if( LastGameObject != null ) + { + _drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); + } + + return ret; + } + + + // Remove DrawObjects from the list when they are destroyed. + private delegate void CharacterBaseDestructorDelegate( IntPtr drawBase ); + + [Signature( "E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30", + DetourName = nameof( CharacterBaseDestructorDetour ) )] + private readonly Hook< CharacterBaseDestructorDelegate > _characterBaseDestructorHook = null!; + + private void CharacterBaseDestructorDetour( IntPtr drawBase ) + { + _drawObjectToObject.Remove( drawBase ); + _characterBaseDestructorHook!.Original.Invoke( drawBase ); + } + + + // EnableDraw is what creates DrawObjects for gameObjects, + // so we always keep track of the current GameObject to be able to link it to the DrawObject. + private delegate void EnableDrawDelegate( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ); + + [Signature( "E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0", DetourName = nameof( EnableDrawDetour ) )] + private readonly Hook< EnableDrawDelegate > _enableDrawHook = null!; + + private void EnableDrawDetour( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ) + { + var oldObject = LastGameObject; + LastGameObject = ( GameObject* )gameObject; + _enableDrawHook!.Original.Invoke( gameObject, b, c, d ); + LastGameObject = oldObject; + } + + // Not fully understood. The game object the weapon is loaded for is seemingly found at a1 + 8, + // so we use that. + private delegate void WeaponReloadFunc( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 ); + + [Signature( "E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof( WeaponReloadDetour ) )] + private readonly Hook< WeaponReloadFunc > _weaponReloadHook = null!; + + public void WeaponReloadDetour( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 ) + { + var oldGame = LastGameObject; + LastGameObject = *( GameObject** )( a1 + 8 ); + _weaponReloadHook!.Original( a1, a2, a3, a4, a5, a6, a7 ); + LastGameObject = oldGame; + } + + // Update collections linked to Game/DrawObjects due to a change in collection configuration. + private void CheckCollections( CollectionType type, ModCollection? _1, ModCollection? _2, string? name ) + { + if( type is CollectionType.Inactive or CollectionType.Current ) + { + return; + } + + foreach( var (key, (_, idx)) in _drawObjectToObject.ToArray() ) + { + if( !VerifyEntry( key, idx, out var obj ) ) + { + _drawObjectToObject.Remove( key ); + } + + var newCollection = IdentifyCollection( obj ); + _drawObjectToObject[ key ] = ( newCollection, idx ); + } + } + + // Find all current DrawObjects used in the GameObject table. + // We do not iterate the Dalamud table because it does not work when not logged in. + private void InitializeDrawObjects() + { + for( var i = 0; i < Dalamud.Objects.Length; ++i ) + { + var ptr = ( GameObject* )Dalamud.Objects.GetObjectAddress( i ); + if( ptr != null && ptr->IsCharacter() && ptr->DrawObject != null ) + { + _drawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr ), ptr->ObjectIndex ); + } + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Human.cs b/Penumbra/Interop/Resolver/PathResolver.Human.cs deleted file mode 100644 index c1278436..00000000 --- a/Penumbra/Interop/Resolver/PathResolver.Human.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using Dalamud.Hooking; -using Dalamud.Utility.Signatures; - -namespace Penumbra.Interop.Resolver; - -// We can hook the different Resolve-Functions using just the VTable of Human. -// The other DrawObject VTables and the ResolveRoot function are currently unused. -public unsafe partial class PathResolver -{ - [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress )] - public IntPtr* DrawObjectHumanVTable; - - // [Signature( "48 8D 1D ?? ?? ?? ?? 48 C7 41", ScanType = ScanType.StaticAddress )] - // public IntPtr* DrawObjectVTable; - // - // public const int ResolveRootIdx = 71; - - public const int ResolveSklbIdx = 72; - public const int ResolveMdlIdx = 73; - public const int ResolveSkpIdx = 74; - public const int ResolvePhybIdx = 75; - public const int ResolvePapIdx = 76; - public const int ResolveTmbIdx = 77; - public const int ResolveMPapIdx = 79; - public const int ResolveImcIdx = 81; - public const int ResolveMtrlIdx = 82; - public const int ResolveDecalIdx = 83; - public const int ResolveVfxIdx = 84; - public const int ResolveEidIdx = 85; - - public const int OnModelLoadCompleteIdx = 58; - - public delegate IntPtr GeneralResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ); - public delegate IntPtr MPapResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ); - public delegate IntPtr MaterialResolveDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ); - public delegate IntPtr EidResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3 ); - - public delegate void OnModelLoadCompleteDelegate( IntPtr drawObject ); - - public Hook< GeneralResolveDelegate >? ResolveDecalPathHook; - public Hook< EidResolveDelegate >? ResolveEidPathHook; - public Hook< GeneralResolveDelegate >? ResolveImcPathHook; - public Hook< MPapResolveDelegate >? ResolveMPapPathHook; - public Hook< GeneralResolveDelegate >? ResolveMdlPathHook; - public Hook< MaterialResolveDetour >? ResolveMtrlPathHook; - public Hook< MaterialResolveDetour >? ResolvePapPathHook; - public Hook< GeneralResolveDelegate >? ResolvePhybPathHook; - public Hook< GeneralResolveDelegate >? ResolveSklbPathHook; - public Hook< GeneralResolveDelegate >? ResolveSkpPathHook; - public Hook< EidResolveDelegate >? ResolveTmbPathHook; - public Hook< MaterialResolveDetour >? ResolveVfxPathHook; - - - private void SetupHumanHooks() - { - ResolveDecalPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveDecalIdx ], ResolveDecalDetour ); - ResolveEidPathHook = Hook< EidResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveEidIdx ], ResolveEidDetour ); - ResolveImcPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveImcIdx ], ResolveImcDetour ); - ResolveMPapPathHook = Hook< MPapResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveMPapIdx ], ResolveMPapDetour ); - ResolveMdlPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveMdlIdx ], ResolveMdlDetour ); - ResolveMtrlPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectHumanVTable[ ResolveMtrlIdx ], ResolveMtrlDetour ); - ResolvePapPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectHumanVTable[ ResolvePapIdx ], ResolvePapDetour ); - ResolvePhybPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolvePhybIdx ], ResolvePhybDetour ); - ResolveSklbPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveSklbIdx ], ResolveSklbDetour ); - ResolveSkpPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveSkpIdx ], ResolveSkpDetour ); - ResolveTmbPathHook = Hook< EidResolveDelegate >.FromAddress( DrawObjectHumanVTable[ ResolveTmbIdx ], ResolveTmbDetour ); - ResolveVfxPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectHumanVTable[ ResolveVfxIdx ], ResolveVfxDetour ); - } - - private void EnableHumanHooks() - { - ResolveDecalPathHook?.Enable(); - ResolveEidPathHook?.Enable(); - ResolveImcPathHook?.Enable(); - ResolveMPapPathHook?.Enable(); - ResolveMdlPathHook?.Enable(); - ResolveMtrlPathHook?.Enable(); - ResolvePapPathHook?.Enable(); - ResolvePhybPathHook?.Enable(); - ResolveSklbPathHook?.Enable(); - ResolveSkpPathHook?.Enable(); - ResolveTmbPathHook?.Enable(); - ResolveVfxPathHook?.Enable(); - } - - private void DisableHumanHooks() - { - ResolveDecalPathHook?.Disable(); - ResolveEidPathHook?.Disable(); - ResolveImcPathHook?.Disable(); - ResolveMPapPathHook?.Disable(); - ResolveMdlPathHook?.Disable(); - ResolveMtrlPathHook?.Disable(); - ResolvePapPathHook?.Disable(); - ResolvePhybPathHook?.Disable(); - ResolveSklbPathHook?.Disable(); - ResolveSkpPathHook?.Disable(); - ResolveTmbPathHook?.Disable(); - ResolveVfxPathHook?.Disable(); - } - - private void DisposeHumanHooks() - { - ResolveDecalPathHook?.Dispose(); - ResolveEidPathHook?.Dispose(); - ResolveImcPathHook?.Dispose(); - ResolveMPapPathHook?.Dispose(); - ResolveMdlPathHook?.Dispose(); - ResolveMtrlPathHook?.Dispose(); - ResolvePapPathHook?.Dispose(); - ResolvePhybPathHook?.Dispose(); - ResolveSklbPathHook?.Dispose(); - ResolveSkpPathHook?.Dispose(); - ResolveTmbPathHook?.Dispose(); - ResolveVfxPathHook?.Dispose(); - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs similarity index 60% rename from Penumbra/Interop/Resolver/PathResolver.Data.cs rename to Penumbra/Interop/Resolver/PathResolver.Identification.cs index 5d1125c0..d9c21999 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -1,17 +1,10 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Dalamud.Hooking; using Dalamud.Logging; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; -using Penumbra.Api; using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; @@ -22,148 +15,6 @@ namespace Penumbra.Interop.Resolver; public unsafe partial class PathResolver { - // Keep track of created DrawObjects that are CharacterBase, - // and use the last game object that called EnableDraw to link them. - public delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d ); - - [Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40", DetourName = "CharacterBaseCreateDetour" )] - public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook; - - private ModCollection? _lastCreatedCollection; - public event CreatingCharacterBaseDelegate? CreatingCharacterBase; - - private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) - { - using var cmp = MetaChanger.ChangeCmp( this, out _lastCreatedCollection ); - - if( LastGameObject != null ) - { - var modelPtr = &a; - CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ( IntPtr )modelPtr, b, c ); - } - - var ret = CharacterBaseCreateHook!.Original( a, b, c, d ); - if( LastGameObject != null ) - { - DrawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); - } - - return ret; - } - - - // Remove DrawObjects from the list when they are destroyed. - public delegate void CharacterBaseDestructorDelegate( IntPtr drawBase ); - - [Signature( "E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30", - DetourName = "CharacterBaseDestructorDetour" )] - public Hook< CharacterBaseDestructorDelegate >? CharacterBaseDestructorHook; - - private void CharacterBaseDestructorDetour( IntPtr drawBase ) - { - DrawObjectToObject.Remove( drawBase ); - CharacterBaseDestructorHook!.Original.Invoke( drawBase ); - } - - - // EnableDraw is what creates DrawObjects for gameObjects, - // so we always keep track of the current GameObject to be able to link it to the DrawObject. - public delegate void EnableDrawDelegate( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ); - - [Signature( "E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0" )] - public Hook< EnableDrawDelegate >? EnableDrawHook; - - private void EnableDrawDetour( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ) - { - var oldObject = LastGameObject; - LastGameObject = ( GameObject* )gameObject; - EnableDrawHook!.Original.Invoke( gameObject, b, c, d ); - LastGameObject = oldObject; - } - - // Not fully understood. The game object the weapon is loaded for is seemingly found at a1 + 8, - // so we use that. - public delegate void WeaponReloadFunc( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 ); - - [Signature( "E8 ?? ?? ?? ?? 44 8B 9F" )] - public Hook< WeaponReloadFunc >? WeaponReloadHook; - - public void WeaponReloadDetour( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 ) - { - var oldGame = LastGameObject; - LastGameObject = *( GameObject** )( a1 + 8 ); - WeaponReloadHook!.Original( a1, a2, a3, a4, a5, a6, a7 ); - LastGameObject = oldGame; - } - - private void EnableDataHooks() - { - CharacterBaseCreateHook?.Enable(); - EnableDrawHook?.Enable(); - CharacterBaseDestructorHook?.Enable(); - WeaponReloadHook?.Enable(); - Penumbra.CollectionManager.CollectionChanged += CheckCollections; - LoadTimelineResourcesHook?.Enable(); - CharacterBaseLoadAnimationHook?.Enable(); - LoadSomeAvfxHook?.Enable(); - LoadSomePapHook?.Enable(); - SomeActionLoadHook?.Enable(); - SomeOtherAvfxHook?.Enable(); - } - - private void DisableDataHooks() - { - Penumbra.CollectionManager.CollectionChanged -= CheckCollections; - WeaponReloadHook?.Disable(); - CharacterBaseCreateHook?.Disable(); - EnableDrawHook?.Disable(); - CharacterBaseDestructorHook?.Disable(); - LoadTimelineResourcesHook?.Disable(); - CharacterBaseLoadAnimationHook?.Disable(); - LoadSomeAvfxHook?.Disable(); - LoadSomePapHook?.Disable(); - SomeActionLoadHook?.Disable(); - SomeOtherAvfxHook?.Disable(); - } - - private void DisposeDataHooks() - { - WeaponReloadHook?.Dispose(); - CharacterBaseCreateHook?.Dispose(); - EnableDrawHook?.Dispose(); - CharacterBaseDestructorHook?.Dispose(); - LoadTimelineResourcesHook?.Dispose(); - CharacterBaseLoadAnimationHook?.Dispose(); - LoadSomeAvfxHook?.Dispose(); - LoadSomePapHook?.Dispose(); - SomeActionLoadHook?.Dispose(); - SomeOtherAvfxHook?.Dispose(); - } - - // This map links DrawObjects directly to Actors (by ObjectTable index) and their collections. - // It contains any DrawObjects that correspond to a human actor, even those without specific collections. - internal readonly Dictionary< IntPtr, (ModCollection, int) > DrawObjectToObject = new(); - - // This map links files to their corresponding collection, if it is non-default. - internal readonly ConcurrentDictionary< Utf8String, ModCollection > PathCollections = new(); - - internal GameObject* LastGameObject = null; - - // Check that a linked DrawObject still corresponds to the correct actor and that it still exists, otherwise remove it. - private bool VerifyEntry( IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject ) - { - gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( gameObjectIdx ); - var draw = ( DrawObject* )drawObject; - if( gameObject != null && ( gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject ) ) - { - return true; - } - - gameObject = null; - DrawObjectToObject.Remove( drawObject ); - return false; - } - // Obtain the name of the current player, if one exists. private static string? GetPlayerName() => Dalamud.Objects[ 0 ]?.Name.ToString(); @@ -244,6 +95,13 @@ public unsafe partial class PathResolver return null; } + var parent = Cutscenes[ gameObject->ObjectIndex ]; + if( parent != null ) + { + return parent.Name.ToString(); + } + + // should not really happen but keep it in as a emergency case. var player = Dalamud.Objects[ 0 ]; if( player == null ) { @@ -327,12 +185,14 @@ public unsafe partial class PathResolver // Only OwnerName can be applied to something with a non-empty name, and that is the specific case we want to handle. var actualName = gameObject->ObjectIndex switch { - 240 => Penumbra.Config.UseCharacterCollectionInMainWindow ? GetPlayerName() : null, // character window - 241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor. - 242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on - 243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview - >= 200 => GetCutsceneName( gameObject ), - _ => null, + 240 => Penumbra.Config.UseCharacterCollectionInMainWindow ? GetPlayerName() : null, // character window + 241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor. + 242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on + 243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview + + >= ObjectReloader.CutsceneStartIdx and < ObjectReloader.CutsceneEndIdx => GetCutsceneName( gameObject ), + + _ => null, } ?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString(); @@ -466,77 +326,4 @@ public unsafe partial class PathResolver return false; } - - - // Update collections linked to Game/DrawObjects due to a change in collection configuration. - private void CheckCollections( CollectionType type, ModCollection? _1, ModCollection? _2, string? name ) - { - if( type is CollectionType.Inactive or CollectionType.Current ) - { - return; - } - - foreach( var (key, (_, idx)) in DrawObjectToObject.ToArray() ) - { - if( !VerifyEntry( key, idx, out var obj ) ) - { - DrawObjectToObject.Remove( key ); - } - - var newCollection = IdentifyCollection( obj ); - DrawObjectToObject[ key ] = ( newCollection, idx ); - } - } - - // Use the stored information to find the GameObject and Collection linked to a DrawObject. - private GameObject* FindParent( IntPtr drawObject, out ModCollection collection ) - { - if( DrawObjectToObject.TryGetValue( drawObject, out var data ) ) - { - var gameObjectIdx = data.Item2; - if( VerifyEntry( drawObject, gameObjectIdx, out var gameObject ) ) - { - collection = data.Item1; - return gameObject; - } - } - - if( LastGameObject != null && ( LastGameObject->DrawObject == null || LastGameObject->DrawObject == ( DrawObject* )drawObject ) ) - { - collection = IdentifyCollection( LastGameObject ); - return LastGameObject; - } - - - collection = IdentifyCollection( null ); - return null; - } - - - // Special handling for paths so that we do not store non-owned temporary strings in the dictionary. - private void SetCollection( Utf8String path, ModCollection collection ) - { - if( PathCollections.ContainsKey( path ) || path.IsOwned ) - { - PathCollections[ path ] = collection; - } - else - { - PathCollections[ path.Clone() ] = collection; - } - } - - // Find all current DrawObjects used in the GameObject table. - // We do not iterate the Dalamud table because it does not work when not logged in. - private void InitializeDrawObjects() - { - for( var i = 0; i < Dalamud.Objects.Length; ++i ) - { - var ptr = ( GameObject* )Dalamud.Objects.GetObjectAddress( i ); - if( ptr != null && ptr->IsCharacter() && ptr->DrawObject != null ) - { - DrawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr ), ptr->ObjectIndex ); - } - } - } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs index 450eceab..9688257c 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Material.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs @@ -4,7 +4,6 @@ using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; -using OtterGui; using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; @@ -12,132 +11,144 @@ using Penumbra.Interop.Structs; namespace Penumbra.Interop.Resolver; -// Materials do contain their own paths to textures and shader packages. -// Those are loaded synchronously. -// Thus, we need to ensure the correct files are loaded when a material is loaded. public unsafe partial class PathResolver { - public delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle ); - - [Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour" )] - public Hook< LoadMtrlFilesDelegate >? LoadMtrlTexHook; - - private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle ) + // Materials do contain their own paths to textures and shader packages. + // Those are loaded synchronously. + // Thus, we need to ensure the correct files are loaded when a material is loaded. + public class MaterialState : IDisposable { - LoadMtrlHelper( mtrlResourceHandle ); - var ret = LoadMtrlTexHook!.Original( mtrlResourceHandle ); - _mtrlCollection = null; - return ret; - } + private readonly PathState _paths; - [Signature( "48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89", - DetourName = "LoadMtrlShpkDetour" )] - public Hook< LoadMtrlFilesDelegate >? LoadMtrlShpkHook; + private ModCollection? _mtrlCollection; - private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle ) - { - LoadMtrlHelper( mtrlResourceHandle ); - var ret = LoadMtrlShpkHook!.Original( mtrlResourceHandle ); - _mtrlCollection = null; - return ret; - } - - private ModCollection? _mtrlCollection; - - private void LoadMtrlHelper( IntPtr mtrlResourceHandle ) - { - if( mtrlResourceHandle == IntPtr.Zero ) + public MaterialState( PathState paths ) { - return; + SignatureHelper.Initialise( this ); + _paths = paths; } - var mtrl = ( MtrlResource* )mtrlResourceHandle; - var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true ); - _mtrlCollection = PathCollections.TryGetValue( mtrlPath, out var c ) ? c : null; - } - - // Check specifically for shpk and tex files whether we are currently in a material load. - private bool HandleMaterialSubFiles( ResourceType type, [NotNullWhen( true )] out ModCollection? collection ) - { - if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk ) + // Check specifically for shpk and tex files whether we are currently in a material load. + public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out ModCollection? collection ) { - collection = _mtrlCollection; - return true; - } + if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk ) + { + collection = _mtrlCollection; + return true; + } - collection = null; - return false; - } - - // We need to set the correct collection for the actual material path that is loaded - // before actually loading the file. - private bool MtrlLoadHandler( Utf8String split, Utf8String path, ResourceManager* resourceManager, - SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret ) - { - ret = 0; - if( fileDescriptor->ResourceHandle->FileType != ResourceType.Mtrl ) - { + collection = null; return false; } - var lastUnderscore = split.LastIndexOf( ( byte )'_' ); - var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString(); - if( Penumbra.TempMods.CollectionByName( name, out var collection ) - || Penumbra.CollectionManager.ByName( name, out collection ) ) + // Materials need to be set per collection so they can load their textures independently from each other. + public static void HandleCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, + out (FullPath?, object?) data ) { + if( nonDefault && type == ResourceType.Mtrl ) + { + var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" ); + data = ( fullPath, collection ); + } + else + { + data = ( resolved, collection ); + } + } + + public void Enable() + { + _loadMtrlShpkHook.Enable(); + _loadMtrlTexHook.Enable(); + Penumbra.ResourceLoader.ResourceLoadCustomization += MtrlLoadHandler; + } + + public void Disable() + { + _loadMtrlShpkHook.Disable(); + _loadMtrlTexHook.Disable(); + Penumbra.ResourceLoader.ResourceLoadCustomization -= MtrlLoadHandler; + } + + public void Dispose() + { + Disable(); + _loadMtrlShpkHook?.Dispose(); + _loadMtrlTexHook?.Dispose(); + } + + // We need to set the correct collection for the actual material path that is loaded + // before actually loading the file. + public bool MtrlLoadHandler( Utf8String split, Utf8String path, ResourceManager* resourceManager, + SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret ) + { + ret = 0; + if( fileDescriptor->ResourceHandle->FileType != ResourceType.Mtrl ) + { + return false; + } + + var lastUnderscore = split.LastIndexOf( ( byte )'_' ); + var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString(); + if( Penumbra.TempMods.CollectionByName( name, out var collection ) + || Penumbra.CollectionManager.ByName( name, out collection ) ) + { #if DEBUG - PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path ); + PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path ); #endif - SetCollection( path, collection ); - } - else - { + _paths.SetCollection( path, collection ); + } + else + { #if DEBUG - PluginLog.Verbose( "Using MtrlLoadHandler with no collection for path {$Path:l}.", path ); + PluginLog.Verbose( "Using MtrlLoadHandler with no collection for path {$Path:l}.", path ); #endif + } + + // Force isSync = true for this call. I don't really understand why, + // or where the difference even comes from. + // Was called with True on my client and with false on other peoples clients, + // which caused problems. + ret = Penumbra.ResourceLoader.DefaultLoadResource( path, resourceManager, fileDescriptor, priority, true ); + _paths.Consume( path, out _ ); + return true; } - // Force isSync = true for this call. I don't really understand why, - // or where the difference even comes from. - // Was called with True on my client and with false on other peoples clients, - // which caused problems. - ret = Penumbra.ResourceLoader.DefaultLoadResource( path, resourceManager, fileDescriptor, priority, true ); - PathCollections.TryRemove( path, out _ ); - return true; - } + private delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle ); - // Materials need to be set per collection so they can load their textures independently from each other. - private static void HandleMtrlCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, - out (FullPath?, object?) data ) - { - if( nonDefault && type == ResourceType.Mtrl ) + [Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = nameof( LoadMtrlTexDetour ) )] + private readonly Hook< LoadMtrlFilesDelegate > _loadMtrlTexHook = null!; + + private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle ) { - var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" ); - data = ( fullPath, collection ); + LoadMtrlHelper( mtrlResourceHandle ); + var ret = _loadMtrlTexHook!.Original( mtrlResourceHandle ); + _mtrlCollection = null; + return ret; } - else + + [Signature( "48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89", + DetourName = nameof( LoadMtrlShpkDetour ) )] + private readonly Hook< LoadMtrlFilesDelegate > _loadMtrlShpkHook = null!; + + private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle ) { - data = ( resolved, collection ); + LoadMtrlHelper( mtrlResourceHandle ); + var ret = _loadMtrlShpkHook!.Original( mtrlResourceHandle ); + _mtrlCollection = null; + return ret; } - } - private void EnableMtrlHooks() - { - LoadMtrlShpkHook?.Enable(); - LoadMtrlTexHook?.Enable(); - Penumbra.ResourceLoader.ResourceLoadCustomization += MtrlLoadHandler; - } + private void LoadMtrlHelper( IntPtr mtrlResourceHandle ) + { + if( mtrlResourceHandle == IntPtr.Zero ) + { + return; + } - private void DisableMtrlHooks() - { - LoadMtrlShpkHook?.Disable(); - LoadMtrlTexHook?.Disable(); - Penumbra.ResourceLoader.ResourceLoadCustomization -= MtrlLoadHandler; - } - - private void DisposeMtrlHooks() - { - LoadMtrlShpkHook?.Dispose(); - LoadMtrlTexHook?.Dispose(); + var mtrl = ( MtrlResource* )mtrlResourceHandle; + var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true ); + _mtrlCollection = _paths.TryGetValue( mtrlPath, out var c ) ? c : null; + } } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index 670c4044..73390514 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -1,9 +1,8 @@ using System; using Dalamud.Hooking; -using Dalamud.Logging; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.Collections; -using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Resolver; @@ -33,136 +32,133 @@ namespace Penumbra.Interop.Resolver; public unsafe partial class PathResolver { - public delegate void UpdateModelDelegate( IntPtr drawObject ); - - [Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = "UpdateModelsDetour" )] - public Hook< UpdateModelDelegate >? UpdateModelsHook; - - private void UpdateModelsDetour( IntPtr drawObject ) + public unsafe class MetaState : IDisposable { - // Shortcut because this is called all the time. - // Same thing is checked at the beginning of the original function. - if( *( int* )( drawObject + 0x90c ) == 0 ) + private readonly PathResolver _parent; + + public MetaState( PathResolver parent, IntPtr* humanVTable ) { - return; + SignatureHelper.Initialise( this ); + _parent = parent; + _onModelLoadCompleteHook = Hook< OnModelLoadCompleteDelegate >.FromAddress( humanVTable[ 58 ], OnModelLoadCompleteDetour ); } - var collection = GetCollection( drawObject ); - if( collection != null ) + public void Enable() { - using var eqp = MetaChanger.ChangeEqp( collection ); - using var eqdp = MetaChanger.ChangeEqdp( collection ); - UpdateModelsHook!.Original.Invoke( drawObject ); - } - else - { - UpdateModelsHook!.Original.Invoke( drawObject ); - } - } - - [Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C", - DetourName = "GetEqpIndirectDetour" )] - public Hook< OnModelLoadCompleteDelegate >? GetEqpIndirectHook; - - private void GetEqpIndirectDetour( IntPtr drawObject ) - { - // Shortcut because this is also called all the time. - // Same thing is checked at the beginning of the original function. - if( ( *( byte* )( drawObject + 0xa30 ) & 1 ) == 0 || *( ulong* )( drawObject + 0xa28 ) == 0 ) - { - return; + _getEqpIndirectHook.Enable(); + _updateModelsHook.Enable(); + _onModelLoadCompleteHook.Enable(); + _setupVisorHook.Enable(); + _rspSetupCharacterHook.Enable(); } - using var eqp = MetaChanger.ChangeEqp( this, drawObject ); - GetEqpIndirectHook!.Original( drawObject ); - } - - public Hook< OnModelLoadCompleteDelegate >? OnModelLoadCompleteHook; - - private void OnModelLoadCompleteDetour( IntPtr drawObject ) - { - var collection = GetCollection( drawObject ); - if( collection != null ) + public void Disable() { - using var eqp = MetaChanger.ChangeEqp( collection ); - using var eqdp = MetaChanger.ChangeEqdp( collection ); - OnModelLoadCompleteHook!.Original.Invoke( drawObject ); - } - else - { - OnModelLoadCompleteHook!.Original.Invoke( drawObject ); - } - } - - // GMP. This gets called every time when changing visor state, and it accesses the gmp file itself, - // but it only applies a changed gmp file after a redraw for some reason. - public delegate byte SetupVisorDelegate( IntPtr drawObject, ushort modelId, byte visorState ); - - [Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = "SetupVisorDetour" )] - public Hook< SetupVisorDelegate >? SetupVisorHook; - - private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState ) - { - using var gmp = MetaChanger.ChangeGmp( this, drawObject ); - return SetupVisorHook!.Original( drawObject, modelId, visorState ); - } - - // RSP - public delegate void RspSetupCharacterDelegate( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 ); - - [Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 88 54 24 ?? 57 41 56", DetourName = "RspSetupCharacterDetour" )] - public Hook< RspSetupCharacterDelegate >? RspSetupCharacterHook; - - private void RspSetupCharacterDetour( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 ) - { - using var rsp = MetaChanger.ChangeCmp( this, drawObject ); - RspSetupCharacterHook!.Original( drawObject, unk2, unk3, unk4, unk5 ); - } - - private void SetupMetaHooks() - { - OnModelLoadCompleteHook = - Hook< OnModelLoadCompleteDelegate >.FromAddress( DrawObjectHumanVTable[ OnModelLoadCompleteIdx ], OnModelLoadCompleteDetour ); - } - - private void EnableMetaHooks() - { - GetEqpIndirectHook?.Enable(); - UpdateModelsHook?.Enable(); - OnModelLoadCompleteHook?.Enable(); - SetupVisorHook?.Enable(); - RspSetupCharacterHook?.Enable(); - } - - private void DisableMetaHooks() - { - GetEqpIndirectHook?.Disable(); - UpdateModelsHook?.Disable(); - OnModelLoadCompleteHook?.Disable(); - SetupVisorHook?.Disable(); - RspSetupCharacterHook?.Disable(); - } - - private void DisposeMetaHooks() - { - GetEqpIndirectHook?.Dispose(); - UpdateModelsHook?.Dispose(); - OnModelLoadCompleteHook?.Dispose(); - SetupVisorHook?.Dispose(); - RspSetupCharacterHook?.Dispose(); - } - - private ModCollection? GetCollection( IntPtr drawObject ) - { - var parent = FindParent( drawObject, out var collection ); - if( parent == null || collection == Penumbra.CollectionManager.Default ) - { - return null; + _getEqpIndirectHook.Disable(); + _updateModelsHook.Disable(); + _onModelLoadCompleteHook.Disable(); + _setupVisorHook.Disable(); + _rspSetupCharacterHook.Disable(); } - return collection.HasCache ? collection : null; - } + public void Dispose() + { + _getEqpIndirectHook.Dispose(); + _updateModelsHook.Dispose(); + _onModelLoadCompleteHook.Dispose(); + _setupVisorHook.Dispose(); + _rspSetupCharacterHook.Dispose(); + } + private delegate void OnModelLoadCompleteDelegate( IntPtr drawObject ); + private readonly Hook< OnModelLoadCompleteDelegate > _onModelLoadCompleteHook; + + private void OnModelLoadCompleteDetour( IntPtr drawObject ) + { + var collection = GetCollection( drawObject ); + if( collection != null ) + { + using var eqp = MetaChanger.ChangeEqp( collection ); + using var eqdp = MetaChanger.ChangeEqdp( collection ); + _onModelLoadCompleteHook.Original.Invoke( drawObject ); + } + else + { + _onModelLoadCompleteHook.Original.Invoke( drawObject ); + } + } + + + private delegate void UpdateModelDelegate( IntPtr drawObject ); + + [Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = nameof( UpdateModelsDetour ) )] + private readonly Hook< UpdateModelDelegate > _updateModelsHook = null!; + + private void UpdateModelsDetour( IntPtr drawObject ) + { + // Shortcut because this is called all the time. + // Same thing is checked at the beginning of the original function. + if( *( int* )( drawObject + 0x90c ) == 0 ) + { + return; + } + + var collection = GetCollection( drawObject ); + if( collection != null ) + { + using var eqp = MetaChanger.ChangeEqp( collection ); + using var eqdp = MetaChanger.ChangeEqdp( collection ); + _updateModelsHook.Original.Invoke( drawObject ); + } + else + { + _updateModelsHook.Original.Invoke( drawObject ); + } + } + + [Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C", + DetourName = nameof( GetEqpIndirectDetour ) )] + private readonly Hook< OnModelLoadCompleteDelegate > _getEqpIndirectHook = null!; + + private void GetEqpIndirectDetour( IntPtr drawObject ) + { + // Shortcut because this is also called all the time. + // Same thing is checked at the beginning of the original function. + if( ( *( byte* )( drawObject + 0xa30 ) & 1 ) == 0 || *( ulong* )( drawObject + 0xa28 ) == 0 ) + { + return; + } + + using var eqp = MetaChanger.ChangeEqp( _parent, drawObject ); + _getEqpIndirectHook.Original( drawObject ); + } + + + // GMP. This gets called every time when changing visor state, and it accesses the gmp file itself, + // but it only applies a changed gmp file after a redraw for some reason. + private delegate byte SetupVisorDelegate( IntPtr drawObject, ushort modelId, byte visorState ); + + [Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = nameof( SetupVisorDetour ) )] + private readonly Hook< SetupVisorDelegate > _setupVisorHook = null!; + + private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState ) + { + using var gmp = MetaChanger.ChangeGmp( _parent, drawObject ); + return _setupVisorHook.Original( drawObject, modelId, visorState ); + } + + // RSP + private delegate void RspSetupCharacterDelegate( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 ); + + [Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 88 54 24 ?? 57 41 56", DetourName = nameof( RspSetupCharacterDetour ) )] + private readonly Hook< RspSetupCharacterDelegate > _rspSetupCharacterHook = null!; + + private void RspSetupCharacterDetour( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 ) + { + using var rsp = MetaChanger.ChangeCmp( _parent, drawObject ); + _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); + } + } // Small helper to handle setting metadata and reverting it at the end of the function. // Since eqp and eqdp may be called multiple times in a row, we need to count them, @@ -194,11 +190,12 @@ public unsafe partial class PathResolver public static MetaChanger ChangeEqp( PathResolver resolver, IntPtr drawObject ) { - var collection = resolver.GetCollection( drawObject ); + var collection = GetCollection( drawObject ); if( collection != null ) { return ChangeEqp( collection ); } + return new MetaChanger( MetaManipulation.Type.Unknown ); } @@ -207,12 +204,13 @@ public unsafe partial class PathResolver { if( modelType < 10 ) { - var collection = resolver.GetCollection( drawObject ); + var collection = GetCollection( drawObject ); if( collection != null ) { return ChangeEqdp( collection ); } } + return new MetaChanger( MetaManipulation.Type.Unknown ); } @@ -224,31 +222,33 @@ public unsafe partial class PathResolver public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject ) { - var collection = resolver.GetCollection( drawObject ); + var collection = GetCollection( drawObject ); if( collection != null ) { collection.SetGmpFiles(); return new MetaChanger( MetaManipulation.Type.Gmp ); } + return new MetaChanger( MetaManipulation.Type.Unknown ); } public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject ) { - var collection = resolver.GetCollection( drawObject ); + var collection = GetCollection( drawObject ); if( collection != null ) { collection.SetEstFiles(); return new MetaChanger( MetaManipulation.Type.Est ); } + return new MetaChanger( MetaManipulation.Type.Unknown ); } - public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection? collection ) + public static MetaChanger ChangeCmp( GameObject* gameObject, out ModCollection? collection ) { - if( resolver.LastGameObject != null ) + if( gameObject != null ) { - collection = IdentifyCollection( resolver.LastGameObject ); + collection = IdentifyCollection( gameObject ); if( collection != Penumbra.CollectionManager.Default && collection.HasCache ) { collection.SetCmpFiles(); @@ -265,12 +265,13 @@ public unsafe partial class PathResolver public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject ) { - var collection = resolver.GetCollection( drawObject ); + var collection = GetCollection( drawObject ); if( collection != null ) { collection.SetCmpFiles(); return new MetaChanger( MetaManipulation.Type.Rsp ); } + return new MetaChanger( MetaManipulation.Type.Unknown ); } diff --git a/Penumbra/Interop/Resolver/PathResolver.Monster.cs b/Penumbra/Interop/Resolver/PathResolver.Monster.cs deleted file mode 100644 index 5ef487a6..00000000 --- a/Penumbra/Interop/Resolver/PathResolver.Monster.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using Dalamud.Hooking; -using Dalamud.Utility.Signatures; - -namespace Penumbra.Interop.Resolver; - -public unsafe partial class PathResolver -{ - [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0 48 89 83 ?? ?? ?? ?? 48 89 83 ?? ?? ?? ?? C7 83", ScanType = ScanType.StaticAddress )] - public IntPtr* DrawObjectMonsterVTable; - - public Hook? ResolveMonsterDecalPathHook; - public Hook? ResolveMonsterEidPathHook; - public Hook? ResolveMonsterImcPathHook; - public Hook? ResolveMonsterMPapPathHook; - public Hook? ResolveMonsterMdlPathHook; - public Hook? ResolveMonsterMtrlPathHook; - public Hook? ResolveMonsterPapPathHook; - public Hook? ResolveMonsterPhybPathHook; - public Hook? ResolveMonsterSklbPathHook; - public Hook? ResolveMonsterSkpPathHook; - public Hook? ResolveMonsterTmbPathHook; - public Hook? ResolveMonsterVfxPathHook; - - private void SetupMonsterHooks() - { - ResolveMonsterDecalPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveDecalIdx], ResolveMonsterDecalDetour ); - ResolveMonsterEidPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveEidIdx], ResolveMonsterEidDetour ); - ResolveMonsterImcPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveImcIdx], ResolveMonsterImcDetour ); - ResolveMonsterMPapPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveMPapIdx], ResolveMonsterMPapDetour ); - ResolveMonsterMdlPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveMdlIdx], ResolveMonsterMdlDetour ); - ResolveMonsterMtrlPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveMtrlIdx], ResolveMonsterMtrlDetour ); - ResolveMonsterPapPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolvePapIdx], ResolveMonsterPapDetour ); - ResolveMonsterPhybPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolvePhybIdx], ResolveMonsterPhybDetour ); - ResolveMonsterSklbPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveSklbIdx], ResolveMonsterSklbDetour ); - ResolveMonsterSkpPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveSkpIdx], ResolveMonsterSkpDetour ); - ResolveMonsterTmbPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveTmbIdx], ResolveMonsterTmbDetour ); - ResolveMonsterVfxPathHook = Hook.FromAddress( DrawObjectMonsterVTable[ResolveVfxIdx], ResolveMonsterVfxDetour ); - } - - private void EnableMonsterHooks() - { - ResolveMonsterDecalPathHook?.Enable(); - ResolveMonsterEidPathHook?.Enable(); - ResolveMonsterImcPathHook?.Enable(); - ResolveMonsterMPapPathHook?.Enable(); - ResolveMonsterMdlPathHook?.Enable(); - ResolveMonsterMtrlPathHook?.Enable(); - ResolveMonsterPapPathHook?.Enable(); - ResolveMonsterPhybPathHook?.Enable(); - ResolveMonsterSklbPathHook?.Enable(); - ResolveMonsterSkpPathHook?.Enable(); - ResolveMonsterTmbPathHook?.Enable(); - ResolveMonsterVfxPathHook?.Enable(); - } - - private void DisableMonsterHooks() - { - ResolveMonsterDecalPathHook?.Disable(); - ResolveMonsterEidPathHook?.Disable(); - ResolveMonsterImcPathHook?.Disable(); - ResolveMonsterMPapPathHook?.Disable(); - ResolveMonsterMdlPathHook?.Disable(); - ResolveMonsterMtrlPathHook?.Disable(); - ResolveMonsterPapPathHook?.Disable(); - ResolveMonsterPhybPathHook?.Disable(); - ResolveMonsterSklbPathHook?.Disable(); - ResolveMonsterSkpPathHook?.Disable(); - ResolveMonsterTmbPathHook?.Disable(); - ResolveMonsterVfxPathHook?.Disable(); - } - - private void DisposeMonsterHooks() - { - ResolveMonsterDecalPathHook?.Dispose(); - ResolveMonsterEidPathHook?.Dispose(); - ResolveMonsterImcPathHook?.Dispose(); - ResolveMonsterMPapPathHook?.Dispose(); - ResolveMonsterMdlPathHook?.Dispose(); - ResolveMonsterMtrlPathHook?.Dispose(); - ResolveMonsterPapPathHook?.Dispose(); - ResolveMonsterPhybPathHook?.Dispose(); - ResolveMonsterSklbPathHook?.Dispose(); - ResolveMonsterSkpPathHook?.Dispose(); - ResolveMonsterTmbPathHook?.Dispose(); - ResolveMonsterVfxPathHook?.Dispose(); - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.PathState.cs b/Penumbra/Interop/Resolver/PathResolver.PathState.cs new file mode 100644 index 00000000..ab40fe1e --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.PathState.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Dalamud.Utility.Signatures; +using Penumbra.Collections; +using Penumbra.GameData.ByteString; + +namespace Penumbra.Interop.Resolver; + +public unsafe partial class PathResolver +{ + public class PathState : IDisposable + { + [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress )] + public readonly IntPtr* HumanVTable = null!; + + [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 B8 ?? ?? ?? ?? 66 89 83 ?? ?? ?? ?? 48 8B C3 48 89 8B ?? ?? ?? ?? 48 89 8B", + ScanType = ScanType.StaticAddress )] + private readonly IntPtr* _weaponVTable = null!; + + [Signature( "48 8D 05 ?? ?? ?? ?? 45 33 C0 48 89 03 BA", ScanType = ScanType.StaticAddress )] + private readonly IntPtr* _demiHumanVTable = null!; + + [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0 48 89 83 ?? ?? ?? ?? 48 89 83 ?? ?? ?? ?? C7 83", ScanType = ScanType.StaticAddress )] + private readonly IntPtr* _monsterVTable = null!; + + private readonly ResolverHooks _human; + private readonly ResolverHooks _weapon; + private readonly ResolverHooks _demiHuman; + private readonly ResolverHooks _monster; + + // This map links files to their corresponding collection, if it is non-default. + private readonly ConcurrentDictionary< Utf8String, ModCollection > _pathCollections = new(); + + public PathState( PathResolver parent ) + { + SignatureHelper.Initialise( this ); + _human = new ResolverHooks( parent, HumanVTable, ResolverHooks.Type.Human ); + _weapon = new ResolverHooks( parent, _weaponVTable, ResolverHooks.Type.Weapon ); + _demiHuman = new ResolverHooks( parent, _demiHumanVTable, ResolverHooks.Type.Other ); + _monster = new ResolverHooks( parent, _monsterVTable, ResolverHooks.Type.Other ); + } + + public void Enable() + { + _human.Enable(); + _weapon.Enable(); + _demiHuman.Enable(); + _monster.Enable(); + } + + public void Disable() + { + _human.Disable(); + _weapon.Disable(); + _demiHuman.Disable(); + _monster.Disable(); + } + + public void Dispose() + { + _human.Dispose(); + _weapon.Dispose(); + _demiHuman.Dispose(); + _monster.Dispose(); + } + + public int Count + => _pathCollections.Count; + + public IEnumerable< KeyValuePair< Utf8String, ModCollection > > Paths + => _pathCollections; + + public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out ModCollection? collection ) + => _pathCollections.TryGetValue( path, out collection ); + + public bool Consume( Utf8String path, [NotNullWhen( true )] out ModCollection? collection ) + => _pathCollections.TryRemove( path, out collection ); + + // Just add or remove the resolved path. + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + public IntPtr ResolvePath( ModCollection collection, IntPtr path ) + { + if( path == IntPtr.Zero ) + { + return path; + } + + var gamePath = new Utf8String( ( byte* )path ); + SetCollection( gamePath, collection ); + return path; + } + + // Special handling for paths so that we do not store non-owned temporary strings in the dictionary. + public void SetCollection( Utf8String path, ModCollection collection ) + { + if( _pathCollections.ContainsKey( path ) || path.IsOwned ) + { + _pathCollections[ path ] = collection; + } + else + { + _pathCollections[ path.Clone() ] = collection; + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs deleted file mode 100644 index 3892fcbd..00000000 --- a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Penumbra.Collections; -using Penumbra.GameData.ByteString; - -namespace Penumbra.Interop.Resolver; - -// The actual resolve detours are basically all the same. -public unsafe partial class PathResolver -{ - // Humans - private IntPtr ResolveDecalDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveDecalPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveEidDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolvePathDetour( drawObject, ResolveEidPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveImcDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveMPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) - => ResolvePathDetour( drawObject, ResolveMPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveMdlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) - { - using var eqdp = MetaChanger.ChangeEqdp( this, drawObject, modelType ); - return ResolvePathDetour( drawObject, ResolveMdlPathHook!.Original( drawObject, path, unk3, modelType ) ); - } - - private IntPtr ResolveMtrlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolvePapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - { - using var est = MetaChanger.ChangeEst( this, drawObject ); - return ResolvePathDetour( drawObject, ResolvePapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - } - - private IntPtr ResolvePhybDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - { - using var est = MetaChanger.ChangeEst( this, drawObject ); - return ResolvePathDetour( drawObject, ResolvePhybPathHook!.Original( drawObject, path, unk3, unk4 ) ); - } - - private IntPtr ResolveSklbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - { - using var est = MetaChanger.ChangeEst( this, drawObject ); - return ResolvePathDetour( drawObject, ResolveSklbPathHook!.Original( drawObject, path, unk3, unk4 ) ); - } - - private IntPtr ResolveSkpDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - { - using var est = MetaChanger.ChangeEst( this, drawObject ); - return ResolvePathDetour( drawObject, ResolveSkpPathHook!.Original( drawObject, path, unk3, unk4 ) ); - } - - private IntPtr ResolveTmbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolvePathDetour( drawObject, ResolveTmbPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - - // Weapons - private IntPtr ResolveWeaponDecalDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponDecalPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveWeaponEidDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponEidPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveWeaponImcDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveWeaponMPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponMPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveWeaponMdlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponMdlPathHook!.Original( drawObject, path, unk3, modelType ) ); - - private IntPtr ResolveWeaponMtrlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveWeaponPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveWeaponPhybDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponPhybPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveWeaponSklbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponSklbPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveWeaponSkpDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponSkpPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveWeaponTmbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponTmbPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveWeaponVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolveWeaponPathDetour( drawObject, ResolveWeaponVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - // Monsters - private IntPtr ResolveMonsterDecalDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveMonsterDecalPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveMonsterEidDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolvePathDetour( drawObject, ResolveMonsterEidPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveMonsterImcDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveMonsterImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveMonsterMPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) - => ResolvePathDetour( drawObject, ResolveMonsterMPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveMonsterMdlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) - => ResolvePathDetour( drawObject, ResolveMonsterMdlPathHook!.Original( drawObject, path, unk3, modelType ) ); - - private IntPtr ResolveMonsterMtrlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveMonsterMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveMonsterPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveMonsterPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveMonsterPhybDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveMonsterPhybPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveMonsterSklbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveMonsterSklbPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveMonsterSkpDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveMonsterSkpPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveMonsterTmbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolvePathDetour( drawObject, ResolveMonsterTmbPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveMonsterVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveMonsterVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - // Demihumans - private IntPtr ResolveDemiDecalDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveDemiDecalPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveDemiEidDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolvePathDetour( drawObject, ResolveDemiEidPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveDemiImcDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveDemiImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveDemiMPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) - => ResolvePathDetour( drawObject, ResolveDemiMPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveDemiMdlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) - => ResolvePathDetour( drawObject, ResolveDemiMdlPathHook!.Original( drawObject, path, unk3, modelType ) ); - - private IntPtr ResolveDemiMtrlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveDemiMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveDemiPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveDemiPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - private IntPtr ResolveDemiPhybDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveDemiPhybPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveDemiSklbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveDemiSklbPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveDemiSkpDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - => ResolvePathDetour( drawObject, ResolveDemiSkpPathHook!.Original( drawObject, path, unk3, unk4 ) ); - - private IntPtr ResolveDemiTmbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) - => ResolvePathDetour( drawObject, ResolveDemiTmbPathHook!.Original( drawObject, path, unk3 ) ); - - private IntPtr ResolveDemiVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) - => ResolvePathDetour( drawObject, ResolveDemiVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - - - // Implementation - [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] - private IntPtr ResolvePathDetour( IntPtr drawObject, IntPtr path ) - => ResolvePathDetour( FindParent( drawObject, out var collection ) == null - ? Penumbra.CollectionManager.Default - : collection, path ); - - // Weapons have the characters DrawObject as a parent, - // but that may not be set yet when creating a new object, so we have to do the same detour - // as for Human DrawObjects that are just being created. - [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] - private IntPtr ResolveWeaponPathDetour( IntPtr drawObject, IntPtr path ) - { - var parent = FindParent( drawObject, out var collection ); - if( parent != null ) - { - return ResolvePathDetour( collection, path ); - } - - var parentObject = ( ( DrawObject* )drawObject )->Object.ParentObject; - if( parentObject == null && LastGameObject != null ) - { - var c2 = IdentifyCollection( LastGameObject ); - DrawObjectToObject[ drawObject ] = ( c2, LastGameObject->ObjectIndex ); - return ResolvePathDetour( c2, path ); - } - - parent = FindParent( ( IntPtr )parentObject, out collection ); - return ResolvePathDetour( parent == null - ? Penumbra.CollectionManager.Default - : collection, path ); - } - - // Just add or remove the resolved path. - [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] - private IntPtr ResolvePathDetour( ModCollection collection, IntPtr path ) - { - if( path == IntPtr.Zero ) - { - return path; - } - - var gamePath = new Utf8String( ( byte* )path ); - SetCollection( gamePath, collection ); - return path; - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs new file mode 100644 index 00000000..79c53d31 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs @@ -0,0 +1,258 @@ +using System; +using System.Runtime.CompilerServices; +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; + +namespace Penumbra.Interop.Resolver; + +public partial class PathResolver +{ + public unsafe class ResolverHooks : IDisposable + { + public enum Type + { + Human, + Weapon, + Other, + } + + private delegate IntPtr GeneralResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ); + private delegate IntPtr MPapResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ); + private delegate IntPtr MaterialResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ); + private delegate IntPtr EidResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3 ); + + private readonly Hook< GeneralResolveDelegate > _resolveDecalPathHook; + private readonly Hook< EidResolveDelegate > _resolveEidPathHook; + private readonly Hook< GeneralResolveDelegate > _resolveImcPathHook; + private readonly Hook< MPapResolveDelegate > _resolveMPapPathHook; + private readonly Hook< GeneralResolveDelegate > _resolveMdlPathHook; + private readonly Hook< MaterialResolveDelegate > _resolveMtrlPathHook; + private readonly Hook< MaterialResolveDelegate > _resolvePapPathHook; + private readonly Hook< GeneralResolveDelegate > _resolvePhybPathHook; + private readonly Hook< GeneralResolveDelegate > _resolveSklbPathHook; + private readonly Hook< GeneralResolveDelegate > _resolveSkpPathHook; + private readonly Hook< EidResolveDelegate > _resolveTmbPathHook; + private readonly Hook< MaterialResolveDelegate > _resolveVfxPathHook; + + private readonly PathResolver _parent; + + public ResolverHooks( PathResolver parent, IntPtr* vTable, Type type ) + { + _parent = parent; + _resolveDecalPathHook = Create< GeneralResolveDelegate >( vTable[ 83 ], type, ResolveDecalWeapon, ResolveDecal ); + _resolveEidPathHook = Create< EidResolveDelegate >( vTable[ 85 ], type, ResolveEidWeapon, ResolveEid ); + _resolveImcPathHook = Create< GeneralResolveDelegate >( vTable[ 81 ], type, ResolveImcWeapon, ResolveImc ); + _resolveMPapPathHook = Create< MPapResolveDelegate >( vTable[ 79 ], type, ResolveMPapWeapon, ResolveMPap ); + _resolveMdlPathHook = Create< GeneralResolveDelegate >( vTable[ 73 ], type, ResolveMdlWeapon, ResolveMdl, ResolveMdlHuman ); + _resolveMtrlPathHook = Create< MaterialResolveDelegate >( vTable[ 82 ], type, ResolveMtrlWeapon, ResolveMtrl ); + _resolvePapPathHook = Create< MaterialResolveDelegate >( vTable[ 76 ], type, ResolvePapWeapon, ResolvePap, ResolvePapHuman ); + _resolvePhybPathHook = Create< GeneralResolveDelegate >( vTable[ 75 ], type, ResolvePhybWeapon, ResolvePhyb, ResolvePhybHuman ); + _resolveSklbPathHook = Create< GeneralResolveDelegate >( vTable[ 72 ], type, ResolveSklbWeapon, ResolveSklb, ResolveSklbHuman ); + _resolveSkpPathHook = Create< GeneralResolveDelegate >( vTable[ 74 ], type, ResolveSkpWeapon, ResolveSkp, ResolveSkpHuman ); + _resolveTmbPathHook = Create< EidResolveDelegate >( vTable[ 77 ], type, ResolveTmbWeapon, ResolveTmb ); + _resolveVfxPathHook = Create< MaterialResolveDelegate >( vTable[ 84 ], type, ResolveVfxWeapon, ResolveVfx ); + } + + public void Enable() + { + _resolveDecalPathHook.Enable(); + _resolveEidPathHook.Enable(); + _resolveImcPathHook.Enable(); + _resolveMPapPathHook.Enable(); + _resolveMdlPathHook.Enable(); + _resolveMtrlPathHook.Enable(); + _resolvePapPathHook.Enable(); + _resolvePhybPathHook.Enable(); + _resolveSklbPathHook.Enable(); + _resolveSkpPathHook.Enable(); + _resolveTmbPathHook.Enable(); + _resolveVfxPathHook.Enable(); + } + + public void Disable() + { + _resolveDecalPathHook.Disable(); + _resolveEidPathHook.Disable(); + _resolveImcPathHook.Disable(); + _resolveMPapPathHook.Disable(); + _resolveMdlPathHook.Disable(); + _resolveMtrlPathHook.Disable(); + _resolvePapPathHook.Disable(); + _resolvePhybPathHook.Disable(); + _resolveSklbPathHook.Disable(); + _resolveSkpPathHook.Disable(); + _resolveTmbPathHook.Disable(); + _resolveVfxPathHook.Disable(); + } + + public void Dispose() + { + _resolveDecalPathHook.Dispose(); + _resolveEidPathHook.Dispose(); + _resolveImcPathHook.Dispose(); + _resolveMPapPathHook.Dispose(); + _resolveMdlPathHook.Dispose(); + _resolveMtrlPathHook.Dispose(); + _resolvePapPathHook.Dispose(); + _resolvePhybPathHook.Dispose(); + _resolveSklbPathHook.Dispose(); + _resolveSkpPathHook.Dispose(); + _resolveTmbPathHook.Dispose(); + _resolveVfxPathHook.Dispose(); + } + + private IntPtr ResolveDecal( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePath( drawObject, _resolveDecalPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveEid( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolvePath( drawObject, _resolveEidPathHook.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveImc( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePath( drawObject, _resolveImcPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveMPap( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) + => ResolvePath( drawObject, _resolveMPapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolveMdl( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) + => ResolvePath( drawObject, _resolveMdlPathHook.Original( drawObject, path, unk3, modelType ) ); + + private IntPtr ResolveMtrl( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePath( drawObject, _resolveMtrlPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolvePap( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePath( drawObject, _resolvePapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolvePhyb( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePath( drawObject, _resolvePhybPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveSklb( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePath( drawObject, _resolveSklbPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveSkp( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePath( drawObject, _resolveSkpPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveTmb( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolvePath( drawObject, _resolveTmbPathHook.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveVfx( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePath( drawObject, _resolveVfxPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + + private IntPtr ResolveMdlHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) + { + using var eqdp = MetaChanger.ChangeEqdp( _parent, drawObject, modelType ); + return ResolvePath( drawObject, _resolveMdlPathHook.Original( drawObject, path, unk3, modelType ) ); + } + + private IntPtr ResolvePapHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + { + using var est = MetaChanger.ChangeEst( _parent, drawObject ); + return ResolvePath( drawObject, _resolvePapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + } + + private IntPtr ResolvePhybHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var est = MetaChanger.ChangeEst( _parent, drawObject ); + return ResolvePath( drawObject, _resolvePhybPathHook.Original( drawObject, path, unk3, unk4 ) ); + } + + private IntPtr ResolveSklbHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var est = MetaChanger.ChangeEst( _parent, drawObject ); + return ResolvePath( drawObject, _resolveSklbPathHook.Original( drawObject, path, unk3, unk4 ) ); + } + + private IntPtr ResolveSkpHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var est = MetaChanger.ChangeEst( _parent, drawObject ); + return ResolvePath( drawObject, _resolveSkpPathHook.Original( drawObject, path, unk3, unk4 ) ); + } + + + private IntPtr ResolveDecalWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolveWeaponPath( drawObject, _resolveDecalPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveEidWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolveWeaponPath( drawObject, _resolveEidPathHook.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveImcWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolveWeaponPath( drawObject, _resolveImcPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveMPapWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) + => ResolveWeaponPath( drawObject, _resolveMPapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolveMdlWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) + => ResolveWeaponPath( drawObject, _resolveMdlPathHook.Original( drawObject, path, unk3, modelType ) ); + + private IntPtr ResolveMtrlWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolveWeaponPath( drawObject, _resolveMtrlPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolvePapWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolveWeaponPath( drawObject, _resolvePapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolvePhybWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolveWeaponPath( drawObject, _resolvePhybPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveSklbWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolveWeaponPath( drawObject, _resolveSklbPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveSkpWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolveWeaponPath( drawObject, _resolveSkpPathHook.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveTmbWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolveWeaponPath( drawObject, _resolveTmbPathHook.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveVfxWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolveWeaponPath( drawObject, _resolveVfxPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); + + + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private static Hook< T > Create< T >( IntPtr address, Type type, T weapon, T other, T human ) where T : Delegate + { + var del = type switch + { + Type.Human => human, + Type.Weapon => weapon, + _ => other, + }; + return Hook< T >.FromAddress( address, del ); + } + + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private static Hook< T > Create< T >( IntPtr address, Type type, T weapon, T other ) where T : Delegate + => Create( address, type, weapon, other, other ); + + + // Implementation + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private IntPtr ResolvePath( IntPtr drawObject, IntPtr path ) + => _parent._paths.ResolvePath( FindParent( drawObject, out var collection ) == null + ? Penumbra.CollectionManager.Default + : collection, path ); + + // Weapons have the characters DrawObject as a parent, + // but that may not be set yet when creating a new object, so we have to do the same detour + // as for Human DrawObjects that are just being created. + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private IntPtr ResolveWeaponPath( IntPtr drawObject, IntPtr path ) + { + var parent = FindParent( drawObject, out var collection ); + if( parent != null ) + { + return _parent._paths.ResolvePath( collection, path ); + } + + var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject; + var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject ); + if( parentCollection != null ) + { + return _parent._paths.ResolvePath( parentCollection, path ); + } + + parent = FindParent( parentObject, out collection ); + return _parent._paths.ResolvePath( parent == null + ? Penumbra.CollectionManager.Default + : collection, path ); + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Weapon.cs b/Penumbra/Interop/Resolver/PathResolver.Weapon.cs deleted file mode 100644 index 364bfa9f..00000000 --- a/Penumbra/Interop/Resolver/PathResolver.Weapon.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Dalamud.Hooking; -using Dalamud.Utility.Signatures; - -namespace Penumbra.Interop.Resolver; - -public unsafe partial class PathResolver -{ - [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 B8 ?? ?? ?? ?? 66 89 83 ?? ?? ?? ?? 48 8B C3 48 89 8B ?? ?? ?? ?? 48 89 8B", - ScanType = ScanType.StaticAddress )] - public IntPtr* DrawObjectWeaponVTable; - - public Hook< GeneralResolveDelegate >? ResolveWeaponDecalPathHook; - public Hook< EidResolveDelegate >? ResolveWeaponEidPathHook; - public Hook< GeneralResolveDelegate >? ResolveWeaponImcPathHook; - public Hook< MPapResolveDelegate >? ResolveWeaponMPapPathHook; - public Hook< GeneralResolveDelegate >? ResolveWeaponMdlPathHook; - public Hook< MaterialResolveDetour >? ResolveWeaponMtrlPathHook; - public Hook< MaterialResolveDetour >? ResolveWeaponPapPathHook; - public Hook< GeneralResolveDelegate >? ResolveWeaponPhybPathHook; - public Hook< GeneralResolveDelegate >? ResolveWeaponSklbPathHook; - public Hook< GeneralResolveDelegate >? ResolveWeaponSkpPathHook; - public Hook< EidResolveDelegate >? ResolveWeaponTmbPathHook; - public Hook< MaterialResolveDetour >? ResolveWeaponVfxPathHook; - - private void SetupWeaponHooks() - { - ResolveWeaponDecalPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveDecalIdx ], ResolveWeaponDecalDetour ); - ResolveWeaponEidPathHook = Hook< EidResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveEidIdx ], ResolveWeaponEidDetour ); - ResolveWeaponImcPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveImcIdx ], ResolveWeaponImcDetour ); - ResolveWeaponMPapPathHook = Hook< MPapResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveMPapIdx ], ResolveWeaponMPapDetour ); - ResolveWeaponMdlPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveMdlIdx ], ResolveWeaponMdlDetour ); - ResolveWeaponMtrlPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectWeaponVTable[ ResolveMtrlIdx ], ResolveWeaponMtrlDetour ); - ResolveWeaponPapPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectWeaponVTable[ ResolvePapIdx ], ResolveWeaponPapDetour ); - ResolveWeaponPhybPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolvePhybIdx ], ResolveWeaponPhybDetour ); - ResolveWeaponSklbPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveSklbIdx ], ResolveWeaponSklbDetour ); - ResolveWeaponSkpPathHook = Hook< GeneralResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveSkpIdx ], ResolveWeaponSkpDetour ); - ResolveWeaponTmbPathHook = Hook< EidResolveDelegate >.FromAddress( DrawObjectWeaponVTable[ ResolveTmbIdx ], ResolveWeaponTmbDetour ); - ResolveWeaponVfxPathHook = Hook< MaterialResolveDetour >.FromAddress( DrawObjectWeaponVTable[ ResolveVfxIdx ], ResolveWeaponVfxDetour ); - } - - private void EnableWeaponHooks() - { - ResolveWeaponDecalPathHook?.Enable(); - ResolveWeaponEidPathHook?.Enable(); - ResolveWeaponImcPathHook?.Enable(); - ResolveWeaponMPapPathHook?.Enable(); - ResolveWeaponMdlPathHook?.Enable(); - ResolveWeaponMtrlPathHook?.Enable(); - ResolveWeaponPapPathHook?.Enable(); - ResolveWeaponPhybPathHook?.Enable(); - ResolveWeaponSklbPathHook?.Enable(); - ResolveWeaponSkpPathHook?.Enable(); - ResolveWeaponTmbPathHook?.Enable(); - ResolveWeaponVfxPathHook?.Enable(); - } - - private void DisableWeaponHooks() - { - ResolveWeaponDecalPathHook?.Disable(); - ResolveWeaponEidPathHook?.Disable(); - ResolveWeaponImcPathHook?.Disable(); - ResolveWeaponMPapPathHook?.Disable(); - ResolveWeaponMdlPathHook?.Disable(); - ResolveWeaponMtrlPathHook?.Disable(); - ResolveWeaponPapPathHook?.Disable(); - ResolveWeaponPhybPathHook?.Disable(); - ResolveWeaponSklbPathHook?.Disable(); - ResolveWeaponSkpPathHook?.Disable(); - ResolveWeaponTmbPathHook?.Disable(); - ResolveWeaponVfxPathHook?.Disable(); - } - - private void DisposeWeaponHooks() - { - ResolveWeaponDecalPathHook?.Dispose(); - ResolveWeaponEidPathHook?.Dispose(); - ResolveWeaponImcPathHook?.Dispose(); - ResolveWeaponMPapPathHook?.Dispose(); - ResolveWeaponMdlPathHook?.Dispose(); - ResolveWeaponMtrlPathHook?.Dispose(); - ResolveWeaponPapPathHook?.Dispose(); - ResolveWeaponPhybPathHook?.Dispose(); - ResolveWeaponSklbPathHook?.Dispose(); - ResolveWeaponSkpPathHook?.Dispose(); - ResolveWeaponTmbPathHook?.Dispose(); - ResolveWeaponVfxPathHook?.Dispose(); - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index f8c99e1c..300cf1f3 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -1,7 +1,9 @@ using System; -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; using Dalamud.Logging; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.Collections; using Penumbra.GameData.ByteString; @@ -17,18 +19,24 @@ namespace Penumbra.Interop.Resolver; // to resolve paths for character collections. public partial class PathResolver : IDisposable { - private readonly ResourceLoader _loader; public bool Enabled { get; private set; } - public PathResolver( ResourceLoader loader ) + private readonly ResourceLoader _loader; + private static readonly CutsceneCharacters Cutscenes = new(); + private static readonly DrawObjectState DrawObjects = new(); + private readonly AnimationState _animations; + private readonly PathState _paths; + private readonly MetaState _meta; + private readonly MaterialState _materials; + + public unsafe PathResolver( ResourceLoader loader ) { - _loader = loader; SignatureHelper.Initialise( this ); - SetupHumanHooks(); - SetupWeaponHooks(); - SetupMonsterHooks(); - SetupDemiHooks(); - SetupMetaHooks(); + _loader = loader; + _animations = new AnimationState( DrawObjects ); + _paths = new PathState( this ); + _meta = new MetaState( this, _paths.HumanVTable ); + _materials = new MaterialState( _paths ); } // The modified resolver that handles game path resolving. @@ -40,10 +48,10 @@ public partial class PathResolver : IDisposable // If not use the default collection. // We can remove paths after they have actually been loaded. // A potential next request will add the path anew. - var nonDefault = HandleMaterialSubFiles( type, out var collection ) - || PathCollections.TryRemove( gamePath.Path, out collection ) - || HandleAnimationFile( type, gamePath, out collection ) - || HandleDecalFile( type, gamePath, out collection ); + var nonDefault = _materials.HandleSubFiles( type, out var collection ) + || _paths.Consume( gamePath.Path, out collection ) + || _animations.HandleFiles( type, gamePath, out collection ) + || DrawObjects.HandleDecalFile( type, gamePath, out collection ); if( !nonDefault || collection == null ) { collection = Penumbra.CollectionManager.Default; @@ -56,67 +64,10 @@ public partial class PathResolver : IDisposable // so that the functions loading tex and shpk can find that path and use its collection. // We also need to handle defaulted materials against a non-default collection. var path = resolved == null ? gamePath.Path.ToString() : resolved.Value.FullName; - HandleMtrlCollection( collection, path, nonDefault, type, resolved, out data ); + MaterialState.HandleCollection( collection, path, nonDefault, type, resolved, out data ); return true; } - private bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out ModCollection? collection ) - { - if( type == ResourceType.Tex - && _lastCreatedCollection != null - && gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) ) - { - collection = _lastCreatedCollection; - return true; - } - - collection = null; - return false; - } - - private bool HandleAnimationFile( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out ModCollection? collection ) - { - switch( type ) - { - case ResourceType.Tmb: - case ResourceType.Pap: - case ResourceType.Scd: - if( _animationLoadCollection != null ) - { - collection = _animationLoadCollection; - return true; - } - - break; - case ResourceType.Avfx: - _lastAvfxCollection = _animationLoadCollection ?? Penumbra.CollectionManager.Default; - if( _animationLoadCollection != null ) - { - collection = _animationLoadCollection; - return true; - } - - break; - case ResourceType.Atex: - if( _lastAvfxCollection != null ) - { - collection = _lastAvfxCollection; - return true; - } - - if( _animationLoadCollection != null ) - { - collection = _animationLoadCollection; - return true; - } - - break; - } - - collection = null; - return false; - } - public void Enable() { if( Enabled ) @@ -125,15 +76,12 @@ public partial class PathResolver : IDisposable } Enabled = true; - InitializeDrawObjects(); - - EnableHumanHooks(); - EnableWeaponHooks(); - EnableMonsterHooks(); - EnableDemiHooks(); - EnableMtrlHooks(); - EnableDataHooks(); - EnableMetaHooks(); + Cutscenes.Enable(); + DrawObjects.Enable(); + _animations.Enable(); + _paths.Enable(); + _meta.Enable(); + _materials.Enable(); _loader.ResolvePathCustomization += CharacterResolver; PluginLog.Debug( "Character Path Resolver enabled." ); @@ -147,16 +95,12 @@ public partial class PathResolver : IDisposable } Enabled = false; - DisableHumanHooks(); - DisableWeaponHooks(); - DisableMonsterHooks(); - DisableDemiHooks(); - DisableMtrlHooks(); - DisableDataHooks(); - DisableMetaHooks(); - - DrawObjectToObject.Clear(); - PathCollections.Clear(); + _animations.Disable(); + DrawObjects.Disable(); + Cutscenes.Disable(); + _paths.Disable(); + _meta.Disable(); + _materials.Disable(); _loader.ResolvePathCustomization -= CharacterResolver; PluginLog.Debug( "Character Path Resolver disabled." ); @@ -165,18 +109,60 @@ public partial class PathResolver : IDisposable public void Dispose() { Disable(); - DisposeHumanHooks(); - DisposeWeaponHooks(); - DisposeMonsterHooks(); - DisposeDemiHooks(); - DisposeMtrlHooks(); - DisposeDataHooks(); - DisposeMetaHooks(); + _paths.Dispose(); + _animations.Dispose(); + DrawObjects.Dispose(); + Cutscenes.Dispose(); + _meta.Dispose(); + _materials.Dispose(); } - public unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject ) + public static unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject ) { var parent = FindParent( drawObject, out var collection ); return ( ( IntPtr )parent, collection ); } + + public int CutsceneActor( int idx ) + => Cutscenes.GetParentIndex( idx ); + + // Use the stored information to find the GameObject and Collection linked to a DrawObject. + public static unsafe GameObject* FindParent( IntPtr drawObject, out ModCollection collection ) + { + if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) ) + { + collection = data.Item1; + return gameObject; + } + + if( DrawObjects.LastGameObject != null + && ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) ) + { + collection = IdentifyCollection( DrawObjects.LastGameObject ); + return DrawObjects.LastGameObject; + } + + collection = IdentifyCollection( null ); + return null; + } + + private static unsafe ModCollection? GetCollection( IntPtr drawObject ) + { + var parent = FindParent( drawObject, out var collection ); + if( parent == null || collection == Penumbra.CollectionManager.Default ) + { + return null; + } + + return collection.HasCache ? collection : null; + } + + internal IEnumerable< KeyValuePair< Utf8String, ModCollection > > PathCollections + => _paths.Paths; + + internal IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjectMap + => DrawObjects.DrawObjects; + + internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors + => Cutscenes.Actors; } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 62d136ff..5ac267c3 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -11,6 +11,7 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.ByteString; using Penumbra.Interop.Loader; +using Penumbra.Interop.Resolver; using Penumbra.Interop.Structs; using CharacterUtility = Penumbra.Interop.CharacterUtility; @@ -155,45 +156,63 @@ public partial class ConfigWindow return; } - using var drawTree = ImRaii.TreeNode( "Draw Object to Object" ); - if( drawTree ) + using( var drawTree = ImRaii.TreeNode( "Draw Object to Object" ) ) { - using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit ); - if( table ) + if( drawTree ) { - foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectToObject ) + using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit ); + if( table ) { - ImGui.TableNextColumn(); - ImGui.TextUnformatted( ptr.ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.TextUnformatted( idx.ToString() ); - ImGui.TableNextColumn(); - var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx ); - var (address, name) = - obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" ); - ImGui.TextUnformatted( address ); - ImGui.TableNextColumn(); - ImGui.TextUnformatted( name ); - ImGui.TableNextColumn(); - ImGui.TextUnformatted( c.Name ); + foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectMap ) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted( ptr.ToString( "X" ) ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( idx.ToString() ); + ImGui.TableNextColumn(); + var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx ); + var (address, name) = + obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" ); + ImGui.TextUnformatted( address ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( name ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( c.Name ); + } } } } - drawTree.Dispose(); - - using var pathTree = ImRaii.TreeNode( "Path Collections" ); - if( pathTree ) + using( var pathTree = ImRaii.TreeNode( "Path Collections" ) ) { - using var table = ImRaii.Table( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ); + if( pathTree ) + { + using var table = ImRaii.Table( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ); + if( table ) + { + foreach( var (path, collection) in _window._penumbra.PathResolver.PathCollections ) + { + ImGui.TableNextColumn(); + ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( collection.Name ); + } + } + } + } + + using var cutsceneTree = ImRaii.TreeNode( "Cutscene Actors" ); + if( cutsceneTree ) + { + using var table = ImRaii.Table( "###PCutsceneResolverTable", 2, ImGuiTableFlags.SizingFixedFit ); if( table ) { - foreach( var (path, collection) in _window._penumbra.PathResolver.PathCollections ) + foreach( var (idx, actor) in _window._penumbra.PathResolver.CutsceneActors ) { ImGui.TableNextColumn(); - ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); + ImGui.TextUnformatted( $"Cutscene Actor {idx}" ); ImGui.TableNextColumn(); - ImGui.TextUnformatted( collection.Name ); + ImGui.TextUnformatted( actor.Name.ToString() ); } } }