mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +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.
|
||||
public (IntPtr, string) GetDrawObjectInfo( IntPtr drawObject );
|
||||
|
||||
// Obtain the parent game object index for an unnamed cutscene actor by its index.
|
||||
public int GetCutsceneParentIndex( int actor );
|
||||
|
||||
// Obtain a list of all installed mods. The first string is their directory name, the second string is their mod name.
|
||||
public IList< (string, string) > GetModList();
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ public class IpcTester : IDisposable
|
|||
private string _currentDrawObjectString = string.Empty;
|
||||
private string _currentReversePath = string.Empty;
|
||||
private IntPtr _currentDrawObject = IntPtr.Zero;
|
||||
private int _currentCutsceneActor = 0;
|
||||
private string _lastCreatedGameObjectName = string.Empty;
|
||||
private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
|
||||
|
||||
|
|
@ -231,6 +232,8 @@ public class IpcTester : IDisposable
|
|||
: IntPtr.Zero;
|
||||
}
|
||||
|
||||
ImGui.InputInt( "Cutscene Actor", ref _currentCutsceneActor, 0 );
|
||||
|
||||
using var table = ImRaii.Table( string.Empty, 3, ImGuiTableFlags.SizingFixedFit );
|
||||
if( !table )
|
||||
{
|
||||
|
|
@ -263,6 +266,10 @@ public class IpcTester : IDisposable
|
|||
ImGui.TextUnformatted( ptr == IntPtr.Zero ? $"No Actor Associated, {collection}" : $"{ptr:X}, {collection}" );
|
||||
}
|
||||
|
||||
DrawIntro( PenumbraIpc.LabelProviderGetDrawObjectInfo, "Cutscene Parent" );
|
||||
ImGui.TextUnformatted( _pi.GetIpcSubscriber< int, int >( PenumbraIpc.LabelProviderGetCutsceneParentIndex )
|
||||
.InvokeFunc( _currentCutsceneActor ).ToString() );
|
||||
|
||||
DrawIntro( PenumbraIpc.LabelProviderReverseResolvePath, "Reversed Game Paths" );
|
||||
if( _currentReversePath.Length > 0 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Common.Configuration;
|
||||
using Lumina.Data;
|
||||
using Newtonsoft.Json;
|
||||
using OtterGui;
|
||||
|
|
@ -16,14 +15,13 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.Interop.Resolver;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Api;
|
||||
|
||||
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||
{
|
||||
public (int, int) ApiVersion
|
||||
=> ( 4, 11 );
|
||||
=> ( 4, 12 );
|
||||
|
||||
private Penumbra? _penumbra;
|
||||
private Lumina.GameData? _lumina;
|
||||
|
|
@ -43,8 +41,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
|
||||
public event CreatingCharacterBaseDelegate? CreatingCharacterBase
|
||||
{
|
||||
add => _penumbra!.PathResolver.CreatingCharacterBase += value;
|
||||
remove => _penumbra!.PathResolver.CreatingCharacterBase -= value;
|
||||
add => PathResolver.DrawObjectState.CreatingCharacterBase += value;
|
||||
remove => PathResolver.DrawObjectState.CreatingCharacterBase -= value;
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
|
|
@ -54,8 +52,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
{
|
||||
_penumbra = penumbra;
|
||||
_lumina = ( Lumina.GameData? )Dalamud.GameData.GetType()
|
||||
.GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic )
|
||||
?.GetValue( Dalamud.GameData );
|
||||
.GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic )
|
||||
?.GetValue( Dalamud.GameData );
|
||||
foreach( var collection in Penumbra.CollectionManager )
|
||||
{
|
||||
SubscribeToCollection( collection );
|
||||
|
|
@ -221,10 +219,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
public (IntPtr, string) GetDrawObjectInfo( IntPtr drawObject )
|
||||
{
|
||||
CheckInitialized();
|
||||
var (obj, collection) = _penumbra!.PathResolver.IdentifyDrawObject( drawObject );
|
||||
var (obj, collection) = PathResolver.IdentifyDrawObject( drawObject );
|
||||
return ( obj, collection.Name );
|
||||
}
|
||||
|
||||
public int GetCutsceneParentIndex( int actor )
|
||||
{
|
||||
CheckInitialized();
|
||||
return _penumbra!.PathResolver.CutsceneActor( actor );
|
||||
}
|
||||
|
||||
public IList< (string, string) > GetModList()
|
||||
{
|
||||
CheckInitialized();
|
||||
|
|
@ -429,7 +433,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
}
|
||||
|
||||
if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character )
|
||||
|| Penumbra.TempMods.Collections.ContainsKey( character ) )
|
||||
|| Penumbra.TempMods.Collections.ContainsKey( character ) )
|
||||
{
|
||||
return ( PenumbraApiEc.CharacterCollectionExists, string.Empty );
|
||||
}
|
||||
|
|
@ -475,7 +479,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
{
|
||||
CheckInitialized();
|
||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
||||
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
||||
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
||||
{
|
||||
return PenumbraApiEc.CollectionMissing;
|
||||
}
|
||||
|
|
@ -512,7 +516,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
{
|
||||
CheckInitialized();
|
||||
if( !Penumbra.TempMods.CollectionByName( collectionName, out var collection )
|
||||
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
||||
&& !Penumbra.CollectionManager.ByName( collectionName, out collection ) )
|
||||
{
|
||||
return PenumbraApiEc.CollectionMissing;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@ public partial class PenumbraIpc
|
|||
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
|
||||
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
|
||||
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
|
||||
public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex";
|
||||
public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath";
|
||||
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
|
||||
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
|
||||
|
|
@ -269,6 +270,7 @@ public partial class PenumbraIpc
|
|||
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
|
||||
internal ICallGateProvider< string, string >? ProviderResolvePlayer;
|
||||
internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo;
|
||||
internal ICallGateProvider< int, int >? ProviderGetCutsceneParentIndex;
|
||||
internal ICallGateProvider< string, string, string[] >? ProviderReverseResolvePath;
|
||||
internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer;
|
||||
internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase;
|
||||
|
|
@ -315,6 +317,16 @@ public partial class PenumbraIpc
|
|||
PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetDrawObjectInfo}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderGetCutsceneParentIndex = pi.GetIpcProvider<int, int>( LabelProviderGetCutsceneParentIndex );
|
||||
ProviderGetCutsceneParentIndex.RegisterFunc( Api.GetCutsceneParentIndex );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetCutsceneParentIndex}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderReverseResolvePath = pi.GetIpcProvider< string, string, string[] >( LabelProviderReverseResolvePath );
|
||||
|
|
@ -350,6 +362,7 @@ public partial class PenumbraIpc
|
|||
private void DisposeResolveProviders()
|
||||
{
|
||||
ProviderGetDrawObjectInfo?.UnregisterFunc();
|
||||
ProviderGetCutsceneParentIndex?.UnregisterFunc();
|
||||
ProviderResolveDefault?.UnregisterFunc();
|
||||
ProviderResolveCharacter?.UnregisterFunc();
|
||||
ProviderReverseResolvePath?.UnregisterFunc();
|
||||
|
|
|
|||
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.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -22,148 +15,6 @@ namespace Penumbra.Interop.Resolver;
|
|||
|
||||
public unsafe partial class PathResolver
|
||||
{
|
||||
// Keep track of created DrawObjects that are CharacterBase,
|
||||
// and use the last game object that called EnableDraw to link them.
|
||||
public delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40", DetourName = "CharacterBaseCreateDetour" )]
|
||||
public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook;
|
||||
|
||||
private ModCollection? _lastCreatedCollection;
|
||||
public event CreatingCharacterBaseDelegate? CreatingCharacterBase;
|
||||
|
||||
private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d )
|
||||
{
|
||||
using var cmp = MetaChanger.ChangeCmp( this, out _lastCreatedCollection );
|
||||
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
var modelPtr = &a;
|
||||
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ( IntPtr )modelPtr, b, c );
|
||||
}
|
||||
|
||||
var ret = CharacterBaseCreateHook!.Original( a, b, c, d );
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
DrawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Remove DrawObjects from the list when they are destroyed.
|
||||
public delegate void CharacterBaseDestructorDelegate( IntPtr drawBase );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30",
|
||||
DetourName = "CharacterBaseDestructorDetour" )]
|
||||
public Hook< CharacterBaseDestructorDelegate >? CharacterBaseDestructorHook;
|
||||
|
||||
private void CharacterBaseDestructorDetour( IntPtr drawBase )
|
||||
{
|
||||
DrawObjectToObject.Remove( drawBase );
|
||||
CharacterBaseDestructorHook!.Original.Invoke( drawBase );
|
||||
}
|
||||
|
||||
|
||||
// EnableDraw is what creates DrawObjects for gameObjects,
|
||||
// so we always keep track of the current GameObject to be able to link it to the DrawObject.
|
||||
public delegate void EnableDrawDelegate( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0" )]
|
||||
public Hook< EnableDrawDelegate >? EnableDrawHook;
|
||||
|
||||
private void EnableDrawDetour( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d )
|
||||
{
|
||||
var oldObject = LastGameObject;
|
||||
LastGameObject = ( GameObject* )gameObject;
|
||||
EnableDrawHook!.Original.Invoke( gameObject, b, c, d );
|
||||
LastGameObject = oldObject;
|
||||
}
|
||||
|
||||
// Not fully understood. The game object the weapon is loaded for is seemingly found at a1 + 8,
|
||||
// so we use that.
|
||||
public delegate void WeaponReloadFunc( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 44 8B 9F" )]
|
||||
public Hook< WeaponReloadFunc >? WeaponReloadHook;
|
||||
|
||||
public void WeaponReloadDetour( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 )
|
||||
{
|
||||
var oldGame = LastGameObject;
|
||||
LastGameObject = *( GameObject** )( a1 + 8 );
|
||||
WeaponReloadHook!.Original( a1, a2, a3, a4, a5, a6, a7 );
|
||||
LastGameObject = oldGame;
|
||||
}
|
||||
|
||||
private void EnableDataHooks()
|
||||
{
|
||||
CharacterBaseCreateHook?.Enable();
|
||||
EnableDrawHook?.Enable();
|
||||
CharacterBaseDestructorHook?.Enable();
|
||||
WeaponReloadHook?.Enable();
|
||||
Penumbra.CollectionManager.CollectionChanged += CheckCollections;
|
||||
LoadTimelineResourcesHook?.Enable();
|
||||
CharacterBaseLoadAnimationHook?.Enable();
|
||||
LoadSomeAvfxHook?.Enable();
|
||||
LoadSomePapHook?.Enable();
|
||||
SomeActionLoadHook?.Enable();
|
||||
SomeOtherAvfxHook?.Enable();
|
||||
}
|
||||
|
||||
private void DisableDataHooks()
|
||||
{
|
||||
Penumbra.CollectionManager.CollectionChanged -= CheckCollections;
|
||||
WeaponReloadHook?.Disable();
|
||||
CharacterBaseCreateHook?.Disable();
|
||||
EnableDrawHook?.Disable();
|
||||
CharacterBaseDestructorHook?.Disable();
|
||||
LoadTimelineResourcesHook?.Disable();
|
||||
CharacterBaseLoadAnimationHook?.Disable();
|
||||
LoadSomeAvfxHook?.Disable();
|
||||
LoadSomePapHook?.Disable();
|
||||
SomeActionLoadHook?.Disable();
|
||||
SomeOtherAvfxHook?.Disable();
|
||||
}
|
||||
|
||||
private void DisposeDataHooks()
|
||||
{
|
||||
WeaponReloadHook?.Dispose();
|
||||
CharacterBaseCreateHook?.Dispose();
|
||||
EnableDrawHook?.Dispose();
|
||||
CharacterBaseDestructorHook?.Dispose();
|
||||
LoadTimelineResourcesHook?.Dispose();
|
||||
CharacterBaseLoadAnimationHook?.Dispose();
|
||||
LoadSomeAvfxHook?.Dispose();
|
||||
LoadSomePapHook?.Dispose();
|
||||
SomeActionLoadHook?.Dispose();
|
||||
SomeOtherAvfxHook?.Dispose();
|
||||
}
|
||||
|
||||
// This map links DrawObjects directly to Actors (by ObjectTable index) and their collections.
|
||||
// It contains any DrawObjects that correspond to a human actor, even those without specific collections.
|
||||
internal readonly Dictionary< IntPtr, (ModCollection, int) > DrawObjectToObject = new();
|
||||
|
||||
// This map links files to their corresponding collection, if it is non-default.
|
||||
internal readonly ConcurrentDictionary< Utf8String, ModCollection > PathCollections = new();
|
||||
|
||||
internal GameObject* LastGameObject = null;
|
||||
|
||||
// Check that a linked DrawObject still corresponds to the correct actor and that it still exists, otherwise remove it.
|
||||
private bool VerifyEntry( IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject )
|
||||
{
|
||||
gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( gameObjectIdx );
|
||||
var draw = ( DrawObject* )drawObject;
|
||||
if( gameObject != null && ( gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
gameObject = null;
|
||||
DrawObjectToObject.Remove( drawObject );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtain the name of the current player, if one exists.
|
||||
private static string? GetPlayerName()
|
||||
=> Dalamud.Objects[ 0 ]?.Name.ToString();
|
||||
|
|
@ -244,6 +95,13 @@ public unsafe partial class PathResolver
|
|||
return null;
|
||||
}
|
||||
|
||||
var parent = Cutscenes[ gameObject->ObjectIndex ];
|
||||
if( parent != null )
|
||||
{
|
||||
return parent.Name.ToString();
|
||||
}
|
||||
|
||||
// should not really happen but keep it in as a emergency case.
|
||||
var player = Dalamud.Objects[ 0 ];
|
||||
if( player == null )
|
||||
{
|
||||
|
|
@ -327,12 +185,14 @@ public unsafe partial class PathResolver
|
|||
// Only OwnerName can be applied to something with a non-empty name, and that is the specific case we want to handle.
|
||||
var actualName = gameObject->ObjectIndex switch
|
||||
{
|
||||
240 => Penumbra.Config.UseCharacterCollectionInMainWindow ? GetPlayerName() : null, // character window
|
||||
241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor.
|
||||
242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on
|
||||
243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview
|
||||
>= 200 => GetCutsceneName( gameObject ),
|
||||
_ => null,
|
||||
240 => Penumbra.Config.UseCharacterCollectionInMainWindow ? GetPlayerName() : null, // character window
|
||||
241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor.
|
||||
242 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // try-on
|
||||
243 => Penumbra.Config.UseCharacterCollectionInTryOn ? GetPlayerName() : null, // dye preview
|
||||
|
||||
>= ObjectReloader.CutsceneStartIdx and < ObjectReloader.CutsceneEndIdx => GetCutsceneName( gameObject ),
|
||||
|
||||
_ => null,
|
||||
}
|
||||
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
|
||||
|
||||
|
|
@ -466,77 +326,4 @@ public unsafe partial class PathResolver
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Update collections linked to Game/DrawObjects due to a change in collection configuration.
|
||||
private void CheckCollections( CollectionType type, ModCollection? _1, ModCollection? _2, string? name )
|
||||
{
|
||||
if( type is CollectionType.Inactive or CollectionType.Current )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var (key, (_, idx)) in DrawObjectToObject.ToArray() )
|
||||
{
|
||||
if( !VerifyEntry( key, idx, out var obj ) )
|
||||
{
|
||||
DrawObjectToObject.Remove( key );
|
||||
}
|
||||
|
||||
var newCollection = IdentifyCollection( obj );
|
||||
DrawObjectToObject[ key ] = ( newCollection, idx );
|
||||
}
|
||||
}
|
||||
|
||||
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
|
||||
private GameObject* FindParent( IntPtr drawObject, out ModCollection collection )
|
||||
{
|
||||
if( DrawObjectToObject.TryGetValue( drawObject, out var data ) )
|
||||
{
|
||||
var gameObjectIdx = data.Item2;
|
||||
if( VerifyEntry( drawObject, gameObjectIdx, out var gameObject ) )
|
||||
{
|
||||
collection = data.Item1;
|
||||
return gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
if( LastGameObject != null && ( LastGameObject->DrawObject == null || LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
|
||||
{
|
||||
collection = IdentifyCollection( LastGameObject );
|
||||
return LastGameObject;
|
||||
}
|
||||
|
||||
|
||||
collection = IdentifyCollection( null );
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary.
|
||||
private void SetCollection( Utf8String path, ModCollection collection )
|
||||
{
|
||||
if( PathCollections.ContainsKey( path ) || path.IsOwned )
|
||||
{
|
||||
PathCollections[ path ] = collection;
|
||||
}
|
||||
else
|
||||
{
|
||||
PathCollections[ path.Clone() ] = collection;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all current DrawObjects used in the GameObject table.
|
||||
// We do not iterate the Dalamud table because it does not work when not logged in.
|
||||
private void InitializeDrawObjects()
|
||||
{
|
||||
for( var i = 0; i < Dalamud.Objects.Length; ++i )
|
||||
{
|
||||
var ptr = ( GameObject* )Dalamud.Objects.GetObjectAddress( i );
|
||||
if( ptr != null && ptr->IsCharacter() && ptr->DrawObject != null )
|
||||
{
|
||||
DrawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr ), ptr->ObjectIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ using Dalamud.Hooking;
|
|||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using OtterGui;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -12,132 +11,144 @@ using Penumbra.Interop.Structs;
|
|||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
// Materials do contain their own paths to textures and shader packages.
|
||||
// Those are loaded synchronously.
|
||||
// Thus, we need to ensure the correct files are loaded when a material is loaded.
|
||||
public unsafe partial class PathResolver
|
||||
{
|
||||
public delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle );
|
||||
|
||||
[Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour" )]
|
||||
public Hook< LoadMtrlFilesDelegate >? LoadMtrlTexHook;
|
||||
|
||||
private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle )
|
||||
// Materials do contain their own paths to textures and shader packages.
|
||||
// Those are loaded synchronously.
|
||||
// Thus, we need to ensure the correct files are loaded when a material is loaded.
|
||||
public class MaterialState : IDisposable
|
||||
{
|
||||
LoadMtrlHelper( mtrlResourceHandle );
|
||||
var ret = LoadMtrlTexHook!.Original( mtrlResourceHandle );
|
||||
_mtrlCollection = null;
|
||||
return ret;
|
||||
}
|
||||
private readonly PathState _paths;
|
||||
|
||||
[Signature( "48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89",
|
||||
DetourName = "LoadMtrlShpkDetour" )]
|
||||
public Hook< LoadMtrlFilesDelegate >? LoadMtrlShpkHook;
|
||||
private ModCollection? _mtrlCollection;
|
||||
|
||||
private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle )
|
||||
{
|
||||
LoadMtrlHelper( mtrlResourceHandle );
|
||||
var ret = LoadMtrlShpkHook!.Original( mtrlResourceHandle );
|
||||
_mtrlCollection = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private ModCollection? _mtrlCollection;
|
||||
|
||||
private void LoadMtrlHelper( IntPtr mtrlResourceHandle )
|
||||
{
|
||||
if( mtrlResourceHandle == IntPtr.Zero )
|
||||
public MaterialState( PathState paths )
|
||||
{
|
||||
return;
|
||||
SignatureHelper.Initialise( this );
|
||||
_paths = paths;
|
||||
}
|
||||
|
||||
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
||||
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
|
||||
_mtrlCollection = PathCollections.TryGetValue( mtrlPath, out var c ) ? c : null;
|
||||
}
|
||||
|
||||
// Check specifically for shpk and tex files whether we are currently in a material load.
|
||||
private bool HandleMaterialSubFiles( ResourceType type, [NotNullWhen( true )] out ModCollection? collection )
|
||||
{
|
||||
if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk )
|
||||
// Check specifically for shpk and tex files whether we are currently in a material load.
|
||||
public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out ModCollection? collection )
|
||||
{
|
||||
collection = _mtrlCollection;
|
||||
return true;
|
||||
}
|
||||
if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk )
|
||||
{
|
||||
collection = _mtrlCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need to set the correct collection for the actual material path that is loaded
|
||||
// before actually loading the file.
|
||||
private bool MtrlLoadHandler( Utf8String split, Utf8String path, ResourceManager* resourceManager,
|
||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret )
|
||||
{
|
||||
ret = 0;
|
||||
if( fileDescriptor->ResourceHandle->FileType != ResourceType.Mtrl )
|
||||
{
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var lastUnderscore = split.LastIndexOf( ( byte )'_' );
|
||||
var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString();
|
||||
if( Penumbra.TempMods.CollectionByName( name, out var collection )
|
||||
|| Penumbra.CollectionManager.ByName( name, out collection ) )
|
||||
// Materials need to be set per collection so they can load their textures independently from each other.
|
||||
public static void HandleCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
|
||||
out (FullPath?, object?) data )
|
||||
{
|
||||
if( nonDefault && type == ResourceType.Mtrl )
|
||||
{
|
||||
var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" );
|
||||
data = ( fullPath, collection );
|
||||
}
|
||||
else
|
||||
{
|
||||
data = ( resolved, collection );
|
||||
}
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
_loadMtrlShpkHook.Enable();
|
||||
_loadMtrlTexHook.Enable();
|
||||
Penumbra.ResourceLoader.ResourceLoadCustomization += MtrlLoadHandler;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_loadMtrlShpkHook.Disable();
|
||||
_loadMtrlTexHook.Disable();
|
||||
Penumbra.ResourceLoader.ResourceLoadCustomization -= MtrlLoadHandler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disable();
|
||||
_loadMtrlShpkHook?.Dispose();
|
||||
_loadMtrlTexHook?.Dispose();
|
||||
}
|
||||
|
||||
// We need to set the correct collection for the actual material path that is loaded
|
||||
// before actually loading the file.
|
||||
public bool MtrlLoadHandler( Utf8String split, Utf8String path, ResourceManager* resourceManager,
|
||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret )
|
||||
{
|
||||
ret = 0;
|
||||
if( fileDescriptor->ResourceHandle->FileType != ResourceType.Mtrl )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lastUnderscore = split.LastIndexOf( ( byte )'_' );
|
||||
var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString();
|
||||
if( Penumbra.TempMods.CollectionByName( name, out var collection )
|
||||
|| Penumbra.CollectionManager.ByName( name, out collection ) )
|
||||
{
|
||||
#if DEBUG
|
||||
PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path );
|
||||
PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path );
|
||||
#endif
|
||||
SetCollection( path, collection );
|
||||
}
|
||||
else
|
||||
{
|
||||
_paths.SetCollection( path, collection );
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
PluginLog.Verbose( "Using MtrlLoadHandler with no collection for path {$Path:l}.", path );
|
||||
PluginLog.Verbose( "Using MtrlLoadHandler with no collection for path {$Path:l}.", path );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Force isSync = true for this call. I don't really understand why,
|
||||
// or where the difference even comes from.
|
||||
// Was called with True on my client and with false on other peoples clients,
|
||||
// which caused problems.
|
||||
ret = Penumbra.ResourceLoader.DefaultLoadResource( path, resourceManager, fileDescriptor, priority, true );
|
||||
_paths.Consume( path, out _ );
|
||||
return true;
|
||||
}
|
||||
|
||||
// Force isSync = true for this call. I don't really understand why,
|
||||
// or where the difference even comes from.
|
||||
// Was called with True on my client and with false on other peoples clients,
|
||||
// which caused problems.
|
||||
ret = Penumbra.ResourceLoader.DefaultLoadResource( path, resourceManager, fileDescriptor, priority, true );
|
||||
PathCollections.TryRemove( path, out _ );
|
||||
return true;
|
||||
}
|
||||
private delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle );
|
||||
|
||||
// Materials need to be set per collection so they can load their textures independently from each other.
|
||||
private static void HandleMtrlCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
|
||||
out (FullPath?, object?) data )
|
||||
{
|
||||
if( nonDefault && type == ResourceType.Mtrl )
|
||||
[Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = nameof( LoadMtrlTexDetour ) )]
|
||||
private readonly Hook< LoadMtrlFilesDelegate > _loadMtrlTexHook = null!;
|
||||
|
||||
private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle )
|
||||
{
|
||||
var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" );
|
||||
data = ( fullPath, collection );
|
||||
LoadMtrlHelper( mtrlResourceHandle );
|
||||
var ret = _loadMtrlTexHook!.Original( mtrlResourceHandle );
|
||||
_mtrlCollection = null;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
|
||||
[Signature( "48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89",
|
||||
DetourName = nameof( LoadMtrlShpkDetour ) )]
|
||||
private readonly Hook< LoadMtrlFilesDelegate > _loadMtrlShpkHook = null!;
|
||||
|
||||
private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle )
|
||||
{
|
||||
data = ( resolved, collection );
|
||||
LoadMtrlHelper( mtrlResourceHandle );
|
||||
var ret = _loadMtrlShpkHook!.Original( mtrlResourceHandle );
|
||||
_mtrlCollection = null;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableMtrlHooks()
|
||||
{
|
||||
LoadMtrlShpkHook?.Enable();
|
||||
LoadMtrlTexHook?.Enable();
|
||||
Penumbra.ResourceLoader.ResourceLoadCustomization += MtrlLoadHandler;
|
||||
}
|
||||
private void LoadMtrlHelper( IntPtr mtrlResourceHandle )
|
||||
{
|
||||
if( mtrlResourceHandle == IntPtr.Zero )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void DisableMtrlHooks()
|
||||
{
|
||||
LoadMtrlShpkHook?.Disable();
|
||||
LoadMtrlTexHook?.Disable();
|
||||
Penumbra.ResourceLoader.ResourceLoadCustomization -= MtrlLoadHandler;
|
||||
}
|
||||
|
||||
private void DisposeMtrlHooks()
|
||||
{
|
||||
LoadMtrlShpkHook?.Dispose();
|
||||
LoadMtrlTexHook?.Dispose();
|
||||
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
||||
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
|
||||
_mtrlCollection = _paths.TryGetValue( mtrlPath, out var c ) ? c : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
|
@ -33,136 +32,133 @@ namespace Penumbra.Interop.Resolver;
|
|||
|
||||
public unsafe partial class PathResolver
|
||||
{
|
||||
public delegate void UpdateModelDelegate( IntPtr drawObject );
|
||||
|
||||
[Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = "UpdateModelsDetour" )]
|
||||
public Hook< UpdateModelDelegate >? UpdateModelsHook;
|
||||
|
||||
private void UpdateModelsDetour( IntPtr drawObject )
|
||||
public unsafe class MetaState : IDisposable
|
||||
{
|
||||
// Shortcut because this is called all the time.
|
||||
// Same thing is checked at the beginning of the original function.
|
||||
if( *( int* )( drawObject + 0x90c ) == 0 )
|
||||
private readonly PathResolver _parent;
|
||||
|
||||
public MetaState( PathResolver parent, IntPtr* humanVTable )
|
||||
{
|
||||
return;
|
||||
SignatureHelper.Initialise( this );
|
||||
_parent = parent;
|
||||
_onModelLoadCompleteHook = Hook< OnModelLoadCompleteDelegate >.FromAddress( humanVTable[ 58 ], OnModelLoadCompleteDetour );
|
||||
}
|
||||
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
public void Enable()
|
||||
{
|
||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
||||
UpdateModelsHook!.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateModelsHook!.Original.Invoke( drawObject );
|
||||
}
|
||||
}
|
||||
|
||||
[Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C",
|
||||
DetourName = "GetEqpIndirectDetour" )]
|
||||
public Hook< OnModelLoadCompleteDelegate >? GetEqpIndirectHook;
|
||||
|
||||
private void GetEqpIndirectDetour( IntPtr drawObject )
|
||||
{
|
||||
// Shortcut because this is also called all the time.
|
||||
// Same thing is checked at the beginning of the original function.
|
||||
if( ( *( byte* )( drawObject + 0xa30 ) & 1 ) == 0 || *( ulong* )( drawObject + 0xa28 ) == 0 )
|
||||
{
|
||||
return;
|
||||
_getEqpIndirectHook.Enable();
|
||||
_updateModelsHook.Enable();
|
||||
_onModelLoadCompleteHook.Enable();
|
||||
_setupVisorHook.Enable();
|
||||
_rspSetupCharacterHook.Enable();
|
||||
}
|
||||
|
||||
using var eqp = MetaChanger.ChangeEqp( this, drawObject );
|
||||
GetEqpIndirectHook!.Original( drawObject );
|
||||
}
|
||||
|
||||
public Hook< OnModelLoadCompleteDelegate >? OnModelLoadCompleteHook;
|
||||
|
||||
private void OnModelLoadCompleteDetour( IntPtr drawObject )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
public void Disable()
|
||||
{
|
||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
||||
OnModelLoadCompleteHook!.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
OnModelLoadCompleteHook!.Original.Invoke( drawObject );
|
||||
}
|
||||
}
|
||||
|
||||
// GMP. This gets called every time when changing visor state, and it accesses the gmp file itself,
|
||||
// but it only applies a changed gmp file after a redraw for some reason.
|
||||
public delegate byte SetupVisorDelegate( IntPtr drawObject, ushort modelId, byte visorState );
|
||||
|
||||
[Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = "SetupVisorDetour" )]
|
||||
public Hook< SetupVisorDelegate >? SetupVisorHook;
|
||||
|
||||
private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState )
|
||||
{
|
||||
using var gmp = MetaChanger.ChangeGmp( this, drawObject );
|
||||
return SetupVisorHook!.Original( drawObject, modelId, visorState );
|
||||
}
|
||||
|
||||
// RSP
|
||||
public delegate void RspSetupCharacterDelegate( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 );
|
||||
|
||||
[Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 88 54 24 ?? 57 41 56", DetourName = "RspSetupCharacterDetour" )]
|
||||
public Hook< RspSetupCharacterDelegate >? RspSetupCharacterHook;
|
||||
|
||||
private void RspSetupCharacterDetour( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 )
|
||||
{
|
||||
using var rsp = MetaChanger.ChangeCmp( this, drawObject );
|
||||
RspSetupCharacterHook!.Original( drawObject, unk2, unk3, unk4, unk5 );
|
||||
}
|
||||
|
||||
private void SetupMetaHooks()
|
||||
{
|
||||
OnModelLoadCompleteHook =
|
||||
Hook< OnModelLoadCompleteDelegate >.FromAddress( DrawObjectHumanVTable[ OnModelLoadCompleteIdx ], OnModelLoadCompleteDetour );
|
||||
}
|
||||
|
||||
private void EnableMetaHooks()
|
||||
{
|
||||
GetEqpIndirectHook?.Enable();
|
||||
UpdateModelsHook?.Enable();
|
||||
OnModelLoadCompleteHook?.Enable();
|
||||
SetupVisorHook?.Enable();
|
||||
RspSetupCharacterHook?.Enable();
|
||||
}
|
||||
|
||||
private void DisableMetaHooks()
|
||||
{
|
||||
GetEqpIndirectHook?.Disable();
|
||||
UpdateModelsHook?.Disable();
|
||||
OnModelLoadCompleteHook?.Disable();
|
||||
SetupVisorHook?.Disable();
|
||||
RspSetupCharacterHook?.Disable();
|
||||
}
|
||||
|
||||
private void DisposeMetaHooks()
|
||||
{
|
||||
GetEqpIndirectHook?.Dispose();
|
||||
UpdateModelsHook?.Dispose();
|
||||
OnModelLoadCompleteHook?.Dispose();
|
||||
SetupVisorHook?.Dispose();
|
||||
RspSetupCharacterHook?.Dispose();
|
||||
}
|
||||
|
||||
private ModCollection? GetCollection( IntPtr drawObject )
|
||||
{
|
||||
var parent = FindParent( drawObject, out var collection );
|
||||
if( parent == null || collection == Penumbra.CollectionManager.Default )
|
||||
{
|
||||
return null;
|
||||
_getEqpIndirectHook.Disable();
|
||||
_updateModelsHook.Disable();
|
||||
_onModelLoadCompleteHook.Disable();
|
||||
_setupVisorHook.Disable();
|
||||
_rspSetupCharacterHook.Disable();
|
||||
}
|
||||
|
||||
return collection.HasCache ? collection : null;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_getEqpIndirectHook.Dispose();
|
||||
_updateModelsHook.Dispose();
|
||||
_onModelLoadCompleteHook.Dispose();
|
||||
_setupVisorHook.Dispose();
|
||||
_rspSetupCharacterHook.Dispose();
|
||||
}
|
||||
|
||||
private delegate void OnModelLoadCompleteDelegate( IntPtr drawObject );
|
||||
private readonly Hook< OnModelLoadCompleteDelegate > _onModelLoadCompleteHook;
|
||||
|
||||
private void OnModelLoadCompleteDetour( IntPtr drawObject )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
||||
_onModelLoadCompleteHook.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
_onModelLoadCompleteHook.Original.Invoke( drawObject );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private delegate void UpdateModelDelegate( IntPtr drawObject );
|
||||
|
||||
[Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = nameof( UpdateModelsDetour ) )]
|
||||
private readonly Hook< UpdateModelDelegate > _updateModelsHook = null!;
|
||||
|
||||
private void UpdateModelsDetour( IntPtr drawObject )
|
||||
{
|
||||
// Shortcut because this is called all the time.
|
||||
// Same thing is checked at the beginning of the original function.
|
||||
if( *( int* )( drawObject + 0x90c ) == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
||||
_updateModelsHook.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
_updateModelsHook.Original.Invoke( drawObject );
|
||||
}
|
||||
}
|
||||
|
||||
[Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C",
|
||||
DetourName = nameof( GetEqpIndirectDetour ) )]
|
||||
private readonly Hook< OnModelLoadCompleteDelegate > _getEqpIndirectHook = null!;
|
||||
|
||||
private void GetEqpIndirectDetour( IntPtr drawObject )
|
||||
{
|
||||
// Shortcut because this is also called all the time.
|
||||
// Same thing is checked at the beginning of the original function.
|
||||
if( ( *( byte* )( drawObject + 0xa30 ) & 1 ) == 0 || *( ulong* )( drawObject + 0xa28 ) == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var eqp = MetaChanger.ChangeEqp( _parent, drawObject );
|
||||
_getEqpIndirectHook.Original( drawObject );
|
||||
}
|
||||
|
||||
|
||||
// GMP. This gets called every time when changing visor state, and it accesses the gmp file itself,
|
||||
// but it only applies a changed gmp file after a redraw for some reason.
|
||||
private delegate byte SetupVisorDelegate( IntPtr drawObject, ushort modelId, byte visorState );
|
||||
|
||||
[Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = nameof( SetupVisorDetour ) )]
|
||||
private readonly Hook< SetupVisorDelegate > _setupVisorHook = null!;
|
||||
|
||||
private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState )
|
||||
{
|
||||
using var gmp = MetaChanger.ChangeGmp( _parent, drawObject );
|
||||
return _setupVisorHook.Original( drawObject, modelId, visorState );
|
||||
}
|
||||
|
||||
// RSP
|
||||
private delegate void RspSetupCharacterDelegate( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 );
|
||||
|
||||
[Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 88 54 24 ?? 57 41 56", DetourName = nameof( RspSetupCharacterDetour ) )]
|
||||
private readonly Hook< RspSetupCharacterDelegate > _rspSetupCharacterHook = null!;
|
||||
|
||||
private void RspSetupCharacterDetour( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 )
|
||||
{
|
||||
using var rsp = MetaChanger.ChangeCmp( _parent, drawObject );
|
||||
_rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 );
|
||||
}
|
||||
}
|
||||
|
||||
// Small helper to handle setting metadata and reverting it at the end of the function.
|
||||
// Since eqp and eqdp may be called multiple times in a row, we need to count them,
|
||||
|
|
@ -194,11 +190,12 @@ public unsafe partial class PathResolver
|
|||
|
||||
public static MetaChanger ChangeEqp( PathResolver resolver, IntPtr drawObject )
|
||||
{
|
||||
var collection = resolver.GetCollection( drawObject );
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
return ChangeEqp( collection );
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
|
|
@ -207,12 +204,13 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
if( modelType < 10 )
|
||||
{
|
||||
var collection = resolver.GetCollection( drawObject );
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
return ChangeEqdp( collection );
|
||||
}
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
|
|
@ -224,31 +222,33 @@ public unsafe partial class PathResolver
|
|||
|
||||
public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject )
|
||||
{
|
||||
var collection = resolver.GetCollection( drawObject );
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
collection.SetGmpFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Gmp );
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject )
|
||||
{
|
||||
var collection = resolver.GetCollection( drawObject );
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
collection.SetEstFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Est );
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection? collection )
|
||||
public static MetaChanger ChangeCmp( GameObject* gameObject, out ModCollection? collection )
|
||||
{
|
||||
if( resolver.LastGameObject != null )
|
||||
if( gameObject != null )
|
||||
{
|
||||
collection = IdentifyCollection( resolver.LastGameObject );
|
||||
collection = IdentifyCollection( gameObject );
|
||||
if( collection != Penumbra.CollectionManager.Default && collection.HasCache )
|
||||
{
|
||||
collection.SetCmpFiles();
|
||||
|
|
@ -265,12 +265,13 @@ public unsafe partial class PathResolver
|
|||
|
||||
public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject )
|
||||
{
|
||||
var collection = resolver.GetCollection( drawObject );
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
{
|
||||
collection.SetCmpFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Rsp );
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.Diagnostics.CodeAnalysis;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
|
@ -17,18 +19,24 @@ namespace Penumbra.Interop.Resolver;
|
|||
// to resolve paths for character collections.
|
||||
public partial class PathResolver : IDisposable
|
||||
{
|
||||
private readonly ResourceLoader _loader;
|
||||
public bool Enabled { get; private set; }
|
||||
|
||||
public PathResolver( ResourceLoader loader )
|
||||
private readonly ResourceLoader _loader;
|
||||
private static readonly CutsceneCharacters Cutscenes = new();
|
||||
private static readonly DrawObjectState DrawObjects = new();
|
||||
private readonly AnimationState _animations;
|
||||
private readonly PathState _paths;
|
||||
private readonly MetaState _meta;
|
||||
private readonly MaterialState _materials;
|
||||
|
||||
public unsafe PathResolver( ResourceLoader loader )
|
||||
{
|
||||
_loader = loader;
|
||||
SignatureHelper.Initialise( this );
|
||||
SetupHumanHooks();
|
||||
SetupWeaponHooks();
|
||||
SetupMonsterHooks();
|
||||
SetupDemiHooks();
|
||||
SetupMetaHooks();
|
||||
_loader = loader;
|
||||
_animations = new AnimationState( DrawObjects );
|
||||
_paths = new PathState( this );
|
||||
_meta = new MetaState( this, _paths.HumanVTable );
|
||||
_materials = new MaterialState( _paths );
|
||||
}
|
||||
|
||||
// The modified resolver that handles game path resolving.
|
||||
|
|
@ -40,10 +48,10 @@ public partial class PathResolver : IDisposable
|
|||
// If not use the default collection.
|
||||
// We can remove paths after they have actually been loaded.
|
||||
// A potential next request will add the path anew.
|
||||
var nonDefault = HandleMaterialSubFiles( type, out var collection )
|
||||
|| PathCollections.TryRemove( gamePath.Path, out collection )
|
||||
|| HandleAnimationFile( type, gamePath, out collection )
|
||||
|| HandleDecalFile( type, gamePath, out collection );
|
||||
var nonDefault = _materials.HandleSubFiles( type, out var collection )
|
||||
|| _paths.Consume( gamePath.Path, out collection )
|
||||
|| _animations.HandleFiles( type, gamePath, out collection )
|
||||
|| DrawObjects.HandleDecalFile( type, gamePath, out collection );
|
||||
if( !nonDefault || collection == null )
|
||||
{
|
||||
collection = Penumbra.CollectionManager.Default;
|
||||
|
|
@ -56,67 +64,10 @@ public partial class PathResolver : IDisposable
|
|||
// so that the functions loading tex and shpk can find that path and use its collection.
|
||||
// We also need to handle defaulted materials against a non-default collection.
|
||||
var path = resolved == null ? gamePath.Path.ToString() : resolved.Value.FullName;
|
||||
HandleMtrlCollection( collection, path, nonDefault, type, resolved, out data );
|
||||
MaterialState.HandleCollection( collection, path, nonDefault, type, resolved, out data );
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out ModCollection? collection )
|
||||
{
|
||||
if( type == ResourceType.Tex
|
||||
&& _lastCreatedCollection != null
|
||||
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) )
|
||||
{
|
||||
collection = _lastCreatedCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HandleAnimationFile( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out ModCollection? collection )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case ResourceType.Tmb:
|
||||
case ResourceType.Pap:
|
||||
case ResourceType.Scd:
|
||||
if( _animationLoadCollection != null )
|
||||
{
|
||||
collection = _animationLoadCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case ResourceType.Avfx:
|
||||
_lastAvfxCollection = _animationLoadCollection ?? Penumbra.CollectionManager.Default;
|
||||
if( _animationLoadCollection != null )
|
||||
{
|
||||
collection = _animationLoadCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case ResourceType.Atex:
|
||||
if( _lastAvfxCollection != null )
|
||||
{
|
||||
collection = _lastAvfxCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
if( _animationLoadCollection != null )
|
||||
{
|
||||
collection = _animationLoadCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if( Enabled )
|
||||
|
|
@ -125,15 +76,12 @@ public partial class PathResolver : IDisposable
|
|||
}
|
||||
|
||||
Enabled = true;
|
||||
InitializeDrawObjects();
|
||||
|
||||
EnableHumanHooks();
|
||||
EnableWeaponHooks();
|
||||
EnableMonsterHooks();
|
||||
EnableDemiHooks();
|
||||
EnableMtrlHooks();
|
||||
EnableDataHooks();
|
||||
EnableMetaHooks();
|
||||
Cutscenes.Enable();
|
||||
DrawObjects.Enable();
|
||||
_animations.Enable();
|
||||
_paths.Enable();
|
||||
_meta.Enable();
|
||||
_materials.Enable();
|
||||
|
||||
_loader.ResolvePathCustomization += CharacterResolver;
|
||||
PluginLog.Debug( "Character Path Resolver enabled." );
|
||||
|
|
@ -147,16 +95,12 @@ public partial class PathResolver : IDisposable
|
|||
}
|
||||
|
||||
Enabled = false;
|
||||
DisableHumanHooks();
|
||||
DisableWeaponHooks();
|
||||
DisableMonsterHooks();
|
||||
DisableDemiHooks();
|
||||
DisableMtrlHooks();
|
||||
DisableDataHooks();
|
||||
DisableMetaHooks();
|
||||
|
||||
DrawObjectToObject.Clear();
|
||||
PathCollections.Clear();
|
||||
_animations.Disable();
|
||||
DrawObjects.Disable();
|
||||
Cutscenes.Disable();
|
||||
_paths.Disable();
|
||||
_meta.Disable();
|
||||
_materials.Disable();
|
||||
|
||||
_loader.ResolvePathCustomization -= CharacterResolver;
|
||||
PluginLog.Debug( "Character Path Resolver disabled." );
|
||||
|
|
@ -165,18 +109,60 @@ public partial class PathResolver : IDisposable
|
|||
public void Dispose()
|
||||
{
|
||||
Disable();
|
||||
DisposeHumanHooks();
|
||||
DisposeWeaponHooks();
|
||||
DisposeMonsterHooks();
|
||||
DisposeDemiHooks();
|
||||
DisposeMtrlHooks();
|
||||
DisposeDataHooks();
|
||||
DisposeMetaHooks();
|
||||
_paths.Dispose();
|
||||
_animations.Dispose();
|
||||
DrawObjects.Dispose();
|
||||
Cutscenes.Dispose();
|
||||
_meta.Dispose();
|
||||
_materials.Dispose();
|
||||
}
|
||||
|
||||
public unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject )
|
||||
public static unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject )
|
||||
{
|
||||
var parent = FindParent( drawObject, out var collection );
|
||||
return ( ( IntPtr )parent, collection );
|
||||
}
|
||||
|
||||
public int CutsceneActor( int idx )
|
||||
=> Cutscenes.GetParentIndex( idx );
|
||||
|
||||
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
|
||||
public static unsafe GameObject* FindParent( IntPtr drawObject, out ModCollection collection )
|
||||
{
|
||||
if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) )
|
||||
{
|
||||
collection = data.Item1;
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
if( DrawObjects.LastGameObject != null
|
||||
&& ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
|
||||
{
|
||||
collection = IdentifyCollection( DrawObjects.LastGameObject );
|
||||
return DrawObjects.LastGameObject;
|
||||
}
|
||||
|
||||
collection = IdentifyCollection( null );
|
||||
return null;
|
||||
}
|
||||
|
||||
private static unsafe ModCollection? GetCollection( IntPtr drawObject )
|
||||
{
|
||||
var parent = FindParent( drawObject, out var collection );
|
||||
if( parent == null || collection == Penumbra.CollectionManager.Default )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return collection.HasCache ? collection : null;
|
||||
}
|
||||
|
||||
internal IEnumerable< KeyValuePair< Utf8String, ModCollection > > PathCollections
|
||||
=> _paths.Paths;
|
||||
|
||||
internal IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjectMap
|
||||
=> DrawObjects.DrawObjects;
|
||||
|
||||
internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors
|
||||
=> Cutscenes.Actors;
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ using OtterGui;
|
|||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Interop.Loader;
|
||||
using Penumbra.Interop.Resolver;
|
||||
using Penumbra.Interop.Structs;
|
||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||
|
||||
|
|
@ -155,45 +156,63 @@ public partial class ConfigWindow
|
|||
return;
|
||||
}
|
||||
|
||||
using var drawTree = ImRaii.TreeNode( "Draw Object to Object" );
|
||||
if( drawTree )
|
||||
using( var drawTree = ImRaii.TreeNode( "Draw Object to Object" ) )
|
||||
{
|
||||
using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit );
|
||||
if( table )
|
||||
if( drawTree )
|
||||
{
|
||||
foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectToObject )
|
||||
using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit );
|
||||
if( table )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( ptr.ToString( "X" ) );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( idx.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
||||
var (address, name) =
|
||||
obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
||||
ImGui.TextUnformatted( address );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( name );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( c.Name );
|
||||
foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectMap )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( ptr.ToString( "X" ) );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( idx.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
||||
var (address, name) =
|
||||
obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
||||
ImGui.TextUnformatted( address );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( name );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( c.Name );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawTree.Dispose();
|
||||
|
||||
using var pathTree = ImRaii.TreeNode( "Path Collections" );
|
||||
if( pathTree )
|
||||
using( var pathTree = ImRaii.TreeNode( "Path Collections" ) )
|
||||
{
|
||||
using var table = ImRaii.Table( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit );
|
||||
if( pathTree )
|
||||
{
|
||||
using var table = ImRaii.Table( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit );
|
||||
if( table )
|
||||
{
|
||||
foreach( var (path, collection) in _window._penumbra.PathResolver.PathCollections )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( collection.Name );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var cutsceneTree = ImRaii.TreeNode( "Cutscene Actors" );
|
||||
if( cutsceneTree )
|
||||
{
|
||||
using var table = ImRaii.Table( "###PCutsceneResolverTable", 2, ImGuiTableFlags.SizingFixedFit );
|
||||
if( table )
|
||||
{
|
||||
foreach( var (path, collection) in _window._penumbra.PathResolver.PathCollections )
|
||||
foreach( var (idx, actor) in _window._penumbra.PathResolver.CutsceneActors )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
|
||||
ImGui.TextUnformatted( $"Cutscene Actor {idx}" );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( collection.Name );
|
||||
ImGui.TextUnformatted( actor.Name.ToString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue