Add cutscene identification and IPC, reorder PathResolver stuff.

This commit is contained in:
Ottermandias 2022-08-09 22:12:15 +02:00
parent dc61f362fd
commit 8fdd173388
21 changed files with 1326 additions and 1323 deletions

@ -1 +1 @@
Subproject commit 4c92479175161617161d8faf844c8f683aa2d5d2
Subproject commit 09dcd012a3106862f20f045b9ff9e33d168047c4

View file

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

View file

@ -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 )
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<GeneralResolveDelegate>? ResolveMonsterDecalPathHook;
public Hook<EidResolveDelegate>? ResolveMonsterEidPathHook;
public Hook<GeneralResolveDelegate>? ResolveMonsterImcPathHook;
public Hook<MPapResolveDelegate>? ResolveMonsterMPapPathHook;
public Hook<GeneralResolveDelegate>? ResolveMonsterMdlPathHook;
public Hook<MaterialResolveDetour>? ResolveMonsterMtrlPathHook;
public Hook<MaterialResolveDetour>? ResolveMonsterPapPathHook;
public Hook<GeneralResolveDelegate>? ResolveMonsterPhybPathHook;
public Hook<GeneralResolveDelegate>? ResolveMonsterSklbPathHook;
public Hook<GeneralResolveDelegate>? ResolveMonsterSkpPathHook;
public Hook<EidResolveDelegate>? ResolveMonsterTmbPathHook;
public Hook<MaterialResolveDetour>? ResolveMonsterVfxPathHook;
private void SetupMonsterHooks()
{
ResolveMonsterDecalPathHook = Hook<GeneralResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveDecalIdx], ResolveMonsterDecalDetour );
ResolveMonsterEidPathHook = Hook<EidResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveEidIdx], ResolveMonsterEidDetour );
ResolveMonsterImcPathHook = Hook<GeneralResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveImcIdx], ResolveMonsterImcDetour );
ResolveMonsterMPapPathHook = Hook<MPapResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveMPapIdx], ResolveMonsterMPapDetour );
ResolveMonsterMdlPathHook = Hook<GeneralResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveMdlIdx], ResolveMonsterMdlDetour );
ResolveMonsterMtrlPathHook = Hook<MaterialResolveDetour>.FromAddress( DrawObjectMonsterVTable[ResolveMtrlIdx], ResolveMonsterMtrlDetour );
ResolveMonsterPapPathHook = Hook<MaterialResolveDetour>.FromAddress( DrawObjectMonsterVTable[ResolvePapIdx], ResolveMonsterPapDetour );
ResolveMonsterPhybPathHook = Hook<GeneralResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolvePhybIdx], ResolveMonsterPhybDetour );
ResolveMonsterSklbPathHook = Hook<GeneralResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveSklbIdx], ResolveMonsterSklbDetour );
ResolveMonsterSkpPathHook = Hook<GeneralResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveSkpIdx], ResolveMonsterSkpDetour );
ResolveMonsterTmbPathHook = Hook<EidResolveDelegate>.FromAddress( DrawObjectMonsterVTable[ResolveTmbIdx], ResolveMonsterTmbDetour );
ResolveMonsterVfxPathHook = Hook<MaterialResolveDetour>.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();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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