From d6d13594e0d61b10e1e698f07125266aec683c30 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 19 Jun 2022 19:20:02 +0200 Subject: [PATCH] Add Mare Synchronos and MUI API/IPC functions for testing. Not tested myself because how. --- Penumbra/Api/IPenumbraApi.cs | 45 +-- Penumbra/Api/ModsController.cs | 3 +- Penumbra/Api/PenumbraApi.cs | 287 +++++++++++++----- Penumbra/Api/PenumbraIpc.cs | 217 +++++++++++++ Penumbra/Api/SimpleRedirectManager.cs | 125 -------- Penumbra/Api/TempModManager.cs | 261 ++++++++++++++++ .../Collections/ModCollection.Cache.Access.cs | 18 ++ Penumbra/Collections/ModCollection.Cache.cs | 12 +- Penumbra/Collections/ModCollection.cs | 11 + .../Interop/Resolver/PathResolver.Data.cs | 3 +- .../Meta/Manipulations/MetaManipulation.cs | 2 + Penumbra/Mods/Mod.TemporaryMod.cs | 19 +- Penumbra/Penumbra.cs | 8 +- Penumbra/UI/Classes/ModEditWindow.Meta.cs | 8 +- Penumbra/UI/ConfigWindow.DebugTab.cs | 78 ++++- 15 files changed, 857 insertions(+), 240 deletions(-) delete mode 100644 Penumbra/Api/SimpleRedirectManager.cs create mode 100644 Penumbra/Api/TempModManager.cs diff --git a/Penumbra/Api/IPenumbraApi.cs b/Penumbra/Api/IPenumbraApi.cs index 5107562c..9ba916d4 100644 --- a/Penumbra/Api/IPenumbraApi.cs +++ b/Penumbra/Api/IPenumbraApi.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel.Design; using Dalamud.Configuration; using Dalamud.Game.ClientState.Objects.Types; using Lumina.Data; -using OtterGui.Classes; -using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; -using Penumbra.Meta.Manipulations; using Penumbra.Mods; namespace Penumbra.Api; @@ -24,7 +20,7 @@ public delegate void GameObjectRedrawn( IntPtr objectPtr, int objectTableIndex ) public enum PenumbraApiEc { - Okay = 0, + Success = 0, NothingChanged = 1, CollectionMissing = 2, ModMissing = 3, @@ -37,6 +33,7 @@ public enum PenumbraApiEc FileMissing = 9, InvalidManipulation = 10, InvalidArgument = 11, + UnknownError = 255, } public interface IPenumbraApi : IPenumbraApiBase @@ -136,32 +133,36 @@ public interface IPenumbraApi : IPenumbraApiBase // If any setting can not be found, it will not change anything. public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, string option ); - public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, + public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName, IReadOnlyList< string > options ); // Create a temporary collection without actual settings but with a cache. - // If character is non-zero and either no character collection for this character exists or forceOverwriteCharacter is true, + // If no character collection for this character exists or forceOverwriteCharacter is true, // associate this collection to a specific character. - // Can return Okay, CharacterCollectionExists or NothingChanged. - public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter ); + // Can return Okay, CharacterCollectionExists or NothingChanged, as well as the name of the new temporary collection on success. + public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter ); - // Remove a temporary collection if it exists. + // Remove the temporary collection associated with characterName if it exists. // Can return Okay or NothingChanged. - public PenumbraApiEc RemoveTemporaryCollection( string collectionName ); + public PenumbraApiEc RemoveTemporaryCollection( string characterName ); + // Set a temporary mod with the given paths, manipulations and priority and the name tag to all collections. + // Can return Okay, InvalidGamePath, or InvalidManipulation. + public PenumbraApiEc AddTemporaryModAll( string tag, IReadOnlyDictionary< string, string > paths, IReadOnlySet< string > manipCodes, + int priority ); - // Set or remove a specific file redirection or meta manipulation under the name of Tag and with a given priority - // for a given collection, which may be temporary. - // Can return Okay, CollectionMissing, InvalidPath, FileMissing, LowerPriority, or NothingChanged. - public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int priority ); + // Set a temporary mod with the given paths, manipulations and priority and the name tag to the collection with the given name, which can be temporary. + // Can return Okay, MissingCollection InvalidGamePath, or InvalidManipulation. + public PenumbraApiEc AddTemporaryMod( string tag, string collectionName, IReadOnlyDictionary< string, string > paths, + IReadOnlySet< string > manipCodes, + int priority ); - // Can return Okay, CollectionMissing, InvalidManipulation, LowerPriority, or NothingChanged. - public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority ); + // Remove the temporary mod with the given tag and priority from the temporary mods applying to all collections, if it exists. + // Can return Okay or NothingDone. + public PenumbraApiEc RemoveTemporaryModAll( string tag, int priority ); - // Can return Okay, CollectionMissing, InvalidPath, or NothingChanged. - public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath ); - - // Can return Okay, CollectionMissing, InvalidManipulation, or NothingChanged. - public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 ); + // Remove the temporary mod with the given tag and priority from the temporary mods applying to the collection of the given name, which can be temporary. + // Can return Okay or NothingDone. + public PenumbraApiEc RemoveTemporaryMod( string tag, string collectionName, int priority ); } \ No newline at end of file diff --git a/Penumbra/Api/ModsController.cs b/Penumbra/Api/ModsController.cs index a909aab4..082bbb25 100644 --- a/Penumbra/Api/ModsController.cs +++ b/Penumbra/Api/ModsController.cs @@ -37,7 +37,6 @@ public class ModsController : WebApiController return Penumbra.CollectionManager.Current.ResolvedFiles.ToDictionary( o => o.Key.ToString(), o => o.Value.Path.FullName - ) - ?? new Dictionary< string, string >(); + ); } } \ No newline at end of file diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 895d6af9..99898ba5 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -9,9 +9,11 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using Lumina.Data; using Newtonsoft.Json; +using OtterGui; using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; +using Penumbra.Meta.Manipulations; using Penumbra.Mods; using Penumbra.Util; @@ -61,24 +63,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi public event ChangedItemHover? ChangedItemTooltip; - internal bool HasTooltip - => ChangedItemTooltip != null; - - internal void InvokeTooltip( object? it ) - => ChangedItemTooltip?.Invoke( it ); - - internal void InvokeClick( MouseButton button, object? it ) - => ChangedItemClicked?.Invoke( button, it ); - - - private void CheckInitialized() - { - if( !Valid ) - { - throw new Exception( "PluginShare is not initialized." ); - } - } - public void RedrawObject( int tableIndex, RedrawType setting ) { CheckInitialized(); @@ -97,29 +81,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi _penumbra!.ObjectReloader.RedrawObject( gameObject, setting ); } - private void OnGameObjectRedrawn( IntPtr objectAddress, int objectTableIndex ) - { - GameObjectRedrawn?.Invoke( objectAddress, objectTableIndex ); - } - public void RedrawAll( RedrawType setting ) { CheckInitialized(); _penumbra!.ObjectReloader.RedrawAll( setting ); } - private static string ResolvePath( string path, Mod.Manager _, ModCollection collection ) - { - if( !Penumbra.Config.EnableMods ) - { - return path; - } - - var gamePath = Utf8GamePath.FromString( path, out var p, true ) ? p : Utf8GamePath.Empty; - var ret = collection.ResolvePath( gamePath ); - return ret?.ToString() ?? path; - } - public string ResolvePath( string path ) { CheckInitialized(); @@ -145,25 +112,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ret.Select( r => r.ToString() ).ToList(); } - private T? GetFileIntern< T >( string resolvedPath ) where T : FileResource - { - CheckInitialized(); - try - { - if( Path.IsPathRooted( resolvedPath ) ) - { - return _lumina?.GetFileFromDisk< T >( resolvedPath ); - } - - return Dalamud.GameData.GetFile< T >( resolvedPath ); - } - catch( Exception e ) - { - PluginLog.Warning( $"Could not load file {resolvedPath}:\n{e}" ); - return null; - } - } - public T? GetFile< T >( string gamePath ) where T : FileResource => GetFileIntern< T >( ResolvePath( gamePath ) ); @@ -259,11 +207,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi var settings = allowInheritance ? collection.Settings[ mod.Index ] : collection[ mod.Index ].Settings; if( settings == null ) { - return ( PenumbraApiEc.Okay, null ); + return ( PenumbraApiEc.Success, null ); } var shareSettings = settings.ConvertToShareable( mod ); - return ( PenumbraApiEc.Okay, + return ( PenumbraApiEc.Success, ( shareSettings.Enabled, shareSettings.Priority, shareSettings.Settings, collection.Settings[ mod.Index ] != null ) ); } @@ -281,7 +229,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi } - return collection.SetModInheritance( mod.Index, inherit ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; + return collection.SetModInheritance( mod.Index, inherit ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetMod( string collectionName, string modDirectory, string modName, bool enabled ) @@ -297,7 +245,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return PenumbraApiEc.ModMissing; } - return collection.SetModState( mod.Index, enabled ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; + return collection.SetModState( mod.Index, enabled ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetModPriority( string collectionName, string modDirectory, string modName, int priority ) @@ -313,7 +261,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return PenumbraApiEc.ModMissing; } - return collection.SetModPriority( mod.Index, priority ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; + return collection.SetModPriority( mod.Index, priority ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, @@ -344,10 +292,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi var setting = mod.Groups[ groupIdx ].Type == SelectType.Multi ? 1u << optionIdx : ( uint )optionIdx; - return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; + return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } - public PenumbraApiEc TrySetModSetting( string collectionName, string modDirectory, string modName, string optionGroupName, + public PenumbraApiEc TrySetModSettings( string collectionName, string modDirectory, string modName, string optionGroupName, IReadOnlyList< string > optionNames ) { CheckInitialized(); @@ -378,7 +326,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi if( group.Type == SelectType.Single ) { var name = optionNames[ ^1 ]; - var optionIdx = group.IndexOf( o => o.Name == optionNames[^1] ); + var optionIdx = group.IndexOf( o => o.Name == optionNames[ ^1 ] ); if( optionIdx < 0 ) { return PenumbraApiEc.OptionMissing; @@ -400,24 +348,213 @@ public class PenumbraApi : IDisposable, IPenumbraApi } } - return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Okay : PenumbraApiEc.NothingChanged; + return collection.SetModSetting( mod.Index, groupIdx, setting ) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; } - public PenumbraApiEc CreateTemporaryCollection( string collectionName, string? character, bool forceOverwriteCharacter ) - => throw new NotImplementedException(); + public (PenumbraApiEc, string) CreateTemporaryCollection( string tag, string character, bool forceOverwriteCharacter ) + { + CheckInitialized(); + if( !forceOverwriteCharacter && Penumbra.CollectionManager.Characters.ContainsKey( character ) + || Penumbra.TempMods.Collections.ContainsKey( character ) ) + { + return ( PenumbraApiEc.CharacterCollectionExists, string.Empty ); + } - public PenumbraApiEc RemoveTemporaryCollection( string collectionName ) - => throw new NotImplementedException(); + var name = Penumbra.TempMods.SetTemporaryCollection( tag, character ); + return ( PenumbraApiEc.Success, name ); + } - public PenumbraApiEc SetFileRedirection( string tag, string collectionName, string gamePath, string fullPath, int priority ) - => throw new NotImplementedException(); + public PenumbraApiEc RemoveTemporaryCollection( string character ) + { + CheckInitialized(); + if( !Penumbra.TempMods.Collections.ContainsKey( character ) ) + { + return PenumbraApiEc.NothingChanged; + } - public PenumbraApiEc SetMetaManipulation( string tag, string collectionName, string manipulationBase64, int priority ) - => throw new NotImplementedException(); + Penumbra.TempMods.RemoveTemporaryCollection( character ); + return PenumbraApiEc.Success; + } - public PenumbraApiEc RemoveFileRedirection( string tag, string collectionName, string gamePath ) - => throw new NotImplementedException(); + public PenumbraApiEc AddTemporaryModAll( string tag, IReadOnlyDictionary< string, string > paths, IReadOnlySet< string > manipCodes, + int priority ) + { + CheckInitialized(); + if( !ConvertPaths( paths, out var p ) ) + { + return PenumbraApiEc.InvalidGamePath; + } - public PenumbraApiEc RemoveMetaManipulation( string tag, string collectionName, string manipulationBase64 ) - => throw new NotImplementedException(); + if( !ConvertManips( manipCodes, out var m ) ) + { + return PenumbraApiEc.InvalidManipulation; + } + + return Penumbra.TempMods.Register( tag, null, p, m, priority ) switch + { + RedirectResult.Success => PenumbraApiEc.Success, + _ => PenumbraApiEc.UnknownError, + }; + } + + public PenumbraApiEc AddTemporaryMod( string tag, string collectionName, IReadOnlyDictionary< string, string > paths, + IReadOnlySet< string > manipCodes, int priority ) + { + CheckInitialized(); + if( !Penumbra.TempMods.Collections.TryGetValue( collectionName, out var collection ) + && !Penumbra.CollectionManager.ByName( collectionName, out collection ) ) + { + return PenumbraApiEc.CollectionMissing; + } + + if( !ConvertPaths( paths, out var p ) ) + { + return PenumbraApiEc.InvalidGamePath; + } + + if( !ConvertManips( manipCodes, out var m ) ) + { + return PenumbraApiEc.InvalidManipulation; + } + + return Penumbra.TempMods.Register( tag, collection, p, m, priority ) switch + { + RedirectResult.Success => PenumbraApiEc.Success, + _ => PenumbraApiEc.UnknownError, + }; + } + + public PenumbraApiEc RemoveTemporaryModAll( string tag, int priority ) + { + CheckInitialized(); + return Penumbra.TempMods.Unregister( tag, null, priority ) switch + { + RedirectResult.Success => PenumbraApiEc.Success, + RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged, + _ => PenumbraApiEc.UnknownError, + }; + } + + public PenumbraApiEc RemoveTemporaryMod( string tag, string collectionName, int priority ) + { + CheckInitialized(); + if( !Penumbra.TempMods.Collections.TryGetValue( collectionName, out var collection ) + && !Penumbra.CollectionManager.ByName( collectionName, out collection ) ) + { + return PenumbraApiEc.CollectionMissing; + } + + return Penumbra.TempMods.Unregister( tag, collection, priority ) switch + { + RedirectResult.Success => PenumbraApiEc.Success, + RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged, + _ => PenumbraApiEc.UnknownError, + }; + } + + internal bool HasTooltip + => ChangedItemTooltip != null; + + internal void InvokeTooltip( object? it ) + => ChangedItemTooltip?.Invoke( it ); + + internal void InvokeClick( MouseButton button, object? it ) + => ChangedItemClicked?.Invoke( button, it ); + + + private void CheckInitialized() + { + if( !Valid ) + { + throw new Exception( "PluginShare is not initialized." ); + } + } + + 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 ) + { + if( !Penumbra.Config.EnableMods ) + { + return path; + } + + var gamePath = Utf8GamePath.FromString( path, out var p, true ) ? p : Utf8GamePath.Empty; + var ret = collection.ResolvePath( gamePath ); + return ret?.ToString() ?? path; + } + + // Get a file for a resolved path. + private T? GetFileIntern< T >( string resolvedPath ) where T : FileResource + { + CheckInitialized(); + try + { + if( Path.IsPathRooted( resolvedPath ) ) + { + return _lumina?.GetFileFromDisk< T >( resolvedPath ); + } + + return Dalamud.GameData.GetFile< T >( resolvedPath ); + } + catch( Exception e ) + { + PluginLog.Warning( $"Could not load file {resolvedPath}:\n{e}" ); + return null; + } + } + + + // Convert a dictionary of strings to a dictionary of gamepaths to full paths. + // Only returns true if all paths can successfully be converted and added. + private static bool ConvertPaths( IReadOnlyDictionary< string, string > redirections, + [NotNullWhen( true )] out Dictionary< Utf8GamePath, FullPath >? paths ) + { + paths = new Dictionary< Utf8GamePath, FullPath >( redirections.Count ); + foreach( var (gString, fString) in redirections ) + { + if( !Utf8GamePath.FromString( gString, out var path, false ) ) + { + paths = null; + return false; + } + + var fullPath = new FullPath( fString ); + if( !paths.TryAdd( path, fullPath ) ) + { + paths = null; + return false; + } + } + + return true; + } + + // Convert manipulations from transmitted base64 strings to actual manipulations. + // Only returns true if all conversions are successful and distinct. + private static bool ConvertManips( IReadOnlyCollection< string > manipStrings, + [NotNullWhen( true )] out HashSet< MetaManipulation >? manips ) + { + manips = new HashSet< MetaManipulation >( manipStrings.Count ); + foreach( var m in manipStrings ) + { + if( Functions.FromCompressedBase64< MetaManipulation >( m, out var manip ) != MetaManipulation.CurrentVersion ) + { + manips = null; + return false; + } + + if( !manips.Add( manip ) ) + { + manips = null; + return false; + } + } + + return true; + } } \ No newline at end of file diff --git a/Penumbra/Api/PenumbraIpc.cs b/Penumbra/Api/PenumbraIpc.cs index 14f3ea11..1aa4ce24 100644 --- a/Penumbra/Api/PenumbraIpc.cs +++ b/Penumbra/Api/PenumbraIpc.cs @@ -22,6 +22,8 @@ public partial class PenumbraIpc : IDisposable InitializeRedrawProviders( pi ); InitializeChangedItemProviders( pi ); InitializeDataProviders( pi ); + InitializeSettingProviders( pi ); + InitializeTempProviders( pi ); ProviderInitialized?.SendMessage(); } @@ -32,6 +34,8 @@ public partial class PenumbraIpc : IDisposable DisposeRedrawProviders(); DisposeResolveProviders(); DisposeGeneralProviders(); + DisposeSettingProviders(); + DisposeTempProviders(); ProviderDisposed?.SendMessage(); } } @@ -402,4 +406,217 @@ public partial class PenumbraIpc ProviderDefaultCollectionName?.UnregisterFunc(); ProviderCharacterCollectionName?.UnregisterFunc(); } +} + +public partial class PenumbraIpc +{ + public const string LabelProviderGetAvailableModSettings = "Penumbra.GetAvailableModSettings"; + public const string LabelProviderGetCurrentModSettings = "Penumbra.GetCurrentModSettings"; + public const string LabelProviderTryInheritMod = "Penumbra.TryInheritMod"; + public const string LabelProviderTrySetMod = "Penumbra.TrySetMod"; + public const string LabelProviderTrySetModPriority = "Penumbra.TrySetModPriority"; + public const string LabelProviderTrySetModSetting = "Penumbra.TrySetModSetting"; + public const string LabelProviderTrySetModSettings = "Penumbra.TrySetModSettings"; + + internal ICallGateProvider< string, string, IDictionary< string, (IList< string >, Mods.SelectType) >? >? ProviderGetAvailableModSettings; + + internal ICallGateProvider< string, string, string, bool, (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) >? + ProviderGetCurrentModSettings; + + internal ICallGateProvider< string, string, string, bool, PenumbraApiEc >? ProviderTryInheritMod; + internal ICallGateProvider< string, string, string, bool, PenumbraApiEc >? ProviderTrySetMod; + internal ICallGateProvider< string, string, string, int, PenumbraApiEc >? ProviderTrySetModPriority; + internal ICallGateProvider< string, string, string, string, string, PenumbraApiEc >? ProviderTrySetModSetting; + internal ICallGateProvider< string, string, string, string, IReadOnlyList< string >, PenumbraApiEc >? ProviderTrySetModSettings; + + private void InitializeSettingProviders( DalamudPluginInterface pi ) + { + try + { + ProviderGetAvailableModSettings = + pi.GetIpcProvider< string, string, IDictionary< string, (IList< string >, Mods.SelectType) >? >( + LabelProviderGetAvailableModSettings ); + ProviderGetAvailableModSettings.RegisterFunc( Api.GetAvailableModSettings ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetAvailableModSettings}:\n{e}" ); + } + + try + { + ProviderGetCurrentModSettings = + pi.GetIpcProvider< string, string, string, bool, (PenumbraApiEc, (bool, int, IDictionary< string, IList< string > >, bool)?) >( + LabelProviderGetCurrentModSettings ); + ProviderGetCurrentModSettings.RegisterFunc( Api.GetCurrentModSettings ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderGetCurrentModSettings}:\n{e}" ); + } + + try + { + ProviderTryInheritMod = pi.GetIpcProvider< string, string, string, bool, PenumbraApiEc >( LabelProviderTryInheritMod ); + ProviderTryInheritMod.RegisterFunc( Api.TryInheritMod ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderTryInheritMod}:\n{e}" ); + } + + try + { + ProviderTrySetMod = pi.GetIpcProvider< string, string, string, bool, PenumbraApiEc >( LabelProviderTrySetMod ); + ProviderTrySetMod.RegisterFunc( Api.TrySetMod ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetMod}:\n{e}" ); + } + + try + { + ProviderTrySetModPriority = pi.GetIpcProvider< string, string, string, int, PenumbraApiEc >( LabelProviderTrySetModPriority ); + ProviderTrySetModPriority.RegisterFunc( Api.TrySetModPriority ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetModPriority}:\n{e}" ); + } + + try + { + ProviderTrySetModSetting = + pi.GetIpcProvider< string, string, string, string, string, PenumbraApiEc >( LabelProviderTrySetModSetting ); + ProviderTrySetModSetting.RegisterFunc( Api.TrySetModSetting ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetModSetting}:\n{e}" ); + } + + try + { + ProviderTrySetModSettings = + pi.GetIpcProvider< string, string, string, string, IReadOnlyList< string >, PenumbraApiEc >( LabelProviderTrySetModSettings ); + ProviderTrySetModSettings.RegisterFunc( Api.TrySetModSettings ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderTrySetModSettings}:\n{e}" ); + } + } + + private void DisposeSettingProviders() + { + ProviderGetAvailableModSettings?.UnregisterFunc(); + ProviderGetCurrentModSettings?.UnregisterFunc(); + ProviderTryInheritMod?.UnregisterFunc(); + ProviderTrySetMod?.UnregisterFunc(); + ProviderTrySetModPriority?.UnregisterFunc(); + ProviderTrySetModSetting?.UnregisterFunc(); + ProviderTrySetModSettings?.UnregisterFunc(); + } +} + +public partial class PenumbraIpc +{ + public const string LabelProviderCreateTemporaryCollection = "Penumbra.CreateTemporaryCollection"; + public const string LabelProviderRemoveTemporaryCollection = "Penumbra.RemoveTemporaryCollection"; + public const string LabelProviderAddTemporaryModAll = "Penumbra.AddTemporaryModAll"; + public const string LabelProviderAddTemporaryMod = "Penumbra.AddTemporaryMod"; + public const string LabelProviderRemoveTemporaryModAll = "Penumbra.RemoveTemporaryModAll"; + public const string LabelProviderRemoveTemporaryMod = "Penumbra.RemoveTemporaryMod"; + + internal ICallGateProvider< string, string, bool, (PenumbraApiEc, string) >? ProviderCreateTemporaryCollection; + internal ICallGateProvider< string, PenumbraApiEc >? ProviderRemoveTemporaryCollection; + + internal ICallGateProvider< string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >? + ProviderAddTemporaryModAll; + + internal ICallGateProvider< string, string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >? + ProviderAddTemporaryMod; + + internal ICallGateProvider< string, int, PenumbraApiEc >? ProviderRemoveTemporaryModAll; + internal ICallGateProvider< string, string, int, PenumbraApiEc >? ProviderRemoveTemporaryMod; + + private void InitializeTempProviders( DalamudPluginInterface pi ) + { + try + { + ProviderCreateTemporaryCollection = + pi.GetIpcProvider< string, string, bool, (PenumbraApiEc, string) >( LabelProviderCreateTemporaryCollection ); + ProviderCreateTemporaryCollection.RegisterFunc( Api.CreateTemporaryCollection ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreateTemporaryCollection}:\n{e}" ); + } + + try + { + ProviderRemoveTemporaryCollection = + pi.GetIpcProvider< string, PenumbraApiEc >( LabelProviderRemoveTemporaryCollection ); + ProviderRemoveTemporaryCollection.RegisterFunc( Api.RemoveTemporaryCollection ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRemoveTemporaryCollection}:\n{e}" ); + } + + try + { + ProviderAddTemporaryModAll = + pi.GetIpcProvider< string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >( + LabelProviderAddTemporaryModAll ); + ProviderAddTemporaryModAll.RegisterFunc( Api.AddTemporaryModAll ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderAddTemporaryModAll}:\n{e}" ); + } + + try + { + ProviderAddTemporaryMod = + pi.GetIpcProvider< string, string, IReadOnlyDictionary< string, string >, IReadOnlySet< string >, int, PenumbraApiEc >( + LabelProviderAddTemporaryMod ); + ProviderAddTemporaryMod.RegisterFunc( Api.AddTemporaryMod ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderAddTemporaryMod}:\n{e}" ); + } + + try + { + ProviderRemoveTemporaryModAll = pi.GetIpcProvider< string, int, PenumbraApiEc >( LabelProviderRemoveTemporaryModAll ); + ProviderRemoveTemporaryModAll.RegisterFunc( Api.RemoveTemporaryModAll ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRemoveTemporaryModAll}:\n{e}" ); + } + + try + { + ProviderRemoveTemporaryMod = pi.GetIpcProvider< string, string, int, PenumbraApiEc >( LabelProviderRemoveTemporaryMod ); + ProviderRemoveTemporaryMod.RegisterFunc( Api.RemoveTemporaryMod ); + } + catch( Exception e ) + { + PluginLog.Error( $"Error registering IPC provider for {LabelProviderRemoveTemporaryMod}:\n{e}" ); + } + } + + private void DisposeTempProviders() + { + ProviderCreateTemporaryCollection?.UnregisterFunc(); + ProviderRemoveTemporaryCollection?.UnregisterFunc(); + ProviderAddTemporaryModAll?.UnregisterFunc(); + ProviderAddTemporaryMod?.UnregisterFunc(); + ProviderRemoveTemporaryModAll?.UnregisterFunc(); + ProviderRemoveTemporaryMod?.UnregisterFunc(); + } } \ No newline at end of file diff --git a/Penumbra/Api/SimpleRedirectManager.cs b/Penumbra/Api/SimpleRedirectManager.cs deleted file mode 100644 index 2f3e219f..00000000 --- a/Penumbra/Api/SimpleRedirectManager.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Generic; -using Dalamud.Logging; -using Penumbra.Collections; -using Penumbra.GameData.ByteString; -using Penumbra.Mods; - -namespace Penumbra.Api; - -public enum RedirectResult -{ - Registered = 0, - Success = 0, - IdenticalFileRegistered = 1, - InvalidGamePath = 2, - OtherOwner = 3, - NotRegistered = 4, - NoPermission = 5, - FilteredGamePath = 6, - UnknownError = 7, -} - -public class SimpleRedirectManager -{ - internal readonly Dictionary< Utf8GamePath, (FullPath File, string Tag) > Replacements = new(); - public readonly HashSet< string > AllowedTags = new(); - - public void Apply( IDictionary< Utf8GamePath, ModPath > dict ) - { - foreach( var (gamePath, (file, _)) in Replacements ) - { - dict.TryAdd( gamePath, new ModPath(Mod.ForcedFiles, file) ); - } - } - - private RedirectResult? CheckPermission( string tag ) - => AllowedTags.Contains( tag ) ? null : RedirectResult.NoPermission; - - public RedirectResult IsRegistered( Utf8GamePath path, string tag ) - => CheckPermission( tag ) - ?? ( Replacements.TryGetValue( path, out var pair ) - ? pair.Tag == tag ? RedirectResult.Registered : RedirectResult.OtherOwner - : RedirectResult.NotRegistered ); - - public RedirectResult Register( Utf8GamePath path, FullPath file, string tag ) - { - if( CheckPermission( tag ) != null ) - { - return RedirectResult.NoPermission; - } - - if( Mod.FilterFile( path ) ) - { - return RedirectResult.FilteredGamePath; - } - - try - { - if( Replacements.TryGetValue( path, out var pair ) ) - { - if( file.Equals( pair.File ) ) - { - return RedirectResult.IdenticalFileRegistered; - } - - if( tag != pair.Tag ) - { - return RedirectResult.OtherOwner; - } - } - - Replacements[ path ] = ( file, tag ); - return RedirectResult.Success; - } - catch( Exception e ) - { - PluginLog.Error( $"[{tag}] Unknown Error registering simple redirect {path} -> {file}:\n{e}" ); - return RedirectResult.UnknownError; - } - } - - public RedirectResult Unregister( Utf8GamePath path, string tag ) - { - if( CheckPermission( tag ) != null ) - { - return RedirectResult.NoPermission; - } - - try - { - if( !Replacements.TryGetValue( path, out var pair ) ) - { - return RedirectResult.NotRegistered; - } - - if( tag != pair.Tag ) - { - return RedirectResult.OtherOwner; - } - - Replacements.Remove( path ); - return RedirectResult.Success; - } - catch( Exception e ) - { - PluginLog.Error( $"[{tag}] Unknown Error unregistering simple redirect {path}:\n{e}" ); - return RedirectResult.UnknownError; - } - } - - public RedirectResult Register( string path, string file, string tag ) - => Utf8GamePath.FromString( path, out var gamePath, true ) - ? Register( gamePath, new FullPath( file ), tag ) - : RedirectResult.InvalidGamePath; - - public RedirectResult Unregister( string path, string tag ) - => Utf8GamePath.FromString( path, out var gamePath, true ) - ? Unregister( gamePath, tag ) - : RedirectResult.InvalidGamePath; - - public RedirectResult IsRegistered( string path, string tag ) - => Utf8GamePath.FromString( path, out var gamePath, true ) - ? IsRegistered( gamePath, tag ) - : RedirectResult.InvalidGamePath; -} \ No newline at end of file diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs new file mode 100644 index 00000000..b3be6577 --- /dev/null +++ b/Penumbra/Api/TempModManager.cs @@ -0,0 +1,261 @@ +using System.Collections.Generic; +using Penumbra.Collections; +using Penumbra.GameData.ByteString; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods; + +namespace Penumbra.Api; + +public enum RedirectResult +{ + Success = 0, + IdenticalFileRegistered = 1, + NotRegistered = 2, + FilteredGamePath = 3, +} + +public class TempModManager +{ + private readonly Dictionary< ModCollection, List< Mod.TemporaryMod > > _mods = new(); + private readonly List< Mod.TemporaryMod > _modsForAllCollections = new(); + private readonly Dictionary< string, ModCollection > _collections = new(); + + public IReadOnlyDictionary< ModCollection, List< Mod.TemporaryMod > > Mods + => _mods; + + public IReadOnlyList< Mod.TemporaryMod > ModsForAllCollections + => _modsForAllCollections; + + public IReadOnlyDictionary< string, ModCollection > Collections + => _collections; + + // These functions to check specific redirections or meta manipulations for existence are currently unused. + //public bool IsRegistered( string tag, ModCollection? collection, Utf8GamePath gamePath, out FullPath? fullPath, out int priority ) + //{ + // var mod = GetExistingMod( tag, collection, null ); + // if( mod == null ) + // { + // priority = 0; + // fullPath = null; + // return false; + // } + // + // priority = mod.Priority; + // if( mod.Default.Files.TryGetValue( gamePath, out var f ) ) + // { + // fullPath = f; + // return true; + // } + // + // fullPath = null; + // return false; + //} + // + //public bool IsRegistered( string tag, ModCollection? collection, MetaManipulation meta, out MetaManipulation? manipulation, + // out int priority ) + //{ + // var mod = GetExistingMod( tag, collection, null ); + // if( mod == null ) + // { + // priority = 0; + // manipulation = null; + // return false; + // } + // + // priority = mod.Priority; + // // IReadOnlySet has no TryGetValue for some reason. + // if( ( ( HashSet< MetaManipulation > )mod.Default.Manipulations ).TryGetValue( meta, out var manip ) ) + // { + // manipulation = manip; + // return true; + // } + // + // manipulation = null; + // return false; + //} + + // These functions for setting single redirections or manips are currently unused. + //public RedirectResult Register( string tag, ModCollection? collection, Utf8GamePath path, FullPath file, int priority ) + //{ + // if( Mod.FilterFile( path ) ) + // { + // return RedirectResult.FilteredGamePath; + // } + // + // var mod = GetOrCreateMod( tag, collection, priority, out var created ); + // + // var changes = !mod.Default.Files.TryGetValue( path, out var oldFile ) || !oldFile.Equals( file ); + // mod.SetFile( path, file ); + // ApplyModChange( mod, collection, created, false ); + // return changes ? RedirectResult.IdenticalFileRegistered : RedirectResult.Success; + //} + // + //public RedirectResult Register( string tag, ModCollection? collection, MetaManipulation meta, int priority ) + //{ + // var mod = GetOrCreateMod( tag, collection, priority, out var created ); + // var changes = !( ( HashSet< MetaManipulation > )mod.Default.Manipulations ).TryGetValue( meta, out var oldMeta ) + // || !oldMeta.Equals( meta ); + // mod.SetManipulation( meta ); + // ApplyModChange( mod, collection, created, false ); + // return changes ? RedirectResult.IdenticalFileRegistered : RedirectResult.Success; + //} + + public RedirectResult Register( string tag, ModCollection? collection, Dictionary< Utf8GamePath, FullPath > dict, + HashSet< MetaManipulation > manips, int priority ) + { + var mod = GetOrCreateMod( tag, collection, priority, out var created ); + mod.SetAll( dict, manips ); + ApplyModChange( mod, collection, created, false ); + return RedirectResult.Success; + } + + public RedirectResult Unregister( string tag, ModCollection? collection, int? priority ) + { + var list = collection == null ? _modsForAllCollections : _mods.TryGetValue( collection, out var l ) ? l : null; + if( list == null ) + { + return RedirectResult.NotRegistered; + } + + var removed = _modsForAllCollections.RemoveAll( m => + { + if( m.Name != tag || priority != null && m.Priority != priority.Value ) + { + return false; + } + + ApplyModChange( m, collection, false, true ); + return true; + } ); + + if( removed == 0 ) + { + return RedirectResult.NotRegistered; + } + + if( list.Count == 0 && collection != null ) + { + _mods.Remove( collection ); + } + + return RedirectResult.Success; + } + + public string SetTemporaryCollection( string tag, string characterName ) + { + var collection = ModCollection.CreateNewTemporary( tag, characterName ); + _collections[ characterName ] = collection; + return collection.Name; + } + + public bool RemoveTemporaryCollection( string characterName ) + { + if( _collections.Remove( characterName, out var c ) ) + { + _mods.Remove( c ); + return true; + } + + return false; + } + + + // Apply any new changes to the temporary mod. + private static void ApplyModChange( Mod.TemporaryMod mod, ModCollection? collection, bool created, bool removed ) + { + if( collection == null ) + { + if( removed ) + { + foreach( var c in Penumbra.CollectionManager ) + { + c.Remove( mod ); + } + } + else + { + foreach( var c in Penumbra.CollectionManager ) + { + c.Apply( mod, created ); + } + } + } + else + { + if( removed ) + { + collection.Remove( mod ); + } + else + { + collection.Apply( mod, created ); + } + } + } + + // Only find already existing mods, currently unused. + //private Mod.TemporaryMod? GetExistingMod( string tag, ModCollection? collection, int? priority ) + //{ + // var list = collection == null ? _modsForAllCollections : _mods.TryGetValue( collection, out var l ) ? l : null; + // if( list == null ) + // { + // return null; + // } + // + // if( priority != null ) + // { + // return list.Find( m => m.Priority == priority.Value && m.Name == tag ); + // } + // + // Mod.TemporaryMod? highestMod = null; + // var highestPriority = int.MinValue; + // foreach( var m in list ) + // { + // if( highestPriority < m.Priority && m.Name == tag ) + // { + // highestPriority = m.Priority; + // highestMod = m; + // } + // } + // + // return highestMod; + //} + + // Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections). + // Returns the found or created mod and whether it was newly created. + private Mod.TemporaryMod GetOrCreateMod( string tag, ModCollection? collection, int priority, out bool created ) + { + List< Mod.TemporaryMod > list; + if( collection == null ) + { + list = _modsForAllCollections; + } + else if( _mods.TryGetValue( collection, out var l ) ) + { + list = l; + } + else + { + list = new List< Mod.TemporaryMod >(); + _mods.Add( collection, list ); + } + + var mod = list.Find( m => m.Priority == priority && m.Name == tag ); + if( mod == null ) + { + mod = new Mod.TemporaryMod() + { + Name = tag, + Priority = priority, + }; + list.Add( mod ); + created = true; + } + else + { + created = false; + } + + return mod; + } +} \ No newline at end of file diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 24324553..a6fa0ffe 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -35,6 +35,24 @@ public partial class ModCollection private void ForceCacheUpdate() => CalculateEffectiveFileList(); + // Handle temporary mods for this collection. + public void Apply( Mod.TemporaryMod tempMod, bool created ) + { + if( created ) + { + _cache?.AddMod( tempMod, tempMod.TotalManipulations > 0 ); + } + else + { + _cache?.ReloadMod( tempMod, tempMod.TotalManipulations > 0 ); + } + } + + public void Remove( Mod.TemporaryMod tempMod ) + { + _cache?.RemoveMod( tempMod, tempMod.TotalManipulations > 0 ); + } + // Clear the current cache. private void ClearCache() diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 7ffdfb61..62b92f7f 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -20,10 +20,10 @@ public partial class ModCollection // It will only be setup if a collection gets activated in any way. private class Cache : IDisposable { - private readonly ModCollection _collection; + private readonly ModCollection _collection; private readonly SortedList< string, (SingleArray< IMod >, object?) > _changedItems = new(); - public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new(); - public readonly MetaManager MetaManipulations; + public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new(); + public readonly MetaManager MetaManipulations; private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new(); public IEnumerable< SingleArray< ModConflicts > > AllConflicts @@ -160,7 +160,11 @@ public partial class ModCollection _conflicts.Clear(); // Add all forced redirects. - Penumbra.Redirects.Apply( ResolvedFiles ); + foreach( var tempMod in Penumbra.TempMods.ModsForAllCollections.Concat( + Penumbra.TempMods.Mods.TryGetValue( _collection, out var list ) ? list : Array.Empty< Mod.TemporaryMod >() ) ) + { + AddMod( tempMod, false ); + } foreach( var mod in Penumbra.ModManager ) { diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 7d82e28b..1c913a11 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -74,6 +74,17 @@ public partial class ModCollection public static ModCollection CreateNewEmpty( string name ) => new(name, CurrentVersion, new Dictionary< string, ModSettings.SavedSettings >()); + // Create a new temporary collection that does not save and has a negative index. + public static ModCollection CreateNewTemporary(string tag, string characterName) + { + var collection = new ModCollection($"{tag}_{characterName}_temporary", Empty); + collection.ModSettingChanged -= collection.SaveOnChange; + collection.InheritanceChanged -= collection.SaveOnChange; + collection.Index = ~Penumbra.TempMods.Collections.Count; + collection.CreateCache(); + return collection; + } + // Duplicate the calling collection to a new, unique collection of a given name. public ModCollection Duplicate( string name ) => new(name, this); diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs index 7f12e600..afc4f7d1 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Data.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -309,7 +309,8 @@ public unsafe partial class PathResolver } ?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString(); - return Penumbra.CollectionManager.Character( actualName ); + // First check temporary character collections, then the own configuration. + return Penumbra.TempMods.Collections.TryGetValue(actualName, out var c) ? c : Penumbra.CollectionManager.Character( actualName ); } // Update collections linked to Game/DrawObjects due to a change in collection configuration. diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs index 4f2439a9..5732522d 100644 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs @@ -19,6 +19,8 @@ public interface IMetaManipulation< T > [StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )] public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable< MetaManipulation > { + public const int CurrentVersion = 0; + public enum Type : byte { Unknown = 0, diff --git a/Penumbra/Mods/Mod.TemporaryMod.cs b/Penumbra/Mods/Mod.TemporaryMod.cs index 6662f826..35a4795a 100644 --- a/Penumbra/Mods/Mod.TemporaryMod.cs +++ b/Penumbra/Mods/Mod.TemporaryMod.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using OtterGui.Classes; +using Penumbra.GameData.ByteString; +using Penumbra.Meta.Manipulations; namespace Penumbra.Mods; @@ -15,12 +17,27 @@ public sealed partial class Mod public int TotalManipulations => Default.Manipulations.Count; - public ISubMod Default { get; } = new SubMod(); + public ISubMod Default + => _default; public IReadOnlyList< IModGroup > Groups => Array.Empty< IModGroup >(); public IEnumerable< ISubMod > AllSubMods => new[] { Default }; + + private readonly SubMod _default = new(); + + public void SetFile( Utf8GamePath gamePath, FullPath fullPath ) + => _default.FileData[ gamePath ] = fullPath; + + public bool SetManipulation( MetaManipulation manip ) + => _default.ManipulationData.Remove( manip ) | _default.ManipulationData.Add( manip ); + + public void SetAll( Dictionary< Utf8GamePath, FullPath > dict, HashSet< MetaManipulation > manips ) + { + _default.FileData = dict; + _default.ManipulationData = manips; + } } } \ No newline at end of file diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 4470d29a..b892a602 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -57,7 +57,7 @@ public class MainClass : IDalamudPlugin { #if !DEBUG var path = Path.Combine( Dalamud.PluginInterface.DalamudAssetDirectory.Parent?.FullName ?? "INVALIDPATH", "devPlugins", "Penumbra" ); - var dir = new DirectoryInfo( path ); + var dir = new DirectoryInfo( path ); try { @@ -78,7 +78,7 @@ public class MainClass : IDalamudPlugin { #if !DEBUG var checkedDirectory = Dalamud.PluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Name; - var ret = checkedDirectory?.Equals( "installedPlugins", StringComparison.InvariantCultureIgnoreCase ) ?? false; + var ret = checkedDirectory?.Equals( "installedPlugins", StringComparison.InvariantCultureIgnoreCase ) ?? false; if (!ret) PluginLog.Error($"Penumbra is not correctly installed. Application loaded from \"{Dalamud.PluginInterface.AssemblyLocation.Directory!.FullName}\"." ); return !ret; @@ -105,7 +105,7 @@ public class Penumbra : IDisposable public static MetaFileManager MetaFileManager { get; private set; } = null!; public static Mod.Manager ModManager { get; private set; } = null!; public static ModCollection.Manager CollectionManager { get; private set; } = null!; - public static SimpleRedirectManager Redirects { get; private set; } = null!; + public static TempModManager TempMods { get; private set; } = null!; public static ResourceLoader ResourceLoader { get; private set; } = null!; public static FrameworkManager Framework { get; private set; } = null!; public static int ImcExceptions = 0; @@ -138,7 +138,7 @@ public class Penumbra : IDisposable } ResidentResources = new ResidentResourceManager(); - Redirects = new SimpleRedirectManager(); + TempMods = new TempModManager(); MetaFileManager = new MetaFileManager(); ResourceLoader = new ResourceLoader( this ); ResourceLogger = new ResourceLogger( ResourceLoader ); diff --git a/Penumbra/UI/Classes/ModEditWindow.Meta.cs b/Penumbra/UI/Classes/ModEditWindow.Meta.cs index 73d1ff6e..d3a44af2 100644 --- a/Penumbra/UI/Classes/ModEditWindow.Meta.cs +++ b/Penumbra/UI/Classes/ModEditWindow.Meta.cs @@ -856,8 +856,6 @@ public partial class ModEditWindow return newValue != currentValue; } - private const byte CurrentManipulationVersion = 0; - private static void CopyToClipboardButton( string tooltip, Vector2 iconSize, IEnumerable< MetaManipulation > manipulations ) { if( !ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true ) ) @@ -865,7 +863,7 @@ public partial class ModEditWindow return; } - var text = Functions.ToCompressedBase64( manipulations, CurrentManipulationVersion ); + var text = Functions.ToCompressedBase64( manipulations, MetaManipulation.CurrentVersion ); if( text.Length > 0 ) { ImGui.SetClipboardText( text ); @@ -878,7 +876,7 @@ public partial class ModEditWindow { var clipboard = ImGui.GetClipboardText(); var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips ); - if( version == CurrentManipulationVersion && manips != null ) + if( version == MetaManipulation.CurrentVersion && manips != null ) { foreach( var manip in manips ) { @@ -897,7 +895,7 @@ public partial class ModEditWindow { var clipboard = ImGui.GetClipboardText(); var version = Functions.FromCompressedBase64< MetaManipulation[] >( clipboard, out var manips ); - if( version == CurrentManipulationVersion && manips != null ) + if( version == MetaManipulation.CurrentVersion && manips != null ) { _editor!.Meta.Clear(); foreach( var manip in manips ) diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 31b4b405..95be2069 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; @@ -10,8 +11,10 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.Api; +using Penumbra.Collections; using Penumbra.Interop.Loader; using Penumbra.Interop.Structs; +using Penumbra.Mods; using CharacterUtility = Penumbra.Interop.CharacterUtility; namespace Penumbra.UI; @@ -413,11 +416,84 @@ public partial class ConfigWindow foreach( var provider in ipc.GetType().GetFields( BindingFlags.Instance | BindingFlags.NonPublic ) ) { var value = provider.GetValue( ipc ); - if( value != null && dict.TryGetValue( "Label" + provider.Name, out var label )) + if( value != null && dict.TryGetValue( "Label" + provider.Name, out var label ) ) { ImGui.TextUnformatted( label ); } } + + using( var collTree = ImRaii.TreeNode( "Collections" ) ) + { + if( collTree ) + { + using var table = ImRaii.Table( "##collTree", 4 ); + if( table ) + { + foreach( var (character, collection) in Penumbra.TempMods.Collections ) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted( character ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( collection.Name ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( collection.ResolvedFiles.Count.ToString() ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( collection.MetaCache?.Count.ToString() ?? "0" ); + } + } + } + } + + using( var modTree = ImRaii.TreeNode( "Mods" ) ) + { + if( modTree ) + { + using var table = ImRaii.Table( "##modTree", 5 ); + + void PrintList( string collectionName, IReadOnlyList< Mod.TemporaryMod > list ) + { + foreach( var mod in list ) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted( mod.Name ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( mod.Priority.ToString() ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( collectionName ); + ImGui.TableNextColumn(); + ImGui.TextUnformatted( mod.Default.Files.Count.ToString() ); + if( ImGui.IsItemHovered() ) + { + using var tt = ImRaii.Tooltip(); + foreach( var (path, file) in mod.Default.Files ) + { + ImGui.TextUnformatted( $"{path} -> {file}" ); + } + } + + ImGui.TableNextColumn(); + ImGui.TextUnformatted( mod.TotalManipulations.ToString() ); + if( ImGui.IsItemHovered() ) + { + using var tt = ImRaii.Tooltip(); + foreach( var manip in mod.Default.Manipulations ) + { + ImGui.TextUnformatted( manip.ToString() ); + } + } + } + } + + if( table ) + { + PrintList( "All", Penumbra.TempMods.ModsForAllCollections ); + foreach( var (collection, list) in Penumbra.TempMods.Mods ) + { + PrintList( collection.Name, list ); + } + } + } + } } // Helper to print a property and its value in a 2-column table.