From dcdc6d1be1f26427ea3482f516278ef18c40e83f Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 16:09:31 +0200 Subject: [PATCH] add LinkedModCollection to be able to retrospectively verify which gamepath was resolved for which game object --- Penumbra/Api/IPenumbraApi.cs | 3 ++ Penumbra/Api/PenumbraApi.cs | 18 ++++++++--- Penumbra/Api/PenumbraIpc.cs | 17 ++++++++++ Penumbra/Collections/LinkedModCollection.cs | 32 +++++++++++++++++++ .../Interop/Loader/ResourceLoader.Debug.cs | 5 +-- .../Loader/ResourceLoader.Replacement.cs | 7 ++-- Penumbra/Interop/Loader/ResourceLoader.cs | 5 +-- .../Resolver/PathResolver.AnimationState.cs | 10 +++--- .../Resolver/PathResolver.DrawObjectState.cs | 18 +++++------ .../Resolver/PathResolver.Identification.cs | 18 +++++------ .../Interop/Resolver/PathResolver.Material.cs | 18 +++++++---- .../Interop/Resolver/PathResolver.Meta.cs | 24 +++++++------- .../Resolver/PathResolver.PathState.cs | 18 +++++------ .../Resolver/PathResolver.ResolverHooks.cs | 13 ++++---- Penumbra/Interop/Resolver/PathResolver.cs | 20 ++++++------ Penumbra/UI/ConfigWindow.DebugTab.cs | 4 +-- 16 files changed, 151 insertions(+), 79 deletions(-) create mode 100644 Penumbra/Collections/LinkedModCollection.cs diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 69b7479e..57e13baa 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -27,6 +27,7 @@ public delegate void CreatingCharacterBaseDelegate( IntPtr gameObject, ModCollec IntPtr equipData ); public delegate void CreatedCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr drawObject ); +public delegate void GameObjectResourceResolvedDelegate( IntPtr gameObject, string gamePath, string localPath ); public enum PenumbraApiEc { @@ -79,6 +80,8 @@ public interface IPenumbraApi : IPenumbraApiBase // so you can apply flag changes after finishing. public event CreatedCharacterBaseDelegate? CreatedCharacterBase; + public event GameObjectResourceResolvedDelegate GameObjectResourceResolved; + // Queue redrawing of all actors of the given name with the given RedrawType. public void RedrawObject( string name, RedrawType setting ); diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index fb708bbe..0546ac53 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -13,6 +13,7 @@ using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Interop.Resolver; +using Penumbra.Interop.Structs; using Penumbra.Meta.Manipulations; using Penumbra.Mods; @@ -21,7 +22,7 @@ namespace Penumbra.Api; public class PenumbraApi : IDisposable, IPenumbraApi { public (int, int) ApiVersion - => ( 4, 12 ); + => ( 4, 13 ); private Penumbra? _penumbra; private Lumina.GameData? _lumina; @@ -54,7 +55,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi public bool Valid => _penumbra != null; - public PenumbraApi( Penumbra penumbra ) + public unsafe PenumbraApi( Penumbra penumbra ) { _penumbra = penumbra; _lumina = ( Lumina.GameData? )Dalamud.GameData.GetType() @@ -66,10 +67,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi } Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections; + Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded; } - public void Dispose() + public unsafe void Dispose() { + Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded; Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections; _penumbra = null; _lumina = null; @@ -90,6 +93,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Penumbra.Config.ModDirectory; } + private unsafe void OnResourceLoaded( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, LinkedModCollection? resolveData ) + { + if( resolveData == null ) return; + GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), manipulatedPath?.ToString() ?? originalPath.ToString() ); + } + public event Action< string, bool >? ModDirectoryChanged { add => Penumbra.ModManager.ModDirectoryChanged += value; @@ -103,6 +112,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi } public event ChangedItemHover? ChangedItemTooltip; + public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved; public void RedrawObject( int tableIndex, RedrawType setting ) { @@ -232,7 +242,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); var (obj, collection) = PathResolver.IdentifyDrawObject( drawObject ); - return ( obj, collection.Name ); + return ( obj, collection.ModCollection.Name ); } public int GetCutsceneParentIndex( int actor ) diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 34172c92..a38aecce 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -283,6 +283,7 @@ public partial class PenumbraIpc public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath"; public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase"; public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase"; + public const string LabelProviderGameObjectResourcePathResolved = "Penumbra.GameObjectResourcePathResolved"; internal ICallGateProvider< string, string >? ProviderResolveDefault; internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; @@ -293,6 +294,7 @@ public partial class PenumbraIpc internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer; internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase; internal ICallGateProvider< IntPtr, string, IntPtr, object? >? ProviderCreatedCharacterBase; + internal ICallGateProvider? ProviderGameObjectResourcePathResolved; private void InitializeResolveProviders( DalamudPluginInterface pi ) { @@ -387,6 +389,21 @@ public partial class PenumbraIpc { PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatedCharacterBase}:\n{e}" ); } + + try + { + ProviderGameObjectResourcePathResolved = pi.GetIpcProvider( LabelProviderGameObjectResourcePathResolved ); + Api.GameObjectResourceResolved += GameObjectResourceResolvdedEvent; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGameObjectResourcePathResolved}:\n{e}" ); + } + } + + private void GameObjectResourceResolvdedEvent( IntPtr gameObject, string gamePath, string localPath ) + { + ProviderGameObjectResourcePathResolved?.SendMessage( gameObject, gamePath, localPath ); } private void DisposeResolveProviders() diff --git a/Penumbra/Collections/LinkedModCollection.cs b/Penumbra/Collections/LinkedModCollection.cs new file mode 100644 index 00000000..80cd74c7 --- /dev/null +++ b/Penumbra/Collections/LinkedModCollection.cs @@ -0,0 +1,32 @@ +using System; +using FFXIVClientStructs.FFXIV.Client.Game.Object; + +namespace Penumbra.Collections; + +public class LinkedModCollection +{ + private IntPtr? _associatedGameObject; + public IntPtr AssociatedGameObject + { + get => _associatedGameObject ?? IntPtr.Zero; + set => _associatedGameObject = value; + } + public ModCollection ModCollection; + + public LinkedModCollection(ModCollection modCollection) + { + ModCollection = modCollection; + } + + public LinkedModCollection(IntPtr? gameObject, ModCollection collection) + { + AssociatedGameObject = gameObject ?? IntPtr.Zero; + ModCollection = collection; + } + + public unsafe LinkedModCollection(GameObject* gameObject, ModCollection collection) + { + AssociatedGameObject = ( IntPtr )gameObject; + ModCollection = collection; + } +} diff --git a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs index 02af20ce..4e162bca 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs @@ -7,6 +7,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.STD; +using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; @@ -69,7 +70,7 @@ public unsafe partial class ResourceLoader } private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, - object? resolverInfo ) + LinkedModCollection? resolverInfo ) { if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 ) { @@ -243,7 +244,7 @@ public unsafe partial class ResourceLoader private static void LogPath( Utf8GamePath path, bool synchronous ) => PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" ); - private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ ) + private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, LinkedModCollection? _ ) { var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString(); PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" ); diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index bf1932dd..a9a996f6 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -9,6 +9,7 @@ using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; +using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; @@ -113,14 +114,14 @@ public unsafe partial class ResourceLoader // Use the default method of path replacement. - public static (FullPath?, object?) DefaultResolver( Utf8GamePath path ) + public static (FullPath?, LinkedModCollection?) DefaultResolver( Utf8GamePath path ) { var resolved = Penumbra.CollectionManager.Default.ResolvePath( path ); - return ( resolved, null ); + return ( resolved, new LinkedModCollection( Penumbra.CollectionManager.Default ) ); } // Try all resolve path subscribers or use the default replacer. - private (FullPath?, object?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) + private (FullPath?, LinkedModCollection?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) { if( !DoReplacements || _incMode.Value ) { diff --git a/Penumbra/Interop/Loader/ResourceLoader.cs b/Penumbra/Interop/Loader/ResourceLoader.cs index 059c1b5d..8f23d71f 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.cs @@ -2,6 +2,7 @@ using System; using Dalamud.Hooking; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; +using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; @@ -118,7 +119,7 @@ public unsafe partial class ResourceLoader : IDisposable // If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource. // resolveData is additional data returned by the current ResolvePath function and is user-defined. public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, - object? resolveData ); + LinkedModCollection? resolveData ); public event ResourceLoadedDelegate? ResourceLoaded; @@ -133,7 +134,7 @@ public unsafe partial class ResourceLoader : IDisposable // Resolving goes through all subscribed functions in arbitrary order until one returns true, // or uses default resolving if none return true. public delegate bool ResolvePathDelegate( Utf8GamePath path, ResourceCategory category, ResourceType type, int hash, - out (FullPath?, object?) ret ); + out (FullPath?, LinkedModCollection?) ret ); public event ResolvePathDelegate? ResolvePathCustomization; diff --git a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs index 518e4a93..d6106a87 100644 --- a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs @@ -15,8 +15,8 @@ public unsafe partial class PathResolver { private readonly DrawObjectState _drawObjectState; - private ModCollection? _animationLoadCollection; - private ModCollection? _lastAvfxCollection; + private LinkedModCollection? _animationLoadCollection; + private LinkedModCollection? _lastAvfxCollection; public AnimationState( DrawObjectState drawObjectState ) { @@ -24,7 +24,7 @@ public unsafe partial class PathResolver SignatureHelper.Initialise( this ); } - public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out ModCollection? collection ) + public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out LinkedModCollection? collection ) { switch( type ) { @@ -39,7 +39,7 @@ public unsafe partial class PathResolver break; case ResourceType.Avfx: - _lastAvfxCollection = _animationLoadCollection ?? Penumbra.CollectionManager.Default; + _lastAvfxCollection = _animationLoadCollection ?? new LinkedModCollection(Penumbra.CollectionManager.Default); if( _animationLoadCollection != null ) { collection = _animationLoadCollection; @@ -147,7 +147,7 @@ public unsafe partial class PathResolver { var last = _animationLoadCollection; _animationLoadCollection = _drawObjectState.LastCreatedCollection - ?? ( FindParent( drawObject, out var collection ) != null ? collection : Penumbra.CollectionManager.Default ); + ?? ( FindParent( drawObject, out var collection ) != null ? collection : new LinkedModCollection(Penumbra.CollectionManager.Default) ); _characterBaseLoadAnimationHook.Original( drawObject ); _animationLoadCollection = last; } diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index c3c40238..1e6ed477 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -20,13 +20,13 @@ public unsafe partial class PathResolver public static event CreatingCharacterBaseDelegate? CreatingCharacterBase; public static event CreatedCharacterBaseDelegate? CreatedCharacterBase; - public IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjects + public IEnumerable< KeyValuePair< IntPtr, (LinkedModCollection, int) > > DrawObjects => _drawObjectToObject; public int Count => _drawObjectToObject.Count; - public bool TryGetValue( IntPtr drawObject, out (ModCollection, int) value, out GameObject* gameObject ) + public bool TryGetValue( IntPtr drawObject, out (LinkedModCollection, int) value, out GameObject* gameObject ) { gameObject = null; if( !_drawObjectToObject.TryGetValue( drawObject, out value ) ) @@ -40,7 +40,7 @@ public unsafe partial class PathResolver // Set and update a parent object if it exists and a last game object is set. - public ModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject ) + public LinkedModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject ) { if( parentObject == IntPtr.Zero && LastGameObject != null ) { @@ -53,7 +53,7 @@ public unsafe partial class PathResolver } - public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out ModCollection? collection ) + public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out LinkedModCollection? collection ) { if( type == ResourceType.Tex && LastCreatedCollection != null @@ -68,7 +68,7 @@ public unsafe partial class PathResolver } - public ModCollection? LastCreatedCollection + public LinkedModCollection? LastCreatedCollection => _lastCreatedCollection; public GameObject* LastGameObject { get; private set; } @@ -124,8 +124,8 @@ public unsafe partial class PathResolver // 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; + private readonly Dictionary< IntPtr, (LinkedModCollection, int) > _drawObjectToObject = new(); + private LinkedModCollection? _lastCreatedCollection; // Keep track of created DrawObjects that are CharacterBase, // and use the last game object that called EnableDraw to link them. @@ -141,14 +141,14 @@ public unsafe partial class PathResolver if( LastGameObject != null ) { var modelPtr = &a; - CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ( IntPtr )modelPtr, b, c ); + CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c ); } var ret = _characterBaseCreateHook.Original( a, b, c, d ); if( LastGameObject != null ) { _drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); - CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ret ); + CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret ); } return ret; diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index 9ffb6e94..9b0586dd 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -139,11 +139,11 @@ public unsafe partial class PathResolver } // Identify the correct collection for a GameObject by index and name. - private static ModCollection IdentifyCollection( GameObject* gameObject ) + private static LinkedModCollection IdentifyCollection( GameObject* gameObject ) { if( gameObject == null ) { - return Penumbra.CollectionManager.Default; + return new LinkedModCollection(Penumbra.CollectionManager.Default); } try @@ -153,8 +153,8 @@ public unsafe partial class PathResolver // Actors are also not named. So use Yourself > Players > Racial > Default. if( !Dalamud.ClientState.IsLoggedIn ) { - return Penumbra.CollectionManager.ByType( CollectionType.Yourself ) - ?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default ); + return new LinkedModCollection(gameObject, Penumbra.CollectionManager.ByType( CollectionType.Yourself ) + ?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default )); } else { @@ -163,7 +163,7 @@ public unsafe partial class PathResolver && gameObject->ObjectKind == ( byte )ObjectKind.EventNpc && gameObject->DataID is 1011832 or 1011021 ) // cf. "E8 ?? ?? ?? ?? 0F B6 F8 88 45", male or female retainer { - return Penumbra.CollectionManager.Default; + return new LinkedModCollection((IntPtr)gameObject, Penumbra.CollectionManager.Default); } string? actorName = null; @@ -174,7 +174,7 @@ public unsafe partial class PathResolver if( actorName.Length > 0 && CollectionByActorName( actorName, out var actorCollection ) ) { - return actorCollection; + return new LinkedModCollection(gameObject, actorCollection); } } @@ -193,17 +193,17 @@ public unsafe partial class PathResolver ?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString(); // First check temporary character collections, then the own configuration, then special collections. - return CollectionByActorName( actualName, out var c ) + return new LinkedModCollection(gameObject, CollectionByActorName( actualName, out var c ) ? c : CollectionByActor( actualName, gameObject, out c ) ? c - : Penumbra.CollectionManager.Default; + : Penumbra.CollectionManager.Default); } } catch( Exception e ) { PluginLog.Error( $"Error identifying collection:\n{e}" ); - return Penumbra.CollectionManager.Default; + return new LinkedModCollection(gameObject, Penumbra.CollectionManager.Default); } } diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs index 1d92f96d..485eb790 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Material.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs @@ -4,6 +4,7 @@ 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; @@ -20,7 +21,7 @@ public unsafe partial class PathResolver { private readonly PathState _paths; - private ModCollection? _mtrlCollection; + private LinkedModCollection? _mtrlCollection; public MaterialState( PathState paths ) { @@ -29,7 +30,7 @@ public unsafe partial class PathResolver } // 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 ) + public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out LinkedModCollection? collection ) { if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk ) { @@ -42,12 +43,12 @@ public unsafe partial class PathResolver } // 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 ) + public static void HandleCollection( LinkedModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, + out (FullPath?, LinkedModCollection?) data ) { if( nonDefault && type == ResourceType.Mtrl ) { - var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" ); + var fullPath = new FullPath( $"|{collection.ModCollection.Name}_{collection.ModCollection.ChangeCounter}|{path}" ); data = ( fullPath, collection ); } else @@ -96,7 +97,12 @@ public unsafe partial class PathResolver #if DEBUG PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path ); #endif - _paths.SetCollection( path, collection ); + IntPtr gameObjAddr = IntPtr.Zero; + if ( Dalamud.Objects.FindFirst(f => f.Name.TextValue == name, out var gameObj ) ) + { + gameObjAddr = gameObj.Address; + } + _paths.SetCollection( gameObjAddr, path, collection ); } else { diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index 44fe74f2..a2699544 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -82,8 +82,8 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - using var eqp = MetaChanger.ChangeEqp( collection ); - using var eqdp = MetaChanger.ChangeEqdp( collection ); + using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); + using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); _onModelLoadCompleteHook.Original.Invoke( drawObject ); } else @@ -109,8 +109,8 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - using var eqp = MetaChanger.ChangeEqp( collection ); - using var eqdp = MetaChanger.ChangeEqdp( collection ); + using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); + using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); _updateModelsHook.Original.Invoke( drawObject ); } else @@ -217,7 +217,7 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - return ChangeEqp( collection ); + return ChangeEqp( collection.ModCollection ); } return new MetaChanger( MetaManipulation.Type.Unknown ); @@ -231,7 +231,7 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - return ChangeEqdp( collection ); + return ChangeEqdp( collection.ModCollection ); } } @@ -249,7 +249,7 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - collection.SetGmpFiles(); + collection.ModCollection.SetGmpFiles(); return new MetaChanger( MetaManipulation.Type.Gmp ); } @@ -261,21 +261,21 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - collection.SetEstFiles(); + collection.ModCollection.SetEstFiles(); return new MetaChanger( MetaManipulation.Type.Est ); } return new MetaChanger( MetaManipulation.Type.Unknown ); } - public static MetaChanger ChangeCmp( GameObject* gameObject, out ModCollection? collection ) + public static MetaChanger ChangeCmp( GameObject* gameObject, out LinkedModCollection? collection ) { if( gameObject != null ) { collection = IdentifyCollection( gameObject ); - if( collection != Penumbra.CollectionManager.Default && collection.HasCache ) + if( collection.ModCollection != Penumbra.CollectionManager.Default && collection.ModCollection.HasCache ) { - collection.SetCmpFiles(); + collection.ModCollection.SetCmpFiles(); return new MetaChanger( MetaManipulation.Type.Rsp ); } } @@ -292,7 +292,7 @@ public unsafe partial class PathResolver var collection = GetCollection( drawObject ); if( collection != null ) { - collection.SetCmpFiles(); + collection.ModCollection.SetCmpFiles(); return new MetaChanger( MetaManipulation.Type.Rsp ); } diff --git a/Penumbra/Interop/Resolver/PathResolver.PathState.cs b/Penumbra/Interop/Resolver/PathResolver.PathState.cs index ab40fe1e..6ee4aefc 100644 --- a/Penumbra/Interop/Resolver/PathResolver.PathState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.PathState.cs @@ -32,7 +32,7 @@ public unsafe partial class PathResolver private readonly ResolverHooks _monster; // This map links files to their corresponding collection, if it is non-default. - private readonly ConcurrentDictionary< Utf8String, ModCollection > _pathCollections = new(); + private readonly ConcurrentDictionary< Utf8String, LinkedModCollection > _pathCollections = new(); public PathState( PathResolver parent ) { @@ -70,18 +70,18 @@ public unsafe partial class PathResolver public int Count => _pathCollections.Count; - public IEnumerable< KeyValuePair< Utf8String, ModCollection > > Paths + public IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > Paths => _pathCollections; - public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out ModCollection? collection ) + public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection ) => _pathCollections.TryGetValue( path, out collection ); - public bool Consume( Utf8String path, [NotNullWhen( true )] out ModCollection? collection ) + public bool Consume( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? 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 ) + public IntPtr ResolvePath( IntPtr? gameObject, ModCollection collection, IntPtr path ) { if( path == IntPtr.Zero ) { @@ -89,20 +89,20 @@ public unsafe partial class PathResolver } var gamePath = new Utf8String( ( byte* )path ); - SetCollection( gamePath, collection ); + SetCollection( gameObject, 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 ) + public void SetCollection( IntPtr? gameObject, Utf8String path, ModCollection collection ) { if( _pathCollections.ContainsKey( path ) || path.IsOwned ) { - _pathCollections[ path ] = collection; + _pathCollections[ path ] = new LinkedModCollection(gameObject, collection); } else { - _pathCollections[ path.Clone() ] = collection; + _pathCollections[ path.Clone() ] = new LinkedModCollection(gameObject, collection); } } } diff --git a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs index 79c53d31..2b0bd8aa 100644 --- a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs +++ b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.CompilerServices; using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.Collections; namespace Penumbra.Interop.Resolver; @@ -226,9 +227,9 @@ public partial class PathResolver // Implementation [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private IntPtr ResolvePath( IntPtr drawObject, IntPtr path ) - => _parent._paths.ResolvePath( FindParent( drawObject, out var collection ) == null + => _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _), FindParent( drawObject, out var collection ) == null ? Penumbra.CollectionManager.Default - : collection, path ); + : collection.ModCollection, 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 @@ -239,20 +240,20 @@ public partial class PathResolver var parent = FindParent( drawObject, out var collection ); if( parent != null ) { - return _parent._paths.ResolvePath( collection, path ); + return _parent._paths.ResolvePath( (IntPtr)parent, collection.ModCollection, path ); } var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject; var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject ); if( parentCollection != null ) { - return _parent._paths.ResolvePath( parentCollection, path ); + return _parent._paths.ResolvePath( (IntPtr)FindParent(parentObject, out _), parentCollection.ModCollection, path ); } parent = FindParent( parentObject, out collection ); - return _parent._paths.ResolvePath( parent == null + return _parent._paths.ResolvePath( (IntPtr?)parent, parent == null ? Penumbra.CollectionManager.Default - : collection, path ); + : collection.ModCollection, path ); } } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index 300cf1f3..76164318 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -40,7 +40,7 @@ public partial class PathResolver : IDisposable } // The modified resolver that handles game path resolving. - private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, object?) data ) + private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, LinkedModCollection?) data ) { // Check if the path was marked for a specific collection, // or if it is a file loaded by a material, and if we are currently in a material load, @@ -54,11 +54,11 @@ public partial class PathResolver : IDisposable || DrawObjects.HandleDecalFile( type, gamePath, out collection ); if( !nonDefault || collection == null ) { - collection = Penumbra.CollectionManager.Default; + collection = new LinkedModCollection(Penumbra.CollectionManager.Default); } // Resolve using character/default collection first, otherwise forced, as usual. - var resolved = collection.ResolvePath( gamePath ); + var resolved = collection.ModCollection.ResolvePath( gamePath ); // Since mtrl files load their files separately, we need to add the new, resolved path // so that the functions loading tex and shpk can find that path and use its collection. @@ -117,7 +117,7 @@ public partial class PathResolver : IDisposable _materials.Dispose(); } - public static unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject ) + public static unsafe (IntPtr, LinkedModCollection) IdentifyDrawObject( IntPtr drawObject ) { var parent = FindParent( drawObject, out var collection ); return ( ( IntPtr )parent, collection ); @@ -127,7 +127,7 @@ public partial class PathResolver : IDisposable => 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 ) + public static unsafe GameObject* FindParent( IntPtr drawObject, out LinkedModCollection collection ) { if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) ) { @@ -146,21 +146,21 @@ public partial class PathResolver : IDisposable return null; } - private static unsafe ModCollection? GetCollection( IntPtr drawObject ) + private static unsafe LinkedModCollection? GetCollection( IntPtr drawObject ) { var parent = FindParent( drawObject, out var collection ); - if( parent == null || collection == Penumbra.CollectionManager.Default ) + if( parent == null || collection.ModCollection == Penumbra.CollectionManager.Default ) { return null; } - return collection.HasCache ? collection : null; + return collection.ModCollection.HasCache ? collection : null; } - internal IEnumerable< KeyValuePair< Utf8String, ModCollection > > PathCollections + internal IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > PathCollections => _paths.Paths; - internal IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjectMap + internal IEnumerable< KeyValuePair< IntPtr, (LinkedModCollection, int) > > DrawObjectMap => DrawObjects.DrawObjects; internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 764dc452..59ea02d2 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -177,7 +177,7 @@ public partial class ConfigWindow ImGui.TableNextColumn(); ImGui.TextUnformatted( name ); ImGui.TableNextColumn(); - ImGui.TextUnformatted( c.Name ); + ImGui.TextUnformatted( c.ModCollection.Name ); } } } @@ -195,7 +195,7 @@ public partial class ConfigWindow ImGui.TableNextColumn(); ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); ImGui.TableNextColumn(); - ImGui.TextUnformatted( collection.Name ); + ImGui.TextUnformatted( collection.ModCollection.Name ); } } }