diff --git a/Penumbra/Api/HttpApi.cs b/Penumbra/Api/HttpApi.cs index e6d31104..0d9ef997 100644 --- a/Penumbra/Api/HttpApi.cs +++ b/Penumbra/Api/HttpApi.cs @@ -26,10 +26,10 @@ public class HttpApi : IDisposable private readonly IPenumbraApi _api; private WebServer? _server; - public HttpApi(IPenumbraApi api) + public HttpApi(Configuration config, IPenumbraApi api) { _api = api; - if (Penumbra.Config.EnableHttpApi) + if (config.EnableHttpApi) CreateWebServer(); } diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index eb9dbe07..32366bed 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -19,6 +19,7 @@ using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI; using Penumbra.Collections.Manager; +using Penumbra.Util; namespace Penumbra.Api; @@ -39,7 +40,8 @@ public class IpcTester : IDisposable private readonly ModSettings _modSettings; private readonly Temporary _temporary; - public IpcTester(DalamudPluginInterface pi, PenumbraIpcProviders ipcProviders, ModManager modManager) + public IpcTester(DalamudPluginInterface pi, PenumbraIpcProviders ipcProviders, ModManager modManager, CollectionManager collections, + TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService) { _ipcProviders = ipcProviders; _pluginState = new PluginState(pi); @@ -52,7 +54,7 @@ public class IpcTester : IDisposable _meta = new Meta(pi); _mods = new Mods(pi); _modSettings = new ModSettings(pi); - _temporary = new Temporary(pi, modManager); + _temporary = new Temporary(pi, modManager, collections, tempMods, tempCollections, saveService); UnsubscribeEvents(); } @@ -1151,11 +1153,20 @@ public class IpcTester : IDisposable { private readonly DalamudPluginInterface _pi; private readonly ModManager _modManager; + private readonly CollectionManager _collections; + private readonly TempModManager _tempMods; + private readonly TempCollectionManager _tempCollections; + private readonly SaveService _saveService; - public Temporary(DalamudPluginInterface pi, ModManager modManager) + public Temporary(DalamudPluginInterface pi, ModManager modManager, CollectionManager collections, TempModManager tempMods, + TempCollectionManager tempCollections, SaveService saveService) { - _pi = pi; - _modManager = modManager; + _pi = pi; + _modManager = modManager; + _collections = collections; + _tempMods = tempMods; + _tempCollections = tempCollections; + _saveService = saveService; } public string LastCreatedCollectionName = string.Empty; @@ -1223,7 +1234,7 @@ public class IpcTester : IDisposable DrawIntro(Ipc.CreateTemporaryCollection.Label, "Copy Existing Collection"); if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero, "Copies the effective list from the collection named in Temporary Mod Name...", - !Penumbra.CollectionManager.Storage.ByName(_tempModName, out var copyCollection)) + !_collections.Storage.ByName(_tempModName, out var copyCollection)) && copyCollection is { HasCache: true }) { var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString()); @@ -1249,7 +1260,7 @@ public class IpcTester : IDisposable public void DrawCollections() { - using var collTree = ImRaii.TreeNode("Collections##TempCollections"); + using var collTree = ImRaii.TreeNode("Temporary Collections##TempCollections"); if (!collTree) return; @@ -1257,26 +1268,26 @@ public class IpcTester : IDisposable if (!table) return; - foreach (var collection in Penumbra.TempCollections.Values) + foreach (var collection in _tempCollections.Values) { ImGui.TableNextColumn(); - var character = Penumbra.TempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName) + var character = _tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName) .FirstOrDefault() ?? "Unknown"; if (ImGui.Button($"Save##{collection.Name}")) - TemporaryMod.SaveTempCollection(_modManager, collection, character); + TemporaryMod.SaveTempCollection(_saveService, _modManager, collection, character); ImGuiUtil.DrawTableColumn(collection.Name); ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString()); ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0"); ImGuiUtil.DrawTableColumn(string.Join(", ", - Penumbra.TempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName))); + _tempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName))); } } public void DrawMods() { - using var modTree = ImRaii.TreeNode("Mods##TempMods"); + using var modTree = ImRaii.TreeNode("Temporary Mods##TempMods"); if (!modTree) return; @@ -1314,8 +1325,8 @@ public class IpcTester : IDisposable if (table) { - PrintList("All", Penumbra.TempMods.ModsForAllCollections); - foreach (var (collection, list) in Penumbra.TempMods.Mods) + PrintList("All", _tempMods.ModsForAllCollections); + foreach (var (collection, list) in _tempMods.Mods) PrintList(collection.Name, list); } } diff --git a/Penumbra/Api/PenumbraIpcProviders.cs b/Penumbra/Api/PenumbraIpcProviders.cs index 1457b3a6..36245110 100644 --- a/Penumbra/Api/PenumbraIpcProviders.cs +++ b/Penumbra/Api/PenumbraIpcProviders.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; -using Penumbra.Mods; +using Penumbra.Collections.Manager; using Penumbra.Mods.Manager; +using Penumbra.Util; namespace Penumbra.Api; @@ -114,7 +115,8 @@ public class PenumbraIpcProviders : IDisposable internal readonly FuncProvider RemoveTemporaryModAll; internal readonly FuncProvider RemoveTemporaryMod; - public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager) + public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager, CollectionManager collections, + TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService) { Api = api; @@ -226,7 +228,7 @@ public class PenumbraIpcProviders : IDisposable RemoveTemporaryModAll = Ipc.RemoveTemporaryModAll.Provider(pi, Api.RemoveTemporaryModAll); RemoveTemporaryMod = Ipc.RemoveTemporaryMod.Provider(pi, Api.RemoveTemporaryMod); - Tester = new IpcTester(pi, this, modManager); + Tester = new IpcTester(pi, this, modManager, collections, tempMods, tempCollections, saveService); Initialized.Invoke(); } diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index d630ad31..e62fd1b9 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -26,7 +26,7 @@ public class CollectionCache : IDisposable private readonly ModCollection _collection; public readonly SortedList, object?)> _changedItems = new(); public readonly Dictionary ResolvedFiles = new(); - public readonly MetaCache MetaManipulations; + public readonly MetaCache Meta; public readonly Dictionary> _conflicts = new(); public IEnumerable> AllConflicts @@ -50,18 +50,18 @@ public class CollectionCache : IDisposable // The cache reacts through events on its collection changing. public CollectionCache(CollectionCacheManager manager, ModCollection collection) { - _manager = manager; - _collection = collection; - MetaManipulations = new MetaCache(manager.MetaFileManager, _collection); + _manager = manager; + _collection = collection; + Meta = new MetaCache(manager.MetaFileManager, _collection); } public void Dispose() { - MetaManipulations.Dispose(); + Meta.Dispose(); } ~CollectionCache() - => MetaManipulations.Dispose(); + => Meta.Dispose(); // Resolve a given game path according to this collection. public FullPath? ResolvePath(Utf8GamePath gameResourcePath) @@ -119,16 +119,16 @@ public class CollectionCache : IDisposable return ret; } - /// Force a file to be resolved to a specific path regardless of conflicts. + /// Force a file to be resolved to a specific path regardless of conflicts. internal void ForceFile(Utf8GamePath path, FullPath fullPath) { if (CheckFullPath(path, fullPath)) ResolvedFiles[path] = new ModPath(Mod.ForcedFiles, fullPath); } - /// Force a file resolve to be removed. + /// Force a file resolve to be removed. internal void RemoveFile(Utf8GamePath path) - => ResolvedFiles.Remove(path); + => ResolvedFiles.Remove(path); public void ReloadMod(IMod mod, bool addMetaChanges) { @@ -151,8 +151,8 @@ public class CollectionCache : IDisposable foreach (var manipulation in mod.AllSubMods.SelectMany(s => s.Manipulations)) { - if (MetaManipulations.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod) - MetaManipulations.RevertMod(manipulation); + if (Meta.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod) + Meta.RevertMod(manipulation); } _conflicts.Remove(mod); @@ -175,11 +175,7 @@ public class CollectionCache : IDisposable if (addMetaChanges) { ++_collection.ChangeCounter; - if (_collection == Penumbra.CollectionManager.Active.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods) - { - Penumbra.ResidentResources.Reload(); - MetaManipulations.SetFiles(); - } + _manager.MetaFileManager.ApplyDefaultFiles(_collection); } } @@ -225,11 +221,7 @@ public class CollectionCache : IDisposable if ((mod is TemporaryMod temp ? temp.TotalManipulations : Penumbra.ModCaches[mod.Index].TotalManipulations) > 0) AddMetaFiles(); - if (_collection == Penumbra.CollectionManager.Active.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods) - { - Penumbra.ResidentResources.Reload(); - MetaManipulations.SetFiles(); - } + _manager.MetaFileManager.ApplyDefaultFiles(_collection); } } @@ -335,9 +327,9 @@ public class CollectionCache : IDisposable // Inside the same mod, conflicts are not recorded. private void AddManipulation(MetaManipulation manip, IMod mod) { - if (!MetaManipulations.TryGetValue(manip, out var existingMod)) + if (!Meta.TryGetValue(manip, out var existingMod)) { - MetaManipulations.ApplyMod(manip, mod); + Meta.ApplyMod(manip, mod); return; } @@ -346,13 +338,13 @@ public class CollectionCache : IDisposable return; if (AddConflict(manip, mod, existingMod)) - MetaManipulations.ApplyMod(manip, mod); + Meta.ApplyMod(manip, mod); } // Add all necessary meta file redirects. public void AddMetaFiles() - => MetaManipulations.SetImcFiles(); + => Meta.SetImcFiles(); // Identify and record all manipulated objects for this entire collection. @@ -367,7 +359,7 @@ public class CollectionCache : IDisposable _changedItems.Clear(); // Skip IMCs because they would result in far too many false-positive items, // since they are per set instead of per item-slot/item/variant. - var identifier = Penumbra.Identifier; + var identifier = _manager.MetaFileManager.Identifier.AwaitedService; var items = new SortedList(512); void AddItems(IMod mod) @@ -391,7 +383,7 @@ public class CollectionCache : IDisposable AddItems(modPath.Mod); } - foreach (var (manip, mod) in MetaManipulations) + foreach (var (manip, mod) in Meta) { ModCacheManager.ComputeChangedItems(identifier, items, manip); AddItems(mod); diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index 467d0617..4e3bb0cf 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -112,7 +112,7 @@ public class CollectionCacheManager : IDisposable private void FullRecalculation(ModCollection collection, CollectionCache cache) { cache.ResolvedFiles.Clear(); - cache.MetaManipulations.Reset(); + cache.Meta.Reset(); cache._conflicts.Clear(); // Add all forced redirects. diff --git a/Penumbra/Collections/Manager/ActiveCollections.cs b/Penumbra/Collections/Manager/ActiveCollections.cs index 8fdeaa08..1e083d90 100644 --- a/Penumbra/Collections/Manager/ActiveCollections.cs +++ b/Penumbra/Collections/Manager/ActiveCollections.cs @@ -10,7 +10,6 @@ using Penumbra.GameData.Actors; using Penumbra.Services; using Penumbra.UI; using Penumbra.Util; -using static OtterGui.Raii.ImRaii; namespace Penumbra.Collections.Manager; @@ -22,7 +21,7 @@ public class ActiveCollections : ISavable, IDisposable private readonly CommunicatorService _communicator; private readonly SaveService _saveService; - public ActiveCollections(CollectionStorage storage, ActorService actors, CommunicatorService communicator, SaveService saveService) + public ActiveCollections(Configuration config, CollectionStorage storage, ActorService actors, CommunicatorService communicator, SaveService saveService) { _storage = storage; _communicator = communicator; @@ -30,7 +29,7 @@ public class ActiveCollections : ISavable, IDisposable Current = storage.DefaultNamed; Default = storage.DefaultNamed; Interface = storage.DefaultNamed; - Individuals = new IndividualCollections(actors.AwaitedService); + Individuals = new IndividualCollections(actors.AwaitedService, config); _communicator.CollectionChange.Subscribe(OnCollectionChange); LoadCollections(); UpdateCurrentCollectionInUse(); diff --git a/Penumbra/Collections/Manager/IndividualCollections.Access.cs b/Penumbra/Collections/Manager/IndividualCollections.Access.cs index 32e7fd17..b81e72c1 100644 --- a/Penumbra/Collections/Manager/IndividualCollections.Access.cs +++ b/Penumbra/Collections/Manager/IndividualCollections.Access.cs @@ -9,10 +9,10 @@ using Penumbra.String; namespace Penumbra.Collections.Manager; -public sealed partial class IndividualCollections : IReadOnlyList< (string DisplayName, ModCollection Collection) > +public sealed partial class IndividualCollections : IReadOnlyList<(string DisplayName, ModCollection Collection)> { - public IEnumerator< (string DisplayName, ModCollection Collection) > GetEnumerator() - => _assignments.Select( t => ( t.DisplayName, t.Collection ) ).GetEnumerator(); + public IEnumerator<(string DisplayName, ModCollection Collection)> GetEnumerator() + => _assignments.Select(t => (t.DisplayName, t.Collection)).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -20,59 +20,51 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ public int Count => _assignments.Count; - public (string DisplayName, ModCollection Collection) this[ int index ] - => ( _assignments[ index ].DisplayName, _assignments[ index ].Collection ); + public (string DisplayName, ModCollection Collection) this[int index] + => (_assignments[index].DisplayName, _assignments[index].Collection); - public bool TryGetCollection( ActorIdentifier identifier, [NotNullWhen( true )] out ModCollection? collection ) + public bool TryGetCollection(ActorIdentifier identifier, [NotNullWhen(true)] out ModCollection? collection) { - if( Count == 0 ) + if (Count == 0) { collection = null; return false; } - switch( identifier.Type ) + switch (identifier.Type) { - case IdentifierType.Player: return CheckWorlds( identifier, out collection ); + case IdentifierType.Player: return CheckWorlds(identifier, out collection); case IdentifierType.Retainer: { - if( _individuals.TryGetValue( identifier, out collection ) ) - { + if (_individuals.TryGetValue(identifier, out collection)) return true; - } - if( identifier.Retainer is not ActorIdentifier.RetainerType.Mannequin && Penumbra.Config.UseOwnerNameForCharacterCollection ) - { - return CheckWorlds( _actorManager.GetCurrentPlayer(), out collection ); - } + if (identifier.Retainer is not ActorIdentifier.RetainerType.Mannequin && _config.UseOwnerNameForCharacterCollection) + return CheckWorlds(_actorManager.GetCurrentPlayer(), out collection); break; } case IdentifierType.Owned: { - if( CheckWorlds( identifier, out collection! ) ) - { + if (CheckWorlds(identifier, out collection!)) return true; - } // Handle generic NPC - var npcIdentifier = _actorManager.CreateIndividualUnchecked( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, identifier.Kind, identifier.DataId ); - if( npcIdentifier.IsValid && _individuals.TryGetValue( npcIdentifier, out collection ) ) - { + var npcIdentifier = _actorManager.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, + identifier.Kind, identifier.DataId); + if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection)) return true; - } // Handle Ownership. - if( Penumbra.Config.UseOwnerNameForCharacterCollection ) - { - identifier = _actorManager.CreateIndividualUnchecked( IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, ObjectKind.None, uint.MaxValue ); - return CheckWorlds( identifier, out collection ); - } + if (!_config.UseOwnerNameForCharacterCollection) + return false; - return false; + identifier = _actorManager.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld, + ObjectKind.None, uint.MaxValue); + return CheckWorlds(identifier, out collection); } - case IdentifierType.Npc: return _individuals.TryGetValue( identifier, out collection ); - case IdentifierType.Special: return CheckWorlds( ConvertSpecialIdentifier( identifier ).Item1, out collection ); + case IdentifierType.Npc: return _individuals.TryGetValue(identifier, out collection); + case IdentifierType.Special: return CheckWorlds(ConvertSpecialIdentifier(identifier).Item1, out collection); } collection = null; @@ -94,80 +86,71 @@ public sealed partial class IndividualCollections : IReadOnlyList< (string Displ Invalid, } - public (ActorIdentifier, SpecialResult) ConvertSpecialIdentifier( ActorIdentifier identifier ) + public (ActorIdentifier, SpecialResult) ConvertSpecialIdentifier(ActorIdentifier identifier) { - if( identifier.Type != IdentifierType.Special ) - { - return ( identifier, SpecialResult.Invalid ); - } + if (identifier.Type != IdentifierType.Special) + return (identifier, SpecialResult.Invalid); - if( _actorManager.ResolvePartyBannerPlayer( identifier.Special, out var id ) ) - { - return ( id, SpecialResult.PartyBanner ); - } + if (_actorManager.ResolvePartyBannerPlayer(identifier.Special, out var id)) + return (id, SpecialResult.PartyBanner); - if( _actorManager.ResolvePvPBannerPlayer( identifier.Special, out id ) ) - { - return ( id, SpecialResult.PvPBanner ); - } + if (_actorManager.ResolvePvPBannerPlayer(identifier.Special, out id)) + return (id, SpecialResult.PvPBanner); - if( _actorManager.ResolveMahjongPlayer( identifier.Special, out id ) ) - { - return ( id, SpecialResult.Mahjong ); - } + if (_actorManager.ResolveMahjongPlayer(identifier.Special, out id)) + return (id, SpecialResult.Mahjong); - switch( identifier.Special ) + switch (identifier.Special) { - case ScreenActor.CharacterScreen when Penumbra.Config.UseCharacterCollectionInMainWindow: return ( _actorManager.GetCurrentPlayer(), SpecialResult.CharacterScreen ); - case ScreenActor.FittingRoom when Penumbra.Config.UseCharacterCollectionInTryOn: return ( _actorManager.GetCurrentPlayer(), SpecialResult.FittingRoom ); - case ScreenActor.DyePreview when Penumbra.Config.UseCharacterCollectionInTryOn: return ( _actorManager.GetCurrentPlayer(), SpecialResult.DyePreview ); - case ScreenActor.Portrait when Penumbra.Config.UseCharacterCollectionsInCards: return ( _actorManager.GetCurrentPlayer(), SpecialResult.Portrait ); + case ScreenActor.CharacterScreen when _config.UseCharacterCollectionInMainWindow: + return (_actorManager.GetCurrentPlayer(), SpecialResult.CharacterScreen); + case ScreenActor.FittingRoom when _config.UseCharacterCollectionInTryOn: + return (_actorManager.GetCurrentPlayer(), SpecialResult.FittingRoom); + case ScreenActor.DyePreview when _config.UseCharacterCollectionInTryOn: + return (_actorManager.GetCurrentPlayer(), SpecialResult.DyePreview); + case ScreenActor.Portrait when _config.UseCharacterCollectionsInCards: + return (_actorManager.GetCurrentPlayer(), SpecialResult.Portrait); case ScreenActor.ExamineScreen: { identifier = _actorManager.GetInspectPlayer(); - if( identifier.IsValid ) - { - return ( Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect ); - } + if (identifier.IsValid) + return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect); identifier = _actorManager.GetCardPlayer(); - if( identifier.IsValid ) - { - return ( Penumbra.Config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card ); - } + if (identifier.IsValid) + return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card); - return Penumbra.Config.UseCharacterCollectionInTryOn ? ( _actorManager.GetGlamourPlayer(), SpecialResult.Glamour ) : ( identifier, SpecialResult.Invalid ); + return _config.UseCharacterCollectionInTryOn + ? (_actorManager.GetGlamourPlayer(), SpecialResult.Glamour) + : (identifier, SpecialResult.Invalid); } - default: return ( identifier, SpecialResult.Invalid ); + default: return (identifier, SpecialResult.Invalid); } } - public bool TryGetCollection( GameObject? gameObject, out ModCollection? collection ) - => TryGetCollection( _actorManager.FromObject( gameObject, true, false, false ), out collection ); + public bool TryGetCollection(GameObject? gameObject, out ModCollection? collection) + => TryGetCollection(_actorManager.FromObject(gameObject, true, false, false), out collection); - public unsafe bool TryGetCollection( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection ) - => TryGetCollection( _actorManager.FromObject( gameObject, out _, true, false, false ), out collection ); + public unsafe bool TryGetCollection(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection) + => TryGetCollection(_actorManager.FromObject(gameObject, out _, true, false, false), out collection); - private bool CheckWorlds( ActorIdentifier identifier, out ModCollection? collection ) + private bool CheckWorlds(ActorIdentifier identifier, out ModCollection? collection) { - if( !identifier.IsValid ) + if (!identifier.IsValid) { collection = null; return false; } - if( _individuals.TryGetValue( identifier, out collection ) ) - { + if (_individuals.TryGetValue(identifier, out collection)) return true; - } - identifier = _actorManager.CreateIndividualUnchecked( identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId ); - if( identifier.IsValid && _individuals.TryGetValue( identifier, out collection ) ) - { + identifier = _actorManager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, + identifier.DataId); + if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection)) return true; - } collection = null; return false; } -} \ No newline at end of file +} diff --git a/Penumbra/Collections/Manager/IndividualCollections.cs b/Penumbra/Collections/Manager/IndividualCollections.cs index 28059ecf..80fa6089 100644 --- a/Penumbra/Collections/Manager/IndividualCollections.cs +++ b/Penumbra/Collections/Manager/IndividualCollections.cs @@ -12,6 +12,7 @@ namespace Penumbra.Collections.Manager; public sealed partial class IndividualCollections { + private readonly Configuration _config; private readonly ActorManager _actorManager; private readonly List<(string DisplayName, IReadOnlyList Identifiers, ModCollection Collection)> _assignments = new(); private readonly Dictionary _individuals = new(); @@ -20,11 +21,17 @@ public sealed partial class IndividualCollections => _assignments; // TODO - public IndividualCollections(ActorService actorManager) - => _actorManager = actorManager.AwaitedService; + public IndividualCollections(ActorService actorManager, Configuration config) + { + _config = config; + _actorManager = actorManager.AwaitedService; + } - public IndividualCollections(ActorManager actorManager) - => _actorManager = actorManager; + public IndividualCollections(ActorManager actorManager, Configuration config) + { + _actorManager = actorManager; + _config = config; + } public enum AddResult { @@ -234,7 +241,7 @@ public sealed partial class IndividualCollections => identifier.IsValid ? Index(DisplayString(identifier)) : -1; private string DisplayString(ActorIdentifier identifier) - { + { return identifier.Type switch { IdentifierType.Player => $"{identifier.PlayerName} ({_actorManager.Data.ToWorldName(identifier.HomeWorld)})", diff --git a/Penumbra/Collections/Manager/TempCollectionManager.cs b/Penumbra/Collections/Manager/TempCollectionManager.cs index 29733382..34a8db3c 100644 --- a/Penumbra/Collections/Manager/TempCollectionManager.cs +++ b/Penumbra/Collections/Manager/TempCollectionManager.cs @@ -19,11 +19,11 @@ public class TempCollectionManager : IDisposable private readonly CollectionStorage _storage; private readonly Dictionary _customCollections = new(); - public TempCollectionManager(CommunicatorService communicator, ActorService actors, CollectionStorage storage) + public TempCollectionManager(Configuration config, CommunicatorService communicator, ActorService actors, CollectionStorage storage) { _communicator = communicator; _storage = storage; - Collections = new IndividualCollections(actors); + Collections = new IndividualCollections(actors, config); _communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange); } diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index f50e2472..2ccdf3c3 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -49,12 +49,12 @@ public partial class ModCollection // Obtain data from the cache. internal MetaCache? MetaCache - => _cache?.MetaManipulations; + => _cache?.Meta; public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) { if (_cache != null) - return _cache.MetaManipulations.GetImcFile(path, out file); + return _cache.Meta.GetImcFile(path, out file); file = null; return false; @@ -80,7 +80,7 @@ public partial class ModCollection } else { - _cache.MetaManipulations.SetFiles(); + _cache.Meta.SetFiles(); Penumbra.Log.Debug($"Set CharacterUtility resources for collection {Name}."); } } @@ -90,28 +90,28 @@ public partial class ModCollection if (_cache == null) Penumbra.CharacterUtility.ResetResource(idx); else - _cache.MetaManipulations.SetFile(idx); + _cache.Meta.SetFile(idx); } // Used for short periods of changed files. public CharacterUtility.MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) - => _cache?.MetaManipulations.TemporarilySetEqdpFile(genderRace, accessory) + => _cache?.Meta.TemporarilySetEqdpFile(genderRace, accessory) ?? Penumbra.CharacterUtility.TemporarilyResetResource(Interop.Structs.CharacterUtilityData.EqdpIdx(genderRace, accessory)); public CharacterUtility.MetaList.MetaReverter TemporarilySetEqpFile() - => _cache?.MetaManipulations.TemporarilySetEqpFile() + => _cache?.Meta.TemporarilySetEqpFile() ?? Penumbra.CharacterUtility.TemporarilyResetResource(MetaIndex.Eqp); public CharacterUtility.MetaList.MetaReverter TemporarilySetGmpFile() - => _cache?.MetaManipulations.TemporarilySetGmpFile() + => _cache?.Meta.TemporarilySetGmpFile() ?? Penumbra.CharacterUtility.TemporarilyResetResource(MetaIndex.Gmp); public CharacterUtility.MetaList.MetaReverter TemporarilySetCmpFile() - => _cache?.MetaManipulations.TemporarilySetCmpFile() + => _cache?.Meta.TemporarilySetCmpFile() ?? Penumbra.CharacterUtility.TemporarilyResetResource(MetaIndex.HumanCmp); public CharacterUtility.MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type) - => _cache?.MetaManipulations.TemporarilySetEstFile(type) + => _cache?.Meta.TemporarilySetEstFile(type) ?? Penumbra.CharacterUtility.TemporarilyResetResource((MetaIndex)type); } diff --git a/Penumbra/CommandHandler.cs b/Penumbra/CommandHandler.cs index bdc16d75..bda1a8e5 100644 --- a/Penumbra/CommandHandler.cs +++ b/Penumbra/CommandHandler.cs @@ -550,19 +550,19 @@ public class CommandHandler : IDisposable private void Print(string text) { - if (Penumbra.Config.PrintSuccessfulCommandsToChat) + if (_config.PrintSuccessfulCommandsToChat) _chat.Print(text); } private void Print(DefaultInterpolatedStringHandler text) { - if (Penumbra.Config.PrintSuccessfulCommandsToChat) + if (_config.PrintSuccessfulCommandsToChat) _chat.Print(text.ToStringAndClear()); } private void Print(Func text) { - if (Penumbra.Config.PrintSuccessfulCommandsToChat) + if (_config.PrintSuccessfulCommandsToChat) _chat.Print(text()); } } diff --git a/Penumbra/Import/Structs/TexToolsStructs.cs b/Penumbra/Import/Structs/TexToolsStructs.cs index cdd70c53..2a160c62 100644 --- a/Penumbra/Import/Structs/TexToolsStructs.cs +++ b/Penumbra/Import/Structs/TexToolsStructs.cs @@ -12,7 +12,7 @@ internal static class DefaultTexToolsData } [Serializable] -internal class SimpleMod +public class SimpleMod { public string Name = string.Empty; public string Category = string.Empty; @@ -24,14 +24,14 @@ internal class SimpleMod } [Serializable] -internal class ModPackPage +public class ModPackPage { public int PageIndex = 0; public ModGroup[] ModGroups = Array.Empty(); } [Serializable] -internal class ModGroup +public class ModGroup { public string GroupName = string.Empty; public GroupType SelectionType = GroupType.Single; @@ -40,7 +40,7 @@ internal class ModGroup } [Serializable] -internal class OptionList +public class OptionList { public string Name = string.Empty; public string Description = string.Empty; @@ -52,7 +52,7 @@ internal class OptionList } [Serializable] -internal class ExtendedModPack +public class ExtendedModPack { public string PackVersion = string.Empty; public string Name = DefaultTexToolsData.Name; @@ -65,7 +65,7 @@ internal class ExtendedModPack } [Serializable] -internal class SimpleModPack +public class SimpleModPack { public string TtmpVersion = string.Empty; public string Name = DefaultTexToolsData.Name; diff --git a/Penumbra/Import/TexToolsImporter.Archives.cs b/Penumbra/Import/TexToolsImporter.Archives.cs index 6234e9ca..13200a9c 100644 --- a/Penumbra/Import/TexToolsImporter.Archives.cs +++ b/Penumbra/Import/TexToolsImporter.Archives.cs @@ -18,11 +18,13 @@ namespace Penumbra.Import; public partial class TexToolsImporter { - // Extract regular compressed archives that are folders containing penumbra-formatted mods. - // The mod has to either contain a meta.json at top level, or one folder deep. - // If the meta.json is one folder deep, all other files have to be in the same folder. - // The extracted folder gets its name either from that one top-level folder or from the mod name. - // All data is extracted without manipulation of the files or metadata. + /// + /// Extract regular compressed archives that are folders containing penumbra-formatted mods. + /// The mod has to either contain a meta.json at top level, or one folder deep. + /// If the meta.json is one folder deep, all other files have to be in the same folder. + /// The extracted folder gets its name either from that one top-level folder or from the mod name. + /// All data is extracted without manipulation of the files or metadata. + /// private DirectoryInfo HandleRegularArchive( FileInfo modPackFile ) { using var zfs = modPackFile.OpenRead(); @@ -111,7 +113,7 @@ public partial class TexToolsImporter } _currentModDirectory.Refresh(); - ModCreator.SplitMultiGroups( _currentModDirectory ); + _modManager.Creator.SplitMultiGroups( _currentModDirectory ); return _currentModDirectory; } diff --git a/Penumbra/Import/TexToolsImporter.ModPack.cs b/Penumbra/Import/TexToolsImporter.ModPack.cs index 5c06dcdc..6de16612 100644 --- a/Penumbra/Import/TexToolsImporter.ModPack.cs +++ b/Penumbra/Import/TexToolsImporter.ModPack.cs @@ -40,7 +40,7 @@ public partial class TexToolsImporter // Open the mod data file from the mod pack as a SqPackStream _streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" ); ExtractSimpleModList( _currentModDirectory, modList ); - ModCreator.CreateDefaultFiles( _currentModDirectory ); + _modManager.Creator.CreateDefaultFiles( _currentModDirectory ); ResetStreamDisposer(); return _currentModDirectory; } @@ -97,7 +97,7 @@ public partial class TexToolsImporter // Open the mod data file from the mod pack as a SqPackStream _streamDisposer = GetSqPackStreamStream( extractedModPack, "TTMPD.mpd" ); ExtractSimpleModList( _currentModDirectory, modList.SimpleModsList ); - ModCreator.CreateDefaultFiles( _currentModDirectory ); + _modManager.Creator.CreateDefaultFiles( _currentModDirectory ); ResetStreamDisposer(); return _currentModDirectory; } @@ -185,7 +185,7 @@ public partial class TexToolsImporter var optionFolder = ModCreator.NewSubFolderName( groupFolder, option.Name ) ?? new DirectoryInfo( Path.Combine( groupFolder.FullName, $"Option {i + optionIdx + 1}" ) ); ExtractSimpleModList( optionFolder, option.ModsJsons ); - options.Add( ModCreator.CreateSubMod( _currentModDirectory, optionFolder, option ) ); + options.Add( _modManager.Creator.CreateSubMod( _currentModDirectory, optionFolder, option ) ); if( option.IsChecked ) { defaultSettings = group.SelectionType == GroupType.Multi @@ -211,7 +211,7 @@ public partial class TexToolsImporter } } - ModCreator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority, + _modManager.Creator.CreateOptionGroup( _currentModDirectory, group.SelectionType, name, groupPriority, groupPriority, defaultSettings ?? 0, group.Description, options ); ++groupPriority; } @@ -219,7 +219,7 @@ public partial class TexToolsImporter } ResetStreamDisposer(); - ModCreator.CreateDefaultFiles( _currentModDirectory ); + _modManager.Creator.CreateDefaultFiles( _currentModDirectory ); return _currentModDirectory; } diff --git a/Penumbra/Import/TexToolsMeta.Export.cs b/Penumbra/Import/TexToolsMeta.Export.cs index 759474e9..bee31cb2 100644 --- a/Penumbra/Import/TexToolsMeta.Export.cs +++ b/Penumbra/Import/TexToolsMeta.Export.cs @@ -12,7 +12,26 @@ using Penumbra.Meta.Manipulations; namespace Penumbra.Import; public partial class TexToolsMeta -{ +{ + public static void WriteTexToolsMeta(MetaFileManager manager, IEnumerable manipulations, DirectoryInfo basePath) + { + var files = ConvertToTexTools(manager, manipulations); + + foreach (var (file, data) in files) + { + var path = Path.Combine(basePath.FullName, file); + try + { + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllBytes(path, data); + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not write meta file {path}:\n{e}"); + } + } + } + public static Dictionary< string, byte[] > ConvertToTexTools( MetaFileManager manager, IEnumerable< MetaManipulation > manips ) { var ret = new Dictionary< string, byte[] >(); diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs index 654760ca..72ebac34 100644 --- a/Penumbra/Meta/MetaFileManager.cs +++ b/Penumbra/Meta/MetaFileManager.cs @@ -9,6 +9,7 @@ using Penumbra.GameData; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; using Penumbra.Meta.Files; +using Penumbra.Services; using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager; namespace Penumbra.Meta; @@ -21,9 +22,10 @@ public unsafe class MetaFileManager internal readonly DataManager GameData; internal readonly ActiveCollections ActiveCollections; internal readonly ValidityChecker ValidityChecker; + internal readonly IdentifierService Identifier; public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, DataManager gameData, - ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker) + ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker, IdentifierService identifier) { CharacterUtility = characterUtility; ResidentResources = residentResources; @@ -31,6 +33,7 @@ public unsafe class MetaFileManager ActiveCollections = activeCollections; Config = config; ValidityChecker = validityChecker; + Identifier = identifier; SignatureHelper.Initialise(this); } @@ -55,7 +58,7 @@ public unsafe class MetaFileManager return; ResidentResources.Reload(); - collection._cache?.MetaManipulations.SetFiles(); + collection._cache?.Meta.SetFiles(); } /// diff --git a/Penumbra/Mods/Editor/DuplicateManager.cs b/Penumbra/Mods/Editor/DuplicateManager.cs index 20b6e019..b2ec750f 100644 --- a/Penumbra/Mods/Editor/DuplicateManager.cs +++ b/Penumbra/Mods/Editor/DuplicateManager.cs @@ -244,7 +244,7 @@ public class DuplicateManager try { var mod = new Mod(modDirectory); - mod.Reload(_modManager, true, out _); + _modManager.Creator.ReloadMod(mod, true, out _); Finished = false; _files.UpdateAll(mod, mod.Default); diff --git a/Penumbra/Mods/Editor/IMod.cs b/Penumbra/Mods/Editor/IMod.cs index 658922cf..5f32122c 100644 --- a/Penumbra/Mods/Editor/IMod.cs +++ b/Penumbra/Mods/Editor/IMod.cs @@ -13,5 +13,5 @@ public interface IMod public ISubMod Default { get; } public IReadOnlyList< IModGroup > Groups { get; } - public IEnumerable< ISubMod > AllSubMods { get; } + public IEnumerable< SubMod > AllSubMods { get; } } \ No newline at end of file diff --git a/Penumbra/Mods/Editor/ModFileEditor.cs b/Penumbra/Mods/Editor/ModFileEditor.cs index cf1ff027..17505550 100644 --- a/Penumbra/Mods/Editor/ModFileEditor.cs +++ b/Penumbra/Mods/Editor/ModFileEditor.cs @@ -160,7 +160,7 @@ public class ModFileEditor if (deletions <= 0) return; - mod.Reload(_modManager, false, out _); + _modManager.Creator.ReloadMod(mod, false, out _); _files.UpdateAll(mod, option); } diff --git a/Penumbra/Mods/Manager/ModCacheManager.cs b/Penumbra/Mods/Manager/ModCacheManager.cs index 6ef43454..c25a6872 100644 --- a/Penumbra/Mods/Manager/ModCacheManager.cs +++ b/Penumbra/Mods/Manager/ModCacheManager.cs @@ -221,10 +221,10 @@ public class ModCacheManager : IDisposable, IReadOnlyList if (!_identifier.Valid) return; - foreach (var gamePath in mod.AllRedirects) + foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys))) _identifier.AwaitedService.Identify(cache.ChangedItems, gamePath.ToString()); - foreach (var manip in mod.AllManipulations) + foreach (var manip in mod.AllSubMods.SelectMany(m => m.Manipulations)) ComputeChangedItems(_identifier.AwaitedService, cache.ChangedItems, manip); cache.LowerChangedItemsString = string.Join("\0", cache.ChangedItems.Keys.Select(k => k.ToLowerInvariant())); diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index 86bd826e..15fc5e92 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -116,7 +116,7 @@ public class ModDataEditor return changes; } - public ModDataChangeType LoadMeta(Mod mod) + public ModDataChangeType LoadMeta(ModCreator creator, Mod mod) { var metaFile = _saveService.FileNames.ModMetaPath(mod); if (!File.Exists(metaFile)) @@ -171,7 +171,7 @@ public class ModDataEditor } if (newFileVersion != ModMeta.FileVersion) - if (ModMigration.Migrate(_saveService, mod, json, ref newFileVersion)) + if (ModMigration.Migrate(creator, _saveService, mod, json, ref newFileVersion)) { changes |= ModDataChangeType.Migration; _saveService.ImmediateSave(new ModMeta(mod)); diff --git a/Penumbra/Mods/Manager/ModFileSystem.cs b/Penumbra/Mods/Manager/ModFileSystem.cs index 2d5201ad..76f4e1d6 100644 --- a/Penumbra/Mods/Manager/ModFileSystem.cs +++ b/Penumbra/Mods/Manager/ModFileSystem.cs @@ -15,14 +15,14 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable { private readonly ModManager _modManager; private readonly CommunicatorService _communicator; - private readonly FilenameService _files; + private readonly SaveService _saveService; // Create a new ModFileSystem from the currently loaded mods and the current sort order file. - public ModFileSystem(ModManager modManager, CommunicatorService communicator, FilenameService files) + public ModFileSystem(ModManager modManager, CommunicatorService communicator, SaveService saveService) { _modManager = modManager; _communicator = communicator; - _files = files; + _saveService = saveService; Reload(); Changed += OnChange; _communicator.ModDiscoveryFinished.Subscribe(Reload); @@ -66,8 +66,8 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable private void Reload() { // TODO - if (Load(new FileInfo(_files.FilesystemFile), _modManager, ModToIdentifier, ModToName)) - Penumbra.SaveService.ImmediateSave(this); + if (Load(new FileInfo(_saveService.FileNames.FilesystemFile), _modManager, ModToIdentifier, ModToName)) + _saveService.ImmediateSave(this); Penumbra.Log.Debug("Reloaded mod filesystem."); } @@ -76,7 +76,7 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3) { if (type != FileSystemChangeType.Reload) - Penumbra.SaveService.QueueSave(this); + _saveService.QueueSave(this); } // Update sort order when defaulted mod names change. @@ -111,7 +111,7 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable break; case ModPathChangeType.Moved: - Penumbra.SaveService.QueueSave(this); + _saveService.QueueSave(this); break; case ModPathChangeType.Reloaded: // Nothing diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 7acb2417..ae22c5e9 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -24,18 +24,21 @@ public sealed class ModManager : ModStorage private readonly Configuration _config; private readonly CommunicatorService _communicator; + public readonly ModCreator Creator; public readonly ModDataEditor DataEditor; public readonly ModOptionEditor OptionEditor; public DirectoryInfo BasePath { get; private set; } = null!; public bool Valid { get; private set; } - public ModManager(Configuration config, CommunicatorService communicator, ModDataEditor dataEditor, ModOptionEditor optionEditor) + public ModManager(Configuration config, CommunicatorService communicator, ModDataEditor dataEditor, ModOptionEditor optionEditor, + ModCreator creator) { _config = config; _communicator = communicator; DataEditor = dataEditor; OptionEditor = optionEditor; + Creator = creator; SetBaseDirectory(config.ModDirectory, true); DiscoverMods(); } @@ -73,8 +76,8 @@ public sealed class ModManager : ModStorage if (this.Any(m => m.ModPath.Name == modFolder.Name)) return; - ModCreator.SplitMultiGroups(modFolder); - var mod = Mod.LoadMod(this, modFolder, true); + Creator.SplitMultiGroups(modFolder); + var mod = Creator.LoadMod(modFolder, true); if (mod == null) return; @@ -119,7 +122,7 @@ public sealed class ModManager : ModStorage var oldName = mod.Name; _communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath); - if (!mod.Reload(Penumbra.ModManager, true, out var metaChange)) + if (!Creator.ReloadMod(mod, true, out var metaChange)) { Penumbra.Log.Warning(mod.Name.Length == 0 ? $"Reloading mod {oldName} has failed, new name is empty. Deleting instead." @@ -185,7 +188,7 @@ public sealed class ModManager : ModStorage dir.Refresh(); mod.ModPath = dir; - if (!mod.Reload(Penumbra.ModManager, false, out var metaChange)) + if (!Creator.ReloadMod(mod, false, out var metaChange)) { Penumbra.Log.Error($"Error reloading moved mod {mod.Name}."); return; @@ -307,7 +310,7 @@ public sealed class ModManager : ModStorage var queue = new ConcurrentQueue(); Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir => { - var mod = Mod.LoadMod(this, dir, false); + var mod = Creator.LoadMod(dir, false); if (mod != null) queue.Enqueue(mod); }); diff --git a/Penumbra/Mods/Manager/ModMigration.cs b/Penumbra/Mods/Manager/ModMigration.cs index 196c7ed5..c41c40dc 100644 --- a/Penumbra/Mods/Manager/ModMigration.cs +++ b/Penumbra/Mods/Manager/ModMigration.cs @@ -20,8 +20,8 @@ public static partial class ModMigration [GeneratedRegex("^group_", RegexOptions.Compiled)] private static partial Regex GroupStartRegex(); - public static bool Migrate(SaveService saveService, Mod mod, JObject json, ref uint fileVersion) - => MigrateV0ToV1(saveService, mod, json, ref fileVersion) || MigrateV1ToV2(mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion); + public static bool Migrate(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion) + => MigrateV0ToV1(creator, saveService, mod, json, ref fileVersion) || MigrateV1ToV2(saveService, mod, ref fileVersion) || MigrateV2ToV3(mod, ref fileVersion); private static bool MigrateV2ToV3(Mod _, ref uint fileVersion) { @@ -33,13 +33,13 @@ public static partial class ModMigration return true; } - private static bool MigrateV1ToV2(Mod mod, ref uint fileVersion) + private static bool MigrateV1ToV2(SaveService saveService, Mod mod, ref uint fileVersion) { if (fileVersion > 1) return false; - if (!mod.GroupFiles.All(g => GroupRegex().IsMatch(g.Name))) - foreach (var (group, index) in mod.GroupFiles.WithIndex().ToArray()) + if (!saveService.FileNames.GetOptionGroupFiles(mod).All(g => GroupRegex().IsMatch(g.Name))) + foreach (var (group, index) in saveService.FileNames.GetOptionGroupFiles(mod).WithIndex().ToArray()) { var newName = GroupStartRegex().Replace(group.Name, $"group_{index + 1:D3}_"); try @@ -58,7 +58,7 @@ public static partial class ModMigration return true; } - private static bool MigrateV0ToV1(SaveService saveService, Mod mod, JObject json, ref uint fileVersion) + private static bool MigrateV0ToV1(ModCreator creator, SaveService saveService, Mod mod, JObject json, ref uint fileVersion) { if (fileVersion > 0) return false; @@ -69,21 +69,21 @@ public static partial class ModMigration var priority = 1; var seenMetaFiles = new HashSet(); foreach (var group in groups.Values) - ConvertGroup(mod, group, ref priority, seenMetaFiles); + ConvertGroup(creator, mod, group, ref priority, seenMetaFiles); foreach (var unusedFile in mod.FindUnusedFiles().Where(f => !seenMetaFiles.Contains(f))) { if (unusedFile.ToGamePath(mod.ModPath, out var gamePath) - && !mod._default.FileData.TryAdd(gamePath, unusedFile)) - Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod._default.FileData[gamePath]}."); + && !mod.Default.FileData.TryAdd(gamePath, unusedFile)) + Penumbra.Log.Error($"Could not add {gamePath} because it already points to {mod.Default.FileData[gamePath]}."); } - mod._default.FileSwapData.Clear(); - mod._default.FileSwapData.EnsureCapacity(swaps.Count); + mod.Default.FileSwapData.Clear(); + mod.Default.FileSwapData.EnsureCapacity(swaps.Count); foreach (var (gamePath, swapPath) in swaps) - mod._default.FileSwapData.Add(gamePath, swapPath); + mod.Default.FileSwapData.Add(gamePath, swapPath); - mod._default.IncorporateMetaChanges(mod.ModPath, true); + creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true); foreach (var (_, index) in mod.Groups.WithIndex()) saveService.ImmediateSave(new ModSaveGroup(mod, index)); @@ -118,7 +118,7 @@ public static partial class ModMigration return true; } - private static void ConvertGroup(Mod mod, OptionGroupV0 group, ref int priority, HashSet seenMetaFiles) + private static void ConvertGroup(ModCreator creator, Mod mod, OptionGroupV0 group, ref int priority, HashSet seenMetaFiles) { if (group.Options.Count == 0) return; @@ -134,15 +134,15 @@ public static partial class ModMigration Priority = priority++, Description = string.Empty, }; - mod._groups.Add(newMultiGroup); + mod.Groups.Add(newMultiGroup); foreach (var option in group.Options) - newMultiGroup.PrioritizedOptions.Add((SubModFromOption(mod, option, seenMetaFiles), optionPriority++)); + newMultiGroup.PrioritizedOptions.Add((SubModFromOption(creator, mod, option, seenMetaFiles), optionPriority++)); break; case GroupType.Single: if (group.Options.Count == 1) { - AddFilesToSubMod(mod._default, mod.ModPath, group.Options[0], seenMetaFiles); + AddFilesToSubMod(mod.Default, mod.ModPath, group.Options[0], seenMetaFiles); return; } @@ -152,9 +152,9 @@ public static partial class ModMigration Priority = priority++, Description = string.Empty, }; - mod._groups.Add(newSingleGroup); + mod.Groups.Add(newSingleGroup); foreach (var option in group.Options) - newSingleGroup.OptionData.Add(SubModFromOption(mod, option, seenMetaFiles)); + newSingleGroup.OptionData.Add(SubModFromOption(creator, mod, option, seenMetaFiles)); break; } @@ -173,11 +173,11 @@ public static partial class ModMigration } } - private static SubMod SubModFromOption(Mod mod, OptionV0 option, HashSet seenMetaFiles) + private static SubMod SubModFromOption(ModCreator creator, Mod mod, OptionV0 option, HashSet seenMetaFiles) { var subMod = new SubMod(mod) { Name = option.OptionName }; AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles); - subMod.IncorporateMetaChanges(mod.ModPath, false); + creator.IncorporateMetaChanges(subMod, mod.ModPath, false); return subMod; } diff --git a/Penumbra/Mods/Manager/ModOptionEditor.cs b/Penumbra/Mods/Manager/ModOptionEditor.cs index b1029822..ed978000 100644 --- a/Penumbra/Mods/Manager/ModOptionEditor.cs +++ b/Penumbra/Mods/Manager/ModOptionEditor.cs @@ -46,11 +46,11 @@ public class ModOptionEditor /// Change the type of a group given by mod and index to type, if possible. public void ChangeModGroupType(Mod mod, int groupIdx, GroupType type) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; if (group.Type == type) return; - mod._groups[groupIdx] = group.Convert(type); + mod.Groups[groupIdx] = group.Convert(type); _saveService.QueueSave(new ModSaveGroup(mod, groupIdx)); _communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1); } @@ -58,7 +58,7 @@ public class ModOptionEditor /// Change the settings stored as default options in a mod. public void ChangeModGroupDefaultOption(Mod mod, int groupIdx, uint defaultOption) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; if (group.DefaultSettings == defaultOption) return; @@ -70,7 +70,7 @@ public class ModOptionEditor /// Rename an option group if possible. public void RenameModGroup(Mod mod, int groupIdx, string newName) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; var oldName = group.Name; if (oldName == newName || !VerifyFileName(mod, group, newName, true)) return; @@ -93,9 +93,9 @@ public class ModOptionEditor if (!VerifyFileName(mod, null, newName, true)) return; - var maxPriority = mod._groups.Count == 0 ? 0 : mod._groups.Max(o => o.Priority) + 1; + var maxPriority = mod.Groups.Count == 0 ? 0 : mod.Groups.Max(o => o.Priority) + 1; - mod._groups.Add(type == GroupType.Multi + mod.Groups.Add(type == GroupType.Multi ? new MultiModGroup { Name = newName, @@ -106,16 +106,16 @@ public class ModOptionEditor Name = newName, Priority = maxPriority, }); - _saveService.ImmediateSave(new ModSaveGroup(mod, mod._groups.Count - 1)); - _communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod._groups.Count - 1, -1, -1); + _saveService.ImmediateSave(new ModSaveGroup(mod, mod.Groups.Count - 1)); + _communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod.Groups.Count - 1, -1, -1); } /// Delete a given option group. Fires an event to prepare before actually deleting. public void DeleteModGroup(Mod mod, int groupIdx) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; _communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1); - mod._groups.RemoveAt(groupIdx); + mod.Groups.RemoveAt(groupIdx); UpdateSubModPositions(mod, groupIdx); _saveService.SaveAllOptionGroups(mod); _communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1); @@ -124,7 +124,7 @@ public class ModOptionEditor /// Move the index of a given option group. public void MoveModGroup(Mod mod, int groupIdxFrom, int groupIdxTo) { - if (!mod._groups.Move(groupIdxFrom, groupIdxTo)) + if (!mod.Groups.Move(groupIdxFrom, groupIdxTo)) return; UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo)); @@ -135,7 +135,7 @@ public class ModOptionEditor /// Change the description of the given option group. public void ChangeGroupDescription(Mod mod, int groupIdx, string newDescription) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; if (group.Description == newDescription) return; @@ -152,7 +152,7 @@ public class ModOptionEditor /// Change the description of the given option. public void ChangeOptionDescription(Mod mod, int groupIdx, int optionIdx, string newDescription) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; var option = group[optionIdx]; if (option.Description == newDescription || option is not SubMod s) return; @@ -165,7 +165,7 @@ public class ModOptionEditor /// Change the internal priority of the given option group. public void ChangeGroupPriority(Mod mod, int groupIdx, int newPriority) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; if (group.Priority == newPriority) return; @@ -182,7 +182,7 @@ public class ModOptionEditor /// Change the internal priority of the given option. public void ChangeOptionPriority(Mod mod, int groupIdx, int optionIdx, int newPriority) { - switch (mod._groups[groupIdx]) + switch (mod.Groups[groupIdx]) { case SingleModGroup: ChangeGroupPriority(mod, groupIdx, newPriority); @@ -201,7 +201,7 @@ public class ModOptionEditor /// Rename the given option. public void RenameOption(Mod mod, int groupIdx, int optionIdx, string newName) { - switch (mod._groups[groupIdx]) + switch (mod.Groups[groupIdx]) { case SingleModGroup s: if (s.OptionData[optionIdx].Name == newName) @@ -225,7 +225,7 @@ public class ModOptionEditor /// Add a new empty option of the given name for the given group. public void AddOption(Mod mod, int groupIdx, string newName) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; var subMod = new SubMod(mod) { Name = newName }; subMod.SetPosition(groupIdx, group.Count); switch (group) @@ -248,7 +248,7 @@ public class ModOptionEditor if (option is not SubMod o) return; - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; if (group.Type is GroupType.Multi && group.Count >= IModGroup.MaxMultiOptions) { Penumbra.Log.Error( @@ -276,7 +276,7 @@ public class ModOptionEditor /// Delete the given option from the given group. public void DeleteOption(Mod mod, int groupIdx, int optionIdx) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; _communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1); switch (group) { @@ -297,7 +297,7 @@ public class ModOptionEditor /// Move an option inside the given option group. public void MoveOption(Mod mod, int groupIdx, int optionIdxFrom, int optionIdxTo) { - var group = mod._groups[groupIdx]; + var group = mod.Groups[groupIdx]; if (!group.MoveOption(optionIdxFrom, optionIdxTo)) return; @@ -379,7 +379,7 @@ public class ModOptionEditor /// Update the indices stored in options from a given group on. private static void UpdateSubModPositions(Mod mod, int fromGroup) { - foreach (var (group, groupIdx) in mod._groups.WithIndex().Skip(fromGroup)) + foreach (var (group, groupIdx) in mod.Groups.WithIndex().Skip(fromGroup)) { foreach (var (o, optionIdx) in group.OfType().WithIndex()) o.SetPosition(groupIdx, optionIdx); @@ -390,9 +390,9 @@ public class ModOptionEditor private static SubMod GetSubMod(Mod mod, int groupIdx, int optionIdx) { if (groupIdx == -1 && optionIdx == 0) - return mod._default; + return mod.Default; - return mod._groups[groupIdx] switch + return mod.Groups[groupIdx] switch { SingleModGroup s => s.OptionData[optionIdx], MultiModGroup m => m.PrioritizedOptions[optionIdx].Mod, diff --git a/Penumbra/Mods/Mod.BasePath.cs b/Penumbra/Mods/Mod.BasePath.cs index eb9571c2..28258ded 100644 --- a/Penumbra/Mods/Mod.BasePath.cs +++ b/Penumbra/Mods/Mod.BasePath.cs @@ -31,76 +31,6 @@ public partial class Mod internal Mod( DirectoryInfo modPath ) { ModPath = modPath; - _default = new SubMod( this ); - } - - public static Mod? LoadMod( ModManager modManager, DirectoryInfo modPath, bool incorporateMetaChanges ) - { - modPath.Refresh(); - if( !modPath.Exists ) - { - Penumbra.Log.Error( $"Supplied mod directory {modPath} does not exist." ); - return null; - } - - var mod = new Mod(modPath); - if (mod.Reload(modManager, incorporateMetaChanges, out _)) - return mod; - - // Can not be base path not existing because that is checked before. - Penumbra.Log.Warning( $"Mod at {modPath} without name is not supported." ); - return null; - - } - - internal bool Reload(ModManager modManager, bool incorporateMetaChanges, out ModDataChangeType modDataChange ) - { - modDataChange = ModDataChangeType.Deletion; - ModPath.Refresh(); - if( !ModPath.Exists ) - { - return false; - } - - modDataChange = modManager.DataEditor.LoadMeta(this); - if( modDataChange.HasFlag( ModDataChangeType.Deletion ) || Name.Length == 0 ) - { - return false; - } - - modManager.DataEditor.LoadLocalData(this); - - LoadDefaultOption(); - LoadAllGroups(); - if( incorporateMetaChanges ) - { - IncorporateAllMetaChanges(true); - } - - return true; - } - - // Convert all .meta and .rgsp files to their respective meta changes and add them to their options. - // Deletes the source files if delete is true. - private void IncorporateAllMetaChanges( bool delete ) - { - var changes = false; - List< string > deleteList = new(); - foreach( var subMod in AllSubMods.OfType< SubMod >() ) - { - var (localChanges, localDeleteList) = subMod.IncorporateMetaChanges( ModPath, false ); - changes |= localChanges; - if( delete ) - { - deleteList.AddRange( localDeleteList ); - } - } - - SubMod.DeleteDeleteList( deleteList, delete ); - - if( changes ) - { - Penumbra.SaveService.SaveAllOptionGroups(this); - } + Default = new SubMod( this ); } } \ No newline at end of file diff --git a/Penumbra/Mods/Mod.Creator.cs b/Penumbra/Mods/Mod.Creator.cs deleted file mode 100644 index 6233ece9..00000000 --- a/Penumbra/Mods/Mod.Creator.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OtterGui; -using OtterGui.Filesystem; -using Penumbra.Api.Enums; -using Penumbra.Import.Structs; -using Penumbra.String.Classes; - -namespace Penumbra.Mods; - -internal static partial class ModCreator -{ - /// - /// Create and return a new directory based on the given directory and name, that is
- /// - Not Empty.
- /// - Unique, by appending (digit) for duplicates.
- /// - Containing no symbols invalid for FFXIV or windows paths.
- ///
- /// - /// - /// - /// - /// - public static DirectoryInfo CreateModFolder( DirectoryInfo outDirectory, string modListName, bool create = true ) - { - var name = modListName; - if( name.Length == 0 ) - { - name = "_"; - } - - var newModFolderBase = NewOptionDirectory( outDirectory, name ); - var newModFolder = newModFolderBase.FullName.ObtainUniqueFile(); - if( newModFolder.Length == 0 ) - { - throw new IOException( "Could not create mod folder: too many folders of the same name exist." ); - } - - if( create ) - { - Directory.CreateDirectory( newModFolder ); - } - - return new DirectoryInfo( newModFolder ); - } - - /// - /// Create the name for a group or option subfolder based on its parent folder and given name. - /// subFolderName should never be empty, and the result is unique and contains no invalid symbols. - /// - public static DirectoryInfo? NewSubFolderName( DirectoryInfo parentFolder, string subFolderName ) - { - var newModFolderBase = NewOptionDirectory( parentFolder, subFolderName ); - var newModFolder = newModFolderBase.FullName.ObtainUniqueFile(); - return newModFolder.Length == 0 ? null : new DirectoryInfo( newModFolder ); - } - - /// Create a file for an option group from given data. - public static void CreateOptionGroup( DirectoryInfo baseFolder, GroupType type, string name, - int priority, int index, uint defaultSettings, string desc, IEnumerable< ISubMod > subMods ) - { - switch( type ) - { - case GroupType.Multi: - { - var group = new MultiModGroup() - { - Name = name, - Description = desc, - Priority = priority, - DefaultSettings = defaultSettings, - }; - group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) ); - Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index)); - break; - } - case GroupType.Single: - { - var group = new SingleModGroup() - { - Name = name, - Description = desc, - Priority = priority, - DefaultSettings = defaultSettings, - }; - group.OptionData.AddRange( subMods.OfType< SubMod >() ); - Penumbra.SaveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index)); - break; - } - } - } - - /// Create the data for a given sub mod from its data and the folder it is based on. - public static ISubMod CreateSubMod( DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option ) - { - var list = optionFolder.EnumerateNonHiddenFiles() - .Select( f => ( Utf8GamePath.FromFile( f, optionFolder, out var gamePath, true ), gamePath, new FullPath( f ) ) ) - .Where( t => t.Item1 ); - - var mod = new SubMod( null! ) // Mod is irrelevant here, only used for saving. - { - Name = option.Name, - Description = option.Description, - }; - foreach( var (_, gamePath, file) in list ) - { - mod.FileData.TryAdd( gamePath, file ); - } - - mod.IncorporateMetaChanges( baseFolder, true ); - return mod; - } - - /// Create an empty sub mod for single groups with None options. - internal static ISubMod CreateEmptySubMod( string name ) - => new SubMod( null! ) // Mod is irrelevant here, only used for saving. - { - Name = name, - }; - - /// - /// Create the default data file from all unused files that were not handled before - /// and are used in sub mods. - /// - internal static void CreateDefaultFiles( DirectoryInfo directory ) - { - var mod = new Mod( directory ); - mod.Reload( Penumbra.ModManager, false, out _ ); - foreach( var file in mod.FindUnusedFiles() ) - { - if( Utf8GamePath.FromFile( new FileInfo( file.FullName ), directory, out var gamePath, true ) ) - mod._default.FileData.TryAdd( gamePath, file ); - } - - mod._default.IncorporateMetaChanges( directory, true ); - Penumbra.SaveService.ImmediateSave(new ModSaveGroup(mod, -1)); - } - - /// Return the name of a new valid directory based on the base directory and the given name. - public static DirectoryInfo NewOptionDirectory( DirectoryInfo baseDir, string optionName ) - => new(Path.Combine( baseDir.FullName, ReplaceBadXivSymbols( optionName ) )); - - /// Normalize for nicer names, and remove invalid symbols or invalid paths. - public static string ReplaceBadXivSymbols( string s, string replacement = "_" ) - { - switch( s ) - { - case ".": return replacement; - case "..": return replacement + replacement; - } - - StringBuilder sb = new(s.Length); - foreach( var c in s.Normalize( NormalizationForm.FormKC ) ) - { - if( c.IsInvalidInPath() ) - { - sb.Append( replacement ); - } - else - { - sb.Append( c ); - } - } - - return sb.ToString(); - } - - public static void SplitMultiGroups( DirectoryInfo baseDir ) - { - var mod = new Mod( baseDir ); - - var files = mod.GroupFiles.ToList(); - var idx = 0; - var reorder = false; - foreach( var groupFile in files ) - { - ++idx; - try - { - if( reorder ) - { - var newName = $"{baseDir.FullName}\\group_{idx:D3}{groupFile.Name[ 9.. ]}"; - Penumbra.Log.Debug( $"Moving {groupFile.Name} to {Path.GetFileName( newName )} due to reordering after multi group split." ); - groupFile.MoveTo( newName, false ); - } - } - catch( Exception ex ) - { - throw new Exception( "Could not reorder group file after splitting multi group on .pmp import.", ex ); - } - - try - { - var json = JObject.Parse( File.ReadAllText( groupFile.FullName ) ); - if( json[ nameof( IModGroup.Type ) ]?.ToObject< GroupType >() is not GroupType.Multi ) - { - continue; - } - - var name = json[ nameof( IModGroup.Name ) ]?.ToObject< string >() ?? string.Empty; - if( name.Length == 0 ) - { - continue; - } - - - var options = json[ "Options" ]?.Children().ToList(); - if( options == null ) - { - continue; - } - - if( options.Count <= IModGroup.MaxMultiOptions ) - { - continue; - } - - Penumbra.Log.Information( $"Splitting multi group {name} in {mod.Name} due to {options.Count} being too many options." ); - var clone = json.DeepClone(); - reorder = true; - foreach( var o in options.Skip( IModGroup.MaxMultiOptions ) ) - { - o.Remove(); - } - - var newOptions = clone[ "Options" ]!.Children().ToList(); - foreach( var o in newOptions.Take( IModGroup.MaxMultiOptions ) ) - { - o.Remove(); - } - - var match = DuplicateNumber().Match( name ); - var startNumber = match.Success ? int.Parse( match.Groups[ 0 ].Value ) : 1; - name = match.Success ? name[ ..4 ] : name; - var oldName = $"{name}, Part {startNumber}"; - var oldPath = $"{baseDir.FullName}\\group_{idx:D3}_{oldName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json"; - var newName = $"{name}, Part {startNumber + 1}"; - var newPath = $"{baseDir.FullName}\\group_{++idx:D3}_{newName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json"; - json[ nameof( IModGroup.Name ) ] = oldName; - clone[ nameof( IModGroup.Name ) ] = newName; - - clone[ nameof( IModGroup.DefaultSettings ) ] = 0u; - - Penumbra.Log.Debug( $"Writing the first {IModGroup.MaxMultiOptions} options to {Path.GetFileName( oldPath )} after split." ); - using( var oldFile = File.CreateText( oldPath ) ) - { - using var j = new JsonTextWriter( oldFile ) - { - Formatting = Formatting.Indented, - }; - json.WriteTo( j ); - } - - Penumbra.Log.Debug( $"Writing the remaining {options.Count - IModGroup.MaxMultiOptions} options to {Path.GetFileName( newPath )} after split." ); - using( var newFile = File.CreateText( newPath ) ) - { - using var j = new JsonTextWriter( newFile ) - { - Formatting = Formatting.Indented, - }; - clone.WriteTo( j ); - } - - Penumbra.Log.Debug( - $"Deleting the old group file at {groupFile.Name} after splitting it into {Path.GetFileName( oldPath )} and {Path.GetFileName( newPath )}." ); - groupFile.Delete(); - } - catch( Exception ex ) - { - throw new Exception( $"Could not split multi group file {groupFile.Name} on .pmp import.", ex ); - } - } - } - - [GeneratedRegex(@", Part (\d+)$", RegexOptions.NonBacktracking )] - private static partial Regex DuplicateNumber(); -} \ No newline at end of file diff --git a/Penumbra/Mods/Mod.Files.cs b/Penumbra/Mods/Mod.Files.cs deleted file mode 100644 index 8820dd7b..00000000 --- a/Penumbra/Mods/Mod.Files.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Newtonsoft.Json.Linq; -using OtterGui; -using Penumbra.Api.Enums; -using Penumbra.Meta; -using Penumbra.Meta.Manipulations; -using Penumbra.String.Classes; - -namespace Penumbra.Mods; - -public partial class Mod -{ - public ISubMod Default - => _default; - - public IReadOnlyList Groups - => _groups; - - internal readonly SubMod _default; - internal readonly List _groups = new(); - - public IEnumerable AllSubMods - => _groups.SelectMany(o => o).Prepend(_default); - - public IEnumerable AllManipulations - => AllSubMods.SelectMany(s => s.Manipulations); - - public IEnumerable AllRedirects - => AllSubMods.SelectMany(s => s.Files.Keys.Concat(s.FileSwaps.Keys)); - - public IEnumerable AllFiles - => AllSubMods.SelectMany(o => o.Files) - .Select(p => p.Value); - - public IEnumerable GroupFiles - => ModPath.EnumerateFiles("group_*.json"); - - public List FindUnusedFiles() - { - var modFiles = AllFiles.ToHashSet(); - return ModPath.EnumerateDirectories() - .Where(d => !d.IsHidden()) - .SelectMany(FileExtensions.EnumerateNonHiddenFiles) - .Select(f => new FullPath(f)) - .Where(f => !modFiles.Contains(f)) - .ToList(); - } - - private static IModGroup? LoadModGroup(Mod mod, FileInfo file, int groupIdx) - { - if (!File.Exists(file.FullName)) - return null; - - try - { - var json = JObject.Parse(File.ReadAllText(file.FullName)); - switch (json[nameof(Type)]?.ToObject() ?? GroupType.Single) - { - case GroupType.Multi: return MultiModGroup.Load(mod, json, groupIdx); - case GroupType.Single: return SingleModGroup.Load(mod, json, groupIdx); - } - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not read mod group from {file.FullName}:\n{e}"); - } - - return null; - } - - private void LoadAllGroups() - { - _groups.Clear(); - var changes = false; - foreach (var file in GroupFiles) - { - var group = LoadModGroup(this, file, _groups.Count); - if (group != null && _groups.All(g => g.Name != group.Name)) - { - changes = changes || Penumbra.Filenames.OptionGroupFile(ModPath.FullName, Groups.Count, group.Name) != file.FullName; - _groups.Add(group); - } - else - { - changes = true; - } - } - - if (changes) - Penumbra.SaveService.SaveAllOptionGroups(this); - } - - private void LoadDefaultOption() - { - var defaultFile = Penumbra.Filenames.OptionGroupFile(this, -1); - _default.SetPosition(-1, 0); - try - { - if (!File.Exists(defaultFile)) - _default.Load(ModPath, new JObject(), out _); - else - _default.Load(ModPath, JObject.Parse(File.ReadAllText(defaultFile)), out _); - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not parse default file for {Name}:\n{e}"); - } - } - - public void WriteAllTexToolsMeta(MetaFileManager manager) - { - try - { - _default.WriteTexToolsMeta(manager, ModPath); - foreach (var group in Groups) - { - var dir = ModCreator.NewOptionDirectory(ModPath, group.Name); - if (!dir.Exists) - dir.Create(); - - foreach (var option in group.OfType()) - { - var optionDir = ModCreator.NewOptionDirectory(dir, option.Name); - if (!optionDir.Exists) - optionDir.Create(); - - option.WriteTexToolsMeta(manager, optionDir); - } - } - } - catch (Exception e) - { - Penumbra.Log.Error($"Error writing TexToolsMeta:\n{e}"); - } - } -} diff --git a/Penumbra/Mods/Mod.cs b/Penumbra/Mods/Mod.cs index bc7a2c4d..24a78370 100644 --- a/Penumbra/Mods/Mod.cs +++ b/Penumbra/Mods/Mod.cs @@ -1,6 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; +using OtterGui; using OtterGui.Classes; +using Penumbra.Import; +using Penumbra.Meta; +using Penumbra.String.Classes; namespace Penumbra.Mods; @@ -29,7 +34,61 @@ public sealed partial class Mod : IMod public bool Favorite { get; internal set; } = false; + // Options + public readonly SubMod Default; + public readonly List Groups = new(); + + ISubMod IMod.Default + => Default; + + IReadOnlyList IMod.Groups + => Groups; + + public IEnumerable AllSubMods + => Groups.SelectMany(o => o).OfType().Prepend(Default); + + public List FindUnusedFiles() + { + var modFiles = AllSubMods.SelectMany(o => o.Files) + .Select(p => p.Value) + .ToHashSet(); + return ModPath.EnumerateDirectories() + .Where(d => !d.IsHidden()) + .SelectMany(FileExtensions.EnumerateNonHiddenFiles) + .Select(f => new FullPath(f)) + .Where(f => !modFiles.Contains(f)) + .ToList(); + } + // Access public override string ToString() => Name.Text; + + public void WriteAllTexToolsMeta(MetaFileManager manager) + { + try + { + TexToolsMeta.WriteTexToolsMeta(manager, Default.Manipulations, ModPath); + foreach (var group in Groups) + { + var dir = ModCreator.NewOptionDirectory(ModPath, group.Name); + if (!dir.Exists) + dir.Create(); + + foreach (var option in group.OfType()) + { + var optionDir = ModCreator.NewOptionDirectory(dir, option.Name); + if (!optionDir.Exists) + optionDir.Create(); + + TexToolsMeta.WriteTexToolsMeta(manager, option.Manipulations, optionDir); + } + } + } + catch (Exception e) + { + Penumbra.Log.Error($"Error writing TexToolsMeta:\n{e}"); + } + } + } diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs new file mode 100644 index 00000000..b0add38f --- /dev/null +++ b/Penumbra/Mods/ModCreator.cs @@ -0,0 +1,476 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Dalamud.Interface.Internal.Notifications; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Filesystem; +using Penumbra.Api.Enums; +using Penumbra.GameData; +using Penumbra.Import; +using Penumbra.Import.Structs; +using Penumbra.Meta; +using Penumbra.Mods.Manager; +using Penumbra.String.Classes; +using Penumbra.Util; + +namespace Penumbra.Mods; + +public partial class ModCreator +{ + private readonly Configuration _config; + private readonly SaveService _saveService; + private readonly ModDataEditor _dataEditor; + private readonly MetaFileManager _metaFileManager; + private readonly IGamePathParser _gamePathParser; + + public ModCreator(SaveService saveService, Configuration config, ModDataEditor dataEditor, MetaFileManager metaFileManager, + IGamePathParser gamePathParser) + { + _saveService = saveService; + _config = config; + _dataEditor = dataEditor; + _metaFileManager = metaFileManager; + _gamePathParser = gamePathParser; + } + + /// Creates directory and files necessary for a new mod without adding it to the manager. + public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "") + { + try + { + var newDir = CreateModFolder(basePath, newName); + _dataEditor.CreateMeta(newDir, newName, _config.DefaultModAuthor, description, "1.0", string.Empty); + CreateDefaultFiles(newDir); + return newDir; + } + catch (Exception e) + { + Penumbra.ChatService.NotificationMessage($"Could not create directory for new Mod {newName}:\n{e}", "Failure", + NotificationType.Error); + return null; + } + } + + /// Load a mod by its directory. + public Mod? LoadMod(DirectoryInfo modPath, bool incorporateMetaChanges) + { + modPath.Refresh(); + if (!modPath.Exists) + { + Penumbra.Log.Error($"Supplied mod directory {modPath} does not exist."); + return null; + } + + var mod = new Mod(modPath); + if (ReloadMod(mod, incorporateMetaChanges, out _)) + return mod; + + // Can not be base path not existing because that is checked before. + Penumbra.Log.Warning($"Mod at {modPath} without name is not supported."); + return null; + } + + /// Reload a mod from its mod path. + public bool ReloadMod(Mod mod, bool incorporateMetaChanges, out ModDataChangeType modDataChange) + { + modDataChange = ModDataChangeType.Deletion; + if (!Directory.Exists(mod.ModPath.FullName)) + return false; + + modDataChange = _dataEditor.LoadMeta(this, mod); + if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0) + return false; + + _dataEditor.LoadLocalData(mod); + LoadDefaultOption(mod); + LoadAllGroups(mod); + if (incorporateMetaChanges) + IncorporateAllMetaChanges(mod, true); + + return true; + } + + /// Load all option groups for a given mod. + public void LoadAllGroups(Mod mod) + { + mod.Groups.Clear(); + var changes = false; + foreach (var file in _saveService.FileNames.GetOptionGroupFiles(mod)) + { + var group = LoadModGroup(mod, file, mod.Groups.Count); + if (group != null && mod.Groups.All(g => g.Name != group.Name)) + { + changes = changes + || _saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name) != file.FullName; + mod.Groups.Add(group); + } + else + { + changes = true; + } + } + + if (changes) + _saveService.SaveAllOptionGroups(mod); + } + + /// Load the default option for a given mod. + public void LoadDefaultOption(Mod mod) + { + var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1); + mod.Default.SetPosition(-1, 0); + try + { + if (!File.Exists(defaultFile)) + mod.Default.Load(mod.ModPath, new JObject(), out _); + else + mod.Default.Load(mod.ModPath, JObject.Parse(File.ReadAllText(defaultFile)), out _); + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not parse default file for {mod.Name}:\n{e}"); + } + } + + /// + /// Create and return a new directory based on the given directory and name, that is
+ /// - Not Empty.
+ /// - Unique, by appending (digit) for duplicates.
+ /// - Containing no symbols invalid for FFXIV or windows paths.
+ ///
+ public static DirectoryInfo CreateModFolder(DirectoryInfo outDirectory, string modListName, bool create = true) + { + var name = modListName; + if (name.Length == 0) + name = "_"; + + var newModFolderBase = NewOptionDirectory(outDirectory, name); + var newModFolder = newModFolderBase.FullName.ObtainUniqueFile(); + if (newModFolder.Length == 0) + throw new IOException("Could not create mod folder: too many folders of the same name exist."); + + if (create) + Directory.CreateDirectory(newModFolder); + + return new DirectoryInfo(newModFolder); + } + + /// + /// Convert all .meta and .rgsp files to their respective meta changes and add them to their options. + /// Deletes the source files if delete is true. + /// + public void IncorporateAllMetaChanges(Mod mod, bool delete) + { + var changes = false; + List deleteList = new(); + foreach (var subMod in mod.AllSubMods) + { + var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false); + changes |= localChanges; + if (delete) + deleteList.AddRange(localDeleteList); + } + + SubMod.DeleteDeleteList(deleteList, delete); + + if (!changes) + return; + + _saveService.SaveAllOptionGroups(mod); + _saveService.ImmediateSave(new ModSaveGroup(mod.ModPath, mod.Default)); + } + + + /// + /// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod. + /// If delete is true, the files are deleted afterwards. + /// + public (bool Changes, List DeleteList) IncorporateMetaChanges(SubMod option, DirectoryInfo basePath, bool delete) + { + var deleteList = new List(); + var oldSize = option.ManipulationData.Count; + var deleteString = delete ? "with deletion." : "without deletion."; + foreach (var (key, file) in option.Files.ToList()) + { + var ext1 = key.Extension().AsciiToLower().ToString(); + var ext2 = file.Extension.ToLowerInvariant(); + try + { + if (ext1 == ".meta" || ext2 == ".meta") + { + option.FileData.Remove(key); + if (!file.Exists) + continue; + + var meta = new TexToolsMeta(_metaFileManager, _gamePathParser, File.ReadAllBytes(file.FullName), + _config.KeepDefaultMetaChanges); + Penumbra.Log.Verbose( + $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}"); + deleteList.Add(file.FullName); + option.ManipulationData.UnionWith(meta.MetaManipulations); + } + else if (ext1 == ".rgsp" || ext2 == ".rgsp") + { + option.FileData.Remove(key); + if (!file.Exists) + continue; + + var rgsp = TexToolsMeta.FromRgspFile(Penumbra.MetaFileManager, file.FullName, File.ReadAllBytes(file.FullName), + _config.KeepDefaultMetaChanges); + Penumbra.Log.Verbose( + $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); + deleteList.Add(file.FullName); + + option.ManipulationData.UnionWith(rgsp.MetaManipulations); + } + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}"); + } + } + + SubMod.DeleteDeleteList(deleteList, delete); + return (oldSize < option.ManipulationData.Count, deleteList); + } + + /// + /// Create the name for a group or option subfolder based on its parent folder and given name. + /// subFolderName should never be empty, and the result is unique and contains no invalid symbols. + /// + public static DirectoryInfo? NewSubFolderName(DirectoryInfo parentFolder, string subFolderName) + { + var newModFolderBase = NewOptionDirectory(parentFolder, subFolderName); + var newModFolder = newModFolderBase.FullName.ObtainUniqueFile(); + return newModFolder.Length == 0 ? null : new DirectoryInfo(newModFolder); + } + + /// Create a file for an option group from given data. + public void CreateOptionGroup(DirectoryInfo baseFolder, GroupType type, string name, + int priority, int index, uint defaultSettings, string desc, IEnumerable subMods) + { + switch (type) + { + case GroupType.Multi: + { + var group = new MultiModGroup() + { + Name = name, + Description = desc, + Priority = priority, + DefaultSettings = defaultSettings, + }; + group.PrioritizedOptions.AddRange(subMods.OfType().Select((s, idx) => (s, idx))); + _saveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index)); + break; + } + case GroupType.Single: + { + var group = new SingleModGroup() + { + Name = name, + Description = desc, + Priority = priority, + DefaultSettings = defaultSettings, + }; + group.OptionData.AddRange(subMods.OfType()); + _saveService.ImmediateSave(new ModSaveGroup(baseFolder, group, index)); + break; + } + } + } + + /// Create the data for a given sub mod from its data and the folder it is based on. + public ISubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option) + { + var list = optionFolder.EnumerateNonHiddenFiles() + .Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f))) + .Where(t => t.Item1); + + var mod = new SubMod(null!) // Mod is irrelevant here, only used for saving. + { + Name = option.Name, + Description = option.Description, + }; + foreach (var (_, gamePath, file) in list) + mod.FileData.TryAdd(gamePath, file); + + IncorporateMetaChanges(mod, baseFolder, true); + return mod; + } + + /// Create an empty sub mod for single groups with None options. + internal static ISubMod CreateEmptySubMod(string name) + => new SubMod(null!) // Mod is irrelevant here, only used for saving. + { + Name = name, + }; + + /// + /// Create the default data file from all unused files that were not handled before + /// and are used in sub mods. + /// + internal void CreateDefaultFiles(DirectoryInfo directory) + { + var mod = new Mod(directory); + ReloadMod(mod, false, out _); + foreach (var file in mod.FindUnusedFiles()) + { + if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath, true)) + mod.Default.FileData.TryAdd(gamePath, file); + } + + IncorporateMetaChanges(mod.Default, directory, true); + _saveService.ImmediateSave(new ModSaveGroup(mod, -1)); + } + + /// Return the name of a new valid directory based on the base directory and the given name. + public static DirectoryInfo NewOptionDirectory(DirectoryInfo baseDir, string optionName) + => new(Path.Combine(baseDir.FullName, ReplaceBadXivSymbols(optionName))); + + /// Normalize for nicer names, and remove invalid symbols or invalid paths. + public static string ReplaceBadXivSymbols(string s, string replacement = "_") + { + switch (s) + { + case ".": return replacement; + case "..": return replacement + replacement; + } + + StringBuilder sb = new(s.Length); + foreach (var c in s.Normalize(NormalizationForm.FormKC)) + { + if (c.IsInvalidInPath()) + sb.Append(replacement); + else + sb.Append(c); + } + + return sb.ToString(); + } + + public void SplitMultiGroups(DirectoryInfo baseDir) + { + var mod = new Mod(baseDir); + + var files = _saveService.FileNames.GetOptionGroupFiles(mod).ToList(); + var idx = 0; + var reorder = false; + foreach (var groupFile in files) + { + ++idx; + try + { + if (reorder) + { + var newName = $"{baseDir.FullName}\\group_{idx:D3}{groupFile.Name[9..]}"; + Penumbra.Log.Debug($"Moving {groupFile.Name} to {Path.GetFileName(newName)} due to reordering after multi group split."); + groupFile.MoveTo(newName, false); + } + } + catch (Exception ex) + { + throw new Exception("Could not reorder group file after splitting multi group on .pmp import.", ex); + } + + try + { + var json = JObject.Parse(File.ReadAllText(groupFile.FullName)); + if (json[nameof(IModGroup.Type)]?.ToObject() is not GroupType.Multi) + continue; + + var name = json[nameof(IModGroup.Name)]?.ToObject() ?? string.Empty; + if (name.Length == 0) + continue; + + + var options = json["Options"]?.Children().ToList(); + if (options is not { Count: > IModGroup.MaxMultiOptions }) + continue; + + Penumbra.Log.Information($"Splitting multi group {name} in {mod.Name} due to {options.Count} being too many options."); + var clone = json.DeepClone(); + reorder = true; + foreach (var o in options.Skip(IModGroup.MaxMultiOptions)) + o.Remove(); + + var newOptions = clone["Options"]!.Children().ToList(); + foreach (var o in newOptions.Take(IModGroup.MaxMultiOptions)) + o.Remove(); + + var match = DuplicateNumber().Match(name); + var startNumber = match.Success ? int.Parse(match.Groups[0].Value) : 1; + name = match.Success ? name[..4] : name; + var oldName = $"{name}, Part {startNumber}"; + var oldPath = $"{baseDir.FullName}\\group_{idx:D3}_{oldName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json"; + var newName = $"{name}, Part {startNumber + 1}"; + var newPath = $"{baseDir.FullName}\\group_{++idx:D3}_{newName.RemoveInvalidPathSymbols().ToLowerInvariant()}.json"; + json[nameof(IModGroup.Name)] = oldName; + clone[nameof(IModGroup.Name)] = newName; + + clone[nameof(IModGroup.DefaultSettings)] = 0u; + + Penumbra.Log.Debug($"Writing the first {IModGroup.MaxMultiOptions} options to {Path.GetFileName(oldPath)} after split."); + using (var oldFile = File.CreateText(oldPath)) + { + using var j = new JsonTextWriter(oldFile) + { + Formatting = Formatting.Indented, + }; + json.WriteTo(j); + } + + Penumbra.Log.Debug( + $"Writing the remaining {options.Count - IModGroup.MaxMultiOptions} options to {Path.GetFileName(newPath)} after split."); + using (var newFile = File.CreateText(newPath)) + { + using var j = new JsonTextWriter(newFile) + { + Formatting = Formatting.Indented, + }; + clone.WriteTo(j); + } + + Penumbra.Log.Debug( + $"Deleting the old group file at {groupFile.Name} after splitting it into {Path.GetFileName(oldPath)} and {Path.GetFileName(newPath)}."); + groupFile.Delete(); + } + catch (Exception ex) + { + throw new Exception($"Could not split multi group file {groupFile.Name} on .pmp import.", ex); + } + } + } + + [GeneratedRegex(@", Part (\d+)$", RegexOptions.NonBacktracking)] + private static partial Regex DuplicateNumber(); + + + /// Load an option group for a specific mod by its file and index. + private static IModGroup? LoadModGroup(Mod mod, FileInfo file, int groupIdx) + { + if (!File.Exists(file.FullName)) + return null; + + try + { + var json = JObject.Parse(File.ReadAllText(file.FullName)); + switch (json[nameof(Type)]?.ToObject() ?? GroupType.Single) + { + case GroupType.Multi: return MultiModGroup.Load(mod, json, groupIdx); + case GroupType.Single: return SingleModGroup.Load(mod, json, groupIdx); + } + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not read mod group from {file.FullName}:\n{e}"); + } + + return null; + } +} diff --git a/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs b/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs index 459d5ffb..89b3d5bf 100644 --- a/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs +++ b/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs @@ -93,57 +93,6 @@ public sealed class SubMod : ISubMod ManipulationData.Add(s); } - // If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod. - // If delete is true, the files are deleted afterwards. - public (bool Changes, List DeleteList) IncorporateMetaChanges(DirectoryInfo basePath, bool delete) - { - var deleteList = new List(); - var oldSize = ManipulationData.Count; - var deleteString = delete ? "with deletion." : "without deletion."; - foreach (var (key, file) in Files.ToList()) - { - var ext1 = key.Extension().AsciiToLower().ToString(); - var ext2 = file.Extension.ToLowerInvariant(); - try - { - if (ext1 == ".meta" || ext2 == ".meta") - { - FileData.Remove(key); - if (!file.Exists) - continue; - - var meta = new TexToolsMeta(Penumbra.MetaFileManager, Penumbra.GamePathParser, File.ReadAllBytes(file.FullName), - Penumbra.Config.KeepDefaultMetaChanges); - Penumbra.Log.Verbose( - $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}"); - deleteList.Add(file.FullName); - ManipulationData.UnionWith(meta.MetaManipulations); - } - else if (ext1 == ".rgsp" || ext2 == ".rgsp") - { - FileData.Remove(key); - if (!file.Exists) - continue; - - var rgsp = TexToolsMeta.FromRgspFile(Penumbra.MetaFileManager, file.FullName, File.ReadAllBytes(file.FullName), - Penumbra.Config.KeepDefaultMetaChanges); - Penumbra.Log.Verbose( - $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); - deleteList.Add(file.FullName); - - ManipulationData.UnionWith(rgsp.MetaManipulations); - } - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}"); - } - } - - DeleteDeleteList(deleteList, delete); - return (oldSize < ManipulationData.Count, deleteList); - } - internal static void DeleteDeleteList(IEnumerable deleteList, bool delete) { if (!delete) @@ -161,63 +110,4 @@ public sealed class SubMod : ISubMod } } } - - public void WriteTexToolsMeta(MetaFileManager manager, DirectoryInfo basePath, bool test = false) - { - var files = TexToolsMeta.ConvertToTexTools(manager, Manipulations); - - foreach (var (file, data) in files) - { - var path = Path.Combine(basePath.FullName, file); - try - { - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - File.WriteAllBytes(path, data); - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not write meta file {path}:\n{e}"); - } - } - - if (test) - TestMetaWriting(manager, files); - } - - [Conditional("DEBUG")] - private void TestMetaWriting(MetaFileManager manager, Dictionary files) - { - var meta = new HashSet(Manipulations.Count); - foreach (var (file, data) in files) - { - try - { - var x = file.EndsWith("rgsp") - ? TexToolsMeta.FromRgspFile(manager, file, data, Penumbra.Config.KeepDefaultMetaChanges) - : new TexToolsMeta(manager, Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges); - meta.UnionWith(x.MetaManipulations); - } - catch - { - // ignored - } - } - - if (!Manipulations.SetEquals(meta)) - { - Penumbra.Log.Information("Meta Sets do not equal."); - foreach (var (m1, m2) in Manipulations.Zip(meta)) - Penumbra.Log.Information($"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}"); - - foreach (var m in Manipulations.Skip(meta.Count)) - Penumbra.Log.Information($"{m} {m.EntryToString()} "); - - foreach (var m in meta.Skip(Manipulations.Count)) - Penumbra.Log.Information($"{m} {m.EntryToString()} "); - } - else - { - Penumbra.Log.Information("Meta Sets are equal."); - } - } } diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index 3f0664cb..9019b468 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -7,6 +7,7 @@ using Penumbra.Collections; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Manager; using Penumbra.String.Classes; +using Penumbra.Util; namespace Penumbra.Mods; @@ -19,33 +20,33 @@ public class TemporaryMod : IMod public int TotalManipulations => Default.Manipulations.Count; - public ISubMod Default - => _default; + public readonly SubMod Default; + + ISubMod IMod.Default + => Default; public IReadOnlyList< IModGroup > Groups => Array.Empty< IModGroup >(); - public IEnumerable< ISubMod > AllSubMods + public IEnumerable< SubMod > AllSubMods => new[] { Default }; - private readonly SubMod _default; - public TemporaryMod() - => _default = new SubMod( this ); + => Default = new SubMod( this ); public void SetFile( Utf8GamePath gamePath, FullPath fullPath ) - => _default.FileData[ gamePath ] = fullPath; + => Default.FileData[ gamePath ] = fullPath; public bool SetManipulation( MetaManipulation manip ) - => _default.ManipulationData.Remove( manip ) | _default.ManipulationData.Add( 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; + Default.FileData = dict; + Default.ManipulationData = manips; } - public static void SaveTempCollection( ModManager modManager, ModCollection collection, string? character = null ) + public static void SaveTempCollection( SaveService saveService, ModManager modManager, ModCollection collection, string? character = null ) { DirectoryInfo? dir = null; try @@ -55,7 +56,7 @@ public class TemporaryMod : IMod modManager.DataEditor.CreateMeta( dir, collection.Name, character ?? Penumbra.Config.DefaultModAuthor, $"Mod generated from temporary collection {collection.Name} for {character ?? "Unknown Character"}.", null, null ); var mod = new Mod( dir ); - var defaultMod = (SubMod) mod.Default; + var defaultMod = mod.Default; foreach( var (gamePath, fullPath) in collection.ResolvedFiles ) { if( gamePath.Path.EndsWith( ".imc"u8 ) ) @@ -84,7 +85,7 @@ public class TemporaryMod : IMod foreach( var manip in collection.MetaCache?.Manipulations ?? Array.Empty< MetaManipulation >() ) defaultMod.ManipulationData.Add( manip ); - Penumbra.SaveService.ImmediateSave(new ModSaveGroup(dir, defaultMod)); + saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod)); modManager.AddMod( dir ); Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}." ); } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 150fd9c9..2ff848d8 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -8,7 +8,6 @@ using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Microsoft.Extensions.DependencyInjection; using OtterGui; -using OtterGui.Classes; using OtterGui.Log; using Penumbra.Api; using Penumbra.Api.Enums; @@ -28,8 +27,8 @@ using Penumbra.Interop.Services; using Penumbra.Mods.Manager; using Penumbra.Collections.Manager; using Penumbra.Mods; -using Penumbra.Meta; - +using Penumbra.Meta; + namespace Penumbra; public class Penumbra : IDalamudPlugin @@ -39,32 +38,26 @@ public class Penumbra : IDalamudPlugin public static Logger Log { get; private set; } = null!; public static ChatService ChatService { get; private set; } = null!; - public static FilenameService Filenames { get; private set; } = null!; - public static SaveService SaveService { get; private set; } = null!; public static Configuration Config { get; private set; } = null!; - public static ResidentResourceManager ResidentResources { get; private set; } = null!; - public static CharacterUtility CharacterUtility { get; private set; } = null!; - public static MetaFileManager MetaFileManager { get; private set; } = null!; - public static ModManager ModManager { get; private set; } = null!; - public static ModCacheManager ModCaches { get; private set; } = null!; - public static CollectionManager CollectionManager { get; private set; } = null!; - public static TempCollectionManager TempCollections { get; private set; } = null!; - public static TempModManager TempMods { get; private set; } = null!; - public static ActorManager Actors { get; private set; } = null!; - public static IObjectIdentifier Identifier { get; private set; } = null!; - public static IGamePathParser GamePathParser { get; private set; } = null!; - public static StainService StainService { get; private set; } = null!; + public static CharacterUtility CharacterUtility { get; private set; } = null!; + public static MetaFileManager MetaFileManager { get; private set; } = null!; + public static ModManager ModManager { get; private set; } = null!; + public static ModCacheManager ModCaches { get; private set; } = null!; + public static CollectionManager CollectionManager { get; private set; } = null!; + public static ActorManager Actors { get; private set; } = null!; - // TODO - public static ValidityChecker ValidityChecker { get; private set; } = null!; + public readonly RedrawService RedrawService; + public readonly ModFileSystem ModFileSystem; + public HttpApi HttpApi = null!; + internal ConfigWindow? ConfigWindow { get; private set; } - public readonly RedrawService RedrawService; - public readonly ModFileSystem ModFileSystem; - public HttpApi HttpApi = null!; - internal ConfigWindow? ConfigWindow { get; private set; } - private PenumbraWindowSystem? _windowSystem; - private bool _disposed; + private readonly ValidityChecker _validityChecker; + private readonly ResidentResourceManager _residentResources; + private readonly TempModManager _tempMods; + private readonly TempCollectionManager _tempCollections; + private PenumbraWindowSystem? _windowSystem; + private bool _disposed; private readonly PenumbraNew _tmp; @@ -73,29 +66,24 @@ public class Penumbra : IDalamudPlugin Log = PenumbraNew.Log; try { - _tmp = new PenumbraNew(this, pluginInterface); - ChatService = _tmp.Services.GetRequiredService(); - Filenames = _tmp.Services.GetRequiredService(); - SaveService = _tmp.Services.GetRequiredService(); - ValidityChecker = _tmp.Services.GetRequiredService(); + _tmp = new PenumbraNew(this, pluginInterface); + ChatService = _tmp.Services.GetRequiredService(); + _validityChecker = _tmp.Services.GetRequiredService(); _tmp.Services.GetRequiredService(); Config = _tmp.Services.GetRequiredService(); CharacterUtility = _tmp.Services.GetRequiredService(); MetaFileManager = _tmp.Services.GetRequiredService(); Actors = _tmp.Services.GetRequiredService().AwaitedService; - Identifier = _tmp.Services.GetRequiredService().AwaitedService; - GamePathParser = _tmp.Services.GetRequiredService(); - StainService = _tmp.Services.GetRequiredService(); - TempMods = _tmp.Services.GetRequiredService(); - ResidentResources = _tmp.Services.GetRequiredService(); + _tempMods = _tmp.Services.GetRequiredService(); + _residentResources = _tmp.Services.GetRequiredService(); _tmp.Services.GetRequiredService(); ModManager = _tmp.Services.GetRequiredService(); CollectionManager = _tmp.Services.GetRequiredService(); - TempCollections = _tmp.Services.GetRequiredService(); + _tempCollections = _tmp.Services.GetRequiredService(); ModFileSystem = _tmp.Services.GetRequiredService(); RedrawService = _tmp.Services.GetRequiredService(); _tmp.Services.GetRequiredService(); - ModCaches = _tmp.Services.GetRequiredService(); + ModCaches = _tmp.Services.GetRequiredService(); using (var t = _tmp.Services.GetRequiredService().Measure(StartTimeType.PathResolver)) { _tmp.Services.GetRequiredService(); @@ -104,14 +92,14 @@ public class Penumbra : IDalamudPlugin SetupInterface(); SetupApi(); - ValidityChecker.LogExceptions(); + _validityChecker.LogExceptions(); Log.Information( - $"Penumbra Version {ValidityChecker.Version}, Commit #{ValidityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}."); + $"Penumbra Version {_validityChecker.Version}, Commit #{_validityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}."); OtterTex.NativeDll.Initialize(pluginInterface.AssemblyLocation.DirectoryName); Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}."); if (CharacterUtility.Ready) - ResidentResources.Reload(); + _residentResources.Reload(); } catch { @@ -173,7 +161,7 @@ public class Penumbra : IDalamudPlugin if (CharacterUtility.Ready) { CollectionManager.Active.Default.SetFiles(); - ResidentResources.Reload(); + _residentResources.Reload(); RedrawService.RedrawAll(RedrawType.Redraw); } } @@ -182,7 +170,7 @@ public class Penumbra : IDalamudPlugin if (CharacterUtility.Ready) { CharacterUtility.ResetAll(); - ResidentResources.Reload(); + _residentResources.Reload(); RedrawService.RedrawAll(RedrawType.Redraw); } } @@ -211,8 +199,8 @@ public class Penumbra : IDalamudPlugin var exists = Config.ModDirectory.Length > 0 && Directory.Exists(Config.ModDirectory); var drive = exists ? new DriveInfo(new DirectoryInfo(Config.ModDirectory).Root.FullName) : null; sb.AppendLine("**Settings**"); - sb.Append($"> **`Plugin Version: `** {ValidityChecker.Version}\n"); - sb.Append($"> **`Commit Hash: `** {ValidityChecker.CommitHash}\n"); + sb.Append($"> **`Plugin Version: `** {_validityChecker.Version}\n"); + sb.Append($"> **`Commit Hash: `** {_validityChecker.CommitHash}\n"); sb.Append($"> **`Enable Mods: `** {Config.EnableMods}\n"); sb.Append($"> **`Enable HTTP API: `** {Config.EnableHttpApi}\n"); sb.Append($"> **`Operating System: `** {(DalamudUtil.IsLinux() ? "Mac/Linux (Wine)" : "Windows")}\n"); @@ -235,9 +223,9 @@ public class Penumbra : IDalamudPlugin $"> **`Mods with FileSwaps: `** {ModCaches.Count(m => m.TotalSwapCount > 0)}, Total: {ModCaches.Sum(m => m.TotalSwapCount)}\n"); sb.Append( $"> **`Mods with Meta Manipulations:`** {ModCaches.Count(m => m.TotalManipulations > 0)}, Total {ModCaches.Sum(m => m.TotalManipulations)}\n"); - sb.Append($"> **`IMC Exceptions Thrown: `** {ValidityChecker.ImcExceptions.Count}\n"); + sb.Append($"> **`IMC Exceptions Thrown: `** {_validityChecker.ImcExceptions.Count}\n"); sb.Append( - $"> **`#Temp Mods: `** {TempMods.Mods.Sum(kvp => kvp.Value.Count) + TempMods.ModsForAllCollections.Count}\n"); + $"> **`#Temp Mods: `** {_tempMods.Mods.Sum(kvp => kvp.Value.Count) + _tempMods.ModsForAllCollections.Count}\n"); string CharacterName(ActorIdentifier id, string name) { @@ -259,8 +247,8 @@ public class Penumbra : IDalamudPlugin sb.AppendLine("**Collections**"); sb.Append($"> **`#Collections: `** {CollectionManager.Storage.Count - 1}\n"); - sb.Append($"> **`#Temp Collections: `** {TempCollections.Count}\n"); - sb.Append($"> **`Active Collections: `** {CollectionManager.Caches.Count - TempCollections.Count}\n"); + sb.Append($"> **`#Temp Collections: `** {_tempCollections.Count}\n"); + sb.Append($"> **`Active Collections: `** {CollectionManager.Caches.Count - _tempCollections.Count}\n"); sb.Append($"> **`Base Collection: `** {CollectionManager.Active.Default.AnonymizedName}\n"); sb.Append($"> **`Interface Collection: `** {CollectionManager.Active.Interface.AnonymizedName}\n"); sb.Append($"> **`Selected Collection: `** {CollectionManager.Active.Current.AnonymizedName}\n"); diff --git a/Penumbra/PenumbraNew.cs b/Penumbra/PenumbraNew.cs index 94305567..d32bed09 100644 --- a/Penumbra/PenumbraNew.cs +++ b/Penumbra/PenumbraNew.cs @@ -95,6 +95,7 @@ public class PenumbraNew services.AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 7f2966ff..d602ed3c 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -218,22 +218,26 @@ public class ItemSwapTab : IDisposable, ITab _useCurrentCollection ? _collectionManager.Active.Current : null); break; case SwapType.Hair when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId, + _swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace), + (SetId)_sourceId, (SetId)_targetId, _useCurrentCollection ? _collectionManager.Active.Current : null); break; case SwapType.Face when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId, + _swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace), + (SetId)_sourceId, (SetId)_targetId, _useCurrentCollection ? _collectionManager.Active.Current : null); break; case SwapType.Ears when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization(_metaFileManager, BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera), (SetId)_sourceId, + _swapData.LoadCustomization(_metaFileManager, BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera), + (SetId)_sourceId, (SetId)_targetId, _useCurrentCollection ? _collectionManager.Active.Current : null); break; case SwapType.Tail when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId, + _swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace), + (SetId)_sourceId, (SetId)_targetId, _useCurrentCollection ? _collectionManager.Active.Current : null); break; @@ -276,9 +280,10 @@ public class ItemSwapTab : IDisposable, ITab private void CreateMod() { - var newDir = ModCreator.CreateModFolder(_modManager.BasePath, _newModName); - _modManager.DataEditor.CreateMeta(newDir, _newModName, _config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty); - ModCreator.CreateDefaultFiles(newDir); + var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName, CreateDescription()); + if (newDir == null) + return; + _modManager.AddMod(newDir); if (!_swapData.WriteMod(_modManager, _modManager[^1], _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps)) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorSet.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorSet.cs index 5a338b9c..26c7c2ae 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorSet.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorSet.cs @@ -13,7 +13,7 @@ namespace Penumbra.UI.AdvancedWindow; public partial class ModEditWindow { - private static bool DrawMaterialColorSetChange( MtrlFile file, bool disabled ) + private bool DrawMaterialColorSetChange( MtrlFile file, bool disabled ) { if( !file.ColorSets.Any( c => c.HasRows ) ) { @@ -95,9 +95,9 @@ public partial class ModEditWindow } } - private static bool DrawPreviewDye( MtrlFile file, bool disabled ) + private bool DrawPreviewDye( MtrlFile file, bool disabled ) { - var (dyeId, (name, dyeColor, _)) = Penumbra.StainService.StainCombo.CurrentSelection; + var (dyeId, (name, dyeColor, _)) = _stainService.StainCombo.CurrentSelection; var tt = dyeId == 0 ? "Select a preview dye first." : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled."; if( ImGuiUtil.DrawDisabledButton( "Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0 ) ) { @@ -106,7 +106,7 @@ public partial class ModEditWindow { for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i ) { - ret |= file.ApplyDyeTemplate( Penumbra.StainService.StmFile, j, i, dyeId ); + ret |= file.ApplyDyeTemplate( _stainService.StmFile, j, i, dyeId ); } } @@ -115,7 +115,7 @@ public partial class ModEditWindow ImGui.SameLine(); var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye"; - Penumbra.StainService.StainCombo.Draw( label, dyeColor, string.Empty, true ); + _stainService.StainCombo.Draw( label, dyeColor, string.Empty, true ); return false; } @@ -217,7 +217,7 @@ public partial class ModEditWindow return false; } - private static bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled ) + private bool DrawColorSetRow( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled ) { static bool FixFloat( ref float val, float current ) { @@ -355,10 +355,10 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if( hasDye ) { - if( Penumbra.StainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize + if(_stainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize + ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) ) { - file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainService.TemplateCombo.CurrentSelection; + file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = _stainService.TemplateCombo.CurrentSelection; ret = true; } @@ -376,10 +376,10 @@ public partial class ModEditWindow return ret; } - private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize ) + private bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize ) { - var stain = Penumbra.StainService.StainCombo.CurrentSelection.Key; - if( stain == 0 || !Penumbra.StainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) ) + var stain = _stainService.StainCombo.CurrentSelection.Key; + if( stain == 0 || !_stainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) ) { return false; } @@ -390,7 +390,7 @@ public partial class ModEditWindow var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ), "Apply the selected dye to this row.", disabled, true ); - ret = ret && file.ApplyDyeTemplate( Penumbra.StainService.StmFile, colorSetIdx, rowIdx, stain ); + ret = ret && file.ApplyDyeTemplate(_stainService.StmFile, colorSetIdx, rowIdx, stain ); ImGui.SameLine(); ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" ); diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 31fc2a49..4edb2701 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -15,7 +15,8 @@ using Penumbra.Import.Textures; using Penumbra.Interop.ResourceTree; using Penumbra.Meta; using Penumbra.Mods; -using Penumbra.Mods.Manager; +using Penumbra.Mods.Manager; +using Penumbra.Services; using Penumbra.String.Classes; using Penumbra.UI.Classes; using Penumbra.Util; @@ -33,6 +34,7 @@ public partial class ModEditWindow : Window, IDisposable private readonly ItemSwapTab _itemSwapTab; private readonly DataManager _gameData; private readonly MetaFileManager _metaFileManager; + private readonly StainService _stainService; private Mod? _mod; private Vector2 _iconSize = Vector2.Zero; @@ -495,17 +497,18 @@ public partial class ModEditWindow : Window, IDisposable } public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, DataManager gameData, - Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches, MetaFileManager metaFileManager) + Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches, MetaFileManager metaFileManager, StainService stainService) : base(WindowBaseLabel) { - _performance = performance; - _itemSwapTab = itemSwapTab; - _config = config; - _editor = editor; - _modCaches = modCaches; - _metaFileManager = metaFileManager; - _gameData = gameData; - _fileDialog = fileDialog; + _performance = performance; + _itemSwapTab = itemSwapTab; + _config = config; + _editor = editor; + _modCaches = modCaches; + _metaFileManager = metaFileManager; + _stainService = stainService; + _gameData = gameData; + _fileDialog = fileDialog; _materialTab = new FileEditor(this, gameData, config, _fileDialog, "Materials", ".mtrl", () => _editor.Files.Mtrl, DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty, bytes => new MtrlTab(this, new MtrlFile(bytes))); diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 9d4883b1..7484a344 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -66,7 +66,7 @@ public sealed class ConfigWindow : Window UiHelpers.SetupCommonSizes(); try { - if (Penumbra.ValidityChecker.ImcExceptions.Count > 0) + if (_validityChecker.ImcExceptions.Count > 0) { DrawProblemWindow( $"There were {_validityChecker.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n" @@ -75,14 +75,14 @@ public sealed class ConfigWindow : Window + "Please use the Launcher's Repair Game Files function to repair your client installation."); DrawImcExceptions(); } - else if (!Penumbra.ValidityChecker.IsValidSourceRepo) + else if (!_validityChecker.IsValidSourceRepo) { DrawProblemWindow( $"You are loading a release version of Penumbra from the repository \"{_pluginInterface.SourceRepository}\" instead of the official repository.\n" + $"Please use the official repository at {ValidityChecker.Repository}.\n\n" + "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it."); } - else if (Penumbra.ValidityChecker.IsNotInstalledPenumbra) + else if (_validityChecker.IsNotInstalledPenumbra) { DrawProblemWindow( $"You are loading a release version of Penumbra from \"{_pluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\" instead of the installedPlugins directory.\n\n" @@ -90,7 +90,7 @@ public sealed class ConfigWindow : Window + "If you do not know how to do this, please take a look at the readme in Penumbras github repository or join us in discord.\n" + "If you are developing for Penumbra and see this, you should compile your version in debug mode to avoid it."); } - else if (Penumbra.ValidityChecker.DevPenumbraExists) + else if (_validityChecker.DevPenumbraExists) { DrawProblemWindow( $"You are loading a installed version of Penumbra from \"{_pluginInterface.AssemblyLocation.Directory?.FullName ?? "Unknown"}\", " diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 448ddd00..32844294 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -119,18 +119,14 @@ public sealed class ModFileSystemSelector : FileSystemSelector