diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 44eb92ee..65b406ca 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -18,6 +18,8 @@ public delegate void ChangedItemHover( object? item ); public delegate void ChangedItemClick( MouseButton button, object? item ); public delegate void GameObjectRedrawn( IntPtr objectPtr, int objectTableIndex ); public delegate void ModSettingChanged( ModSettingChange type, string collectionName, string modDirectory, bool inherited ); +public delegate void CreatingCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr customize, IntPtr equipData ); + public enum PenumbraApiEc { Success = 0, @@ -50,13 +52,17 @@ public interface IPenumbraApi : IPenumbraApiBase // Events that are fired before and after the content of a mod settings panel are drawn. // Both are fired inside the child window of the settings panel itself. - public event Action? PreSettingsPanelDraw; - public event Action? PostSettingsPanelDraw; + public event Action< string >? PreSettingsPanelDraw; + public event Action< string >? PostSettingsPanelDraw; // Triggered when the user clicks a listed changed object in a mod tab. public event ChangedItemClick? ChangedItemClicked; public event GameObjectRedrawn? GameObjectRedrawn; + // Triggered when a character base is created and a corresponding gameObject could be found, + // before the Draw Object is actually created, so customize and equipdata can be manipulated beforehand. + public event CreatingCharacterBaseDelegate? CreatingCharacterBase; + // 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 fc50f1aa..40f22ce3 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -31,6 +30,7 @@ public class IpcTester : IDisposable private readonly ICallGateSubscriber< string, object? > _postSettingsDraw; private readonly ICallGateSubscriber< IntPtr, int, object? > _redrawn; private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged; + private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, object? > _characterBaseCreated; private readonly List< DateTimeOffset > _initializedList = new(); private readonly List< DateTimeOffset > _disposedList = new(); @@ -45,12 +45,15 @@ public class IpcTester : IDisposable _preSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPreSettingsDraw ); _postSettingsDraw = _pi.GetIpcSubscriber< string, object? >( PenumbraIpc.LabelProviderPostSettingsDraw ); _settingChanged = _pi.GetIpcSubscriber< ModSettingChange, string, string, bool, object? >( PenumbraIpc.LabelProviderModSettingChanged ); + _characterBaseCreated = + _pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase ); _initialized.Subscribe( AddInitialized ); _disposed.Subscribe( AddDisposed ); _redrawn.Subscribe( SetLastRedrawn ); _preSettingsDraw.Subscribe( UpdateLastDrawnMod ); _postSettingsDraw.Subscribe( UpdateLastDrawnMod ); _settingChanged.Subscribe( UpdateLastModSetting ); + _characterBaseCreated.Subscribe( UpdateLastCreated ); } public void Dispose() @@ -63,6 +66,7 @@ public class IpcTester : IDisposable _preSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); _postSettingsDraw.Unsubscribe( UpdateLastDrawnMod ); _settingChanged.Unsubscribe( UpdateLastModSetting ); + _characterBaseCreated.Unsubscribe( UpdateLastCreated ); } private void AddInitialized() @@ -165,7 +169,8 @@ public class IpcTester : IDisposable DrawIntro( PenumbraIpc.LabelProviderPostSettingsDraw, "Last Drawn Mod" ); ImGui.TextUnformatted( _lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None" ); DrawIntro( PenumbraIpc.LabelProviderApiVersion, "Current Version" ); - ImGui.TextUnformatted( _pi.GetIpcSubscriber< int >( PenumbraIpc.LabelProviderApiVersion ).InvokeFunc().ToString() ); + var (breaking, features) = _pi.GetIpcSubscriber< (int, int) >( PenumbraIpc.LabelProviderApiVersion ).InvokeFunc(); + ImGui.TextUnformatted( $"{breaking}.{features:D4}" ); DrawIntro( PenumbraIpc.LabelProviderGetModDirectory, "Current Mod Directory" ); ImGui.TextUnformatted( _pi.GetIpcSubscriber< string >( PenumbraIpc.LabelProviderGetModDirectory ).InvokeFunc() ); DrawIntro( PenumbraIpc.LabelProviderGetConfiguration, "Configuration" ); @@ -191,11 +196,20 @@ public class IpcTester : IDisposable } } - private string _currentResolvePath = string.Empty; - private string _currentResolveCharacter = string.Empty; - private string _currentDrawObjectString = string.Empty; - private string _currentReversePath = string.Empty; - private IntPtr _currentDrawObject = IntPtr.Zero; + private string _currentResolvePath = string.Empty; + private string _currentResolveCharacter = string.Empty; + private string _currentDrawObjectString = string.Empty; + private string _currentReversePath = string.Empty; + private IntPtr _currentDrawObject = IntPtr.Zero; + private string _lastCreatedGameObjectName = string.Empty; + private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue; + + private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3 ) + { + var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject; + _lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString(); + _lastCreatedGameObjectTime = DateTimeOffset.Now; + } private void DrawResolve() { @@ -263,6 +277,12 @@ public class IpcTester : IDisposable } } } + + DrawIntro( PenumbraIpc.LabelProviderCreatingCharacterBase, "Last Drawobject created" ); + if( _lastCreatedGameObjectTime < DateTimeOffset.Now ) + { + ImGui.TextUnformatted( $"for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" ); + } } private string _redrawName = string.Empty; diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index b81335b9..b15ff691 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -28,11 +28,23 @@ public class PenumbraApi : IDisposable, IPenumbraApi private readonly Dictionary< ModCollection, ModCollection.ModSettingChangeDelegate > _delegates = new(); - public event Action? PreSettingsPanelDraw; - public event Action? PostSettingsPanelDraw; - public event GameObjectRedrawn? GameObjectRedrawn; + public event Action< string >? PreSettingsPanelDraw; + public event Action< string >? PostSettingsPanelDraw; + + public event GameObjectRedrawn? GameObjectRedrawn + { + add => _penumbra!.ObjectReloader.GameObjectRedrawn += value; + remove => _penumbra!.ObjectReloader.GameObjectRedrawn -= value; + } + public event ModSettingChanged? ModSettingChanged; + public event CreatingCharacterBaseDelegate? CreatingCharacterBase + { + add => _penumbra!.PathResolver.CreatingCharacterBase += value; + remove => _penumbra!.PathResolver.CreatingCharacterBase -= value; + } + public bool Valid => _penumbra != null; @@ -42,7 +54,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi _lumina = ( Lumina.GameData? )Dalamud.GameData.GetType() .GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic ) ?.GetValue( Dalamud.GameData ); - _penumbra.ObjectReloader.GameObjectRedrawn += OnGameObjectRedrawn; foreach( var collection in Penumbra.CollectionManager ) { SubscribeToCollection( collection ); @@ -54,7 +65,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi public void Dispose() { Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections; - _penumbra!.ObjectReloader.GameObjectRedrawn -= OnGameObjectRedrawn; _penumbra = null; _lumina = null; foreach( var collection in Penumbra.CollectionManager ) @@ -249,9 +259,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi public PenumbraApiEc AddMod( string modDirectory ) { CheckInitialized(); - var dir = new DirectoryInfo( Path.Combine(Penumbra.ModManager.BasePath.FullName, modDirectory) ); + var dir = new DirectoryInfo( Path.Combine( Penumbra.ModManager.BasePath.FullName, modDirectory ) ); if( !dir.Exists ) + { return PenumbraApiEc.FileMissing; + } Penumbra.ModManager.AddMod( dir ); return PenumbraApiEc.Success; @@ -521,11 +533,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi } } - private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) - { - GameObjectRedrawn?.Invoke( objectAddress, objectTableIndex ); - } - // Resolve a path given by string for a specific collection. private static string ResolvePath( string path, Mod.Manager _, ModCollection collection ) { @@ -645,9 +652,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi } } - public void InvokePreSettingsPanel(string modDirectory) - => PreSettingsPanelDraw?.Invoke(modDirectory); + public void InvokePreSettingsPanel( string modDirectory ) + => PreSettingsPanelDraw?.Invoke( modDirectory ); - public void InvokePostSettingsPanel(string modDirectory) - => PostSettingsPanelDraw?.Invoke(modDirectory); + public void InvokePostSettingsPanel( string modDirectory ) + => PostSettingsPanelDraw?.Invoke( modDirectory ); } \ No newline at end of file diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 6d2cd98e..017b988b 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -52,13 +52,13 @@ public partial class PenumbraIpc public const string LabelProviderPreSettingsDraw = "Penumbra.PreSettingsDraw"; public const string LabelProviderPostSettingsDraw = "Penumbra.PostSettingsDraw"; - internal ICallGateProvider< object? >? ProviderInitialized; - internal ICallGateProvider< object? >? ProviderDisposed; + internal ICallGateProvider< object? >? ProviderInitialized; + internal ICallGateProvider< object? >? ProviderDisposed; internal ICallGateProvider< int >? ProviderApiVersion; - internal ICallGateProvider< string >? ProviderGetModDirectory; - internal ICallGateProvider< string >? ProviderGetConfiguration; - internal ICallGateProvider? ProviderPreSettingsDraw; - internal ICallGateProvider? ProviderPostSettingsDraw; + internal ICallGateProvider< string >? ProviderGetModDirectory; + internal ICallGateProvider< string >? ProviderGetConfiguration; + internal ICallGateProvider< string, object? >? ProviderPreSettingsDraw; + internal ICallGateProvider< string, object? >? ProviderPostSettingsDraw; private void InitializeGeneralProviders( DalamudPluginInterface pi ) { @@ -82,7 +82,7 @@ public partial class PenumbraIpc try { - ProviderApiVersion = pi.GetIpcProvider< int >( LabelProviderApiVersion ); + ProviderApiVersion = pi.GetIpcProvider< (int Breaking, int Features) >( LabelProviderApiVersion ); ProviderApiVersion.RegisterFunc( () => Api.ApiVersion ); } catch( Exception e ) @@ -112,7 +112,7 @@ public partial class PenumbraIpc try { - ProviderPreSettingsDraw = pi.GetIpcProvider( LabelProviderPreSettingsDraw ); + ProviderPreSettingsDraw = pi.GetIpcProvider< string, object? >( LabelProviderPreSettingsDraw ); Api.PreSettingsPanelDraw += InvokeSettingsPreDraw; } catch( Exception e ) @@ -122,7 +122,7 @@ public partial class PenumbraIpc try { - ProviderPostSettingsDraw = pi.GetIpcProvider( LabelProviderPostSettingsDraw ); + ProviderPostSettingsDraw = pi.GetIpcProvider< string, object? >( LabelProviderPostSettingsDraw ); Api.PostSettingsPanelDraw += InvokeSettingsPostDraw; } catch( Exception e ) @@ -136,6 +136,8 @@ public partial class PenumbraIpc ProviderGetConfiguration?.UnregisterFunc(); ProviderGetModDirectory?.UnregisterFunc(); ProviderApiVersion?.UnregisterFunc(); + Api.PreSettingsPanelDraw -= InvokeSettingsPreDraw; + Api.PostSettingsPanelDraw -= InvokeSettingsPostDraw; } } @@ -232,21 +234,23 @@ public partial class PenumbraIpc ProviderRedrawObject?.UnregisterAction(); ProviderRedrawIndex?.UnregisterAction(); ProviderRedrawAll?.UnregisterAction(); - Api.GameObjectRedrawn -= OnGameObjectRedrawn; + Api.GameObjectRedrawn -= OnGameObjectRedrawn; } } public partial class PenumbraIpc { - public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; - public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; - public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo"; - public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath"; + public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; + public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; + public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo"; + public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath"; + public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase"; - internal ICallGateProvider< string, string >? ProviderResolveDefault; - internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; - internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo; - internal ICallGateProvider< string, string, IList< string > >? ProviderReverseResolvePath; + internal ICallGateProvider< string, string >? ProviderResolveDefault; + internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; + internal ICallGateProvider< IntPtr, (IntPtr, string) >? ProviderGetDrawObjectInfo; + internal ICallGateProvider< string, string, IList< string > >? ProviderReverseResolvePath; + internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase; private void InitializeResolveProviders( DalamudPluginInterface pi ) { @@ -289,6 +293,16 @@ public partial class PenumbraIpc { PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetDrawObjectInfo}:\n{e}" ); } + + try + { + ProviderCreatingCharacterBase = pi.GetIpcProvider< IntPtr, string, IntPtr, IntPtr, object? >( LabelProviderCreatingCharacterBase ); + Api.CreatingCharacterBase += CreatingCharacterBaseEvent; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatingCharacterBase}:\n{e}" ); + } } private void DisposeResolveProviders() @@ -297,6 +311,12 @@ public partial class PenumbraIpc ProviderResolveDefault?.UnregisterFunc(); ProviderResolveCharacter?.UnregisterFunc(); ProviderReverseResolvePath?.UnregisterFunc(); + Api.CreatingCharacterBase -= CreatingCharacterBaseEvent; + } + + private void CreatingCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr customize, IntPtr equipData ) + { + ProviderCreatingCharacterBase?.SendMessage( gameObject, collection.Name, customize, equipData ); } } diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 0c9f6c7b..c5d74ce7 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -8,7 +8,6 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; -using Penumbra.GameData.Util; using Penumbra.Interop.Structs; using FileMode = Penumbra.Interop.Structs.FileMode; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; @@ -114,7 +113,7 @@ public unsafe partial class ResourceLoader // Try all resolve path subscribers or use the default replacer. private (FullPath?, object?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) { - if( !DoReplacements || IsInIncRef ) + if( !DoReplacements || IsInIncRef > 0 ) { return ( null, null ); } @@ -270,15 +269,14 @@ public unsafe partial class ResourceLoader // This means, that if the path determined from that is different than the resources path, // a different resource gets loaded or incremented, while the IncRef'd resource stays at 0. // This causes some problems and is hopefully prevented with this. - public bool IsInIncRef { get; private set; } = false; + public int IsInIncRef { get; private set; } = 0; private readonly Hook< ResourceHandleDestructor > _incRefHook; private IntPtr ResourceHandleIncRefDetour( ResourceHandle* handle ) { - var tmp = IsInIncRef; - IsInIncRef = true; + ++IsInIncRef; var ret = _incRefHook.Original( handle ); - IsInIncRef = tmp; + --IsInIncRef; return ret; } } \ No newline at end of file diff --git a/Penumbra/Interop/ObjectReloader.cs b/Penumbra/Interop/ObjectReloader.cs index daf4ae7f..0fcab13e 100644 --- a/Penumbra/Interop/ObjectReloader.cs +++ b/Penumbra/Interop/ObjectReloader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.Types; +using Penumbra.Api; using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; @@ -105,7 +106,7 @@ public sealed unsafe partial class ObjectReloader : IDisposable private readonly List< int > _afterGPoseQueue = new(GPoseSlots); private int _target = -1; - public event Action< IntPtr, int >? GameObjectRedrawn; + public event GameObjectRedrawn? GameObjectRedrawn; public ObjectReloader() => Dalamud.Framework.Update += OnUpdateEvent; diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs index 253cb0e0..ed0bbec4 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -11,6 +11,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; +using Penumbra.Api; using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; @@ -28,11 +29,19 @@ public unsafe partial class PathResolver public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook; private ModCollection? _lastCreatedCollection; + public event CreatingCharacterBaseDelegate? CreatingCharacterBase; + private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) { using var cmp = MetaChanger.ChangeCmp( this, out _lastCreatedCollection ); - var ret = CharacterBaseCreateHook!.Original( a, b, c, d ); + + if( LastGameObject != null ) + { + CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, b, c ); + } + + var ret = CharacterBaseCreateHook!.Original( a, b, c, d ); if( LastGameObject != null ) { DrawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 612fb69e..806fbf94 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -153,6 +153,7 @@ public partial class ConfigWindow return; } + ImGui.TextUnformatted( $"In Increment RefCounter Mode: {Penumbra.ResourceLoader.IsInIncRef}" ); using var drawTree = ImRaii.TreeNode( "Draw Object to Object" ); if( drawTree ) {