mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 05:04:15 +01:00
Add cutscene identification and IPC, reorder PathResolver stuff.
This commit is contained in:
parent
dc61f362fd
commit
8fdd173388
21 changed files with 1326 additions and 1323 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4c92479175161617161d8faf844c8f683aa2d5d2
|
Subproject commit 09dcd012a3106862f20f045b9ff9e33d168047c4
|
||||||
|
|
@ -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.
|
// 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 );
|
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.
|
// 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();
|
public IList< (string, string) > GetModList();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ public class IpcTester : IDisposable
|
||||||
private string _currentDrawObjectString = string.Empty;
|
private string _currentDrawObjectString = string.Empty;
|
||||||
private string _currentReversePath = string.Empty;
|
private string _currentReversePath = string.Empty;
|
||||||
private IntPtr _currentDrawObject = IntPtr.Zero;
|
private IntPtr _currentDrawObject = IntPtr.Zero;
|
||||||
|
private int _currentCutsceneActor = 0;
|
||||||
private string _lastCreatedGameObjectName = string.Empty;
|
private string _lastCreatedGameObjectName = string.Empty;
|
||||||
private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
|
private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
|
||||||
|
|
||||||
|
|
@ -231,6 +232,8 @@ public class IpcTester : IDisposable
|
||||||
: IntPtr.Zero;
|
: IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.InputInt( "Cutscene Actor", ref _currentCutsceneActor, 0 );
|
||||||
|
|
||||||
using var table = ImRaii.Table( string.Empty, 3, ImGuiTableFlags.SizingFixedFit );
|
using var table = ImRaii.Table( string.Empty, 3, ImGuiTableFlags.SizingFixedFit );
|
||||||
if( !table )
|
if( !table )
|
||||||
{
|
{
|
||||||
|
|
@ -263,6 +266,10 @@ public class IpcTester : IDisposable
|
||||||
ImGui.TextUnformatted( ptr == IntPtr.Zero ? $"No Actor Associated, {collection}" : $"{ptr:X}, {collection}" );
|
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" );
|
DrawIntro( PenumbraIpc.LabelProviderReverseResolvePath, "Reversed Game Paths" );
|
||||||
if( _currentReversePath.Length > 0 )
|
if( _currentReversePath.Length > 0 )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using FFXIVClientStructs.FFXIV.Common.Configuration;
|
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
|
@ -16,14 +15,13 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Resolver;
|
using Penumbra.Interop.Resolver;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Api;
|
namespace Penumbra.Api;
|
||||||
|
|
||||||
public class PenumbraApi : IDisposable, IPenumbraApi
|
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
public (int, int) ApiVersion
|
public (int, int) ApiVersion
|
||||||
=> ( 4, 11 );
|
=> ( 4, 12 );
|
||||||
|
|
||||||
private Penumbra? _penumbra;
|
private Penumbra? _penumbra;
|
||||||
private Lumina.GameData? _lumina;
|
private Lumina.GameData? _lumina;
|
||||||
|
|
@ -43,8 +41,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
|
|
||||||
public event CreatingCharacterBaseDelegate? CreatingCharacterBase
|
public event CreatingCharacterBaseDelegate? CreatingCharacterBase
|
||||||
{
|
{
|
||||||
add => _penumbra!.PathResolver.CreatingCharacterBase += value;
|
add => PathResolver.DrawObjectState.CreatingCharacterBase += value;
|
||||||
remove => _penumbra!.PathResolver.CreatingCharacterBase -= value;
|
remove => PathResolver.DrawObjectState.CreatingCharacterBase -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Valid
|
public bool Valid
|
||||||
|
|
@ -54,8 +52,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_lumina = ( Lumina.GameData? )Dalamud.GameData.GetType()
|
_lumina = ( Lumina.GameData? )Dalamud.GameData.GetType()
|
||||||
.GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic )
|
.GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic )
|
||||||
?.GetValue( Dalamud.GameData );
|
?.GetValue( Dalamud.GameData );
|
||||||
foreach( var collection in Penumbra.CollectionManager )
|
foreach( var collection in Penumbra.CollectionManager )
|
||||||
{
|
{
|
||||||
SubscribeToCollection( collection );
|
SubscribeToCollection( collection );
|
||||||
|
|
@ -221,10 +219,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public (IntPtr, string) GetDrawObjectInfo( IntPtr drawObject )
|
public (IntPtr, string) GetDrawObjectInfo( IntPtr drawObject )
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
var (obj, collection) = _penumbra!.PathResolver.IdentifyDrawObject( drawObject );
|
var (obj, collection) = PathResolver.IdentifyDrawObject( drawObject );
|
||||||
return ( obj, collection.Name );
|
return ( obj, collection.Name );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetCutsceneParentIndex( int actor )
|
||||||
|
{
|
||||||
|
CheckInitialized();
|
||||||
|
return _penumbra!.PathResolver.CutsceneActor( actor );
|
||||||
|
}
|
||||||
|
|
||||||
public IList< (string, string) > GetModList()
|
public IList< (string, string) > GetModList()
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
|
|
@ -429,7 +433,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character )
|
if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character )
|
||||||
|| Penumbra.TempMods.Collections.ContainsKey( character ) )
|
|| Penumbra.TempMods.Collections.ContainsKey( character ) )
|
||||||
{
|
{
|
||||||
return ( PenumbraApiEc.CharacterCollectionExists, string.Empty );
|
return ( PenumbraApiEc.CharacterCollectionExists, string.Empty );
|
||||||
}
|
}
|
||||||
|
|
@ -475,7 +479,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
||||||
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
||||||
{
|
{
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
}
|
||||||
|
|
@ -512,7 +516,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
||||||
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
||||||
{
|
{
|
||||||
return PenumbraApiEc.CollectionMissing;
|
return PenumbraApiEc.CollectionMissing;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,7 @@ public partial class PenumbraIpc
|
||||||
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
|
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
|
||||||
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
|
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
|
||||||
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
|
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
|
||||||
|
public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex";
|
||||||
public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath";
|
public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath";
|
||||||
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
|
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
|
||||||
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
|
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
|
||||||
|
|
@ -269,6 +270,7 @@ public partial class PenumbraIpc
|
||||||
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
|
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
|
||||||
internal ICallGateProvider< string, string >? ProviderResolvePlayer;
|
internal ICallGateProvider< string, string >? ProviderResolvePlayer;
|
||||||
internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo;
|
internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo;
|
||||||
|
internal ICallGateProvider< int, int >? ProviderGetCutsceneParentIndex;
|
||||||
internal ICallGateProvider< string, string, string[] >? ProviderReverseResolvePath;
|
internal ICallGateProvider< string, string, string[] >? ProviderReverseResolvePath;
|
||||||
internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer;
|
internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer;
|
||||||
internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase;
|
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}" );
|
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
|
try
|
||||||
{
|
{
|
||||||
ProviderReverseResolvePath = pi.GetIpcProvider< string, string, string[] >( LabelProviderReverseResolvePath );
|
ProviderReverseResolvePath = pi.GetIpcProvider< string, string, string[] >( LabelProviderReverseResolvePath );
|
||||||
|
|
@ -350,6 +362,7 @@ public partial class PenumbraIpc
|
||||||
private void DisposeResolveProviders()
|
private void DisposeResolveProviders()
|
||||||
{
|
{
|
||||||
ProviderGetDrawObjectInfo?.UnregisterFunc();
|
ProviderGetDrawObjectInfo?.UnregisterFunc();
|
||||||
|
ProviderGetCutsceneParentIndex?.UnregisterFunc();
|
||||||
ProviderResolveDefault?.UnregisterFunc();
|
ProviderResolveDefault?.UnregisterFunc();
|
||||||
ProviderResolveCharacter?.UnregisterFunc();
|
ProviderResolveCharacter?.UnregisterFunc();
|
||||||
ProviderReverseResolvePath?.UnregisterFunc();
|
ProviderReverseResolvePath?.UnregisterFunc();
|
||||||
|
|
|
||||||
85
Penumbra/Interop/Resolver/CutsceneCharacters.cs
Normal file
85
Penumbra/Interop/Resolver/CutsceneCharacters.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
215
Penumbra/Interop/Resolver/PathResolver.AnimationState.cs
Normal file
215
Penumbra/Interop/Resolver/PathResolver.AnimationState.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
234
Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs
Normal file
234
Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Penumbra.Api;
|
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -22,148 +15,6 @@ namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
public unsafe partial class PathResolver
|
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.
|
// Obtain the name of the current player, if one exists.
|
||||||
private static string? GetPlayerName()
|
private static string? GetPlayerName()
|
||||||
=> Dalamud.Objects[ 0 ]?.Name.ToString();
|
=> Dalamud.Objects[ 0 ]?.Name.ToString();
|
||||||
|
|
@ -244,6 +95,13 @@ public unsafe partial class PathResolver
|
||||||
return null;
|
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 ];
|
var player = Dalamud.Objects[ 0 ];
|
||||||
if( player == null )
|
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.
|
// 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
|
var actualName = gameObject->ObjectIndex switch
|
||||||
{
|
{
|
||||||
240 => Penumbra.Config.UseCharacterCollectionInMainWindow ? GetPlayerName() : null, // character window
|
240 => Penumbra.Config.UseCharacterCollectionInMainWindow ? GetPlayerName() : null, // character window
|
||||||
241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor.
|
241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor.
|
||||||
242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on
|
242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on
|
||||||
243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview
|
243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview
|
||||||
>= 200 => GetCutsceneName( gameObject ),
|
|
||||||
_ => null,
|
>= ObjectReloader.CutsceneStartIdx and < ObjectReloader.CutsceneEndIdx => GetCutsceneName( gameObject ),
|
||||||
|
|
||||||
|
_ => null,
|
||||||
}
|
}
|
||||||
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
|
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
|
||||||
|
|
||||||
|
|
@ -466,77 +326,4 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
return false;
|
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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using OtterGui;
|
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -12,132 +11,144 @@ using Penumbra.Interop.Structs;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
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 unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
public delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle );
|
// Materials do contain their own paths to textures and shader packages.
|
||||||
|
// Those are loaded synchronously.
|
||||||
[Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour" )]
|
// Thus, we need to ensure the correct files are loaded when a material is loaded.
|
||||||
public Hook< LoadMtrlFilesDelegate >? LoadMtrlTexHook;
|
public class MaterialState : IDisposable
|
||||||
|
|
||||||
private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle )
|
|
||||||
{
|
{
|
||||||
LoadMtrlHelper( mtrlResourceHandle );
|
private readonly PathState _paths;
|
||||||
var ret = LoadMtrlTexHook!.Original( mtrlResourceHandle );
|
|
||||||
_mtrlCollection = null;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Signature( "48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89",
|
private ModCollection? _mtrlCollection;
|
||||||
DetourName = "LoadMtrlShpkDetour" )]
|
|
||||||
public Hook< LoadMtrlFilesDelegate >? LoadMtrlShpkHook;
|
|
||||||
|
|
||||||
private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle )
|
public MaterialState( PathState paths )
|
||||||
{
|
|
||||||
LoadMtrlHelper( mtrlResourceHandle );
|
|
||||||
var ret = LoadMtrlShpkHook!.Original( mtrlResourceHandle );
|
|
||||||
_mtrlCollection = null;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ModCollection? _mtrlCollection;
|
|
||||||
|
|
||||||
private void LoadMtrlHelper( IntPtr mtrlResourceHandle )
|
|
||||||
{
|
|
||||||
if( mtrlResourceHandle == IntPtr.Zero )
|
|
||||||
{
|
{
|
||||||
return;
|
SignatureHelper.Initialise( this );
|
||||||
|
_paths = paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
// Check specifically for shpk and tex files whether we are currently in a material load.
|
||||||
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
|
public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out ModCollection? collection )
|
||||||
_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 )
|
|
||||||
{
|
{
|
||||||
collection = _mtrlCollection;
|
if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk )
|
||||||
return true;
|
{
|
||||||
}
|
collection = _mtrlCollection;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
collection = null;
|
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 )
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastUnderscore = split.LastIndexOf( ( byte )'_' );
|
// Materials need to be set per collection so they can load their textures independently from each other.
|
||||||
var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString();
|
public static void HandleCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
|
||||||
if( Penumbra.TempMods.CollectionByName( name, out var collection )
|
out (FullPath?, object?) data )
|
||||||
|| Penumbra.CollectionManager.ByName( name, out collection ) )
|
|
||||||
{
|
{
|
||||||
|
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
|
#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
|
#endif
|
||||||
SetCollection( path, collection );
|
_paths.SetCollection( path, collection );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#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
|
#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,
|
private delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle );
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Materials need to be set per collection so they can load their textures independently from each other.
|
[Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = nameof( LoadMtrlTexDetour ) )]
|
||||||
private static void HandleMtrlCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
|
private readonly Hook< LoadMtrlFilesDelegate > _loadMtrlTexHook = null!;
|
||||||
out (FullPath?, object?) data )
|
|
||||||
{
|
private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle )
|
||||||
if( nonDefault && type == ResourceType.Mtrl )
|
|
||||||
{
|
{
|
||||||
var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" );
|
LoadMtrlHelper( mtrlResourceHandle );
|
||||||
data = ( fullPath, collection );
|
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()
|
private void LoadMtrlHelper( IntPtr mtrlResourceHandle )
|
||||||
{
|
{
|
||||||
LoadMtrlShpkHook?.Enable();
|
if( mtrlResourceHandle == IntPtr.Zero )
|
||||||
LoadMtrlTexHook?.Enable();
|
{
|
||||||
Penumbra.ResourceLoader.ResourceLoadCustomization += MtrlLoadHandler;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisableMtrlHooks()
|
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
||||||
{
|
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
|
||||||
LoadMtrlShpkHook?.Disable();
|
_mtrlCollection = _paths.TryGetValue( mtrlPath, out var c ) ? c : null;
|
||||||
LoadMtrlTexHook?.Disable();
|
}
|
||||||
Penumbra.ResourceLoader.ResourceLoadCustomization -= MtrlLoadHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeMtrlHooks()
|
|
||||||
{
|
|
||||||
LoadMtrlShpkHook?.Dispose();
|
|
||||||
LoadMtrlTexHook?.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Meta.Files;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
@ -33,136 +32,133 @@ namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
public unsafe partial class PathResolver
|
public unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
public delegate void UpdateModelDelegate( IntPtr drawObject );
|
public unsafe class MetaState : IDisposable
|
||||||
|
|
||||||
[Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = "UpdateModelsDetour" )]
|
|
||||||
public Hook< UpdateModelDelegate >? UpdateModelsHook;
|
|
||||||
|
|
||||||
private void UpdateModelsDetour( IntPtr drawObject )
|
|
||||||
{
|
{
|
||||||
// Shortcut because this is called all the time.
|
private readonly PathResolver _parent;
|
||||||
// Same thing is checked at the beginning of the original function.
|
|
||||||
if( *( int* )( drawObject + 0x90c ) == 0 )
|
public MetaState( PathResolver parent, IntPtr* humanVTable )
|
||||||
{
|
{
|
||||||
return;
|
SignatureHelper.Initialise( this );
|
||||||
|
_parent = parent;
|
||||||
|
_onModelLoadCompleteHook = Hook< OnModelLoadCompleteDelegate >.FromAddress( humanVTable[ 58 ], OnModelLoadCompleteDetour );
|
||||||
}
|
}
|
||||||
|
|
||||||
var collection = GetCollection( drawObject );
|
public void Enable()
|
||||||
if( collection != null )
|
|
||||||
{
|
{
|
||||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
_getEqpIndirectHook.Enable();
|
||||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
_updateModelsHook.Enable();
|
||||||
UpdateModelsHook!.Original.Invoke( drawObject );
|
_onModelLoadCompleteHook.Enable();
|
||||||
}
|
_setupVisorHook.Enable();
|
||||||
else
|
_rspSetupCharacterHook.Enable();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using var eqp = MetaChanger.ChangeEqp( this, drawObject );
|
public void Disable()
|
||||||
GetEqpIndirectHook!.Original( drawObject );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Hook< OnModelLoadCompleteDelegate >? OnModelLoadCompleteHook;
|
|
||||||
|
|
||||||
private void OnModelLoadCompleteDetour( IntPtr drawObject )
|
|
||||||
{
|
|
||||||
var collection = GetCollection( drawObject );
|
|
||||||
if( collection != null )
|
|
||||||
{
|
{
|
||||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
_getEqpIndirectHook.Disable();
|
||||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
_updateModelsHook.Disable();
|
||||||
OnModelLoadCompleteHook!.Original.Invoke( drawObject );
|
_onModelLoadCompleteHook.Disable();
|
||||||
}
|
_setupVisorHook.Disable();
|
||||||
else
|
_rspSetupCharacterHook.Disable();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// 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,
|
// 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 )
|
public static MetaChanger ChangeEqp( PathResolver resolver, IntPtr drawObject )
|
||||||
{
|
{
|
||||||
var collection = resolver.GetCollection( drawObject );
|
var collection = GetCollection( drawObject );
|
||||||
if( collection != null )
|
if( collection != null )
|
||||||
{
|
{
|
||||||
return ChangeEqp( collection );
|
return ChangeEqp( collection );
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,12 +204,13 @@ public unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
if( modelType < 10 )
|
if( modelType < 10 )
|
||||||
{
|
{
|
||||||
var collection = resolver.GetCollection( drawObject );
|
var collection = GetCollection( drawObject );
|
||||||
if( collection != null )
|
if( collection != null )
|
||||||
{
|
{
|
||||||
return ChangeEqdp( collection );
|
return ChangeEqdp( collection );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,31 +222,33 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject )
|
public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject )
|
||||||
{
|
{
|
||||||
var collection = resolver.GetCollection( drawObject );
|
var collection = GetCollection( drawObject );
|
||||||
if( collection != null )
|
if( collection != null )
|
||||||
{
|
{
|
||||||
collection.SetGmpFiles();
|
collection.SetGmpFiles();
|
||||||
return new MetaChanger( MetaManipulation.Type.Gmp );
|
return new MetaChanger( MetaManipulation.Type.Gmp );
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject )
|
public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject )
|
||||||
{
|
{
|
||||||
var collection = resolver.GetCollection( drawObject );
|
var collection = GetCollection( drawObject );
|
||||||
if( collection != null )
|
if( collection != null )
|
||||||
{
|
{
|
||||||
collection.SetEstFiles();
|
collection.SetEstFiles();
|
||||||
return new MetaChanger( MetaManipulation.Type.Est );
|
return new MetaChanger( MetaManipulation.Type.Est );
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
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 )
|
if( collection != Penumbra.CollectionManager.Default && collection.HasCache )
|
||||||
{
|
{
|
||||||
collection.SetCmpFiles();
|
collection.SetCmpFiles();
|
||||||
|
|
@ -265,12 +265,13 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject )
|
public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject )
|
||||||
{
|
{
|
||||||
var collection = resolver.GetCollection( drawObject );
|
var collection = GetCollection( drawObject );
|
||||||
if( collection != null )
|
if( collection != null )
|
||||||
{
|
{
|
||||||
collection.SetCmpFiles();
|
collection.SetCmpFiles();
|
||||||
return new MetaChanger( MetaManipulation.Type.Rsp );
|
return new MetaChanger( MetaManipulation.Type.Rsp );
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
109
Penumbra/Interop/Resolver/PathResolver.PathState.cs
Normal file
109
Penumbra/Interop/Resolver/PathResolver.PathState.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
258
Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs
Normal file
258
Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Collections.Generic;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
|
|
@ -17,18 +19,24 @@ namespace Penumbra.Interop.Resolver;
|
||||||
// to resolve paths for character collections.
|
// to resolve paths for character collections.
|
||||||
public partial class PathResolver : IDisposable
|
public partial class PathResolver : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ResourceLoader _loader;
|
|
||||||
public bool Enabled { get; private set; }
|
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 );
|
SignatureHelper.Initialise( this );
|
||||||
SetupHumanHooks();
|
_loader = loader;
|
||||||
SetupWeaponHooks();
|
_animations = new AnimationState( DrawObjects );
|
||||||
SetupMonsterHooks();
|
_paths = new PathState( this );
|
||||||
SetupDemiHooks();
|
_meta = new MetaState( this, _paths.HumanVTable );
|
||||||
SetupMetaHooks();
|
_materials = new MaterialState( _paths );
|
||||||
}
|
}
|
||||||
|
|
||||||
// The modified resolver that handles game path resolving.
|
// The modified resolver that handles game path resolving.
|
||||||
|
|
@ -40,10 +48,10 @@ public partial class PathResolver : IDisposable
|
||||||
// If not use the default collection.
|
// If not use the default collection.
|
||||||
// We can remove paths after they have actually been loaded.
|
// We can remove paths after they have actually been loaded.
|
||||||
// A potential next request will add the path anew.
|
// A potential next request will add the path anew.
|
||||||
var nonDefault = HandleMaterialSubFiles( type, out var collection )
|
var nonDefault = _materials.HandleSubFiles( type, out var collection )
|
||||||
|| PathCollections.TryRemove( gamePath.Path, out collection )
|
|| _paths.Consume( gamePath.Path, out collection )
|
||||||
|| HandleAnimationFile( type, gamePath, out collection )
|
|| _animations.HandleFiles( type, gamePath, out collection )
|
||||||
|| HandleDecalFile( type, gamePath, out collection );
|
|| DrawObjects.HandleDecalFile( type, gamePath, out collection );
|
||||||
if( !nonDefault || collection == null )
|
if( !nonDefault || collection == null )
|
||||||
{
|
{
|
||||||
collection = Penumbra.CollectionManager.Default;
|
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.
|
// 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.
|
// We also need to handle defaulted materials against a non-default collection.
|
||||||
var path = resolved == null ? gamePath.Path.ToString() : resolved.Value.FullName;
|
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;
|
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()
|
public void Enable()
|
||||||
{
|
{
|
||||||
if( Enabled )
|
if( Enabled )
|
||||||
|
|
@ -125,15 +76,12 @@ public partial class PathResolver : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
InitializeDrawObjects();
|
Cutscenes.Enable();
|
||||||
|
DrawObjects.Enable();
|
||||||
EnableHumanHooks();
|
_animations.Enable();
|
||||||
EnableWeaponHooks();
|
_paths.Enable();
|
||||||
EnableMonsterHooks();
|
_meta.Enable();
|
||||||
EnableDemiHooks();
|
_materials.Enable();
|
||||||
EnableMtrlHooks();
|
|
||||||
EnableDataHooks();
|
|
||||||
EnableMetaHooks();
|
|
||||||
|
|
||||||
_loader.ResolvePathCustomization += CharacterResolver;
|
_loader.ResolvePathCustomization += CharacterResolver;
|
||||||
PluginLog.Debug( "Character Path Resolver enabled." );
|
PluginLog.Debug( "Character Path Resolver enabled." );
|
||||||
|
|
@ -147,16 +95,12 @@ public partial class PathResolver : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
Enabled = false;
|
Enabled = false;
|
||||||
DisableHumanHooks();
|
_animations.Disable();
|
||||||
DisableWeaponHooks();
|
DrawObjects.Disable();
|
||||||
DisableMonsterHooks();
|
Cutscenes.Disable();
|
||||||
DisableDemiHooks();
|
_paths.Disable();
|
||||||
DisableMtrlHooks();
|
_meta.Disable();
|
||||||
DisableDataHooks();
|
_materials.Disable();
|
||||||
DisableMetaHooks();
|
|
||||||
|
|
||||||
DrawObjectToObject.Clear();
|
|
||||||
PathCollections.Clear();
|
|
||||||
|
|
||||||
_loader.ResolvePathCustomization -= CharacterResolver;
|
_loader.ResolvePathCustomization -= CharacterResolver;
|
||||||
PluginLog.Debug( "Character Path Resolver disabled." );
|
PluginLog.Debug( "Character Path Resolver disabled." );
|
||||||
|
|
@ -165,18 +109,60 @@ public partial class PathResolver : IDisposable
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Disable();
|
Disable();
|
||||||
DisposeHumanHooks();
|
_paths.Dispose();
|
||||||
DisposeWeaponHooks();
|
_animations.Dispose();
|
||||||
DisposeMonsterHooks();
|
DrawObjects.Dispose();
|
||||||
DisposeDemiHooks();
|
Cutscenes.Dispose();
|
||||||
DisposeMtrlHooks();
|
_meta.Dispose();
|
||||||
DisposeDataHooks();
|
_materials.Dispose();
|
||||||
DisposeMetaHooks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject )
|
public static unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject )
|
||||||
{
|
{
|
||||||
var parent = FindParent( drawObject, out var collection );
|
var parent = FindParent( drawObject, out var collection );
|
||||||
return ( ( IntPtr )parent, 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;
|
||||||
}
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
|
using Penumbra.Interop.Resolver;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||||
|
|
||||||
|
|
@ -155,45 +156,63 @@ public partial class ConfigWindow
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var drawTree = ImRaii.TreeNode( "Draw Object to Object" );
|
using( var drawTree = ImRaii.TreeNode( "Draw Object to Object" ) )
|
||||||
if( drawTree )
|
|
||||||
{
|
{
|
||||||
using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit );
|
if( drawTree )
|
||||||
if( table )
|
|
||||||
{
|
{
|
||||||
foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectToObject )
|
using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit );
|
||||||
|
if( table )
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectMap )
|
||||||
ImGui.TextUnformatted( ptr.ToString( "X" ) );
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted( idx.ToString() );
|
ImGui.TextUnformatted( ptr.ToString( "X" ) );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
ImGui.TextUnformatted( idx.ToString() );
|
||||||
var (address, name) =
|
ImGui.TableNextColumn();
|
||||||
obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
||||||
ImGui.TextUnformatted( address );
|
var (address, name) =
|
||||||
ImGui.TableNextColumn();
|
obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
||||||
ImGui.TextUnformatted( name );
|
ImGui.TextUnformatted( address );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted( c.Name );
|
ImGui.TextUnformatted( name );
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted( c.Name );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawTree.Dispose();
|
using( var pathTree = ImRaii.TreeNode( "Path Collections" ) )
|
||||||
|
|
||||||
using var pathTree = ImRaii.TreeNode( "Path Collections" );
|
|
||||||
if( pathTree )
|
|
||||||
{
|
{
|
||||||
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 )
|
if( table )
|
||||||
{
|
{
|
||||||
foreach( var (path, collection) in _window._penumbra.PathResolver.PathCollections )
|
foreach( var (idx, actor) in _window._penumbra.PathResolver.CutsceneActors )
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
|
ImGui.TextUnformatted( $"Cutscene Actor {idx}" );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted( collection.Name );
|
ImGui.TextUnformatted( actor.Name.ToString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue