Add Initialized / Disposed IPC, start the rest of the plugin only after obtaining the default meta files,

This commit is contained in:
Ottermandias 2022-05-21 20:49:11 +02:00
parent d15ebddf18
commit 4613461154
3 changed files with 252 additions and 182 deletions

View file

@ -6,163 +6,188 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Ipc;
using Penumbra.GameData.Enums; 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"; var type = ( RedrawType )value;
public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName"; if( Enum.IsDefined( type ) )
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; return type;
if( Enum.IsDefined( type ) )
{
return type;
}
throw new Exception( "The integer provided for a Redraw Function was not a valid RedrawType." );
} }
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 ); ProviderInitialized = pi.GetIpcProvider< object? >( LabelProviderInitialized );
ProviderChangedItemClick?.SendMessage( click, type, id ); }
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 ); ProviderDisposed = pi.GetIpcProvider<object?>( LabelProviderDisposed );
ProviderChangedItemTooltip?.SendMessage( type, id ); }
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderDisposed}:\n{e}" );
} }
try
public PenumbraIpc( DalamudPluginInterface pi, IPenumbraApi api )
{ {
Api = api; ProviderApiVersion = pi.GetIpcProvider< int >( LabelProviderApiVersion );
ProviderApiVersion.RegisterFunc( () => api.ApiVersion );
try }
{ catch( Exception e )
ProviderApiVersion = pi.GetIpcProvider< int >( LabelProviderApiVersion ); {
ProviderApiVersion.RegisterFunc( () => api.ApiVersion ); PluginLog.Error( $"Error registering IPC provider for {LabelProviderApiVersion}:\n{e}" );
}
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}" );
}
} }
public void Dispose() try
{ {
ProviderApiVersion?.UnregisterFunc(); ProviderRedrawName = pi.GetIpcProvider< string, int, object >( LabelProviderRedrawName );
ProviderRedrawName?.UnregisterAction(); ProviderRedrawName.RegisterAction( ( s, i ) => api.RedrawObject( s, CheckRedrawType( i ) ) );
ProviderRedrawObject?.UnregisterAction();
ProviderRedrawAll?.UnregisterAction();
ProviderResolveDefault?.UnregisterFunc();
ProviderResolveCharacter?.UnregisterFunc();
ProviderGetChangedItems?.UnregisterFunc();
Api.ChangedItemClicked -= OnClick;
Api.ChangedItemTooltip -= OnTooltip;
} }
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}" );
}
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;
} }
} }

View file

@ -1,9 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Dalamud.Hooking;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using ImGuiScene;
namespace Penumbra.Interop; 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 )] [Signature( "48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? 00 48 8D 8E ?? ?? 00 00 E8 ?? ?? ?? 00 33 D2", ScanType = ScanType.StaticAddress )]
private readonly Structs.CharacterUtility** _characterUtilityAddress = null; 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 public Structs.CharacterUtility* Address
=> *_characterUtilityAddress; => *_characterUtilityAddress;
public bool Ready { get; private set; }
public event Action LoadingFinished;
// The relevant indices depend on which meta manipulations we allow for. // The relevant indices depend on which meta manipulations we allow for.
// The defines are set in the project configuration. // The defines are set in the project configuration.
public static readonly int[] RelevantIndices public static readonly int[] RelevantIndices
@ -33,7 +28,8 @@ public unsafe class CharacterUtility : IDisposable
.Append( Structs.CharacterUtility.GmpIdx ) .Append( Structs.CharacterUtility.GmpIdx )
#endif #endif
#if USE_EQDP #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 #endif
#if USE_CMP #if USE_CMP
.Append( Structs.CharacterUtility.HumanCmpIdx ) .Append( Structs.CharacterUtility.HumanCmpIdx )
@ -57,38 +53,53 @@ public unsafe class CharacterUtility : IDisposable
{ {
SignatureHelper.Initialise( this ); SignatureHelper.Initialise( this );
if( Address->EqpResource != null && Address->EqpResource->Data != null ) Dalamud.Framework.Update += LoadDefaultResources;
{ LoadingFinished += () => PluginLog.Debug( "Loading of CharacterUtility finished." );
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();
} }
// We store the default data of the resources so we can always restore them. // 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 ) for( var i = 0; i < RelevantIndices.Length; ++i )
{ {
var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ]; if( DefaultResources[ i ].Size == 0 )
DefaultResources[ i ] = resource->GetData(); {
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. // Set the data of one of the stored resources to a given pointer and length.
public bool SetResource( int resourceIdx, IntPtr data, int 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 resource = Address->Resource( resourceIdx );
var ret = resource->SetData( data, length ); var ret = resource->SetData( data, length );
PluginLog.Verbose( "Set resource {Idx} to 0x{NewData:X} ({NewLength} bytes).", resourceIdx, ( ulong )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. // Reset the data of one of the stored resources to its default values.
public void ResetResource( int resourceIdx ) public void ResetResource( int resourceIdx )
{ {
if( !Ready )
{
PluginLog.Error( $"Can not reset {resourceIdx}: CharacterUtility not ready yet." );
return;
}
var relevantIdx = ReverseIndices[ resourceIdx ]; var relevantIdx = ReverseIndices[ resourceIdx ];
var (data, length) = DefaultResources[ relevantIdx ]; var (data, length) = DefaultResources[ relevantIdx ];
var resource = Address->Resource( resourceIdx ); var resource = Address->Resource( resourceIdx );
@ -108,16 +125,22 @@ public unsafe class CharacterUtility : IDisposable
// Return all relevant resources to the default resource. // Return all relevant resources to the default resource.
public void ResetAll() public void ResetAll()
{ {
if( !Ready )
{
PluginLog.Error( "Can not reset all resources: CharacterUtility not ready yet." );
return;
}
foreach( var idx in RelevantIndices ) foreach( var idx in RelevantIndices )
{ {
ResetResource( idx ); ResetResource( idx );
} }
PluginLog.Debug( "Reset all CharacterUtility resources to default." );
PluginLog.Debug( "Reset all CharacterUtility resources to default." );
} }
public void Dispose() public void Dispose()
{ {
ResetAll(); ResetAll();
LoadDataFilesHook.Dispose();
} }
} }

View file

@ -23,11 +23,32 @@ using Penumbra.Mods;
namespace Penumbra; namespace Penumbra;
public class Penumbra : IDalamudPlugin public class MainClass : IDalamudPlugin
{ {
public string Name private Penumbra? _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"; private const string CommandName = "/penumbra";
public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty;
@ -60,9 +81,10 @@ public class Penumbra : IDalamudPlugin
internal WebServer? WebServer; internal WebServer? WebServer;
public Penumbra( DalamudPluginInterface pluginInterface ) public Penumbra( CharacterUtility characterUtility )
{ {
Dalamud.Initialize( pluginInterface ); CharacterUtility = characterUtility;
Framework = new FrameworkManager(); Framework = new FrameworkManager();
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage ); GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
Backup.CreateBackup( PenumbraBackupFiles() ); Backup.CreateBackup( PenumbraBackupFiles() );
@ -75,7 +97,6 @@ public class Penumbra : IDalamudPlugin
} }
ResidentResources = new ResidentResourceManager(); ResidentResources = new ResidentResourceManager();
CharacterUtility = new CharacterUtility();
Redirects = new SimpleRedirectManager(); Redirects = new SimpleRedirectManager();
MetaFileManager = new MetaFileManager(); MetaFileManager = new MetaFileManager();
ResourceLoader = new ResourceLoader( this ); ResourceLoader = new ResourceLoader( this );
@ -94,9 +115,6 @@ public class Penumbra : IDalamudPlugin
ResidentResources.Reload(); ResidentResources.Reload();
Api = new PenumbraApi( this );
Ipc = new PenumbraIpc( pluginInterface, Api );
SubscribeItemLinks();
SetupInterface( out _configWindow, out _launchButton, out _windowSystem ); SetupInterface( out _configWindow, out _launchButton, out _windowSystem );
if( Config.EnableHttpApi ) if( Config.EnableHttpApi )
@ -123,6 +141,10 @@ public class Penumbra : IDalamudPlugin
} }
ResidentResources.Reload(); 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 ) private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system )