From dcdc6d1be1f26427ea3482f516278ef18c40e83f Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 16:09:31 +0200 Subject: [PATCH 1/6] 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 ); } } } From e0000c9ef90015d80ca76e67a3a329965e93b753 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 16:17:01 +0200 Subject: [PATCH 2/6] remove ottergui from material --- Penumbra/Interop/Resolver/PathResolver.Material.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs index 485eb790..b4a6c494 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Material.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs @@ -1,10 +1,10 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; 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; @@ -97,11 +97,9 @@ public unsafe partial class PathResolver #if DEBUG PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path ); #endif - IntPtr gameObjAddr = IntPtr.Zero; - if ( Dalamud.Objects.FindFirst(f => f.Name.TextValue == name, out var gameObj ) ) - { - gameObjAddr = gameObj.Address; - } + + var objFromObjTable = Dalamud.Objects.FirstOrDefault( f => f.Name.TextValue == name ); + IntPtr gameObjAddr = objFromObjTable?.Address ?? IntPtr.Zero; _paths.SetCollection( gameObjAddr, path, collection ); } else From 75182d094b8fa76890884ba8dc6f3186aad23b6a Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 19:36:41 +0200 Subject: [PATCH 3/6] changes to LinkedModCollection nullability --- Penumbra/Collections/LinkedModCollection.cs | 16 +++++----------- .../Interop/Resolver/PathResolver.PathState.cs | 4 ++-- .../Resolver/PathResolver.ResolverHooks.cs | 4 ++-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Penumbra/Collections/LinkedModCollection.cs b/Penumbra/Collections/LinkedModCollection.cs index 80cd74c7..93575544 100644 --- a/Penumbra/Collections/LinkedModCollection.cs +++ b/Penumbra/Collections/LinkedModCollection.cs @@ -1,4 +1,4 @@ -using System; +using System; using FFXIVClientStructs.FFXIV.Client.Game.Object; namespace Penumbra.Collections; @@ -6,11 +6,7 @@ namespace Penumbra.Collections; public class LinkedModCollection { private IntPtr? _associatedGameObject; - public IntPtr AssociatedGameObject - { - get => _associatedGameObject ?? IntPtr.Zero; - set => _associatedGameObject = value; - } + public IntPtr AssociatedGameObject = IntPtr.Zero; public ModCollection ModCollection; public LinkedModCollection(ModCollection modCollection) @@ -18,15 +14,13 @@ public class LinkedModCollection ModCollection = modCollection; } - public LinkedModCollection(IntPtr? gameObject, ModCollection collection) + public LinkedModCollection(IntPtr gameObject, ModCollection collection) { - AssociatedGameObject = gameObject ?? IntPtr.Zero; + AssociatedGameObject = gameObject; ModCollection = collection; } - public unsafe LinkedModCollection(GameObject* gameObject, ModCollection collection) + public unsafe LinkedModCollection(GameObject* gameObject, ModCollection collection) : this((IntPtr)gameObject, collection) { - AssociatedGameObject = ( IntPtr )gameObject; - ModCollection = collection; } } diff --git a/Penumbra/Interop/Resolver/PathResolver.PathState.cs b/Penumbra/Interop/Resolver/PathResolver.PathState.cs index 6ee4aefc..6a1d745a 100644 --- a/Penumbra/Interop/Resolver/PathResolver.PathState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.PathState.cs @@ -81,7 +81,7 @@ public unsafe partial class PathResolver // Just add or remove the resolved path. [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] - public IntPtr ResolvePath( IntPtr? gameObject, ModCollection collection, IntPtr path ) + public IntPtr ResolvePath( IntPtr gameObject, ModCollection collection, IntPtr path ) { if( path == IntPtr.Zero ) { @@ -94,7 +94,7 @@ public unsafe partial class PathResolver } // Special handling for paths so that we do not store non-owned temporary strings in the dictionary. - public void SetCollection( IntPtr? gameObject, Utf8String path, ModCollection collection ) + public void SetCollection( IntPtr gameObject, Utf8String path, ModCollection collection ) { if( _pathCollections.ContainsKey( path ) || path.IsOwned ) { diff --git a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs index 2b0bd8aa..adc3dfeb 100644 --- a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs +++ b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs @@ -227,7 +227,7 @@ public partial class PathResolver // Implementation [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private IntPtr ResolvePath( IntPtr drawObject, IntPtr path ) - => _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _), FindParent( drawObject, out var collection ) == null + => _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _) ?? IntPtr.Zero, FindParent( drawObject, out var collection ) == null ? Penumbra.CollectionManager.Default : collection.ModCollection, path ); @@ -251,7 +251,7 @@ public partial class PathResolver } parent = FindParent( parentObject, out collection ); - return _parent._paths.ResolvePath( (IntPtr?)parent, parent == null + return _parent._paths.ResolvePath( (IntPtr?)parent ?? IntPtr.Zero, parent == null ? Penumbra.CollectionManager.Default : collection.ModCollection, path ); } From d12a3dd152cfeb08ce79d73c53ba044022803103 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Sep 2022 13:30:07 +0200 Subject: [PATCH 4/6] Rework ResolveData. --- Penumbra/Api/PenumbraApi.cs | 11 +-- Penumbra/Collections/LinkedModCollection.cs | 26 ------- Penumbra/Collections/ResolveData.cs | 46 ++++++++++++ .../Interop/Loader/ResourceLoader.Debug.cs | 10 +-- .../Loader/ResourceLoader.Replacement.cs | 8 +-- Penumbra/Interop/Loader/ResourceLoader.cs | 6 +- .../Resolver/PathResolver.AnimationState.cs | 71 ++++++++++--------- .../Resolver/PathResolver.DrawObjectState.cs | 22 +++--- .../Resolver/PathResolver.Identification.cs | 25 ++++--- .../Interop/Resolver/PathResolver.Material.cs | 34 ++++----- .../Interop/Resolver/PathResolver.Meta.cs | 50 ++++++------- .../Resolver/PathResolver.PathState.cs | 12 ++-- .../Resolver/PathResolver.ResolverHooks.cs | 2 +- Penumbra/Interop/Resolver/PathResolver.cs | 46 ++++++------ 14 files changed, 199 insertions(+), 170 deletions(-) delete mode 100644 Penumbra/Collections/LinkedModCollection.cs create mode 100644 Penumbra/Collections/ResolveData.cs diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 0546ac53..f6292d82 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -67,12 +67,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi } Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections; - Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded; + Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded; } public unsafe void Dispose() { - Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded; + Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded; Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections; _penumbra = null; _lumina = null; @@ -93,10 +93,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi return Penumbra.Config.ModDirectory; } - private unsafe void OnResourceLoaded( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, LinkedModCollection? resolveData ) + private unsafe void OnResourceLoaded( ResourceHandle* _, Utf8GamePath originalPath, FullPath? manipulatedPath, + ResolveData resolveData ) { - if( resolveData == null ) return; - GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), manipulatedPath?.ToString() ?? originalPath.ToString() ); + GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), + manipulatedPath?.ToString() ?? originalPath.ToString() ); } public event Action< string, bool >? ModDirectoryChanged diff --git a/Penumbra/Collections/LinkedModCollection.cs b/Penumbra/Collections/LinkedModCollection.cs deleted file mode 100644 index 93575544..00000000 --- a/Penumbra/Collections/LinkedModCollection.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using FFXIVClientStructs.FFXIV.Client.Game.Object; - -namespace Penumbra.Collections; - -public class LinkedModCollection -{ - private IntPtr? _associatedGameObject; - public IntPtr AssociatedGameObject = IntPtr.Zero; - public ModCollection ModCollection; - - public LinkedModCollection(ModCollection modCollection) - { - ModCollection = modCollection; - } - - public LinkedModCollection(IntPtr gameObject, ModCollection collection) - { - AssociatedGameObject = gameObject; - ModCollection = collection; - } - - public unsafe LinkedModCollection(GameObject* gameObject, ModCollection collection) : this((IntPtr)gameObject, collection) - { - } -} diff --git a/Penumbra/Collections/ResolveData.cs b/Penumbra/Collections/ResolveData.cs new file mode 100644 index 00000000..348b06a9 --- /dev/null +++ b/Penumbra/Collections/ResolveData.cs @@ -0,0 +1,46 @@ +using System; +using FFXIVClientStructs.FFXIV.Client.Game.Object; + +namespace Penumbra.Collections; + +public readonly struct ResolveData +{ + public static readonly ResolveData Invalid = new(ModCollection.Empty); + + public readonly ModCollection ModCollection; + public readonly IntPtr AssociatedGameObject; + + public bool Valid + => ModCollection != ModCollection.Empty; + + public ResolveData() + { + ModCollection = ModCollection.Empty; + AssociatedGameObject = IntPtr.Zero; + } + + public ResolveData( ModCollection collection, IntPtr gameObject ) + { + ModCollection = collection; + AssociatedGameObject = gameObject; + } + + public ResolveData( ModCollection collection ) + : this( collection, IntPtr.Zero ) + { } + + public override string ToString() + => ModCollection.Name; +} + +public static class ResolveDataExtensions +{ + public static ResolveData ToResolveData( this ModCollection collection ) + => new(collection); + + public static ResolveData ToResolveData( this ModCollection collection, IntPtr ptr ) + => new(collection, ptr); + + public static unsafe ResolveData ToResolveData( this ModCollection collection, void* ptr ) + => new(collection, ( IntPtr )ptr); +} \ No newline at end of file diff --git a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs index 4e162bca..ada91c0a 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs @@ -48,7 +48,7 @@ public unsafe partial class ResourceLoader public Utf8GamePath OriginalPath; public FullPath ManipulatedPath; public ResourceCategory Category; - public object? ResolverInfo; + public ResolveData ResolverInfo; public ResourceType Extension; } @@ -59,18 +59,18 @@ public unsafe partial class ResourceLoader public void EnableDebug() { - _decRefHook?.Enable(); + _decRefHook.Enable(); ResourceLoaded += AddModifiedDebugInfo; } public void DisableDebug() { - _decRefHook?.Disable(); + _decRefHook.Disable(); ResourceLoaded -= AddModifiedDebugInfo; } private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, - LinkedModCollection? resolverInfo ) + ResolveData resolverInfo ) { if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 ) { @@ -244,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, LinkedModCollection? _ ) + private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData _ ) { 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 a9a996f6..f14f19b5 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -114,18 +114,18 @@ public unsafe partial class ResourceLoader // Use the default method of path replacement. - public static (FullPath?, LinkedModCollection?) DefaultResolver( Utf8GamePath path ) + public static (FullPath?, ResolveData) DefaultResolver( Utf8GamePath path ) { var resolved = Penumbra.CollectionManager.Default.ResolvePath( path ); - return ( resolved, new LinkedModCollection( Penumbra.CollectionManager.Default ) ); + return ( resolved, Penumbra.CollectionManager.Default.ToResolveData() ); } // Try all resolve path subscribers or use the default replacer. - private (FullPath?, LinkedModCollection?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) + private (FullPath?, ResolveData) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) { if( !DoReplacements || _incMode.Value ) { - return ( null, null ); + return ( null, ResolveData.Invalid ); } path = path.ToLower(); diff --git a/Penumbra/Interop/Loader/ResourceLoader.cs b/Penumbra/Interop/Loader/ResourceLoader.cs index 8f23d71f..44d55214 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.cs @@ -117,9 +117,9 @@ public unsafe partial class ResourceLoader : IDisposable // Event fired whenever a resource is returned. // 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. + // resolveData is additional data returned by the current ResolvePath function which can contain the collection and associated game object. public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, - LinkedModCollection? resolveData ); + ResolveData resolveData ); public event ResourceLoadedDelegate? ResourceLoaded; @@ -134,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?, LinkedModCollection?) ret ); + out (FullPath?, ResolveData) ret ); public event ResolvePathDelegate? ResolvePathCustomization; diff --git a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs index d6106a87..7211d109 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 LinkedModCollection? _animationLoadCollection; - private LinkedModCollection? _lastAvfxCollection; + private ResolveData _animationLoadData = ResolveData.Invalid; + private ResolveData _lastAvfxData = ResolveData.Invalid; public AnimationState( DrawObjectState drawObjectState ) { @@ -24,46 +24,48 @@ public unsafe partial class PathResolver SignatureHelper.Initialise( this ); } - public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out LinkedModCollection? collection ) + public bool HandleFiles( ResourceType type, Utf8GamePath _, out ResolveData resolveData ) { switch( type ) { case ResourceType.Tmb: case ResourceType.Pap: case ResourceType.Scd: - if( _animationLoadCollection != null ) + if( _animationLoadData.Valid ) { - collection = _animationLoadCollection; + resolveData = _animationLoadData; return true; } break; case ResourceType.Avfx: - _lastAvfxCollection = _animationLoadCollection ?? new LinkedModCollection(Penumbra.CollectionManager.Default); - if( _animationLoadCollection != null ) + _lastAvfxData = _animationLoadData.Valid + ? _animationLoadData + : Penumbra.CollectionManager.Default.ToResolveData(); + if( _animationLoadData.Valid ) { - collection = _animationLoadCollection; + resolveData = _animationLoadData; return true; } break; case ResourceType.Atex: - if( _lastAvfxCollection != null ) + if( _lastAvfxData.Valid ) { - collection = _lastAvfxCollection; + resolveData = _lastAvfxData; return true; } - if( _animationLoadCollection != null ) + if( _animationLoadData.Valid ) { - collection = _animationLoadCollection; + resolveData = _animationLoadData; return true; } break; } - collection = null; + resolveData = ResolveData.Invalid; return false; } @@ -107,7 +109,7 @@ public unsafe partial class PathResolver private ulong LoadTimelineResourcesDetour( IntPtr timeline ) { ulong ret; - var old = _animationLoadCollection; + var old = _animationLoadData; try { if( timeline != IntPtr.Zero ) @@ -117,11 +119,11 @@ public unsafe partial class PathResolver if( idx >= 0 && idx < Dalamud.Objects.Length ) { var obj = Dalamud.Objects[ idx ]; - _animationLoadCollection = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : null; + _animationLoadData = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : ResolveData.Invalid; } else { - _animationLoadCollection = null; + _animationLoadData = ResolveData.Invalid; } } } @@ -130,7 +132,7 @@ public unsafe partial class PathResolver ret = _loadTimelineResourcesHook.Original( timeline ); } - _animationLoadCollection = old; + _animationLoadData = old; return ret; } @@ -145,11 +147,14 @@ public unsafe partial class PathResolver private void CharacterBaseLoadAnimationDetour( IntPtr drawObject ) { - var last = _animationLoadCollection; - _animationLoadCollection = _drawObjectState.LastCreatedCollection - ?? ( FindParent( drawObject, out var collection ) != null ? collection : new LinkedModCollection(Penumbra.CollectionManager.Default) ); + var last = _animationLoadData; + _animationLoadData = _drawObjectState.LastCreatedCollection.Valid + ? _drawObjectState.LastCreatedCollection + : FindParent( drawObject, out var collection ) != null + ? collection + : Penumbra.CollectionManager.Default.ToResolveData(); _characterBaseLoadAnimationHook.Original( drawObject ); - _animationLoadCollection = last; + _animationLoadData = last; } @@ -160,10 +165,10 @@ public unsafe partial class PathResolver private ulong LoadSomeAvfxDetour( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 ) { - var last = _animationLoadCollection; - _animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); + var last = _animationLoadData; + _animationLoadData = IdentifyCollection( ( GameObject* )gameObject ); var ret = _loadSomeAvfxHook.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 ); - _animationLoadCollection = last; + _animationLoadData = last; return ret; } @@ -177,18 +182,18 @@ public unsafe partial class PathResolver private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 ) { var timelinePtr = a1 + 0x50; - var last = _animationLoadCollection; + var last = _animationLoadData; 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 ) ); + _animationLoadData = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) ); } } _loadSomePapHook.Original( a1, a2, a3, a4 ); - _animationLoadCollection = last; + _animationLoadData = last; } // Seems to load character actions when zoning or changing class, maybe. @@ -197,10 +202,10 @@ public unsafe partial class PathResolver private void SomeActionLoadDetour( IntPtr gameObject ) { - var last = _animationLoadCollection; - _animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); + var last = _animationLoadData; + _animationLoadData = IdentifyCollection( ( GameObject* )gameObject ); _someActionLoadHook.Original( gameObject ); - _animationLoadCollection = last; + _animationLoadData = last; } [Signature( "E8 ?? ?? ?? ?? 44 84 A3", DetourName = nameof( SomeOtherAvfxDetour ) )] @@ -208,11 +213,11 @@ public unsafe partial class PathResolver private void SomeOtherAvfxDetour( IntPtr unk ) { - var last = _animationLoadCollection; + var last = _animationLoadData; var gameObject = ( GameObject* )( unk - 0x8D0 ); - _animationLoadCollection = IdentifyCollection( gameObject ); + _animationLoadData = IdentifyCollection( gameObject ); _someOtherAvfxHook.Original( unk ); - _animationLoadCollection = last; + _animationLoadData = last; } } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index 1e6ed477..7c9690fc 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, (LinkedModCollection, int) > > DrawObjects + public IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjects => _drawObjectToObject; public int Count => _drawObjectToObject.Count; - public bool TryGetValue( IntPtr drawObject, out (LinkedModCollection, int) value, out GameObject* gameObject ) + public bool TryGetValue( IntPtr drawObject, out (ResolveData, 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 LinkedModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject ) + public ResolveData CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject ) { if( parentObject == IntPtr.Zero && LastGameObject != null ) { @@ -49,26 +49,26 @@ public unsafe partial class PathResolver return collection; } - return null; + return ResolveData.Invalid; } - public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out LinkedModCollection? collection ) + public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData ) { if( type == ResourceType.Tex - && LastCreatedCollection != null + && LastCreatedCollection.Valid && gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) ) { - collection = LastCreatedCollection!; + resolveData = LastCreatedCollection; return true; } - collection = null; + resolveData = ResolveData.Invalid; return false; } - public LinkedModCollection? LastCreatedCollection + public ResolveData 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, (LinkedModCollection, int) > _drawObjectToObject = new(); - private LinkedModCollection? _lastCreatedCollection; + private readonly Dictionary< IntPtr, (ResolveData, int) > _drawObjectToObject = new(); + private ResolveData _lastCreatedCollection = ResolveData.Invalid; // Keep track of created DrawObjects that are CharacterBase, // and use the last game object that called EnableDraw to link them. diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index 9b0586dd..39efe25b 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -40,7 +40,7 @@ public unsafe partial class PathResolver return null; } - var ui = ( AtkUnitBase* )addon; + var ui = ( AtkUnitBase* )addon; var nodeId = Dalamud.GameData.GetExcelSheet< Title >()?.GetRow( *_inspectTitleId )?.IsPrefix == true ? 2u : 6u; var text = ( AtkTextNode* )ui->UldManager.SearchNodeById( nodeId ); @@ -61,7 +61,8 @@ public unsafe partial class PathResolver { return null; } - var data = *( byte** )( (byte*) agent + 0x28 ); + + var data = *( byte** )( ( byte* )agent + 0x28 ); if( data == null ) { return null; @@ -139,11 +140,11 @@ public unsafe partial class PathResolver } // Identify the correct collection for a GameObject by index and name. - private static LinkedModCollection IdentifyCollection( GameObject* gameObject ) + private static ResolveData IdentifyCollection( GameObject* gameObject ) { if( gameObject == null ) { - return new LinkedModCollection(Penumbra.CollectionManager.Default); + return new ResolveData( Penumbra.CollectionManager.Default ); } try @@ -153,8 +154,9 @@ public unsafe partial class PathResolver // Actors are also not named. So use Yourself > Players > Racial > Default. if( !Dalamud.ClientState.IsLoggedIn ) { - return new LinkedModCollection(gameObject, Penumbra.CollectionManager.ByType( CollectionType.Yourself ) - ?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default )); + var collection = Penumbra.CollectionManager.ByType( CollectionType.Yourself ) + ?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default ); + return collection.ToResolveData( gameObject ); } else { @@ -163,7 +165,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 new LinkedModCollection((IntPtr)gameObject, Penumbra.CollectionManager.Default); + return Penumbra.CollectionManager.Default.ToResolveData( gameObject ); } string? actorName = null; @@ -174,7 +176,7 @@ public unsafe partial class PathResolver if( actorName.Length > 0 && CollectionByActorName( actorName, out var actorCollection ) ) { - return new LinkedModCollection(gameObject, actorCollection); + return actorCollection.ToResolveData( gameObject ); } } @@ -193,17 +195,18 @@ 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 new LinkedModCollection(gameObject, CollectionByActorName( actualName, out var c ) + var collection = CollectionByActorName( actualName, out var c ) ? c : CollectionByActor( actualName, gameObject, out c ) ? c - : Penumbra.CollectionManager.Default); + : Penumbra.CollectionManager.Default; + return collection.ToResolveData( gameObject ); } } catch( Exception e ) { PluginLog.Error( $"Error identifying collection:\n{e}" ); - return new LinkedModCollection(gameObject, Penumbra.CollectionManager.Default); + return Penumbra.CollectionManager.Default.ToResolveData( gameObject ); } } diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs index b4a6c494..6e81dbac 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Material.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs @@ -21,7 +21,7 @@ public unsafe partial class PathResolver { private readonly PathState _paths; - private LinkedModCollection? _mtrlCollection; + private ResolveData _mtrlData = ResolveData.Invalid; public MaterialState( PathState paths ) { @@ -30,30 +30,30 @@ 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 LinkedModCollection? collection ) + public bool HandleSubFiles( ResourceType type, out ResolveData collection ) { - if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk ) + if( _mtrlData.Valid && type is ResourceType.Tex or ResourceType.Shpk ) { - collection = _mtrlCollection; + collection = _mtrlData; return true; } - collection = null; + collection = ResolveData.Invalid; return false; } // Materials need to be set per collection so they can load their textures independently from each other. - public static void HandleCollection( LinkedModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, - out (FullPath?, LinkedModCollection?) data ) + public static void HandleCollection( ResolveData resolveData, string path, bool nonDefault, ResourceType type, FullPath? resolved, + out (FullPath?, ResolveData) data ) { if( nonDefault && type == ResourceType.Mtrl ) { - var fullPath = new FullPath( $"|{collection.ModCollection.Name}_{collection.ModCollection.ChangeCounter}|{path}" ); - data = ( fullPath, collection ); + var fullPath = new FullPath( $"|{resolveData.ModCollection.Name}_{resolveData.ModCollection.ChangeCounter}|{path}" ); + data = ( fullPath, resolveData ); } else { - data = ( resolved, collection ); + data = ( resolved, resolveData ); } } @@ -74,8 +74,8 @@ public unsafe partial class PathResolver public void Dispose() { Disable(); - _loadMtrlShpkHook?.Dispose(); - _loadMtrlTexHook?.Dispose(); + _loadMtrlShpkHook.Dispose(); + _loadMtrlTexHook.Dispose(); } // We need to set the correct collection for the actual material path that is loaded @@ -97,9 +97,9 @@ public unsafe partial class PathResolver #if DEBUG PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path ); #endif - + var objFromObjTable = Dalamud.Objects.FirstOrDefault( f => f.Name.TextValue == name ); - IntPtr gameObjAddr = objFromObjTable?.Address ?? IntPtr.Zero; + var gameObjAddr = objFromObjTable?.Address ?? IntPtr.Zero; _paths.SetCollection( gameObjAddr, path, collection ); } else @@ -127,7 +127,7 @@ public unsafe partial class PathResolver { LoadMtrlHelper( mtrlResourceHandle ); var ret = _loadMtrlTexHook.Original( mtrlResourceHandle ); - _mtrlCollection = null; + _mtrlData = ResolveData.Invalid; return ret; } @@ -139,7 +139,7 @@ public unsafe partial class PathResolver { LoadMtrlHelper( mtrlResourceHandle ); var ret = _loadMtrlShpkHook.Original( mtrlResourceHandle ); - _mtrlCollection = null; + _mtrlData = ResolveData.Invalid; return ret; } @@ -152,7 +152,7 @@ public unsafe partial class PathResolver var mtrl = ( MtrlResource* )mtrlResourceHandle; var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true ); - _mtrlCollection = _paths.TryGetValue( mtrlPath, out var c ) ? c : null; + _mtrlData = _paths.TryGetValue( mtrlPath, out var c ) ? c : ResolveData.Invalid; } } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index a2699544..e305b6db 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -79,8 +79,8 @@ public unsafe partial class PathResolver private void OnModelLoadCompleteDetour( IntPtr drawObject ) { - var collection = GetCollection( drawObject ); - if( collection != null ) + var collection = GetResolveData( drawObject ); + if( collection.Valid ) { using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); @@ -106,8 +106,8 @@ public unsafe partial class PathResolver return; } - var collection = GetCollection( drawObject ); - if( collection != null ) + var collection = GetResolveData( drawObject ); + if( collection.Valid ) { using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); @@ -212,24 +212,24 @@ public unsafe partial class PathResolver return new MetaChanger( MetaManipulation.Type.Eqp ); } - public static MetaChanger ChangeEqp( PathResolver resolver, IntPtr drawObject ) + public static MetaChanger ChangeEqp( PathResolver _, IntPtr drawObject ) { - var collection = GetCollection( drawObject ); - if( collection != null ) + var resolveData = GetResolveData( drawObject ); + if( resolveData.Valid ) { - return ChangeEqp( collection.ModCollection ); + return ChangeEqp( resolveData.ModCollection ); } return new MetaChanger( MetaManipulation.Type.Unknown ); } // We only need to change anything if it is actually equipment here. - public static MetaChanger ChangeEqdp( PathResolver resolver, IntPtr drawObject, uint modelType ) + public static MetaChanger ChangeEqdp( PathResolver _, IntPtr drawObject, uint modelType ) { if( modelType < 10 ) { - var collection = GetCollection( drawObject ); - if( collection != null ) + var collection = GetResolveData( drawObject ); + if( collection.Valid ) { return ChangeEqdp( collection.ModCollection ); } @@ -246,10 +246,10 @@ public unsafe partial class PathResolver public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject ) { - var collection = GetCollection( drawObject ); - if( collection != null ) + var resolveData = GetResolveData( drawObject ); + if( resolveData.Valid ) { - collection.ModCollection.SetGmpFiles(); + resolveData.ModCollection.SetGmpFiles(); return new MetaChanger( MetaManipulation.Type.Gmp ); } @@ -258,30 +258,30 @@ public unsafe partial class PathResolver public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject ) { - var collection = GetCollection( drawObject ); - if( collection != null ) + var resolveData = GetResolveData( drawObject ); + if( resolveData.Valid ) { - collection.ModCollection.SetEstFiles(); + resolveData.ModCollection.SetEstFiles(); return new MetaChanger( MetaManipulation.Type.Est ); } return new MetaChanger( MetaManipulation.Type.Unknown ); } - public static MetaChanger ChangeCmp( GameObject* gameObject, out LinkedModCollection? collection ) + public static MetaChanger ChangeCmp( GameObject* gameObject, out ResolveData resolveData ) { if( gameObject != null ) { - collection = IdentifyCollection( gameObject ); - if( collection.ModCollection != Penumbra.CollectionManager.Default && collection.ModCollection.HasCache ) + resolveData = IdentifyCollection( gameObject ); + if( resolveData.ModCollection != Penumbra.CollectionManager.Default && resolveData.ModCollection.HasCache ) { - collection.ModCollection.SetCmpFiles(); + resolveData.ModCollection.SetCmpFiles(); return new MetaChanger( MetaManipulation.Type.Rsp ); } } else { - collection = null; + resolveData = ResolveData.Invalid; } return new MetaChanger( MetaManipulation.Type.Unknown ); @@ -289,10 +289,10 @@ public unsafe partial class PathResolver public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject ) { - var collection = GetCollection( drawObject ); - if( collection != null ) + var resolveData = GetResolveData( drawObject ); + if( resolveData.Valid ) { - collection.ModCollection.SetCmpFiles(); + resolveData.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 6a1d745a..f5c11771 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, LinkedModCollection > _pathCollections = new(); + private readonly ConcurrentDictionary< Utf8String, ResolveData > _pathCollections = new(); public PathState( PathResolver parent ) { @@ -70,13 +70,13 @@ public unsafe partial class PathResolver public int Count => _pathCollections.Count; - public IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > Paths + public IEnumerable< KeyValuePair< Utf8String, ResolveData > > Paths => _pathCollections; - public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection ) + public bool TryGetValue( Utf8String path, out ResolveData collection ) => _pathCollections.TryGetValue( path, out collection ); - public bool Consume( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection ) + public bool Consume( Utf8String path, out ResolveData collection ) => _pathCollections.TryRemove( path, out collection ); // Just add or remove the resolved path. @@ -98,11 +98,11 @@ public unsafe partial class PathResolver { if( _pathCollections.ContainsKey( path ) || path.IsOwned ) { - _pathCollections[ path ] = new LinkedModCollection(gameObject, collection); + _pathCollections[ path ] = collection.ToResolveData( gameObject ); } else { - _pathCollections[ path.Clone() ] = new LinkedModCollection(gameObject, collection); + _pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject ); } } } diff --git a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs index adc3dfeb..02729c07 100644 --- a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs +++ b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs @@ -245,7 +245,7 @@ public partial class PathResolver var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject; var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject ); - if( parentCollection != null ) + if( parentCollection.Valid ) { return _parent._paths.ResolvePath( (IntPtr)FindParent(parentObject, out _), parentCollection.ModCollection, path ); } diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index 76164318..06536b30 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?, LinkedModCollection?) data ) + private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) 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, @@ -48,23 +48,23 @@ 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 = _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 ) + var nonDefault = _materials.HandleSubFiles( type, out var resolveData ) + || _paths.Consume( gamePath.Path, out resolveData ) + || _animations.HandleFiles( type, gamePath, out resolveData ) + || DrawObjects.HandleDecalFile( type, gamePath, out resolveData ); + if( !nonDefault || !resolveData.Valid ) { - collection = new LinkedModCollection(Penumbra.CollectionManager.Default); + resolveData = Penumbra.CollectionManager.Default.ToResolveData(); } // Resolve using character/default collection first, otherwise forced, as usual. - var resolved = collection.ModCollection.ResolvePath( gamePath ); + var resolved = resolveData.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. // We also need to handle defaulted materials against a non-default collection. var path = resolved == null ? gamePath.Path.ToString() : resolved.Value.FullName; - MaterialState.HandleCollection( collection, path, nonDefault, type, resolved, out data ); + MaterialState.HandleCollection( resolveData, path, nonDefault, type, resolved, out data ); return true; } @@ -117,50 +117,50 @@ public partial class PathResolver : IDisposable _materials.Dispose(); } - public static unsafe (IntPtr, LinkedModCollection) IdentifyDrawObject( IntPtr drawObject ) + public static unsafe (IntPtr, ResolveData) IdentifyDrawObject( IntPtr drawObject ) { - var parent = FindParent( drawObject, out var collection ); - return ( ( IntPtr )parent, collection ); + var parent = FindParent( drawObject, out var resolveData ); + return ( ( IntPtr )parent, resolveData ); } 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 LinkedModCollection collection ) + public static unsafe GameObject* FindParent( IntPtr drawObject, out ResolveData resolveData ) { if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) ) { - collection = data.Item1; + resolveData = data.Item1; return gameObject; } if( DrawObjects.LastGameObject != null && ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) ) { - collection = IdentifyCollection( DrawObjects.LastGameObject ); + resolveData = IdentifyCollection( DrawObjects.LastGameObject ); return DrawObjects.LastGameObject; } - collection = IdentifyCollection( null ); + resolveData = IdentifyCollection( null ); return null; } - private static unsafe LinkedModCollection? GetCollection( IntPtr drawObject ) + private static unsafe ResolveData GetResolveData( IntPtr drawObject ) { - var parent = FindParent( drawObject, out var collection ); - if( parent == null || collection.ModCollection == Penumbra.CollectionManager.Default ) + var parent = FindParent( drawObject, out var resolveData ); + if( parent == null || resolveData.ModCollection == Penumbra.CollectionManager.Default ) { - return null; + return ResolveData.Invalid; } - return collection.ModCollection.HasCache ? collection : null; + return resolveData.ModCollection.HasCache ? resolveData : ResolveData.Invalid; } - internal IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > PathCollections + internal IEnumerable< KeyValuePair< Utf8String, ResolveData > > PathCollections => _paths.Paths; - internal IEnumerable< KeyValuePair< IntPtr, (LinkedModCollection, int) > > DrawObjectMap + internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap => DrawObjects.DrawObjects; internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors From 0f35dd69f9e018ffe363dd2d6d4b4c06621de3d2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Sep 2022 14:01:12 +0200 Subject: [PATCH 5/6] Add IPC test, optimize tester a little, only call event when game object available. --- Penumbra/Api/IPenumbraApi.cs | 4 +- Penumbra/Api/IpcTester.cs | 88 +++++++++++++++++++++------- Penumbra/Api/PenumbraApi.cs | 7 ++- Penumbra/Api/PenumbraIpc.cs | 23 ++++---- Penumbra/UI/ConfigWindow.DebugTab.cs | 1 + 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 57e13baa..05b56c80 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -80,7 +80,9 @@ public interface IPenumbraApi : IPenumbraApiBase // so you can apply flag changes after finishing. public event CreatedCharacterBaseDelegate? CreatedCharacterBase; - public event GameObjectResourceResolvedDelegate GameObjectResourceResolved; + // Triggered whenever a resource is redirected by Penumbra for a specific, identified game object. + // Does not trigger if the resource is not requested for a known game object. + 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/IpcTester.cs b/Penumbra/Api/IpcTester.cs index c251eb35..f5140db4 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -33,9 +33,12 @@ public class IpcTester : IDisposable private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged; private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? > _characterBaseCreating; private readonly ICallGateSubscriber< IntPtr, string, IntPtr, object? > _characterBaseCreated; + private readonly ICallGateSubscriber< IntPtr, string, string, object? > _gameObjectResourcePathResolved; private readonly List< DateTimeOffset > _initializedList = new(); private readonly List< DateTimeOffset > _disposedList = new(); + private bool _subscribed = false; + public IpcTester( DalamudPluginInterface pi, PenumbraIpc ipc ) { @@ -51,31 +54,51 @@ public class IpcTester : IDisposable _characterBaseCreating = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase ); _characterBaseCreated = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, object? >( PenumbraIpc.LabelProviderCreatedCharacterBase ); - _initialized.Subscribe( AddInitialized ); - _disposed.Subscribe( AddDisposed ); - _redrawn.Subscribe( SetLastRedrawn ); - _preSettingsDraw.Subscribe( UpdateLastDrawnMod ); - _postSettingsDraw.Subscribe( UpdateLastDrawnMod ); - _settingChanged.Subscribe( UpdateLastModSetting ); - _characterBaseCreating.Subscribe( UpdateLastCreated ); - _characterBaseCreated.Subscribe( UpdateLastCreated2 ); - _modDirectoryChanged.Subscribe( UpdateModDirectoryChanged ); + _gameObjectResourcePathResolved = + _pi.GetIpcSubscriber< IntPtr, string, string, object? >( PenumbraIpc.LabelProviderGameObjectResourcePathResolved ); + } + + private void SubscribeEvents() + { + if( !_subscribed ) + { + + _initialized.Subscribe( AddInitialized ); + _disposed.Subscribe( AddDisposed ); + _redrawn.Subscribe( SetLastRedrawn ); + _preSettingsDraw.Subscribe( UpdateLastDrawnMod ); + _postSettingsDraw.Subscribe( UpdateLastDrawnMod ); + _settingChanged.Subscribe( UpdateLastModSetting ); + _characterBaseCreating.Subscribe( UpdateLastCreated ); + _characterBaseCreated.Subscribe( UpdateLastCreated2 ); + _modDirectoryChanged.Subscribe( UpdateModDirectoryChanged ); + _gameObjectResourcePathResolved.Subscribe( UpdateGameObjectResourcePath ); + _subscribed = true; + } + } + + public void UnsubscribeEvents() + { + if( _subscribed ) + { + _initialized.Unsubscribe( AddInitialized ); + _disposed.Unsubscribe( AddDisposed ); + _redrawn.Subscribe( SetLastRedrawn ); + _tooltip?.Unsubscribe( AddedTooltip ); + _click?.Unsubscribe( AddedClick ); + _preSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); + _postSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); + _settingChanged.Unsubscribe( UpdateLastModSetting ); + _characterBaseCreating.Unsubscribe( UpdateLastCreated ); + _characterBaseCreated.Unsubscribe( UpdateLastCreated2 ); + _modDirectoryChanged.Unsubscribe( UpdateModDirectoryChanged ); + _gameObjectResourcePathResolved.Unsubscribe( UpdateGameObjectResourcePath ); + _subscribed = false; + } } public void Dispose() - { - _initialized.Unsubscribe( AddInitialized ); - _disposed.Unsubscribe( AddDisposed ); - _redrawn.Subscribe( SetLastRedrawn ); - _tooltip?.Unsubscribe( AddedTooltip ); - _click?.Unsubscribe( AddedClick ); - _preSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); - _postSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); - _settingChanged.Unsubscribe( UpdateLastModSetting ); - _characterBaseCreating.Unsubscribe( UpdateLastCreated ); - _characterBaseCreated.Unsubscribe( UpdateLastCreated2 ); - _modDirectoryChanged.Unsubscribe( UpdateModDirectoryChanged ); - } + => UnsubscribeEvents(); private void AddInitialized() => _initializedList.Add( DateTimeOffset.UtcNow ); @@ -87,6 +110,7 @@ public class IpcTester : IDisposable { try { + SubscribeEvents(); DrawAvailable(); DrawGeneral(); DrawResolve(); @@ -224,6 +248,10 @@ public class IpcTester : IDisposable private string _lastCreatedGameObjectName = string.Empty; private IntPtr _lastCreatedDrawObject = IntPtr.Zero; private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue; + private string _lastResolvedGamePath = string.Empty; + private string _lastResolvedFullPath = string.Empty; + private string _lastResolvedObject = string.Empty; + private DateTimeOffset _lastResolvedGamePathTime = DateTimeOffset.MaxValue; private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 ) { @@ -241,6 +269,15 @@ public class IpcTester : IDisposable _lastCreatedDrawObject = drawObject; } + private unsafe void UpdateGameObjectResourcePath( IntPtr gameObject, string gamePath, string fullPath ) + { + var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject; + _lastResolvedObject = obj != null ? new Utf8String( obj->GetName() ).ToString() : "Unknown"; + _lastResolvedGamePath = gamePath; + _lastResolvedFullPath = fullPath; + _lastResolvedGamePathTime = DateTimeOffset.Now; + } + private void DrawResolve() { using var _ = ImRaii.TreeNode( "Resolve IPC" ); @@ -336,6 +373,13 @@ public class IpcTester : IDisposable ? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" : $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" ); } + + DrawIntro( PenumbraIpc.LabelProviderGameObjectResourcePathResolved, "Last GamePath resolved" ); + if( _lastResolvedGamePathTime < DateTimeOffset.Now ) + { + ImGui.TextUnformatted( + $"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}" ); + } } private string _redrawName = string.Empty; diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index f6292d82..982d5c22 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -96,8 +96,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi private unsafe void OnResourceLoaded( ResourceHandle* _, Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData ) { - GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), - manipulatedPath?.ToString() ?? originalPath.ToString() ); + if( resolveData.AssociatedGameObject != IntPtr.Zero ) + { + GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), + manipulatedPath?.ToString() ?? originalPath.ToString() ); + } } public event Action< string, bool >? ModDirectoryChanged diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index a38aecce..f7c6fd36 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -274,15 +274,15 @@ public partial class PenumbraIpc public partial class PenumbraIpc { - public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; - 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"; - public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase"; + public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; + 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"; + public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase"; public const string LabelProviderGameObjectResourcePathResolved = "Penumbra.GameObjectResourcePathResolved"; internal ICallGateProvider< string, string >? ProviderResolveDefault; @@ -294,7 +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; + internal ICallGateProvider< IntPtr, string, string, object? >? ProviderGameObjectResourcePathResolved; private void InitializeResolveProviders( DalamudPluginInterface pi ) { @@ -392,7 +392,8 @@ public partial class PenumbraIpc try { - ProviderGameObjectResourcePathResolved = pi.GetIpcProvider( LabelProviderGameObjectResourcePathResolved ); + ProviderGameObjectResourcePathResolved = + pi.GetIpcProvider< IntPtr, string, string, object? >( LabelProviderGameObjectResourcePathResolved ); Api.GameObjectResourceResolved += GameObjectResourceResolvdedEvent; } catch( Exception e ) diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 59ea02d2..68169211 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -422,6 +422,7 @@ public partial class ConfigWindow { if( !ImGui.CollapsingHeader( "IPC" ) ) { + _window._penumbra.Ipc.Tester.UnsubscribeEvents(); return; } From 55de29e0ac1d228e596d411b88f0eefdc1bfd97a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Sep 2022 14:09:50 +0200 Subject: [PATCH 6/6] Dispose fix and typo. --- Penumbra/Api/PenumbraIpc.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index f7c6fd36..fa9ada66 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -394,7 +394,7 @@ public partial class PenumbraIpc { ProviderGameObjectResourcePathResolved = pi.GetIpcProvider< IntPtr, string, string, object? >( LabelProviderGameObjectResourcePathResolved ); - Api.GameObjectResourceResolved += GameObjectResourceResolvdedEvent; + Api.GameObjectResourceResolved += GameObjectResourceResolvedEvent; } catch( Exception e ) { @@ -402,7 +402,7 @@ public partial class PenumbraIpc } } - private void GameObjectResourceResolvdedEvent( IntPtr gameObject, string gamePath, string localPath ) + private void GameObjectResourceResolvedEvent( IntPtr gameObject, string gamePath, string localPath ) { ProviderGameObjectResourcePathResolved?.SendMessage( gameObject, gamePath, localPath ); } @@ -415,8 +415,9 @@ public partial class PenumbraIpc ProviderResolveCharacter?.UnregisterFunc(); ProviderReverseResolvePath?.UnregisterFunc(); ProviderReverseResolvePathPlayer?.UnregisterFunc(); - Api.CreatingCharacterBase -= CreatingCharacterBaseEvent; - Api.CreatedCharacterBase -= CreatedCharacterBaseEvent; + Api.CreatingCharacterBase -= CreatingCharacterBaseEvent; + Api.CreatedCharacterBase -= CreatedCharacterBaseEvent; + Api.GameObjectResourceResolved -= GameObjectResourceResolvedEvent; } private void CreatingCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr modelId, IntPtr customize, IntPtr equipData )