From 46134611540e2d6d93eda56cf2014471e8c4f849 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 21 May 2022 20:49:11 +0200 Subject: [PATCH] Add Initialized / Disposed IPC, start the rest of the plugin only after obtaining the default meta files, --- Penumbra/Api/PenumbraIpc.cs | 309 +++++++++++++++------------ Penumbra/Interop/CharacterUtility.cs | 85 +++++--- Penumbra/Penumbra.cs | 40 +++- 3 files changed, 252 insertions(+), 182 deletions(-) diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 2f77cc61..8d2575ce 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -6,163 +6,188 @@ using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using Penumbra.GameData.Enums; -namespace Penumbra.Api +namespace Penumbra.Api; + +public class PenumbraIpc : IDisposable { - public class PenumbraIpc : IDisposable + public const string LabelProviderInitialized = "Penumbra.Initialized"; + public const string LabelProviderDisposed = "Penumbra.Disposed"; + public const string LabelProviderApiVersion = "Penumbra.ApiVersion"; + public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName"; + public const string LabelProviderRedrawObject = "Penumbra.RedrawObject"; + public const string LabelProviderRedrawAll = "Penumbra.RedrawAll"; + public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; + public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; + + public const string LabelProviderChangedItemTooltip = "Penumbra.ChangedItemTooltip"; + public const string LabelProviderChangedItemClick = "Penumbra.ChangedItemClick"; + public const string LabelProviderGetChangedItems = "Penumbra.GetChangedItems"; + + internal ICallGateProvider< object? >? ProviderInitialized; + internal ICallGateProvider< object? >? ProviderDisposed; + internal ICallGateProvider< int >? ProviderApiVersion; + internal ICallGateProvider< string, int, object >? ProviderRedrawName; + internal ICallGateProvider< GameObject, int, object >? ProviderRedrawObject; + internal ICallGateProvider< int, object >? ProviderRedrawAll; + internal ICallGateProvider< string, string >? ProviderResolveDefault; + internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; + internal ICallGateProvider< ChangedItemType, uint, object >? ProviderChangedItemTooltip; + internal ICallGateProvider< MouseButton, ChangedItemType, uint, object >? ProviderChangedItemClick; + internal ICallGateProvider< string, IReadOnlyDictionary< string, object? > >? ProviderGetChangedItems; + + internal readonly IPenumbraApi Api; + + private static RedrawType CheckRedrawType( int value ) { - public const string LabelProviderApiVersion = "Penumbra.ApiVersion"; - public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName"; - public const string LabelProviderRedrawObject = "Penumbra.RedrawObject"; - public const string LabelProviderRedrawAll = "Penumbra.RedrawAll"; - public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; - public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; - - public const string LabelProviderChangedItemTooltip = "Penumbra.ChangedItemTooltip"; - public const string LabelProviderChangedItemClick = "Penumbra.ChangedItemClick"; - public const string LabelProviderGetChangedItems = "Penumbra.GetChangedItems"; - - internal ICallGateProvider< int >? ProviderApiVersion; - internal ICallGateProvider< string, int, object >? ProviderRedrawName; - internal ICallGateProvider< GameObject, int, object >? ProviderRedrawObject; - internal ICallGateProvider< int, object >? ProviderRedrawAll; - internal ICallGateProvider< string, string >? ProviderResolveDefault; - internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; - internal ICallGateProvider< ChangedItemType, uint, object >? ProviderChangedItemTooltip; - internal ICallGateProvider< MouseButton, ChangedItemType, uint, object >? ProviderChangedItemClick; - internal ICallGateProvider< string, IReadOnlyDictionary< string, object? > >? ProviderGetChangedItems; - - internal readonly IPenumbraApi Api; - - private static RedrawType CheckRedrawType( int value ) + var type = ( RedrawType )value; + if( Enum.IsDefined( type ) ) { - var type = ( RedrawType )value; - if( Enum.IsDefined( type ) ) - { - return type; - } - - throw new Exception( "The integer provided for a Redraw Function was not a valid RedrawType." ); + return type; } - private void OnClick( MouseButton click, object? item ) + throw new Exception( "The integer provided for a Redraw Function was not a valid RedrawType." ); + } + + private void OnClick( MouseButton click, object? item ) + { + var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item ); + ProviderChangedItemClick?.SendMessage( click, type, id ); + } + + private void OnTooltip( object? item ) + { + var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item ); + ProviderChangedItemTooltip?.SendMessage( type, id ); + } + + + public PenumbraIpc( DalamudPluginInterface pi, IPenumbraApi api ) + { + Api = api; + + try { - var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item ); - ProviderChangedItemClick?.SendMessage( click, type, id ); + ProviderInitialized = pi.GetIpcProvider< object? >( LabelProviderInitialized ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderInitialized}:\n{e}" ); } - private void OnTooltip( object? item ) + try { - var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item ); - ProviderChangedItemTooltip?.SendMessage( type, id ); + ProviderDisposed = pi.GetIpcProvider( LabelProviderDisposed ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderDisposed}:\n{e}" ); } - - public PenumbraIpc( DalamudPluginInterface pi, IPenumbraApi api ) + try { - Api = api; - - try - { - ProviderApiVersion = pi.GetIpcProvider< int >( LabelProviderApiVersion ); - ProviderApiVersion.RegisterFunc( () => api.ApiVersion ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderApiVersion}:\n{e}" ); - } - - try - { - ProviderRedrawName = pi.GetIpcProvider< string, int, object >( LabelProviderRedrawName ); - ProviderRedrawName.RegisterAction( ( s, i ) => api.RedrawObject( s, CheckRedrawType( i ) ) ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawName}:\n{e}" ); - } - - try - { - ProviderRedrawObject = pi.GetIpcProvider< GameObject, int, object >( LabelProviderRedrawObject ); - ProviderRedrawObject.RegisterAction( ( o, i ) => api.RedrawObject( o, CheckRedrawType( i ) ) ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawObject}:\n{e}" ); - } - - try - { - ProviderRedrawAll = pi.GetIpcProvider< int, object >( LabelProviderRedrawAll ); - ProviderRedrawAll.RegisterAction( i => api.RedrawAll( CheckRedrawType( i ) ) ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawAll}:\n{e}" ); - } - - try - { - ProviderResolveDefault = pi.GetIpcProvider< string, string >( LabelProviderResolveDefault ); - ProviderResolveDefault.RegisterFunc( api.ResolvePath ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderResolveDefault}:\n{e}" ); - } - - try - { - ProviderResolveCharacter = pi.GetIpcProvider< string, string, string >( LabelProviderResolveCharacter ); - ProviderResolveCharacter.RegisterFunc( api.ResolvePath ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderResolveCharacter}:\n{e}" ); - } - - try - { - ProviderChangedItemTooltip = pi.GetIpcProvider< ChangedItemType, uint, object >( LabelProviderChangedItemTooltip ); - api.ChangedItemTooltip += OnTooltip; - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemTooltip}:\n{e}" ); - } - - try - { - ProviderChangedItemClick = pi.GetIpcProvider< MouseButton, ChangedItemType, uint, object >( LabelProviderChangedItemClick ); - api.ChangedItemClicked += OnClick; - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemClick}:\n{e}" ); - } - - try - { - ProviderGetChangedItems = pi.GetIpcProvider< string, IReadOnlyDictionary< string, object? > >( LabelProviderGetChangedItems ); - ProviderGetChangedItems.RegisterFunc( api.GetChangedItemsForCollection ); - } - catch( Exception e ) - { - PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemClick}:\n{e}" ); - } + ProviderApiVersion = pi.GetIpcProvider< int >( LabelProviderApiVersion ); + ProviderApiVersion.RegisterFunc( () => api.ApiVersion ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderApiVersion}:\n{e}" ); } - public void Dispose() + try { - ProviderApiVersion?.UnregisterFunc(); - ProviderRedrawName?.UnregisterAction(); - ProviderRedrawObject?.UnregisterAction(); - ProviderRedrawAll?.UnregisterAction(); - ProviderResolveDefault?.UnregisterFunc(); - ProviderResolveCharacter?.UnregisterFunc(); - ProviderGetChangedItems?.UnregisterFunc(); - Api.ChangedItemClicked -= OnClick; - Api.ChangedItemTooltip -= OnTooltip; + ProviderRedrawName = pi.GetIpcProvider< string, int, object >( LabelProviderRedrawName ); + ProviderRedrawName.RegisterAction( ( s, i ) => api.RedrawObject( s, CheckRedrawType( i ) ) ); } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawName}:\n{e}" ); + } + + try + { + ProviderRedrawObject = pi.GetIpcProvider< GameObject, int, object >( LabelProviderRedrawObject ); + ProviderRedrawObject.RegisterAction( ( o, i ) => api.RedrawObject( o, CheckRedrawType( i ) ) ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawObject}:\n{e}" ); + } + + try + { + ProviderRedrawAll = pi.GetIpcProvider< int, object >( LabelProviderRedrawAll ); + ProviderRedrawAll.RegisterAction( i => api.RedrawAll( CheckRedrawType( i ) ) ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawAll}:\n{e}" ); + } + + try + { + ProviderResolveDefault = pi.GetIpcProvider< string, string >( LabelProviderResolveDefault ); + ProviderResolveDefault.RegisterFunc( api.ResolvePath ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderResolveDefault}:\n{e}" ); + } + + try + { + ProviderResolveCharacter = pi.GetIpcProvider< string, string, string >( LabelProviderResolveCharacter ); + ProviderResolveCharacter.RegisterFunc( api.ResolvePath ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderResolveCharacter}:\n{e}" ); + } + + try + { + ProviderChangedItemTooltip = pi.GetIpcProvider< ChangedItemType, uint, object >( LabelProviderChangedItemTooltip ); + api.ChangedItemTooltip += OnTooltip; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemTooltip}:\n{e}" ); + } + + try + { + ProviderChangedItemClick = pi.GetIpcProvider< MouseButton, ChangedItemType, uint, object >( LabelProviderChangedItemClick ); + api.ChangedItemClicked += OnClick; + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemClick}:\n{e}" ); + } + + try + { + ProviderGetChangedItems = pi.GetIpcProvider>( LabelProviderGetChangedItems ); + ProviderGetChangedItems.RegisterFunc( api.GetChangedItemsForCollection ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemClick}:\n{e}" ); + } + + ProviderInitialized?.SendMessage(); + } + + public void Dispose() + { + ProviderDisposed?.SendMessage(); + ProviderInitialized?.UnregisterFunc(); + ProviderApiVersion?.UnregisterFunc(); + ProviderRedrawName?.UnregisterAction(); + ProviderRedrawObject?.UnregisterAction(); + ProviderRedrawAll?.UnregisterAction(); + ProviderResolveDefault?.UnregisterFunc(); + ProviderResolveCharacter?.UnregisterFunc(); + ProviderGetChangedItems?.UnregisterFunc(); + Api.ChangedItemClicked -= OnClick; + Api.ChangedItemTooltip -= OnTooltip; } } \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index 6123bb22..c57d6043 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -1,9 +1,7 @@ using System; using System.Linq; -using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; -using ImGuiScene; namespace Penumbra.Interop; @@ -13,15 +11,12 @@ public unsafe class CharacterUtility : IDisposable [Signature( "48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? 00 48 8D 8E ?? ?? 00 00 E8 ?? ?? ?? 00 33 D2", ScanType = ScanType.StaticAddress )] private readonly Structs.CharacterUtility** _characterUtilityAddress = null; - // The initial function in which all the character resources get loaded. - public delegate void LoadDataFilesDelegate( Structs.CharacterUtility* characterUtility ); - - [Signature( "E8 ?? ?? ?? 00 48 8D 8E ?? ?? 00 00 E8 ?? ?? ?? 00 33 D2", DetourName = "LoadDataFilesDetour")] - public Hook< LoadDataFilesDelegate > LoadDataFilesHook = null!; - public Structs.CharacterUtility* Address => *_characterUtilityAddress; + public bool Ready { get; private set; } + public event Action LoadingFinished; + // The relevant indices depend on which meta manipulations we allow for. // The defines are set in the project configuration. public static readonly int[] RelevantIndices @@ -33,7 +28,8 @@ public unsafe class CharacterUtility : IDisposable .Append( Structs.CharacterUtility.GmpIdx ) #endif #if USE_EQDP - .Concat( Enumerable.Range( Structs.CharacterUtility.EqdpStartIdx, Structs.CharacterUtility.NumEqdpFiles ).Where( i => i != 17 ) ) // TODO: Female Hrothgar + .Concat( Enumerable.Range( Structs.CharacterUtility.EqdpStartIdx, Structs.CharacterUtility.NumEqdpFiles ) + .Where( i => i != 17 ) ) // TODO: Female Hrothgar #endif #if USE_CMP .Append( Structs.CharacterUtility.HumanCmpIdx ) @@ -57,38 +53,53 @@ public unsafe class CharacterUtility : IDisposable { SignatureHelper.Initialise( this ); - if( Address->EqpResource != null && Address->EqpResource->Data != null ) - { - LoadDefaultResources(); - } - else - { - LoadDataFilesHook.Enable(); - } - } - - // Self-disabling hook to set default resources after loading them. - private void LoadDataFilesDetour( Structs.CharacterUtility* characterUtility ) - { - LoadDataFilesHook.Original( characterUtility ); - LoadDefaultResources(); - PluginLog.Debug( "Character Utility resources loaded and defaults stored, disabling hook." ); - LoadDataFilesHook.Disable(); + Dalamud.Framework.Update += LoadDefaultResources; + LoadingFinished += () => PluginLog.Debug( "Loading of CharacterUtility finished." ); } // We store the default data of the resources so we can always restore them. - private void LoadDefaultResources() + private void LoadDefaultResources( object _ ) { + var missingCount = 0; + if( Address == null ) + { + return; + } + for( var i = 0; i < RelevantIndices.Length; ++i ) { - var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ]; - DefaultResources[ i ] = resource->GetData(); + if( DefaultResources[ i ].Size == 0 ) + { + var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ]; + var data = resource->GetData(); + if( data.Data != IntPtr.Zero && data.Length != 0 ) + { + DefaultResources[ i ] = data; + } + else + { + ++missingCount; + } + } + } + + if( missingCount == 0 ) + { + Dalamud.Framework.Update -= LoadDefaultResources; + Ready = true; + LoadingFinished.Invoke(); } } // Set the data of one of the stored resources to a given pointer and length. public bool SetResource( int resourceIdx, IntPtr data, int length ) { + if( !Ready ) + { + PluginLog.Error( $"Can not set resource {resourceIdx}: CharacterUtility not ready yet." ); + return false; + } + var resource = Address->Resource( resourceIdx ); var ret = resource->SetData( data, length ); PluginLog.Verbose( "Set resource {Idx} to 0x{NewData:X} ({NewLength} bytes).", resourceIdx, ( ulong )data, length ); @@ -98,6 +109,12 @@ public unsafe class CharacterUtility : IDisposable // Reset the data of one of the stored resources to its default values. public void ResetResource( int resourceIdx ) { + if( !Ready ) + { + PluginLog.Error( $"Can not reset {resourceIdx}: CharacterUtility not ready yet." ); + return; + } + var relevantIdx = ReverseIndices[ resourceIdx ]; var (data, length) = DefaultResources[ relevantIdx ]; var resource = Address->Resource( resourceIdx ); @@ -108,16 +125,22 @@ public unsafe class CharacterUtility : IDisposable // Return all relevant resources to the default resource. public void ResetAll() { + if( !Ready ) + { + PluginLog.Error( "Can not reset all resources: CharacterUtility not ready yet." ); + return; + } + foreach( var idx in RelevantIndices ) { ResetResource( idx ); } - PluginLog.Debug( "Reset all CharacterUtility resources to default." ); + + PluginLog.Debug( "Reset all CharacterUtility resources to default." ); } public void Dispose() { ResetAll(); - LoadDataFilesHook.Dispose(); } } \ No newline at end of file diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 8c8148aa..98848d63 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -23,11 +23,32 @@ using Penumbra.Mods; namespace Penumbra; -public class Penumbra : IDalamudPlugin +public class MainClass : IDalamudPlugin { - public string Name - => "Penumbra"; + private Penumbra? _penumbra; + private readonly CharacterUtility _characterUtility; + public MainClass( DalamudPluginInterface pluginInterface ) + { + Dalamud.Initialize( pluginInterface ); + _characterUtility = new CharacterUtility(); + _characterUtility.LoadingFinished += () + => _penumbra = new Penumbra( _characterUtility ); + } + + public void Dispose() + { + _penumbra?.Dispose(); + _characterUtility.Dispose(); + } + + public string Name + => Penumbra.Name; +} + +public class Penumbra : IDisposable +{ + public const string Name = "Penumbra"; private const string CommandName = "/penumbra"; public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; @@ -60,9 +81,10 @@ public class Penumbra : IDalamudPlugin internal WebServer? WebServer; - public Penumbra( DalamudPluginInterface pluginInterface ) + public Penumbra( CharacterUtility characterUtility ) { - Dalamud.Initialize( pluginInterface ); + CharacterUtility = characterUtility; + Framework = new FrameworkManager(); GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage ); Backup.CreateBackup( PenumbraBackupFiles() ); @@ -75,7 +97,6 @@ public class Penumbra : IDalamudPlugin } ResidentResources = new ResidentResourceManager(); - CharacterUtility = new CharacterUtility(); Redirects = new SimpleRedirectManager(); MetaFileManager = new MetaFileManager(); ResourceLoader = new ResourceLoader( this ); @@ -94,9 +115,6 @@ public class Penumbra : IDalamudPlugin ResidentResources.Reload(); - Api = new PenumbraApi( this ); - Ipc = new PenumbraIpc( pluginInterface, Api ); - SubscribeItemLinks(); SetupInterface( out _configWindow, out _launchButton, out _windowSystem ); if( Config.EnableHttpApi ) @@ -123,6 +141,10 @@ public class Penumbra : IDalamudPlugin } ResidentResources.Reload(); + + Api = new PenumbraApi( this ); + Ipc = new PenumbraIpc( Dalamud.PluginInterface, Api ); + SubscribeItemLinks(); } private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system )