diff --git a/Penumbra/Collections/Cache/CmpCache.cs b/Penumbra/Collections/Cache/CmpCache.cs index bb87aa88..47d6a441 100644 --- a/Penumbra/Collections/Cache/CmpCache.cs +++ b/Penumbra/Collections/Cache/CmpCache.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using OtterGui.Filesystem; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -10,34 +12,42 @@ namespace Penumbra.Collections.Cache; public struct CmpCache : IDisposable { - private CmpFile? _cmpFile = null; - private readonly List< RspManipulation > _cmpManipulations = new(); - + private CmpFile? _cmpFile = null; + private readonly List _cmpManipulations = new(); + public CmpCache() - {} + { } - public void SetFiles(CollectionCacheManager manager) - => manager.SetFile( _cmpFile, MetaIndex.HumanCmp ); + public void SetFiles(MetaFileManager manager) + => manager.SetFile(_cmpFile, MetaIndex.HumanCmp); - public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(CollectionCacheManager manager) - => manager.TemporarilySetFile( _cmpFile, MetaIndex.HumanCmp ); + public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) + => manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp); - public bool ApplyMod( CollectionCacheManager manager, RspManipulation manip ) + public void Reset() { - _cmpManipulations.AddOrReplace( manip ); - _cmpFile ??= new CmpFile(); - return manip.Apply( _cmpFile ); + if (_cmpFile == null) + return; + + _cmpFile.Reset(_cmpManipulations.Select(m => (m.SubRace, m.Attribute))); + _cmpManipulations.Clear(); } - public bool RevertMod( CollectionCacheManager manager, RspManipulation manip ) + public bool ApplyMod(MetaFileManager manager, RspManipulation manip) + { + _cmpManipulations.AddOrReplace(manip); + _cmpFile ??= new CmpFile(manager); + return manip.Apply(_cmpFile); + } + + public bool RevertMod(MetaFileManager manager, RspManipulation manip) { if (!_cmpManipulations.Remove(manip)) return false; - var def = CmpFile.GetDefault( manip.SubRace, manip.Attribute ); - manip = new RspManipulation( manip.SubRace, manip.Attribute, def ); - return manip.Apply( _cmpFile! ); - + var def = CmpFile.GetDefault(manager, manip.SubRace, manip.Attribute); + manip = new RspManipulation(manip.SubRace, manip.Attribute, def); + return manip.Apply(_cmpFile!); } public void Dispose() @@ -46,4 +56,4 @@ public struct CmpCache : IDisposable _cmpFile = null; _cmpManipulations.Clear(); } -} \ No newline at end of file +} diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index 7c58e5bf..d630ad31 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using Penumbra.Api.Enums; using Penumbra.String.Classes; using Penumbra.Mods.Manager; @@ -51,7 +52,7 @@ public class CollectionCache : IDisposable { _manager = manager; _collection = collection; - MetaManipulations = new MetaCache(_collection); + MetaManipulations = new MetaCache(manager.MetaFileManager, _collection); } public void Dispose() @@ -59,6 +60,9 @@ public class CollectionCache : IDisposable MetaManipulations.Dispose(); } + ~CollectionCache() + => MetaManipulations.Dispose(); + // Resolve a given game path according to this collection. public FullPath? ResolvePath(Utf8GamePath gameResourcePath) { @@ -115,6 +119,17 @@ public class CollectionCache : IDisposable return ret; } + /// 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. + internal void RemoveFile(Utf8GamePath path) + => ResolvedFiles.Remove(path); + public void ReloadMod(IMod mod, bool addMetaChanges) { RemoveMod(mod, addMetaChanges); @@ -234,7 +249,7 @@ public class CollectionCache : IDisposable // Inside the same mod, conflicts are not recorded. private void AddFile(Utf8GamePath path, FullPath file, IMod mod) { - if (!ModCollection.CheckFullPath(path, file)) + if (!CheckFullPath(path, file)) return; if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) @@ -387,4 +402,14 @@ public class CollectionCache : IDisposable Penumbra.Log.Error($"Unknown Error:\n{e}"); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckFullPath(Utf8GamePath path, FullPath fullPath) + { + if (fullPath.InternalName.Length < Utf8GamePath.MaxGamePathLength) + return true; + + Penumbra.Log.Error($"The redirected path is too long to add the redirection\n\t{path}\n\t--> {fullPath}"); + return false; + } } diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index 326b5918..467d0617 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -8,7 +8,7 @@ using OtterGui.Classes; using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; -using Penumbra.Interop.Services; +using Penumbra.Meta; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; @@ -18,16 +18,12 @@ namespace Penumbra.Collections.Cache; public class CollectionCacheManager : IDisposable { private readonly FrameworkManager _framework; - private readonly ActiveCollections _active; private readonly CommunicatorService _communicator; private readonly TempModManager _tempMods; private readonly ModStorage _modStorage; - private readonly ModCacheManager _modCaches; - private readonly Configuration _config; + private readonly ActiveCollections _active; - internal readonly ValidityChecker ValidityChecker; - internal readonly CharacterUtility CharacterUtility; - internal readonly ResidentResourceManager ResidentResources; + internal readonly MetaFileManager MetaFileManager; private readonly Dictionary _caches = new(); @@ -37,20 +33,15 @@ public class CollectionCacheManager : IDisposable public IEnumerable<(ModCollection Collection, CollectionCache Cache)> Active => _caches.Where(c => c.Key.Index > ModCollection.Empty.Index).Select(p => (p.Key, p.Value)); - public CollectionCacheManager(FrameworkManager framework, ActiveCollections active, CommunicatorService communicator, - CharacterUtility characterUtility, TempModManager tempMods, ModStorage modStorage, Configuration config, - ResidentResourceManager residentResources, ModCacheManager modCaches, ValidityChecker validityChecker) + public CollectionCacheManager(FrameworkManager framework, CommunicatorService communicator, + TempModManager tempMods, ModStorage modStorage, MetaFileManager metaFileManager, ActiveCollections active) { - _framework = framework; - _active = active; - _communicator = communicator; - CharacterUtility = characterUtility; - _tempMods = tempMods; - _modStorage = modStorage; - _config = config; - ResidentResources = residentResources; - _modCaches = modCaches; - ValidityChecker = validityChecker; + _framework = framework; + _communicator = communicator; + _tempMods = tempMods; + _modStorage = modStorage; + MetaFileManager = metaFileManager; + _active = active; _communicator.CollectionChange.Subscribe(OnCollectionChange); _communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100); @@ -61,8 +52,8 @@ public class CollectionCacheManager : IDisposable _communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange); CreateNecessaryCaches(); - if (!CharacterUtility.Ready) - CharacterUtility.LoadingFinished += IncrementCounters; + if (!MetaFileManager.CharacterUtility.Ready) + MetaFileManager.CharacterUtility.LoadingFinished += IncrementCounters; } public void Dispose() @@ -74,7 +65,7 @@ public class CollectionCacheManager : IDisposable _communicator.ModOptionChanged.Unsubscribe(OnModOptionChange); _communicator.ModSettingChanged.Unsubscribe(OnModSettingChange); _communicator.CollectionInheritanceChanged.Unsubscribe(OnCollectionInheritanceChange); - CharacterUtility.LoadingFinished -= IncrementCounters; + MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters; } /// Only creates a new cache, does not update an existing one. @@ -98,9 +89,6 @@ public class CollectionCacheManager : IDisposable => _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Name, () => CalculateEffectiveFileListInternal(collection)); - public bool IsDefault(ModCollection collection) - => _active.Default == collection; - private void CalculateEffectiveFileListInternal(ModCollection collection) { // Skip the empty collection. @@ -139,11 +127,7 @@ public class CollectionCacheManager : IDisposable ++collection.ChangeCounter; - if (_active.Default != collection || !CharacterUtility.Ready || !_config.EnableMods) - return; - - ResidentResources.Reload(); - cache.MetaManipulations.SetFiles(); + MetaFileManager.ApplyDefaultFiles(collection); } private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? newCollection, string displayName) @@ -238,7 +222,7 @@ public class CollectionCacheManager : IDisposable { foreach (var (collection, _) in _caches) ++collection.ChangeCounter; - CharacterUtility.LoadingFinished -= IncrementCounters; + MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters; } private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool _) diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index aa882ac4..4a1325ed 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using OtterGui; using OtterGui.Filesystem; using Penumbra.GameData.Enums; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -19,23 +21,29 @@ public readonly struct EqdpCache : IDisposable public EqdpCache() { } - public void SetFiles(CollectionCacheManager manager) + public void SetFiles(MetaFileManager manager) { for (var i = 0; i < CharacterUtilityData.EqdpIndices.Length; ++i) manager.SetFile(_eqdpFiles[i], CharacterUtilityData.EqdpIndices[i]); } - public CharacterUtility.MetaList.MetaReverter? TemporarilySetEqdpFile(CollectionCacheManager manager, GenderRace genderRace, bool accessory) + public void SetFile(MetaFileManager manager, MetaIndex index) { - var idx = CharacterUtilityData.EqdpIdx(genderRace, accessory); - if ((int)idx == -1) - return null; - - var i = CharacterUtilityData.EqdpIndices.IndexOf(idx); - return i != -1 ? manager.TemporarilySetFile(_eqdpFiles[i], idx) : null; + var i = CharacterUtilityData.EqdpIndices.IndexOf(index); + if (i != -1) + manager.SetFile(_eqdpFiles[i], index); } - public void Reset(CollectionCacheManager manager) + public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, GenderRace genderRace, bool accessory) + { + var idx = CharacterUtilityData.EqdpIdx(genderRace, accessory); + Debug.Assert(idx >= 0, $"Invalid Gender, Race or Accessory for EQDP file {genderRace}, {accessory}."); + var i = CharacterUtilityData.EqdpIndices.IndexOf(idx); + Debug.Assert(i >= 0, $"Invalid Gender, Race or Accessory for EQDP file {genderRace}, {accessory}."); + return manager.TemporarilySetFile(_eqdpFiles[i], idx); + } + + public void Reset() { foreach (var file in _eqdpFiles.OfType()) { @@ -46,20 +54,20 @@ public readonly struct EqdpCache : IDisposable _eqdpManipulations.Clear(); } - public bool ApplyMod(CollectionCacheManager manager, EqdpManipulation manip) + public bool ApplyMod(MetaFileManager manager, EqdpManipulation manip) { _eqdpManipulations.AddOrReplace(manip); var file = _eqdpFiles[Array.IndexOf(CharacterUtilityData.EqdpIndices, manip.FileIndex())] ??= - new ExpandedEqdpFile(Names.CombinedRace(manip.Gender, manip.Race), manip.Slot.IsAccessory()); // TODO: female Hrothgar + new ExpandedEqdpFile(manager, Names.CombinedRace(manip.Gender, manip.Race), manip.Slot.IsAccessory()); // TODO: female Hrothgar return manip.Apply(file); } - public bool RevertMod(CollectionCacheManager manager, EqdpManipulation manip) + public bool RevertMod(MetaFileManager manager, EqdpManipulation manip) { if (!_eqdpManipulations.Remove(manip)) return false; - var def = ExpandedEqdpFile.GetDefault(Names.CombinedRace(manip.Gender, manip.Race), manip.Slot.IsAccessory(), manip.SetId); + var def = ExpandedEqdpFile.GetDefault(manager, Names.CombinedRace(manip.Gender, manip.Race), manip.Slot.IsAccessory(), manip.SetId); var file = _eqdpFiles[Array.IndexOf(CharacterUtilityData.EqdpIndices, manip.FileIndex())]!; manip = new EqdpManipulation(def, manip.Slot, manip.Gender, manip.Race, manip.SetId); return manip.Apply(file); diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 8cf19386..f3b7e8f1 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using OtterGui.Filesystem; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -16,29 +18,38 @@ public struct EqpCache : IDisposable public EqpCache() {} - public void SetFiles(CollectionCacheManager manager) + public void SetFiles(MetaFileManager manager) => manager.SetFile( _eqpFile, MetaIndex.Eqp ); - public static void ResetFiles(CollectionCacheManager manager) + public static void ResetFiles(MetaFileManager manager) => manager.SetFile( null, MetaIndex.Eqp ); - public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(CollectionCacheManager manager) + public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) => manager.TemporarilySetFile( _eqpFile, MetaIndex.Eqp ); - public bool ApplyMod( CollectionCacheManager manager, EqpManipulation manip ) + public void Reset() + { + if (_eqpFile == null) + return; + + _eqpFile.Reset(_eqpManipulations.Select(m => (int)m.SetId)); + _eqpManipulations.Clear(); + } + + public bool ApplyMod( MetaFileManager manager, EqpManipulation manip ) { _eqpManipulations.AddOrReplace( manip ); - _eqpFile ??= new ExpandedEqpFile(); + _eqpFile ??= new ExpandedEqpFile(manager); return manip.Apply( _eqpFile ); } - public bool RevertMod( CollectionCacheManager manager, EqpManipulation manip ) + public bool RevertMod( MetaFileManager manager, EqpManipulation manip ) { var idx = _eqpManipulations.FindIndex( manip.Equals ); if (idx < 0) return false; - var def = ExpandedEqpFile.GetDefault( manip.SetId ); + var def = ExpandedEqpFile.GetDefault( manager, manip.SetId ); manip = new EqpManipulation( def, manip.Slot, manip.SetId ); return manip.Apply( _eqpFile! ); diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 76470d87..ac30e0d6 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -4,6 +4,7 @@ using OtterGui.Filesystem; using Penumbra.GameData.Enums; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -16,31 +17,50 @@ public struct EstCache : IDisposable private EstFile? _estBodyFile = null; private EstFile? _estHeadFile = null; - private readonly List< EstManipulation > _estManipulations = new(); - - public EstCache() - {} + private readonly List _estManipulations = new(); - public void SetFiles(CollectionCacheManager manager) + public EstCache() + { } + + public void SetFiles(MetaFileManager manager) { - manager.SetFile( _estFaceFile, MetaIndex.FaceEst ); - manager.SetFile( _estHairFile, MetaIndex.HairEst ); - manager.SetFile( _estBodyFile, MetaIndex.BodyEst ); - manager.SetFile( _estHeadFile, MetaIndex.HeadEst ); + manager.SetFile(_estFaceFile, MetaIndex.FaceEst); + manager.SetFile(_estHairFile, MetaIndex.HairEst); + manager.SetFile(_estBodyFile, MetaIndex.BodyEst); + manager.SetFile(_estHeadFile, MetaIndex.HeadEst); } - public CharacterUtility.MetaList.MetaReverter? TemporarilySetFiles(CollectionCacheManager manager, EstManipulation.EstType type) + public void SetFile(MetaFileManager manager, MetaIndex index) + { + switch (index) + { + case MetaIndex.FaceEst: + manager.SetFile(_estFaceFile, MetaIndex.FaceEst); + break; + case MetaIndex.HairEst: + manager.SetFile(_estHairFile, MetaIndex.HairEst); + break; + case MetaIndex.BodyEst: + manager.SetFile(_estBodyFile, MetaIndex.BodyEst); + break; + case MetaIndex.HeadEst: + manager.SetFile(_estHeadFile, MetaIndex.HeadEst); + break; + } + } + + public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, EstManipulation.EstType type) { var (file, idx) = type switch { - EstManipulation.EstType.Face => ( _estFaceFile, MetaIndex.FaceEst ), - EstManipulation.EstType.Hair => ( _estHairFile, MetaIndex.HairEst ), - EstManipulation.EstType.Body => ( _estBodyFile, MetaIndex.BodyEst ), - EstManipulation.EstType.Head => ( _estHeadFile, MetaIndex.HeadEst ), - _ => ( null, 0 ), + EstManipulation.EstType.Face => (_estFaceFile, MetaIndex.FaceEst), + EstManipulation.EstType.Hair => (_estHairFile, MetaIndex.HairEst), + EstManipulation.EstType.Body => (_estBodyFile, MetaIndex.BodyEst), + EstManipulation.EstType.Head => (_estHeadFile, MetaIndex.HeadEst), + _ => (null, 0), }; - - return idx != 0 ? manager.TemporarilySetFile( file, idx ) : null; + + return manager.TemporarilySetFile(file, idx); } public void Reset() @@ -52,27 +72,27 @@ public struct EstCache : IDisposable _estManipulations.Clear(); } - public bool ApplyMod( CollectionCacheManager manager, EstManipulation m ) + public bool ApplyMod(MetaFileManager manager, EstManipulation m) { - _estManipulations.AddOrReplace( m ); + _estManipulations.AddOrReplace(m); var file = m.Slot switch { - EstManipulation.EstType.Hair => _estHairFile ??= new EstFile( EstManipulation.EstType.Hair ), - EstManipulation.EstType.Face => _estFaceFile ??= new EstFile( EstManipulation.EstType.Face ), - EstManipulation.EstType.Body => _estBodyFile ??= new EstFile( EstManipulation.EstType.Body ), - EstManipulation.EstType.Head => _estHeadFile ??= new EstFile( EstManipulation.EstType.Head ), + EstManipulation.EstType.Hair => _estHairFile ??= new EstFile(manager, EstManipulation.EstType.Hair), + EstManipulation.EstType.Face => _estFaceFile ??= new EstFile(manager, EstManipulation.EstType.Face), + EstManipulation.EstType.Body => _estBodyFile ??= new EstFile(manager, EstManipulation.EstType.Body), + EstManipulation.EstType.Head => _estHeadFile ??= new EstFile(manager, EstManipulation.EstType.Head), _ => throw new ArgumentOutOfRangeException(), }; - return m.Apply( file ); + return m.Apply(file); } - public bool RevertMod( CollectionCacheManager manager, EstManipulation m ) + public bool RevertMod(MetaFileManager manager, EstManipulation m) { if (!_estManipulations.Remove(m)) return false; - var def = EstFile.GetDefault( m.Slot, Names.CombinedRace( m.Gender, m.Race ), m.SetId ); - var manip = new EstManipulation( m.Gender, m.Race, m.Slot, m.SetId, def ); + var def = EstFile.GetDefault(manager, m.Slot, Names.CombinedRace(m.Gender, m.Race), m.SetId); + var manip = new EstManipulation(m.Gender, m.Race, m.Slot, m.SetId, def); var file = m.Slot switch { EstManipulation.EstType.Hair => _estHairFile!, @@ -81,8 +101,7 @@ public struct EstCache : IDisposable EstManipulation.EstType.Head => _estHeadFile!, _ => throw new ArgumentOutOfRangeException(), }; - return manip.Apply( file ); - + return manip.Apply(file); } public void Dispose() @@ -97,4 +116,4 @@ public struct EstCache : IDisposable _estHeadFile = null; _estManipulations.Clear(); } -} \ No newline at end of file +} diff --git a/Penumbra/Collections/Cache/GmpCache.cs b/Penumbra/Collections/Cache/GmpCache.cs index 436dfdbf..2698af7b 100644 --- a/Penumbra/Collections/Cache/GmpCache.cs +++ b/Penumbra/Collections/Cache/GmpCache.cs @@ -4,6 +4,7 @@ using System.Linq; using OtterGui.Filesystem; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -17,13 +18,13 @@ public struct GmpCache : IDisposable public GmpCache() {} - public void SetFiles(CollectionCacheManager manager) + public void SetFiles(MetaFileManager manager) => manager.SetFile( _gmpFile, MetaIndex.Gmp ); - public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(CollectionCacheManager manager) + public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager) => manager.TemporarilySetFile( _gmpFile, MetaIndex.Gmp ); - public void ResetGmp(CollectionCacheManager manager) + public void Reset() { if( _gmpFile == null ) return; @@ -32,19 +33,19 @@ public struct GmpCache : IDisposable _gmpManipulations.Clear(); } - public bool ApplyMod( CollectionCacheManager manager, GmpManipulation manip ) + public bool ApplyMod( MetaFileManager manager, GmpManipulation manip ) { _gmpManipulations.AddOrReplace( manip ); - _gmpFile ??= new ExpandedGmpFile(); + _gmpFile ??= new ExpandedGmpFile(manager); return manip.Apply( _gmpFile ); } - public bool RevertMod( CollectionCacheManager manager, GmpManipulation manip ) + public bool RevertMod( MetaFileManager manager, GmpManipulation manip ) { if (!_gmpManipulations.Remove(manip)) return false; - var def = ExpandedGmpFile.GetDefault( manip.SetId ); + var def = ExpandedGmpFile.GetDefault( manager, manip.SetId ); manip = new GmpManipulation( def, manip.SetId ); return manip.Apply( _gmpFile! ); } diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 43dc73e5..2335fadd 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using OtterGui.Filesystem; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; @@ -16,13 +17,13 @@ public readonly struct ImcCache : IDisposable public ImcCache() { } - public void SetFiles(CollectionCacheManager manager, ModCollection collection) + public void SetFiles(ModCollection collection) { foreach( var path in _imcFiles.Keys ) collection._cache!.ForceFile( path, CreateImcPath( collection, path ) ); } - public void Reset(CollectionCacheManager manager, ModCollection collection) + public void Reset(ModCollection collection) { foreach( var (path, file) in _imcFiles ) { @@ -33,7 +34,7 @@ public readonly struct ImcCache : IDisposable _imcManipulations.Clear(); } - public bool ApplyMod( CollectionCacheManager manager, ModCollection collection, ImcManipulation manip ) + public bool ApplyMod( MetaFileManager manager, ModCollection collection, ImcManipulation manip ) { if( !manip.Valid ) { @@ -46,7 +47,7 @@ public readonly struct ImcCache : IDisposable { if( !_imcFiles.TryGetValue( path, out var file ) ) { - file = new ImcFile( manip ); + file = new ImcFile( manager, manip ); } if( !manip.Apply( file ) ) @@ -73,7 +74,7 @@ public readonly struct ImcCache : IDisposable return false; } - public bool RevertMod( CollectionCacheManager manager, ModCollection collection, ImcManipulation m ) + public bool RevertMod( MetaFileManager manager, ModCollection collection, ImcManipulation m ) { if( !m.Valid || !_imcManipulations.Remove( m ) ) { @@ -86,7 +87,7 @@ public readonly struct ImcCache : IDisposable return false; } - var def = ImcFile.GetDefault( path, m.EquipSlot, m.Variant, out _ ); + var def = ImcFile.GetDefault( manager, path, m.EquipSlot, m.Variant, out _ ); var manip = m.Copy( def ); if( !manip.Apply( file ) ) return false; diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index bee01714..abb77f9d 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -2,19 +2,19 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using OtterGui; +using Penumbra.GameData.Enums; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; +using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.Mods; +using Penumbra.String.Classes; namespace Penumbra.Collections.Cache; -public struct MetaCache : IDisposable, IEnumerable> +public class MetaCache : IDisposable, IEnumerable> { - private readonly CollectionCacheManager _manager; + private readonly MetaFileManager _manager; private readonly ModCollection _collection; private readonly Dictionary _manipulations = new(); private EqpCache _eqpCache = new(); @@ -22,7 +22,7 @@ public struct MetaCache : IDisposable, IEnumerable _manipulations.TryGetValue(manip, out mod); @@ -39,10 +39,10 @@ public struct MetaCache : IDisposable, IEnumerable GetEnumerator(); - public MetaCache(CollectionCacheManager manager, ModCollection collection) + public MetaCache(MetaFileManager manager, ModCollection collection) { - _manager = manager; - _imcCache = new ImcCache(collection); + _manager = manager; + _collection = collection; if (!_manager.CharacterUtility.Ready) _manager.CharacterUtility.LoadingFinished += ApplyStoredManipulations; } @@ -54,17 +54,17 @@ public struct MetaCache : IDisposable, IEnumerable Dispose(); + public bool ApplyMod(MetaManipulation manip, IMod mod) { if (_manipulations.ContainsKey(manip)) @@ -125,11 +128,61 @@ public struct MetaCache : IDisposable, IEnumerable false, }; } + + /// Set a single file. + public void SetFile(MetaIndex metaIndex) + { + switch (metaIndex) + { + case MetaIndex.Eqp: + _eqpCache.SetFiles(_manager); + break; + case MetaIndex.Gmp: + _gmpCache.SetFiles(_manager); + break; + case MetaIndex.HumanCmp: + _cmpCache.SetFiles(_manager); + break; + case MetaIndex.FaceEst: + case MetaIndex.HairEst: + case MetaIndex.HeadEst: + case MetaIndex.BodyEst: + _estCache.SetFile(_manager, metaIndex); + break; + default: + _eqdpCache.SetFile(_manager, metaIndex); + break; + } + } + + /// Set the currently relevant IMC files for the collection cache. + public void SetImcFiles() + => _imcCache.SetFiles(_collection); - // Use this when CharacterUtility becomes ready. + public CharacterUtility.MetaList.MetaReverter TemporarilySetEqpFile() + => _eqpCache.TemporarilySetFiles(_manager); + + public CharacterUtility.MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) + => _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory); + + public CharacterUtility.MetaList.MetaReverter TemporarilySetGmpFile() + => _gmpCache.TemporarilySetFiles(_manager); + + public CharacterUtility.MetaList.MetaReverter TemporarilySetCmpFile() + => _cmpCache.TemporarilySetFiles(_manager); + + public CharacterUtility.MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type) + => _estCache.TemporarilySetFiles(_manager, type); + + + /// Try to obtain a manipulated IMC file. + public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) + => _imcCache.GetImcFile(path, out file); + + /// Use this when CharacterUtility becomes ready. private void ApplyStoredManipulations() { - if (!Penumbra.CharacterUtility.Ready) + if (!_manager.CharacterUtility.Ready) return; var loaded = 0; @@ -149,29 +202,9 @@ public struct MetaCache : IDisposable, IEnumerable file == null - ? _manager.CharacterUtility.TemporarilyResetResource(metaIndex) - : _manager.CharacterUtility.TemporarilySetResource(metaIndex, (IntPtr)file.Data, file.Length); -} \ No newline at end of file +} diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index f37d9163..f50e2472 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -47,27 +47,6 @@ public partial class ModCollection public FullPath? ResolvePath(Utf8GamePath path) => _cache?.ResolvePath(path); - // Force a file to be resolved to a specific path regardless of conflicts. - internal void ForceFile(Utf8GamePath path, FullPath fullPath) - { - if (CheckFullPath(path, fullPath)) - _cache!.ResolvedFiles[path] = new ModPath(Mod.ForcedFiles, fullPath); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool CheckFullPath(Utf8GamePath path, FullPath fullPath) - { - if (fullPath.InternalName.Length < Utf8GamePath.MaxGamePathLength) - return true; - - Penumbra.Log.Error($"The redirected path is too long to add the redirection\n\t{path}\n\t--> {fullPath}"); - return false; - } - - // Force a file resolve to be removed. - internal void RemoveFile(Utf8GamePath path) - => _cache!.ResolvedFiles.Remove(path); - // Obtain data from the cache. internal MetaCache? MetaCache => _cache?.MetaManipulations; @@ -135,3 +114,8 @@ public partial class ModCollection => _cache?.MetaManipulations.TemporarilySetEstFile(type) ?? Penumbra.CharacterUtility.TemporarilyResetResource((MetaIndex)type); } + + +public static class CollectionCacheExtensions +{ +} \ No newline at end of file diff --git a/Penumbra/Import/TexToolsMeta.Deserialization.cs b/Penumbra/Import/TexToolsMeta.Deserialization.cs index e97312b0..dc2047b2 100644 --- a/Penumbra/Import/TexToolsMeta.Deserialization.cs +++ b/Penumbra/Import/TexToolsMeta.Deserialization.cs @@ -3,7 +3,7 @@ using System.IO; using Lumina.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Import.Structs; +using Penumbra.Import.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -12,148 +12,128 @@ namespace Penumbra.Import; public partial class TexToolsMeta { // Deserialize and check Eqp Entries and add them to the list if they are non-default. - private void DeserializeEqpEntry( MetaFileInfo metaFileInfo, byte[]? data ) + private void DeserializeEqpEntry(MetaFileInfo metaFileInfo, byte[]? data) { // Eqp can only be valid for equipment. - if( data == null || !metaFileInfo.EquipSlot.IsEquipment() ) - { + if (data == null || !metaFileInfo.EquipSlot.IsEquipment()) return; - } - var value = Eqp.FromSlotAndBytes( metaFileInfo.EquipSlot, data ); - var def = new EqpManipulation( ExpandedEqpFile.GetDefault( metaFileInfo.PrimaryId ), metaFileInfo.EquipSlot, metaFileInfo.PrimaryId ); - var manip = new EqpManipulation( value, metaFileInfo.EquipSlot, metaFileInfo.PrimaryId ); - if( _keepDefault || def.Entry != manip.Entry ) - { - MetaManipulations.Add( manip ); - } + var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data); + var def = new EqpManipulation(ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId), metaFileInfo.EquipSlot, + metaFileInfo.PrimaryId); + var manip = new EqpManipulation(value, metaFileInfo.EquipSlot, metaFileInfo.PrimaryId); + if (_keepDefault || def.Entry != manip.Entry) + MetaManipulations.Add(manip); } // Deserialize and check Eqdp Entries and add them to the list if they are non-default. - private void DeserializeEqdpEntries( MetaFileInfo metaFileInfo, byte[]? data ) + private void DeserializeEqdpEntries(MetaFileInfo metaFileInfo, byte[]? data) { - if( data == null ) - { + if (data == null) return; - } var num = data.Length / 5; - using var reader = new BinaryReader( new MemoryStream( data ) ); - for( var i = 0; i < num; ++i ) + using var reader = new BinaryReader(new MemoryStream(data)); + for (var i = 0; i < num; ++i) { // Use the SE gender/race code. - var gr = ( GenderRace )reader.ReadUInt32(); + var gr = (GenderRace)reader.ReadUInt32(); var byteValue = reader.ReadByte(); - if( !gr.IsValid() || !metaFileInfo.EquipSlot.IsEquipment() && !metaFileInfo.EquipSlot.IsAccessory() ) - { + if (!gr.IsValid() || !metaFileInfo.EquipSlot.IsEquipment() && !metaFileInfo.EquipSlot.IsAccessory()) continue; - } - var value = Eqdp.FromSlotAndBits( metaFileInfo.EquipSlot, ( byteValue & 1 ) == 1, ( byteValue & 2 ) == 2 ); - var def = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId ), + var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2); + var def = new EqdpManipulation( + ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId), metaFileInfo.EquipSlot, - gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId ); - var manip = new EqdpManipulation( value, metaFileInfo.EquipSlot, gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId ); - if( _keepDefault || def.Entry != manip.Entry ) - { - MetaManipulations.Add( manip ); - } + gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId); + var manip = new EqdpManipulation(value, metaFileInfo.EquipSlot, gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId); + if (_keepDefault || def.Entry != manip.Entry) + MetaManipulations.Add(manip); } } // Deserialize and check Gmp Entries and add them to the list if they are non-default. - private void DeserializeGmpEntry( MetaFileInfo metaFileInfo, byte[]? data ) + private void DeserializeGmpEntry(MetaFileInfo metaFileInfo, byte[]? data) { - if( data == null ) - { + if (data == null) return; - } - using var reader = new BinaryReader( new MemoryStream( data ) ); - var value = ( GmpEntry )reader.ReadUInt32(); + using var reader = new BinaryReader(new MemoryStream(data)); + var value = (GmpEntry)reader.ReadUInt32(); value.UnknownTotal = reader.ReadByte(); - var def = ExpandedGmpFile.GetDefault( metaFileInfo.PrimaryId ); - if( _keepDefault || value != def ) - { - MetaManipulations.Add( new GmpManipulation( value, metaFileInfo.PrimaryId ) ); - } + var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId); + if (_keepDefault || value != def) + MetaManipulations.Add(new GmpManipulation(value, metaFileInfo.PrimaryId)); } // Deserialize and check Est Entries and add them to the list if they are non-default. - private void DeserializeEstEntries( MetaFileInfo metaFileInfo, byte[]? data ) + private void DeserializeEstEntries(MetaFileInfo metaFileInfo, byte[]? data) { - if( data == null ) - { + if (data == null) return; - } var num = data.Length / 6; - using var reader = new BinaryReader( new MemoryStream( data ) ); - for( var i = 0; i < num; ++i ) + using var reader = new BinaryReader(new MemoryStream(data)); + for (var i = 0; i < num; ++i) { - var gr = ( GenderRace )reader.ReadUInt16(); + var gr = (GenderRace)reader.ReadUInt16(); var id = reader.ReadUInt16(); var value = reader.ReadUInt16(); - var type = ( metaFileInfo.SecondaryType, metaFileInfo.EquipSlot ) switch + var type = (metaFileInfo.SecondaryType, metaFileInfo.EquipSlot) switch { (BodySlot.Face, _) => EstManipulation.EstType.Face, (BodySlot.Hair, _) => EstManipulation.EstType.Hair, (_, EquipSlot.Head) => EstManipulation.EstType.Head, (_, EquipSlot.Body) => EstManipulation.EstType.Body, - _ => ( EstManipulation.EstType )0, + _ => (EstManipulation.EstType)0, }; - if( !gr.IsValid() || type == 0 ) - { + if (!gr.IsValid() || type == 0) continue; - } - var def = EstFile.GetDefault( type, gr, id ); - if( _keepDefault || def != value ) - { - MetaManipulations.Add( new EstManipulation( gr.Split().Item1, gr.Split().Item2, type, id, value ) ); - } + var def = EstFile.GetDefault(_metaFileManager, type, gr, id); + if (_keepDefault || def != value) + MetaManipulations.Add(new EstManipulation(gr.Split().Item1, gr.Split().Item2, type, id, value)); } } // Deserialize and check IMC Entries and add them to the list if they are non-default. // This requires requesting a file from Lumina, which may fail due to TexTools corruption or just not existing. // TexTools creates IMC files for off-hand weapon models which may not exist in the game files. - private void DeserializeImcEntries( MetaFileInfo metaFileInfo, byte[]? data ) + private void DeserializeImcEntries(MetaFileInfo metaFileInfo, byte[]? data) { - if( data == null ) - { + if (data == null) return; - } var num = data.Length / 6; - using var reader = new BinaryReader( new MemoryStream( data ) ); - var values = reader.ReadStructures< ImcEntry >( num ); + using var reader = new BinaryReader(new MemoryStream(data)); + var values = reader.ReadStructures(num); ushort i = 0; try { - var manip = new ImcManipulation( metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId, metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot, - new ImcEntry() ); - var def = new ImcFile( manip ); - var partIdx = ImcFile.PartIndex( manip.EquipSlot ); // Gets turned to unknown for things without equip, and unknown turns to 0. - foreach( var value in values ) + var manip = new ImcManipulation(metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId, + metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot, + new ImcEntry()); + var def = new ImcFile(_metaFileManager, manip); + var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0. + foreach (var value in values) { - if( _keepDefault || !value.Equals( def.GetEntry( partIdx, i ) ) ) + if (_keepDefault || !value.Equals(def.GetEntry(partIdx, i))) { - var imc = new ImcManipulation( manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, value ); - if( imc.Valid ) - { - MetaManipulations.Add( imc ); - } + var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, + value); + if (imc.Valid) + MetaManipulations.Add(imc); } ++i; } } - catch( Exception e ) + catch (Exception e) { Penumbra.Log.Warning( $"Could not compute IMC manipulation for {metaFileInfo.PrimaryType} {metaFileInfo.PrimaryId}. This is in all likelihood due to TexTools corrupting your index files.\n" - + $"If the following error looks like Lumina is having trouble to read an IMC file, please do a do-over in TexTools:\n{e}" ); + + $"If the following error looks like Lumina is having trouble to read an IMC file, please do a do-over in TexTools:\n{e}"); } } -} \ No newline at end of file +} diff --git a/Penumbra/Import/TexToolsMeta.Export.cs b/Penumbra/Import/TexToolsMeta.Export.cs index 03aae64a..759474e9 100644 --- a/Penumbra/Import/TexToolsMeta.Export.cs +++ b/Penumbra/Import/TexToolsMeta.Export.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -12,7 +13,7 @@ namespace Penumbra.Import; public partial class TexToolsMeta { - public static Dictionary< string, byte[] > ConvertToTexTools( IEnumerable< MetaManipulation > manips ) + public static Dictionary< string, byte[] > ConvertToTexTools( MetaFileManager manager, IEnumerable< MetaManipulation > manips ) { var ret = new Dictionary< string, byte[] >(); foreach( var group in manips.GroupBy( ManipToPath ) ) @@ -23,8 +24,8 @@ public partial class TexToolsMeta } var bytes = group.Key.EndsWith( ".rgsp" ) - ? WriteRgspFile( group.Key, group ) - : WriteMetaFile( group.Key, group ); + ? WriteRgspFile( manager, group.Key, group ) + : WriteMetaFile( manager, group.Key, group ); if( bytes.Length == 0 ) { continue; @@ -36,7 +37,7 @@ public partial class TexToolsMeta return ret; } - private static byte[] WriteRgspFile( string path, IEnumerable< MetaManipulation > manips ) + private static byte[] WriteRgspFile( MetaFileManager manager, string path, IEnumerable< MetaManipulation > manips ) { var list = manips.GroupBy( m => m.Rsp.Attribute ).ToDictionary( m => m.Key, m => m.Last().Rsp ); using var m = new MemoryStream( 45 ); @@ -54,7 +55,7 @@ public partial class TexToolsMeta { foreach( var attribute in attributes ) { - var value = list.TryGetValue( attribute, out var tmp ) ? tmp.Entry : CmpFile.GetDefault( race, attribute ); + var value = list.TryGetValue( attribute, out var tmp ) ? tmp.Entry : CmpFile.GetDefault( manager, race, attribute ); b.Write( value ); } } @@ -72,7 +73,7 @@ public partial class TexToolsMeta return m.GetBuffer(); } - private static byte[] WriteMetaFile( string path, IEnumerable< MetaManipulation > manips ) + private static byte[] WriteMetaFile( MetaFileManager manager, string path, IEnumerable< MetaManipulation > manips ) { var filteredManips = manips.GroupBy( m => m.ManipulationType ).ToDictionary( p => p.Key, p => p.Select( x => x ) ); @@ -103,7 +104,7 @@ public partial class TexToolsMeta b.Write( ( uint )header ); b.Write( offset ); - var size = WriteData( b, offset, header, data ); + var size = WriteData( manager, b, offset, header, data ); b.Write( size ); offset += size; } @@ -111,7 +112,7 @@ public partial class TexToolsMeta return m.ToArray(); } - private static uint WriteData( BinaryWriter b, uint offset, MetaManipulation.Type type, IEnumerable< MetaManipulation > manips ) + private static uint WriteData( MetaFileManager manager, BinaryWriter b, uint offset, MetaManipulation.Type type, IEnumerable< MetaManipulation > manips ) { var oldPos = b.BaseStream.Position; b.Seek( ( int )offset, SeekOrigin.Begin ); @@ -120,7 +121,7 @@ public partial class TexToolsMeta { case MetaManipulation.Type.Imc: var allManips = manips.ToList(); - var baseFile = new ImcFile( allManips[ 0 ].Imc ); + var baseFile = new ImcFile( manager, allManips[ 0 ].Imc ); foreach( var manip in allManips ) { manip.Imc.Apply( baseFile ); diff --git a/Penumbra/Import/TexToolsMeta.Rgsp.cs b/Penumbra/Import/TexToolsMeta.Rgsp.cs index 8eb0c49a..7bb837ce 100644 --- a/Penumbra/Import/TexToolsMeta.Rgsp.cs +++ b/Penumbra/Import/TexToolsMeta.Rgsp.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Penumbra.GameData.Enums; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -9,7 +10,7 @@ namespace Penumbra.Import; public partial class TexToolsMeta { // Parse a single rgsp file. - public static TexToolsMeta FromRgspFile( string filePath, byte[] data, bool keepDefault ) + public static TexToolsMeta FromRgspFile( MetaFileManager manager, string filePath, byte[] data, bool keepDefault ) { if( data.Length != 45 && data.Length != 42 ) { @@ -25,7 +26,7 @@ public partial class TexToolsMeta var flag = br.ReadByte(); var version = flag != 255 ? ( uint )1 : br.ReadUInt16(); - var ret = new TexToolsMeta( filePath, version ); + var ret = new TexToolsMeta( manager, filePath, version ); // SubRace is offset by one due to Unknown. var subRace = ( SubRace )( version == 1 ? flag + 1 : br.ReadByte() + 1 ); @@ -46,7 +47,7 @@ public partial class TexToolsMeta // Add the given values to the manipulations if they are not default. void Add( RspAttribute attribute, float value ) { - var def = CmpFile.GetDefault( subRace, attribute ); + var def = CmpFile.GetDefault( manager, subRace, attribute ); if( keepDefault || value != def ) { ret.MetaManipulations.Add( new RspManipulation( subRace, attribute, value ) ); diff --git a/Penumbra/Import/TexToolsMeta.cs b/Penumbra/Import/TexToolsMeta.cs index cbf1e9fa..b07ac8ab 100644 --- a/Penumbra/Import/TexToolsMeta.cs +++ b/Penumbra/Import/TexToolsMeta.cs @@ -4,34 +4,37 @@ using System.IO; using System.Text; using Penumbra.GameData; using Penumbra.Import.Structs; +using Penumbra.Meta; using Penumbra.Meta.Manipulations; namespace Penumbra.Import; -// TexTools provices custom generated *.meta files for its modpacks, that contain changes to -// - imc files -// - eqp files -// - gmp files -// - est files -// - eqdp files -// made by the mod. The filename determines to what the changes are applied, and the binary file itself contains changes. -// We parse every *.meta file in a mod and combine all actual changes that do not keep data on default values and that can be applied to the game in a .json. -// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored. -// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file. +/// TexTools provices custom generated *.meta files for its modpacks, that contain changes to +/// - imc files +/// - eqp files +/// - gmp files +/// - est files +/// - eqdp files +/// made by the mod. The filename determines to what the changes are applied, and the binary file itself contains changes. +/// We parse every *.meta file in a mod and combine all actual changes that do not keep data on default values and that can be applied to the game in a .json. +/// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored. +/// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file. public partial class TexToolsMeta -{ - // An empty TexToolsMeta. - public static readonly TexToolsMeta Invalid = new(string.Empty, 0); +{ + /// An empty TexToolsMeta. + public static readonly TexToolsMeta Invalid = new(null!, string.Empty, 0); // The info class determines the files or table locations the changes need to apply to from the filename. - public readonly uint Version; public readonly string FilePath; public readonly List< MetaManipulation > MetaManipulations = new(); private readonly bool _keepDefault = false; + + private readonly MetaFileManager _metaFileManager; - public TexToolsMeta( IGamePathParser parser, byte[] data, bool keepDefault ) - { + public TexToolsMeta( MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault ) + { + _metaFileManager = metaFileManager; _keepDefault = keepDefault; try { @@ -80,10 +83,11 @@ public partial class TexToolsMeta } } - private TexToolsMeta( string filePath, uint version ) + private TexToolsMeta( MetaFileManager metaFileManager, string filePath, uint version ) { - FilePath = filePath; - Version = version; + _metaFileManager = metaFileManager; + FilePath = filePath; + Version = version; } // Read a null terminated string from a binary reader. diff --git a/Penumbra/Interop/Services/MetaFileManager.cs b/Penumbra/Interop/Services/MetaFileManager.cs deleted file mode 100644 index 382a6c84..00000000 --- a/Penumbra/Interop/Services/MetaFileManager.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.System.Memory; -using Penumbra.GameData; - -namespace Penumbra.Interop.Services; - -public unsafe class MetaFileManager -{ - public MetaFileManager() - { - SignatureHelper.Initialise(this); - } - - // Allocate in the games space for file storage. - // We only need this if using any meta file. - [Signature(Sigs.GetFileSpace)] - private readonly IntPtr _getFileSpaceAddress = IntPtr.Zero; - - public IMemorySpace* GetFileSpace() - => ((delegate* unmanaged)_getFileSpaceAddress)(); - - public void* AllocateFileMemory(ulong length, ulong alignment = 0) - => GetFileSpace()->Malloc(length, alignment); - - public void* AllocateFileMemory(int length, int alignment = 0) - => AllocateFileMemory((ulong)length, (ulong)alignment); - - public void* AllocateDefaultMemory(ulong length, ulong alignment = 0) - => GetFileSpace()->Malloc(length, alignment); - - public void* AllocateDefaultMemory(int length, int alignment = 0) - => IMemorySpace.GetDefaultSpace()->Malloc((ulong)length, (ulong)alignment); - - public void Free(IntPtr ptr, int length) - => IMemorySpace.Free((void*)ptr, (ulong)length); -} \ No newline at end of file diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index 1696abf6..e67c5efd 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -8,46 +8,46 @@ using Penumbra.String.Functions; namespace Penumbra.Meta.Files; -// The human.cmp file contains many character-relevant parameters like color sets. -// We only support manipulating the racial scaling parameters at the moment. +/// +/// The human.cmp file contains many character-relevant parameters like color sets. +/// We only support manipulating the racial scaling parameters at the moment. +/// public sealed unsafe class CmpFile : MetaBaseFile { public static readonly CharacterUtility.InternalIndex InternalIndex = - CharacterUtility.ReverseIndices[ ( int )MetaIndex.HumanCmp ]; + CharacterUtility.ReverseIndices[(int)MetaIndex.HumanCmp]; private const int RacialScalingStart = 0x2A800; - public float this[ SubRace subRace, RspAttribute attribute ] + public float this[SubRace subRace, RspAttribute attribute] { - get => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); - set => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ) = value; + get => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4); + set => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4) = value; } public override void Reset() - => MemoryUtility.MemCpyUnchecked( Data, ( byte* )DefaultData.Data, DefaultData.Length ); + => MemoryUtility.MemCpyUnchecked(Data, (byte*)DefaultData.Data, DefaultData.Length); - public void Reset( IEnumerable< (SubRace, RspAttribute) > entries ) + public void Reset(IEnumerable<(SubRace, RspAttribute)> entries) { - foreach( var (r, a) in entries ) - { - this[ r, a ] = GetDefault( r, a ); - } + foreach (var (r, a) in entries) + this[r, a] = GetDefault(Manager, r, a); } - public CmpFile() - : base( MetaIndex.HumanCmp ) + public CmpFile(MetaFileManager manager) + : base(manager, MetaIndex.HumanCmp) { - AllocateData( DefaultData.Length ); + AllocateData(DefaultData.Length); Reset(); } - public static float GetDefault( SubRace subRace, RspAttribute attribute ) + public static float GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( InternalIndex ).Address; - return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); + var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address; + return *(float*)(data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4); } - private static int ToRspIndex( SubRace subRace ) + private static int ToRspIndex(SubRace subRace) => subRace switch { SubRace.Midlander => 0, @@ -67,6 +67,6 @@ public sealed unsafe class CmpFile : MetaBaseFile SubRace.Rava => 70, SubRace.Veena => 71, SubRace.Unknown => 0, - _ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ), + _ => throw new ArgumentOutOfRangeException(nameof(subRace), subRace, null), }; -} \ No newline at end of file +} diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs index 0122fe5a..14275467 100644 --- a/Penumbra/Meta/Files/EqdpFile.cs +++ b/Penumbra/Meta/Files/EqdpFile.cs @@ -8,14 +8,15 @@ using Penumbra.String.Functions; namespace Penumbra.Meta.Files; -// EQDP file structure: -// [Identifier][BlockSize:ushort][BlockCount:ushort] -// BlockCount x [BlockHeader:ushort] -// Containing offsets for blocks, ushort.Max means collapsed. -// Offsets are based on the end of the header, so 0 means IdentifierSize + 4 + BlockCount x 2. -// ExpandedBlockCount x [Entry] - -// Expanded Eqdp File just expands all blocks for easy read and write access to single entries and to keep the same memory for it. +/// +/// EQDP file structure: +/// [Identifier][BlockSize:ushort][BlockCount:ushort] +/// BlockCount x [BlockHeader:ushort] +/// Containing offsets for blocks, ushort.Max means collapsed. +/// Offsets are based on the end of the header, so 0 means IdentifierSize + 4 + BlockCount x 2. +/// ExpandedBlockCount x [Entry] +/// Expanded Eqdp File just expands all blocks for easy read and write access to single entries and to keep the same memory for it. +/// public sealed unsafe class ExpandedEqdpFile : MetaBaseFile { private const ushort BlockHeaderSize = 2; @@ -28,117 +29,103 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile public readonly int DataOffset; public ushort Identifier - => *( ushort* )Data; + => *(ushort*)Data; public ushort BlockSize - => *( ushort* )( Data + 2 ); + => *(ushort*)(Data + 2); public ushort BlockCount - => *( ushort* )( Data + 4 ); + => *(ushort*)(Data + 4); public int Count - => ( Length - DataOffset ) / EqdpEntrySize; + => (Length - DataOffset) / EqdpEntrySize; - public EqdpEntry this[ int idx ] + public EqdpEntry this[int idx] { get { - if( idx >= Count || idx < 0 ) - { + if (idx >= Count || idx < 0) throw new IndexOutOfRangeException(); - } - return ( EqdpEntry )( *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) ); + return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * idx)); } set { - if( idx >= Count || idx < 0 ) - { + if (idx >= Count || idx < 0) throw new IndexOutOfRangeException(); - } - *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) = ( ushort )value; + *(ushort*)(Data + DataOffset + EqdpEntrySize * idx) = (ushort)value; } } public override void Reset() { - var def = ( byte* )DefaultData.Data; - MemoryUtility.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize ); + var def = (byte*)DefaultData.Data; + MemoryUtility.MemCpyUnchecked(Data, def, IdentifierSize + PreambleSize); - var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize ); + var controlPtr = (ushort*)(def + IdentifierSize + PreambleSize); var dataBasePtr = controlPtr + BlockCount; - var myDataPtr = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount ); - var myControlPtr = ( ushort* )( Data + IdentifierSize + PreambleSize ); - for( var i = 0; i < BlockCount; ++i ) + var myDataPtr = (ushort*)(Data + IdentifierSize + PreambleSize + 2 * BlockCount); + var myControlPtr = (ushort*)(Data + IdentifierSize + PreambleSize); + for (var i = 0; i < BlockCount; ++i) { - if( controlPtr[ i ] == CollapsedBlock ) - { - MemoryUtility.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize ); - } + if (controlPtr[i] == CollapsedBlock) + MemoryUtility.MemSet(myDataPtr, 0, BlockSize * EqdpEntrySize); else - { - MemoryUtility.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize ); - } + MemoryUtility.MemCpyUnchecked(myDataPtr, dataBasePtr + controlPtr[i], BlockSize * EqdpEntrySize); - myControlPtr[ i ] = ( ushort )( i * BlockSize ); - myDataPtr += BlockSize; + myControlPtr[i] = (ushort)(i * BlockSize); + myDataPtr += BlockSize; } - MemoryUtility.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) ); + MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data)); } - public void Reset( IEnumerable< int > entries ) + public void Reset(IEnumerable entries) { - foreach( var entry in entries ) - { - this[ entry ] = GetDefault( entry ); - } + foreach (var entry in entries) + this[entry] = GetDefault(entry); } - public ExpandedEqdpFile( GenderRace raceCode, bool accessory ) - : base( CharacterUtilityData.EqdpIdx( raceCode, accessory ) ) + public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory) + : base(manager, CharacterUtilityData.EqdpIdx(raceCode, accessory)) { - var def = ( byte* )DefaultData.Data; - var blockSize = *( ushort* )( def + IdentifierSize ); - var totalBlockCount = *( ushort* )( def + IdentifierSize + 2 ); + var def = (byte*)DefaultData.Data; + var blockSize = *(ushort*)(def + IdentifierSize); + var totalBlockCount = *(ushort*)(def + IdentifierSize + 2); var totalBlockSize = blockSize * EqdpEntrySize; DataOffset = IdentifierSize + PreambleSize + totalBlockCount * BlockHeaderSize; - var fullLength = DataOffset + totalBlockCount * totalBlockSize; - fullLength += ( FileAlignment - ( fullLength & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 ); - AllocateData( fullLength ); + var fullLength = DataOffset + totalBlockCount * totalBlockSize; + fullLength += (FileAlignment - (fullLength & (FileAlignment - 1))) & (FileAlignment - 1); + AllocateData(fullLength); Reset(); } - public EqdpEntry GetDefault( int setIdx ) - => GetDefault( Index, setIdx ); + public EqdpEntry GetDefault(int setIdx) + => GetDefault(Manager, Index, setIdx); - public static EqdpEntry GetDefault( CharacterUtility.InternalIndex idx, int setIdx ) - => GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( idx ).Address, setIdx ); + public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, int setIdx) + => GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setIdx); - public static EqdpEntry GetDefault( byte* data, int setIdx ) + public static EqdpEntry GetDefault(byte* data, int setIdx) { - var blockSize = *( ushort* )( data + IdentifierSize ); - var totalBlockCount = *( ushort* )( data + IdentifierSize + 2 ); + var blockSize = *(ushort*)(data + IdentifierSize); + var totalBlockCount = *(ushort*)(data + IdentifierSize + 2); var blockIdx = setIdx / blockSize; - if( blockIdx >= totalBlockCount ) - { + if (blockIdx >= totalBlockCount) return 0; - } - var block = ( ( ushort* )( data + IdentifierSize + PreambleSize ) )[ blockIdx ]; - if( block == CollapsedBlock ) - { + var block = ((ushort*)(data + IdentifierSize + PreambleSize))[blockIdx]; + if (block == CollapsedBlock) return 0; - } - var blockData = ( ushort* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2 ); - return ( EqdpEntry )( *( blockData + setIdx % blockSize ) ); + var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2); + return (EqdpEntry)(*(blockData + setIdx % blockSize)); } - public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx ) - => GetDefault( CharacterUtility.ReverseIndices[ ( int )CharacterUtilityData.EqdpIdx( raceCode, accessory ) ], setIdx ); -} \ No newline at end of file + public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, int setIdx) + => GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setIdx); +} diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index 8ad3e95f..e21e18c7 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -9,11 +9,13 @@ using Penumbra.String.Functions; namespace Penumbra.Meta.Files; -// EQP/GMP Structure: -// 64 x [Block collapsed or not bit] -// 159 x [EquipmentParameter:ulong] -// (CountSetBits(Block Collapsed or not) - 1) x 160 x [EquipmentParameter:ulong] -// Item 0 does not exist and is sent to Item 1 instead. +/// +/// EQP/GMP Structure: +/// 64 x [Block collapsed or not bit] +/// 159 x [EquipmentParameter:ulong] +/// (CountSetBits(Block Collapsed or not) - 1) x 160 x [EquipmentParameter:ulong] +/// Item 0 does not exist and is sent to Item 1 instead. +/// public unsafe class ExpandedEqpGmpBase : MetaBaseFile { protected const int BlockSize = 160; @@ -24,19 +26,19 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile public const int Count = BlockSize * NumBlocks; public ulong ControlBlock - => *( ulong* )Data; + => *(ulong*)Data; - protected ulong GetInternal( int idx ) + protected ulong GetInternal(int idx) { return idx switch { >= Count => throw new IndexOutOfRangeException(), - <= 1 => *( ( ulong* )Data + 1 ), - _ => *( ( ulong* )Data + idx ), + <= 1 => *((ulong*)Data + 1), + _ => *((ulong*)Data + idx), }; } - protected void SetInternal( int idx, ulong value ) + protected void SetInternal(int idx, ulong value) { idx = idx switch { @@ -45,67 +47,62 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile _ => idx, }; - *( ( ulong* )Data + idx ) = value; + *((ulong*)Data + idx) = value; } - protected virtual void SetEmptyBlock( int idx ) + protected virtual void SetEmptyBlock(int idx) { - MemoryUtility.MemSet( Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize ); + MemoryUtility.MemSet(Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize); } public sealed override void Reset() { - var ptr = ( byte* )DefaultData.Data; - var controlBlock = *( ulong* )ptr; + var ptr = (byte*)DefaultData.Data; + var controlBlock = *(ulong*)ptr; var expandedBlocks = 0; - for( var i = 0; i < NumBlocks; ++i ) + for (var i = 0; i < NumBlocks; ++i) { - var collapsed = ( ( controlBlock >> i ) & 1 ) == 0; - if( !collapsed ) + var collapsed = ((controlBlock >> i) & 1) == 0; + if (!collapsed) { - MemoryUtility.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize ); + MemoryUtility.MemCpyUnchecked(Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, + BlockSize * EntrySize); expandedBlocks++; } else { - SetEmptyBlock( i ); + SetEmptyBlock(i); } } - *( ulong* )Data = ulong.MaxValue; + *(ulong*)Data = ulong.MaxValue; } - public ExpandedEqpGmpBase( bool gmp ) - : base( gmp ? MetaIndex.Gmp : MetaIndex.Eqp ) + public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp) + : base(manager, gmp ? MetaIndex.Gmp : MetaIndex.Eqp) { - AllocateData( MaxSize ); + AllocateData(MaxSize); Reset(); } - protected static ulong GetDefaultInternal( CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def ) + protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResource(fileIndex).Address; - if( setIdx == 0 ) - { + var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address; + if (setIdx == 0) setIdx = 1; - } var blockIdx = setIdx / BlockSize; - if( blockIdx >= NumBlocks ) - { + if (blockIdx >= NumBlocks) return def; - } - var control = *( ulong* )data; + var control = *(ulong*)data; var blockBit = 1ul << blockIdx; - if( ( control & blockBit ) == 0 ) - { + if ((control & blockBit) == 0) return def; - } - var count = BitOperations.PopCount( control & ( blockBit - 1 ) ); + var count = BitOperations.PopCount(control & (blockBit - 1)); var idx = setIdx % BlockSize; - var ptr = ( ulong* )data + BlockSize * count + idx; + var ptr = (ulong*)data + BlockSize * count + idx; return *ptr; } } @@ -113,44 +110,40 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable { public static readonly CharacterUtility.InternalIndex InternalIndex = - CharacterUtility.ReverseIndices[ (int) MetaIndex.Eqp ]; + CharacterUtility.ReverseIndices[(int)MetaIndex.Eqp]; - public ExpandedEqpFile() - : base( false ) + public ExpandedEqpFile(MetaFileManager manager) + : base(manager, false) { } - public EqpEntry this[ int idx ] + public EqpEntry this[int idx] { - get => ( EqpEntry )GetInternal( idx ); - set => SetInternal( idx, ( ulong )value ); + get => (EqpEntry)GetInternal(idx); + set => SetInternal(idx, (ulong)value); } - public static EqpEntry GetDefault( int setIdx ) - => ( EqpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )Eqp.DefaultEntry ); + public static EqpEntry GetDefault(MetaFileManager manager, int setIdx) + => (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry); - protected override unsafe void SetEmptyBlock( int idx ) + protected override unsafe void SetEmptyBlock(int idx) { - var blockPtr = ( ulong* )( Data + idx * BlockSize * EntrySize ); + var blockPtr = (ulong*)(Data + idx * BlockSize * EntrySize); var endPtr = blockPtr + BlockSize; - for( var ptr = blockPtr; ptr < endPtr; ++ptr ) - { - *ptr = ( ulong )Eqp.DefaultEntry; - } + for (var ptr = blockPtr; ptr < endPtr; ++ptr) + *ptr = (ulong)Eqp.DefaultEntry; } - public void Reset( IEnumerable< int > entries ) + public void Reset(IEnumerable entries) { - foreach( var entry in entries ) - { - this[ entry ] = GetDefault( entry ); - } + foreach (var entry in entries) + this[entry] = GetDefault(Manager, entry); } - public IEnumerator< EqpEntry > GetEnumerator() + public IEnumerator GetEnumerator() { - for( var idx = 1; idx < Count; ++idx ) - yield return this[ idx ]; + for (var idx = 1; idx < Count; ++idx) + yield return this[idx]; } IEnumerator IEnumerable.GetEnumerator() @@ -160,35 +153,33 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable { public static readonly CharacterUtility.InternalIndex InternalIndex = - CharacterUtility.ReverseIndices[( int )MetaIndex.Gmp]; + CharacterUtility.ReverseIndices[(int)MetaIndex.Gmp]; - public ExpandedGmpFile() - : base( true ) + public ExpandedGmpFile(MetaFileManager manager) + : base(manager, true) { } - public GmpEntry this[ int idx ] + public GmpEntry this[int idx] { - get => ( GmpEntry )GetInternal( idx ); - set => SetInternal( idx, ( ulong )value ); + get => (GmpEntry)GetInternal(idx); + set => SetInternal(idx, (ulong)value); } - public static GmpEntry GetDefault( int setIdx ) - => ( GmpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )GmpEntry.Default ); + public static GmpEntry GetDefault(MetaFileManager manager, int setIdx) + => (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default); - public void Reset( IEnumerable< int > entries ) + public void Reset(IEnumerable entries) { - foreach( var entry in entries ) - { - this[ entry ] = GetDefault( entry ); - } + foreach (var entry in entries) + this[entry] = GetDefault(Manager, entry); } public IEnumerator GetEnumerator() { - for( var idx = 1; idx < Count; ++idx ) + for (var idx = 1; idx < Count; ++idx) yield return this[idx]; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} \ No newline at end of file +} diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs index d2d36f28..72fae443 100644 --- a/Penumbra/Meta/Files/EstFile.cs +++ b/Penumbra/Meta/Files/EstFile.cs @@ -8,11 +8,13 @@ using Penumbra.String.Functions; namespace Penumbra.Meta.Files; -// EST Structure: -// 1x [NumEntries : UInt32] -// Apparently entries need to be sorted. -// #NumEntries x [SetId : UInt16] [RaceId : UInt16] -// #NumEntries x [SkeletonId : UInt16] +/// +/// EST Structure: +/// 1x [NumEntries : UInt32] +/// Apparently entries need to be sorted. +/// #NumEntries x [SetId : UInt16] [RaceId : UInt16] +/// #NumEntries x [SkeletonId : UInt16] +/// public sealed unsafe class EstFile : MetaBaseFile { private const ushort EntryDescSize = 4; @@ -20,10 +22,10 @@ public sealed unsafe class EstFile : MetaBaseFile private const int IncreaseSize = 512; public int Count - => *( int* )Data; + => *(int*)Data; private int Size - => 4 + Count * ( EntryDescSize + EntrySize ); + => 4 + Count * (EntryDescSize + EntrySize); public enum EstEntryChange { @@ -33,176 +35,154 @@ public sealed unsafe class EstFile : MetaBaseFile Removed, } - public ushort this[ GenderRace genderRace, ushort setId ] + public ushort this[GenderRace genderRace, ushort setId] { get { - var (idx, exists) = FindEntry( genderRace, setId ); - if( !exists ) - { + var (idx, exists) = FindEntry(genderRace, setId); + if (!exists) return 0; - } - return *( ushort* )( Data + EntryDescSize * ( Count + 1 ) + EntrySize * idx ); + return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx); } - set => SetEntry( genderRace, setId, value ); + set => SetEntry(genderRace, setId, value); } - private void InsertEntry( int idx, GenderRace genderRace, ushort setId, ushort skeletonId ) + private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId) { - if( Length < Size + EntryDescSize + EntrySize ) - { - ResizeResources( Length + IncreaseSize ); - } + if (Length < Size + EntryDescSize + EntrySize) + ResizeResources(Length + IncreaseSize); - var control = ( Info* )( Data + 4 ); - var entries = ( ushort* )( control + Count ); + var control = (Info*)(Data + 4); + var entries = (ushort*)(control + Count); - for( var i = Count - 1; i >= idx; --i ) - { - entries[ i + 3 ] = entries[ i ]; - } + for (var i = Count - 1; i >= idx; --i) + entries[i + 3] = entries[i]; - entries[ idx + 2 ] = skeletonId; + entries[idx + 2] = skeletonId; - for( var i = idx - 1; i >= 0; --i ) - { - entries[ i + 2 ] = entries[ i ]; - } + for (var i = idx - 1; i >= 0; --i) + entries[i + 2] = entries[i]; - for( var i = Count - 1; i >= idx; --i ) - { - control[ i + 1 ] = control[ i ]; - } + for (var i = Count - 1; i >= idx; --i) + control[i + 1] = control[i]; - control[ idx ] = new Info( genderRace, setId ); + control[idx] = new Info(genderRace, setId); - *( int* )Data = Count + 1; + *(int*)Data = Count + 1; } - private void RemoveEntry( int idx ) + private void RemoveEntry(int idx) { - var control = ( Info* )( Data + 4 ); - var entries = ( ushort* )( control + Count ); + var control = (Info*)(Data + 4); + var entries = (ushort*)(control + Count); - for( var i = idx; i < Count; ++i ) - { - control[ i ] = control[ i + 1 ]; - } + for (var i = idx; i < Count; ++i) + control[i] = control[i + 1]; - for( var i = 0; i < idx; ++i ) - { - entries[ i - 2 ] = entries[ i ]; - } + for (var i = 0; i < idx; ++i) + entries[i - 2] = entries[i]; - for( var i = idx; i < Count - 1; ++i ) - { - entries[ i - 2 ] = entries[ i + 1 ]; - } + for (var i = idx; i < Count - 1; ++i) + entries[i - 2] = entries[i + 1]; - entries[ Count - 3 ] = 0; - entries[ Count - 2 ] = 0; - entries[ Count - 1 ] = 0; - *( int* )Data = Count - 1; + entries[Count - 3] = 0; + entries[Count - 2] = 0; + entries[Count - 1] = 0; + *(int*)Data = Count - 1; } - [StructLayout( LayoutKind.Sequential, Size = 4 )] - private struct Info : IComparable< Info > + [StructLayout(LayoutKind.Sequential, Size = 4)] + private struct Info : IComparable { public readonly ushort SetId; public readonly GenderRace GenderRace; - public Info( GenderRace gr, ushort setId ) + public Info(GenderRace gr, ushort setId) { GenderRace = gr; SetId = setId; } - public int CompareTo( Info other ) + public int CompareTo(Info other) { - var genderRaceComparison = GenderRace.CompareTo( other.GenderRace ); - return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo( other.SetId ); + var genderRaceComparison = GenderRace.CompareTo(other.GenderRace); + return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo(other.SetId); } } - private static (int, bool) FindEntry( ReadOnlySpan< Info > data, GenderRace genderRace, ushort setId ) + private static (int, bool) FindEntry(ReadOnlySpan data, GenderRace genderRace, ushort setId) { - var idx = data.BinarySearch( new Info( genderRace, setId ) ); - return idx < 0 ? ( ~idx, false ) : ( idx, true ); + var idx = data.BinarySearch(new Info(genderRace, setId)); + return idx < 0 ? (~idx, false) : (idx, true); } - private (int, bool) FindEntry( GenderRace genderRace, ushort setId ) + private (int, bool) FindEntry(GenderRace genderRace, ushort setId) { - var span = new ReadOnlySpan< Info >( Data + 4, Count ); - return FindEntry( span, genderRace, setId ); + var span = new ReadOnlySpan(Data + 4, Count); + return FindEntry(span, genderRace, setId); } - public EstEntryChange SetEntry( GenderRace genderRace, ushort setId, ushort skeletonId ) + public EstEntryChange SetEntry(GenderRace genderRace, ushort setId, ushort skeletonId) { - var (idx, exists) = FindEntry( genderRace, setId ); - if( exists ) + var (idx, exists) = FindEntry(genderRace, setId); + if (exists) { - var value = *( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx ); - if( value == skeletonId ) - { + var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx); + if (value == skeletonId) return EstEntryChange.Unchanged; - } - if( skeletonId == 0 ) + if (skeletonId == 0) { - RemoveEntry( idx ); + RemoveEntry(idx); return EstEntryChange.Removed; } - *( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx ) = skeletonId; + *(ushort*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId; return EstEntryChange.Changed; } - if( skeletonId == 0 ) - { + if (skeletonId == 0) return EstEntryChange.Unchanged; - } - InsertEntry( idx, genderRace, setId, skeletonId ); + InsertEntry(idx, genderRace, setId, skeletonId); return EstEntryChange.Added; } public override void Reset() { var (d, length) = DefaultData; - var data = ( byte* )d; - MemoryUtility.MemCpyUnchecked( Data, data, length ); - MemoryUtility.MemSet( Data + length, 0, Length - length ); + var data = (byte*)d; + MemoryUtility.MemCpyUnchecked(Data, data, length); + MemoryUtility.MemSet(Data + length, 0, Length - length); } - public EstFile( EstManipulation.EstType estType ) - : base( ( MetaIndex )estType ) + public EstFile(MetaFileManager manager, EstManipulation.EstType estType) + : base(manager, (MetaIndex)estType) { var length = DefaultData.Length; - AllocateData( length + IncreaseSize ); + AllocateData(length + IncreaseSize); Reset(); } - public ushort GetDefault( GenderRace genderRace, ushort setId ) - => GetDefault( Index, genderRace, setId ); + public ushort GetDefault(GenderRace genderRace, ushort setId) + => GetDefault(Manager, Index, genderRace, setId); - public static ushort GetDefault( CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId ) + public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( index ).Address; - var count = *( int* )data; - var span = new ReadOnlySpan< Info >( data + 4, count ); - var (idx, found) = FindEntry( span, genderRace, setId ); - if( !found ) - { + var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address; + var count = *(int*)data; + var span = new ReadOnlySpan(data + 4, count); + var (idx, found) = FindEntry(span, genderRace, setId); + if (!found) return 0; - } - return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize ); + return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize); } - public static ushort GetDefault( MetaIndex metaIndex, GenderRace genderRace, ushort setId ) - => GetDefault( CharacterUtility.ReverseIndices[ ( int )metaIndex ], genderRace, setId ); + public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, ushort setId) + => GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId); - public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId ) - => GetDefault( ( MetaIndex )estType, genderRace, setId ); -} \ No newline at end of file + public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, ushort setId) + => GetDefault(manager, (MetaIndex)estType, genderRace, setId); +} diff --git a/Penumbra/Meta/Files/EvpFile.cs b/Penumbra/Meta/Files/EvpFile.cs index c475011a..0b64e1e8 100644 --- a/Penumbra/Meta/Files/EvpFile.cs +++ b/Penumbra/Meta/Files/EvpFile.cs @@ -3,15 +3,16 @@ using Penumbra.Interop.Structs; namespace Penumbra.Meta.Files; - -// EVP file structure: -// [Identifier:3 bytes, EVP] -// [NumModels:ushort] -// NumModels x [ModelId:ushort] -// Containing the relevant model IDs. Seems to be sorted. -// NumModels x [DataArray]:512 Byte] -// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet. -// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect. +/// +/// EVP file structure: +/// [Identifier:3 bytes, EVP] +/// [NumModels:ushort] +/// NumModels x [ModelId:ushort] +/// Containing the relevant model IDs. Seems to be sorted. +/// NumModels x [DataArray]:512 Byte] +/// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet. +/// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect. +/// public unsafe class EvpFile : MetaBaseFile { public const int FlagArraySize = 512; @@ -26,45 +27,39 @@ public unsafe class EvpFile : MetaBaseFile } public int NumModels - => Data[ 3 ]; + => Data[3]; - public ReadOnlySpan< ushort > ModelSetIds + public ReadOnlySpan ModelSetIds => new(Data + 4, NumModels); - public ushort ModelSetId( int idx ) - => idx >= 0 && idx < NumModels ? ( ( ushort* )( Data + 4 ) )[ idx ] : ushort.MaxValue; + public ushort ModelSetId(int idx) + => idx >= 0 && idx < NumModels ? ((ushort*)(Data + 4))[idx] : ushort.MaxValue; - public ReadOnlySpan< EvpFlag > Flags( int idx ) + public ReadOnlySpan Flags(int idx) => new(Data + 4 + idx * FlagArraySize, FlagArraySize); - public EvpFlag Flag( ushort modelSet, int arrayIndex ) + public EvpFlag Flag(ushort modelSet, int arrayIndex) { - if( arrayIndex is >= FlagArraySize or < 0 ) - { + if (arrayIndex is >= FlagArraySize or < 0) return EvpFlag.None; - } var ids = ModelSetIds; - for( var i = 0; i < ids.Length; ++i ) + for (var i = 0; i < ids.Length; ++i) { - var model = ids[ i ]; - if( model < modelSet ) - { + var model = ids[i]; + if (model < modelSet) continue; - } - if( model > modelSet ) - { + if (model > modelSet) break; - } - return Flags( i )[ arrayIndex ]; + return Flags(i)[arrayIndex]; } return EvpFlag.None; } - public EvpFile() - : base( ( MetaIndex )1 ) // TODO: Name + public EvpFile(MetaFileManager manager) + : base(manager, (MetaIndex)1) // TODO: Name { } -} \ No newline at end of file +} diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index 32a9c925..2dc60120 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -1,11 +1,10 @@ using System; using System.Numerics; -using Newtonsoft.Json; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; using Penumbra.Meta.Manipulations; -using Penumbra.Services; +using Penumbra.Services; using Penumbra.String.Classes; using Penumbra.String.Functions; @@ -16,7 +15,7 @@ public class ImcException : Exception public readonly ImcManipulation Manipulation; public readonly string GamePath; - public ImcException( ImcManipulation manip, Utf8GamePath path ) + public ImcException(ImcManipulation manip, Utf8GamePath path) { Manipulation = manip; GamePath = path.ToString(); @@ -34,44 +33,42 @@ public unsafe class ImcFile : MetaBaseFile private const int PreambleSize = 4; public int ActualLength - => NumParts * sizeof( ImcEntry ) * ( Count + 1 ) + PreambleSize; + => NumParts * sizeof(ImcEntry) * (Count + 1) + PreambleSize; public int Count - => CountInternal( Data ); + => CountInternal(Data); public readonly Utf8GamePath Path; public readonly int NumParts; - public ReadOnlySpan< ImcEntry > Span - => new(( ImcEntry* )( Data + PreambleSize ), ( Length - PreambleSize ) / sizeof( ImcEntry )); + public ReadOnlySpan Span + => new((ImcEntry*)(Data + PreambleSize), (Length - PreambleSize) / sizeof(ImcEntry)); - private static int CountInternal( byte* data ) - => *( ushort* )data; + private static int CountInternal(byte* data) + => *(ushort*)data; - private static ushort PartMask( byte* data ) - => *( ushort* )( data + 2 ); + private static ushort PartMask(byte* data) + => *(ushort*)(data + 2); - private static ImcEntry* VariantPtr( byte* data, int partIdx, int variantIdx ) + private static ImcEntry* VariantPtr(byte* data, int partIdx, int variantIdx) { var flag = 1 << partIdx; - if( ( PartMask( data ) & flag ) == 0 || variantIdx > CountInternal( data ) ) - { + if ((PartMask(data) & flag) == 0 || variantIdx > CountInternal(data)) return null; - } - var numParts = BitOperations.PopCount( PartMask( data ) ); - var ptr = ( ImcEntry* )( data + PreambleSize ); + var numParts = BitOperations.PopCount(PartMask(data)); + var ptr = (ImcEntry*)(data + PreambleSize); ptr += variantIdx * numParts + partIdx; return ptr; } - public ImcEntry GetEntry( int partIdx, int variantIdx ) + public ImcEntry GetEntry(int partIdx, int variantIdx) { - var ptr = VariantPtr( Data, partIdx, variantIdx ); + var ptr = VariantPtr(Data, partIdx, variantIdx); return ptr == null ? new ImcEntry() : *ptr; } - public static int PartIndex( EquipSlot slot ) + public static int PartIndex(EquipSlot slot) => slot switch { EquipSlot.Head => 0, @@ -87,52 +84,44 @@ public unsafe class ImcFile : MetaBaseFile _ => 0, }; - public bool EnsureVariantCount( int numVariants ) + public bool EnsureVariantCount(int numVariants) { - if( numVariants <= Count ) - { + if (numVariants <= Count) return true; - } var oldCount = Count; - *( ushort* )Data = ( ushort )numVariants; - if( ActualLength > Length ) + *(ushort*)Data = (ushort)numVariants; + if (ActualLength > Length) { - var newLength = ( ( ( ActualLength - 1 ) >> 7 ) + 1 ) << 7; - Penumbra.Log.Verbose( $"Resized IMC {Path} from {Length} to {newLength}." ); - ResizeResources( newLength ); + var newLength = (((ActualLength - 1) >> 7) + 1) << 7; + Penumbra.Log.Verbose($"Resized IMC {Path} from {Length} to {newLength}."); + ResizeResources(newLength); } - var defaultPtr = ( ImcEntry* )( Data + PreambleSize ); - for( var i = oldCount + 1; i < numVariants + 1; ++i ) - { - MemoryUtility.MemCpyUnchecked( defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof( ImcEntry ) ); - } + var defaultPtr = (ImcEntry*)(Data + PreambleSize); + for (var i = oldCount + 1; i < numVariants + 1; ++i) + MemoryUtility.MemCpyUnchecked(defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof(ImcEntry)); - Penumbra.Log.Verbose( $"Expanded IMC {Path} from {oldCount} to {numVariants} variants." ); + Penumbra.Log.Verbose($"Expanded IMC {Path} from {oldCount} to {numVariants} variants."); return true; } - public bool SetEntry( int partIdx, int variantIdx, ImcEntry entry ) + public bool SetEntry(int partIdx, int variantIdx, ImcEntry entry) { - if( partIdx >= NumParts ) + if (partIdx >= NumParts) + return false; + + EnsureVariantCount(variantIdx); + + var variantPtr = VariantPtr(Data, partIdx, variantIdx); + if (variantPtr == null) { + Penumbra.Log.Error("Error during expansion of imc file."); return false; } - EnsureVariantCount( variantIdx ); - - var variantPtr = VariantPtr( Data, partIdx, variantIdx ); - if( variantPtr == null ) - { - Penumbra.Log.Error( "Error during expansion of imc file." ); + if (variantPtr->Equals(entry)) return false; - } - - if( variantPtr->Equals( entry ) ) - { - return false; - } *variantPtr = entry; return true; @@ -141,70 +130,64 @@ public unsafe class ImcFile : MetaBaseFile public override void Reset() { - var file = DalamudServices.SGameData.GetFile( Path.ToString() ); - fixed( byte* ptr = file!.Data ) + var file = DalamudServices.SGameData.GetFile(Path.ToString()); + fixed (byte* ptr = file!.Data) { - MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length ); - MemoryUtility.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length ); + MemoryUtility.MemCpyUnchecked(Data, ptr, file.Data.Length); + MemoryUtility.MemSet(Data + file.Data.Length, 0, Length - file.Data.Length); } } - public ImcFile( ImcManipulation manip ) - : base( 0 ) + public ImcFile(MetaFileManager manager, ImcManipulation manip) + : base(manager, 0) { Path = manip.GamePath(); - var file = DalamudServices.SGameData.GetFile( Path.ToString() ); - if( file == null ) - { - throw new ImcException( manip, Path ); - } + var file = manager.GameData.GetFile(Path.ToString()); + if (file == null) + throw new ImcException(manip, Path); - fixed( byte* ptr = file.Data ) + fixed (byte* ptr = file.Data) { - NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) ); - AllocateData( file.Data.Length ); - MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length ); + NumParts = BitOperations.PopCount(*(ushort*)(ptr + 2)); + AllocateData(file.Data.Length); + MemoryUtility.MemCpyUnchecked(Data, ptr, file.Data.Length); } } - public static ImcEntry GetDefault( Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists ) - => GetDefault( path.ToString(), slot, variantIdx, out exists ); + public static ImcEntry GetDefault(MetaFileManager manager, Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists) + => GetDefault(manager, path.ToString(), slot, variantIdx, out exists); - public static ImcEntry GetDefault( string path, EquipSlot slot, int variantIdx, out bool exists ) + public static ImcEntry GetDefault(MetaFileManager manager, string path, EquipSlot slot, int variantIdx, out bool exists) { - var file = DalamudServices.SGameData.GetFile( path ); + var file = manager.GameData.GetFile(path); exists = false; - if( file == null ) - { + if (file == null) throw new Exception(); - } - fixed( byte* ptr = file.Data ) + fixed (byte* ptr = file.Data) { - var entry = VariantPtr( ptr, PartIndex( slot ), variantIdx ); - if( entry != null ) - { - exists = true; - return *entry; - } + var entry = VariantPtr(ptr, PartIndex(slot), variantIdx); + if (entry == null) + return new ImcEntry(); - return new ImcEntry(); + exists = true; + return *entry; } } - public void Replace( ResourceHandle* resource ) + public void Replace(ResourceHandle* resource) { var (data, length) = resource->GetData(); - var newData = Penumbra.MetaFileManager.AllocateDefaultMemory( ActualLength, 8 ); - if( newData == null ) + var newData = Penumbra.MetaFileManager.AllocateDefaultMemory(ActualLength, 8); + if (newData == null) { - Penumbra.Log.Error( $"Could not replace loaded IMC data at 0x{( ulong )resource:X}, allocation failed." ); + Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed."); return; } - MemoryUtility.MemCpyUnchecked( newData, Data, ActualLength ); + MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength); - Penumbra.MetaFileManager.Free( data, length ); - resource->SetData( ( IntPtr )newData, ActualLength ); + Penumbra.MetaFileManager.Free(data, length); + resource->SetData((IntPtr)newData, ActualLength); } -} \ No newline at end of file +} diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index 4307a78d..46e87567 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -8,79 +8,78 @@ namespace Penumbra.Meta.Files; public unsafe class MetaBaseFile : IDisposable { - public byte* Data { get; private set; } - public int Length { get; private set; } - public CharacterUtility.InternalIndex Index { get; } + protected readonly MetaFileManager Manager; - public MetaBaseFile( MetaIndex idx ) - => Index = CharacterUtility.ReverseIndices[ ( int )idx ]; + public byte* Data { get; private set; } + public int Length { get; private set; } + public CharacterUtility.InternalIndex Index { get; } + + public MetaBaseFile(MetaFileManager manager, MetaIndex idx) + { + Manager = manager; + Index = CharacterUtility.ReverseIndices[(int)idx]; + } protected (IntPtr Data, int Length) DefaultData - => Penumbra.CharacterUtility.DefaultResource( Index ); + => Manager.CharacterUtility.DefaultResource(Index); - // Reset to default values. + /// Reset to default values. public virtual void Reset() { } - // Obtain memory. - protected void AllocateData( int length ) + /// Obtain memory. + protected void AllocateData(int length) { Length = length; - Data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( length ); - if( length > 0 ) - { - GC.AddMemoryPressure( length ); - } + Data = (byte*)Manager.AllocateFileMemory(length); + if (length > 0) + GC.AddMemoryPressure(length); } - // Free memory. + /// Free memory. protected void ReleaseUnmanagedResources() { - var ptr = ( IntPtr )Data; - MemoryHelper.GameFree( ref ptr, ( ulong )Length ); - if( Length > 0 ) - { - GC.RemoveMemoryPressure( Length ); - } + var ptr = (IntPtr)Data; + MemoryHelper.GameFree(ref ptr, (ulong)Length); + if (Length > 0) + GC.RemoveMemoryPressure(Length); Length = 0; Data = null; } - // Resize memory while retaining data. - protected void ResizeResources( int newLength ) + /// Resize memory while retaining data. + protected void ResizeResources(int newLength) { - if( newLength == Length ) - { + if (newLength == Length) return; - } - var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength ); - if( newLength > Length ) + var data = (byte*)Manager.AllocateFileMemory((ulong)newLength); + if (newLength > Length) { - MemoryUtility.MemCpyUnchecked( data, Data, Length ); - MemoryUtility.MemSet( data + Length, 0, newLength - Length ); + MemoryUtility.MemCpyUnchecked(data, Data, Length); + MemoryUtility.MemSet(data + Length, 0, newLength - Length); } else { - MemoryUtility.MemCpyUnchecked( data, Data, newLength ); + MemoryUtility.MemCpyUnchecked(data, Data, newLength); } ReleaseUnmanagedResources(); - GC.AddMemoryPressure( newLength ); + GC.AddMemoryPressure(newLength); Data = data; Length = newLength; } - // Manually free memory. + /// Manually free memory. public void Dispose() { ReleaseUnmanagedResources(); - GC.SuppressFinalize( this ); + GC.SuppressFinalize(this); } ~MetaBaseFile() { ReleaseUnmanagedResources(); } -} \ No newline at end of file +} diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs new file mode 100644 index 00000000..654760ca --- /dev/null +++ b/Penumbra/Meta/MetaFileManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Runtime.CompilerServices; +using Dalamud.Data; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.System.Memory; +using Penumbra.Collections; +using Penumbra.Collections.Manager; +using Penumbra.GameData; +using Penumbra.Interop.Services; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager; + +namespace Penumbra.Meta; + +public unsafe class MetaFileManager +{ + internal readonly Configuration Config; + internal readonly CharacterUtility CharacterUtility; + internal readonly ResidentResourceManager ResidentResources; + internal readonly DataManager GameData; + internal readonly ActiveCollections ActiveCollections; + internal readonly ValidityChecker ValidityChecker; + + public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, DataManager gameData, + ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker) + { + CharacterUtility = characterUtility; + ResidentResources = residentResources; + GameData = gameData; + ActiveCollections = activeCollections; + Config = config; + ValidityChecker = validityChecker; + SignatureHelper.Initialise(this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void SetFile(MetaBaseFile? file, MetaIndex metaIndex) + { + if (file == null) + CharacterUtility.ResetResource(metaIndex); + else + CharacterUtility.SetResource(metaIndex, (nint)file.Data, file.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public CharacterUtility.MetaList.MetaReverter TemporarilySetFile(MetaBaseFile? file, MetaIndex metaIndex) + => file == null + ? CharacterUtility.TemporarilyResetResource(metaIndex) + : CharacterUtility.TemporarilySetResource(metaIndex, (nint)file.Data, file.Length); + + public void ApplyDefaultFiles(ModCollection collection) + { + if (ActiveCollections.Default != collection || !CharacterUtility.Ready || !Config.EnableMods) + return; + + ResidentResources.Reload(); + collection._cache?.MetaManipulations.SetFiles(); + } + + /// + /// Allocate in the games space for file storage. + /// We only need this if using any meta file. + /// + [Signature(Sigs.GetFileSpace)] + private readonly nint _getFileSpaceAddress = nint.Zero; + + public IMemorySpace* GetFileSpace() + => ((delegate* unmanaged)_getFileSpaceAddress)(); + + public void* AllocateFileMemory(ulong length, ulong alignment = 0) + => GetFileSpace()->Malloc(length, alignment); + + public void* AllocateFileMemory(int length, int alignment = 0) + => AllocateFileMemory((ulong)length, (ulong)alignment); + + public void* AllocateDefaultMemory(ulong length, ulong alignment = 0) + => GetFileSpace()->Malloc(length, alignment); + + public void* AllocateDefaultMemory(int length, int alignment = 0) + => IMemorySpace.GetDefaultSpace()->Malloc((ulong)length, (ulong)alignment); + + public void Free(nint ptr, int length) + => IMemorySpace.Free((void*)ptr, (ulong)length); +} diff --git a/Penumbra/Mods/ItemSwap/CustomizationSwap.cs b/Penumbra/Mods/ItemSwap/CustomizationSwap.cs index 9b346c00..a8aec374 100644 --- a/Penumbra/Mods/ItemSwap/CustomizationSwap.cs +++ b/Penumbra/Mods/ItemSwap/CustomizationSwap.cs @@ -5,6 +5,7 @@ using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; +using Penumbra.Meta; using Penumbra.String.Classes; namespace Penumbra.Mods.ItemSwap; @@ -12,7 +13,7 @@ namespace Penumbra.Mods.ItemSwap; public static class CustomizationSwap { /// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode. - public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo ) + public static FileSwap CreateMdl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo ) { if( idFrom.Value > byte.MaxValue ) { @@ -22,7 +23,7 @@ public static class CustomizationSwap var mdlPathFrom = GamePaths.Character.Mdl.Path( race, slot, idFrom, slot.ToCustomizationType() ); var mdlPathTo = GamePaths.Character.Mdl.Path( race, slot, idTo, slot.ToCustomizationType() ); - var mdl = FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo ); + var mdl = FileSwap.CreateSwap( manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo ); var range = slot == BodySlot.Tail && race is GenderRace.HrothgarMale or GenderRace.HrothgarFemale or GenderRace.HrothgarMaleNpc or GenderRace.HrothgarMaleNpc ? 5 : 1; foreach( ref var materialFileName in mdl.AsMdl()!.Materials.AsSpan() ) @@ -31,7 +32,7 @@ public static class CustomizationSwap foreach( var variant in Enumerable.Range( 1, range ) ) { name = materialFileName; - var mtrl = CreateMtrl( redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged ); + var mtrl = CreateMtrl( manager, redirections, slot, race, idFrom, idTo, ( byte )variant, ref name, ref mdl.DataWasChanged ); mdl.ChildSwaps.Add( mtrl ); } @@ -41,7 +42,7 @@ public static class CustomizationSwap return mdl; } - public static FileSwap CreateMtrl( Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, byte variant, + public static FileSwap CreateMtrl( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, SetId idTo, byte variant, ref string fileName, ref bool dataWasChanged ) { variant = slot is BodySlot.Face or BodySlot.Zear ? byte.MaxValue : variant; @@ -62,20 +63,20 @@ public static class CustomizationSwap dataWasChanged = true; } - var mtrl = FileSwap.CreateSwap( ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath ); - var shpk = CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged ); + var mtrl = FileSwap.CreateSwap( manager, ResourceType.Mtrl, redirections, actualMtrlFromPath, mtrlToPath, actualMtrlFromPath ); + var shpk = CreateShader( manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged ); mtrl.ChildSwaps.Add( shpk ); foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() ) { - var tex = CreateTex( redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged ); + var tex = CreateTex( manager, redirections, slot, race, idFrom, ref texture, ref mtrl.DataWasChanged ); mtrl.ChildSwaps.Add( tex ); } return mtrl; } - public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture, + public static FileSwap CreateTex( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, BodySlot slot, GenderRace race, SetId idFrom, ref MtrlFile.Texture texture, ref bool dataWasChanged ) { var path = texture.Path; @@ -99,13 +100,13 @@ public static class CustomizationSwap dataWasChanged = true; } - return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, path ); + return FileSwap.CreateSwap( manager, ResourceType.Tex, redirections, newPath, path, path ); } - public static FileSwap CreateShader( Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged ) + public static FileSwap CreateShader( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged ) { var path = $"shader/sm5/shpk/{shaderName}"; - return FileSwap.CreateSwap( ResourceType.Shpk, redirections, path, path ); + return FileSwap.CreateSwap( manager, ResourceType.Shpk, redirections, path, path ); } } \ No newline at end of file diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index 1f1cecfb..443376a6 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -10,6 +10,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; @@ -18,41 +19,51 @@ namespace Penumbra.Mods.ItemSwap; public static class EquipmentSwap { - private static EquipSlot[] ConvertSlots( EquipSlot slot, bool rFinger, bool lFinger ) + private static EquipSlot[] ConvertSlots(EquipSlot slot, bool rFinger, bool lFinger) { - if( slot != EquipSlot.RFinger ) - { - return new[] { slot }; - } + if (slot != EquipSlot.RFinger) + return new[] + { + slot, + }; return rFinger ? lFinger - ? new[] { EquipSlot.RFinger, EquipSlot.LFinger } - : new[] { EquipSlot.RFinger } + ? new[] + { + EquipSlot.RFinger, + EquipSlot.LFinger, + } + : new[] + { + EquipSlot.RFinger, + } : lFinger - ? new[] { EquipSlot.LFinger } - : Array.Empty< EquipSlot >(); + ? new[] + { + EquipSlot.LFinger, + } + : Array.Empty(); } - public static Item[] CreateTypeSwap( IObjectIdentifier identifier, List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, - EquipSlot slotFrom, Item itemFrom, EquipSlot slotTo, Item itemTo ) + public static Item[] CreateTypeSwap(MetaFileManager manager, IObjectIdentifier identifier, List swaps, + Func redirections, Func manips, + EquipSlot slotFrom, Item itemFrom, EquipSlot slotTo, Item itemTo) { - LookupItem( itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom ); - LookupItem( itemTo, out var actualSlotTo, out var idTo, out var variantTo ); - if( actualSlotFrom != slotFrom.ToSlot() || actualSlotTo != slotTo.ToSlot() ) - { + LookupItem(itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom); + LookupItem(itemTo, out var actualSlotTo, out var idTo, out var variantTo); + if (actualSlotFrom != slotFrom.ToSlot() || actualSlotTo != slotTo.ToSlot()) throw new ItemSwap.InvalidItemTypeException(); - } - var ( imcFileFrom, variants, affectedItems ) = GetVariants( identifier, slotFrom, idFrom, idTo, variantFrom ); - var imcManip = new ImcManipulation( slotTo, variantTo, idTo.Value, default ); - var imcFileTo = new ImcFile( imcManip ); + var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom); + var imcManip = new ImcManipulation(slotTo, variantTo, idTo.Value, default); + var imcFileTo = new ImcFile(manager, imcManip); var skipFemale = false; var skipMale = false; - var mtrlVariantTo = manips( imcManip.Copy( imcFileTo.GetEntry( ImcFile.PartIndex( slotTo ), variantTo ) ) ).Imc.Entry.MaterialId; - foreach( var gr in Enum.GetValues< GenderRace >() ) + var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo))).Imc.Entry.MaterialId; + foreach (var gr in Enum.GetValues()) { - switch( gr.Split().Item1 ) + switch (gr.Split().Item1) { case Gender.Male when skipMale: continue; case Gender.Female when skipFemale: continue; @@ -60,22 +71,18 @@ public static class EquipmentSwap case Gender.FemaleNpc when skipFemale: continue; } - if( CharacterUtilityData.EqdpIdx( gr, true ) < 0 ) - { + if (CharacterUtilityData.EqdpIdx(gr, true) < 0) continue; - } try { - var eqdp = CreateEqdp( redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo ); - if( eqdp != null ) - { - swaps.Add( eqdp ); - } + var eqdp = CreateEqdp(manager, redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo); + if (eqdp != null) + swaps.Add(eqdp); } - catch( ItemSwap.MissingFileException e ) + catch (ItemSwap.MissingFileException e) { - switch( gr ) + switch (gr) { case GenderRace.MidlanderMale when e.Type == ResourceType.Mdl: skipMale = true; @@ -88,59 +95,54 @@ public static class EquipmentSwap } } - foreach( var variant in variants ) + foreach (var variant in variants) { - var imc = CreateImc( redirections, manips, slotFrom, slotTo, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo ); - swaps.Add( imc ); + var imc = CreateImc(manager, redirections, manips, slotFrom, slotTo, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo); + swaps.Add(imc); } return affectedItems; } - public static Item[] CreateItemSwap( IObjectIdentifier identifier, List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, Item itemFrom, - Item itemTo, bool rFinger = true, bool lFinger = true ) + public static Item[] CreateItemSwap(MetaFileManager manager, IObjectIdentifier identifier, List swaps, + Func redirections, Func manips, Item itemFrom, + Item itemTo, bool rFinger = true, bool lFinger = true) { // Check actual ids, variants and slots. We only support using the same slot. - LookupItem( itemFrom, out var slotFrom, out var idFrom, out var variantFrom ); - LookupItem( itemTo, out var slotTo, out var idTo, out var variantTo ); - if( slotFrom != slotTo ) - { + LookupItem(itemFrom, out var slotFrom, out var idFrom, out var variantFrom); + LookupItem(itemTo, out var slotTo, out var idTo, out var variantTo); + if (slotFrom != slotTo) throw new ItemSwap.InvalidItemTypeException(); - } - var eqp = CreateEqp( manips, slotFrom, idFrom, idTo ); - if( eqp != null ) - { - swaps.Add( eqp ); - } + var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo); + if (eqp != null) + swaps.Add(eqp); - var gmp = CreateGmp( manips, slotFrom, idFrom, idTo ); - if( gmp != null ) - { - swaps.Add( gmp ); - } + var gmp = CreateGmp(manager, manips, slotFrom, idFrom, idTo); + if (gmp != null) + swaps.Add(gmp); - var affectedItems = Array.Empty< Item >(); - foreach( var slot in ConvertSlots( slotFrom, rFinger, lFinger ) ) + var affectedItems = Array.Empty(); + foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger)) { - ( var imcFileFrom, var variants, affectedItems ) = GetVariants( identifier, slot, idFrom, idTo, variantFrom ); - var imcManip = new ImcManipulation( slot, variantTo, idTo.Value, default ); - var imcFileTo = new ImcFile( imcManip ); + (var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom); + var imcManip = new ImcManipulation(slot, variantTo, idTo.Value, default); + var imcFileTo = new ImcFile(manager, imcManip); var isAccessory = slot.IsAccessory(); var estType = slot switch { EquipSlot.Head => EstManipulation.EstType.Head, EquipSlot.Body => EstManipulation.EstType.Body, - _ => ( EstManipulation.EstType )0, + _ => (EstManipulation.EstType)0, }; var skipFemale = false; var skipMale = false; - var mtrlVariantTo = manips( imcManip.Copy( imcFileTo.GetEntry( ImcFile.PartIndex( slot ), variantTo ) ) ).Imc.Entry.MaterialId; - foreach( var gr in Enum.GetValues< GenderRace >() ) + var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slot), variantTo))).Imc.Entry.MaterialId; + foreach (var gr in Enum.GetValues()) { - switch( gr.Split().Item1 ) + switch (gr.Split().Item1) { case Gender.Male when skipMale: continue; case Gender.Female when skipFemale: continue; @@ -148,30 +150,24 @@ public static class EquipmentSwap case Gender.FemaleNpc when skipFemale: continue; } - if( CharacterUtilityData.EqdpIdx( gr, isAccessory ) < 0 ) - { + if (CharacterUtilityData.EqdpIdx(gr, isAccessory) < 0) continue; - } try { - var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo ); - if( eqdp != null ) - { - swaps.Add( eqdp ); - } + var eqdp = CreateEqdp(manager, redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo); + if (eqdp != null) + swaps.Add(eqdp); - var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits( slot ).Item2 ?? false; - var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo, ownMdl ); - if( est != null ) - { - swaps.Add( est ); - } + var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits(slot).Item2 ?? false; + var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl); + if (est != null) + swaps.Add(est); } - catch( ItemSwap.MissingFileException e ) + catch (ItemSwap.MissingFileException e) { - switch( gr ) + switch (gr) { case GenderRace.MidlanderMale when e.Type == ResourceType.Mdl: skipMale = true; @@ -184,33 +180,38 @@ public static class EquipmentSwap } } - foreach( var variant in variants ) + foreach (var variant in variants) { - var imc = CreateImc( redirections, manips, slot, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo ); - swaps.Add( imc ); + var imc = CreateImc(manager, redirections, manips, slot, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo); + swaps.Add(imc); } } return affectedItems; } - public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, GenderRace gr, SetId idFrom, - SetId idTo, byte mtrlTo ) - => CreateEqdp( redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo ); - public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom, - SetId idTo, byte mtrlTo ) + public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, + Func manips, EquipSlot slot, GenderRace gr, SetId idFrom, + SetId idTo, byte mtrlTo) + => CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo); + + public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, + Func manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom, + SetId idTo, byte mtrlTo) { var (gender, race) = gr.Split(); - var eqdpFrom = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, slotFrom.IsAccessory(), idFrom.Value ), slotFrom, gender, race, idFrom.Value ); - var eqdpTo = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, slotTo.IsAccessory(), idTo.Value ), slotTo, gender, race, idTo.Value ); - var meta = new MetaSwap( manips, eqdpFrom, eqdpTo ); - var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits( slotFrom ); - if( ownMdl ) + var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom.Value), slotFrom, gender, + race, idFrom.Value); + var eqdpTo = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotTo.IsAccessory(), idTo.Value), slotTo, gender, race, + idTo.Value); + var meta = new MetaSwap(manips, eqdpFrom, eqdpTo); + var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits(slotFrom); + if (ownMdl) { - var mdl = CreateMdl( redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo ); - meta.ChildSwaps.Add( mdl ); + var mdl = CreateMdl(manager, redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo); + meta.ChildSwaps.Add(mdl); } - else if( !ownMtrl && meta.SwapAppliedIsDefault ) + else if (!ownMtrl && meta.SwapAppliedIsDefault) { meta = null; } @@ -218,97 +219,98 @@ public static class EquipmentSwap return meta; } - public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo ) - => CreateMdl( redirections, slot, slot, gr, idFrom, idTo, mtrlTo ); + public static FileSwap CreateMdl(MetaFileManager manager, Func redirections, EquipSlot slot, GenderRace gr, + SetId idFrom, SetId idTo, byte mtrlTo) + => CreateMdl(manager, redirections, slot, slot, gr, idFrom, idTo, mtrlTo); - public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo ) + public static FileSwap CreateMdl(MetaFileManager manager, Func redirections, EquipSlot slotFrom, EquipSlot slotTo, + GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo) { - var mdlPathFrom = slotFrom.IsAccessory() ? GamePaths.Accessory.Mdl.Path( idFrom, gr, slotFrom ) : GamePaths.Equipment.Mdl.Path( idFrom, gr, slotFrom ); - var mdlPathTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mdl.Path( idTo, gr, slotTo ) : GamePaths.Equipment.Mdl.Path( idTo, gr, slotTo ); - var mdl = FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo ); + var mdlPathFrom = slotFrom.IsAccessory() + ? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom) + : GamePaths.Equipment.Mdl.Path(idFrom, gr, slotFrom); + var mdlPathTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mdl.Path(idTo, gr, slotTo) : GamePaths.Equipment.Mdl.Path(idTo, gr, slotTo); + var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo); - foreach( ref var fileName in mdl.AsMdl()!.Materials.AsSpan() ) + foreach (ref var fileName in mdl.AsMdl()!.Materials.AsSpan()) { - var mtrl = CreateMtrl( redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged ); - if( mtrl != null ) - { - mdl.ChildSwaps.Add( mtrl ); - } + var mtrl = CreateMtrl(manager, redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged); + if (mtrl != null) + mdl.ChildSwaps.Add(mtrl); } return mdl; } - private static void LookupItem( Item i, out EquipSlot slot, out SetId modelId, out byte variant ) + private static void LookupItem(Item i, out EquipSlot slot, out SetId modelId, out byte variant) { - slot = ( ( EquipSlot )i.EquipSlotCategory.Row ).ToSlot(); - if( !slot.IsEquipmentPiece() ) - { + slot = ((EquipSlot)i.EquipSlotCategory.Row).ToSlot(); + if (!slot.IsEquipmentPiece()) throw new ItemSwap.InvalidItemTypeException(); - } - modelId = ( ( Quad )i.ModelMain ).A; - variant = ( byte )( ( Quad )i.ModelMain ).B; + modelId = ((Quad)i.ModelMain).A; + variant = (byte)((Quad)i.ModelMain).B; } - private static (ImcFile, byte[], Item[]) GetVariants( IObjectIdentifier identifier, EquipSlot slotFrom, SetId idFrom, SetId idTo, byte variantFrom ) + private static (ImcFile, byte[], Item[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom, + SetId idFrom, SetId idTo, byte variantFrom) { - var entry = new ImcManipulation( slotFrom, variantFrom, idFrom.Value, default ); - var imc = new ImcFile( entry ); + var entry = new ImcManipulation(slotFrom, variantFrom, idFrom.Value, default); + var imc = new ImcFile(manager, entry); Item[] items; byte[] variants; - if( idFrom.Value == idTo.Value ) + if (idFrom.Value == idTo.Value) { - items = identifier.Identify( idFrom, variantFrom, slotFrom ).ToArray(); - variants = new[] { variantFrom }; + items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray(); + variants = new[] + { + variantFrom, + }; } else { - items = identifier.Identify( slotFrom.IsEquipment() - ? GamePaths.Equipment.Mdl.Path( idFrom, GenderRace.MidlanderMale, slotFrom ) - : GamePaths.Accessory.Mdl.Path( idFrom, GenderRace.MidlanderMale, slotFrom ) ).Select( kvp => kvp.Value ).OfType< Item >().ToArray(); - variants = Enumerable.Range( 0, imc.Count + 1 ).Select( i => ( byte )i ).ToArray(); + items = identifier.Identify(slotFrom.IsEquipment() + ? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom) + : GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType().ToArray(); + variants = Enumerable.Range(0, imc.Count + 1).Select(i => (byte)i).ToArray(); } - return ( imc, variants, items ); + return (imc, variants, items); } - public static MetaSwap? CreateGmp( Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo ) + public static MetaSwap? CreateGmp(MetaFileManager manager, Func manips, EquipSlot slot, SetId idFrom, + SetId idTo) { - if( slot is not EquipSlot.Head ) - { + if (slot is not EquipSlot.Head) return null; - } - var manipFrom = new GmpManipulation( ExpandedGmpFile.GetDefault( idFrom.Value ), idFrom.Value ); - var manipTo = new GmpManipulation( ExpandedGmpFile.GetDefault( idTo.Value ), idTo.Value ); - return new MetaSwap( manips, manipFrom, manipTo ); + var manipFrom = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idFrom.Value), idFrom.Value); + var manipTo = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idTo.Value), idTo.Value); + return new MetaSwap(manips, manipFrom, manipTo); } - public static MetaSwap CreateImc( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo, - byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo ) - => CreateImc( redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo ); + public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, Func manips, EquipSlot slot, + SetId idFrom, SetId idTo, + byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) + => CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo); - public static MetaSwap CreateImc( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, - byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo ) + public static MetaSwap CreateImc(MetaFileManager manager, Func redirections, Func manips, + EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, + byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) { - var entryFrom = imcFileFrom.GetEntry( ImcFile.PartIndex( slotFrom ), variantFrom ); - var entryTo = imcFileTo.GetEntry( ImcFile.PartIndex( slotTo ), variantTo ); - var manipulationFrom = new ImcManipulation( slotFrom, variantFrom, idFrom.Value, entryFrom ); - var manipulationTo = new ImcManipulation( slotTo, variantTo, idTo.Value, entryTo ); - var imc = new MetaSwap( manips, manipulationFrom, manipulationTo ); + var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); + var entryTo = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); + var manipulationFrom = new ImcManipulation(slotFrom, variantFrom, idFrom.Value, entryFrom); + var manipulationTo = new ImcManipulation(slotTo, variantTo, idTo.Value, entryTo); + var imc = new MetaSwap(manips, manipulationFrom, manipulationTo); - var decal = CreateDecal( redirections, imc.SwapToModded.Imc.Entry.DecalId ); - if( decal != null ) - { - imc.ChildSwaps.Add( decal ); - } + var decal = CreateDecal(manager, redirections, imc.SwapToModded.Imc.Entry.DecalId); + if (decal != null) + imc.ChildSwaps.Add(decal); - var avfx = CreateAvfx( redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId ); - if( avfx != null ) - { - imc.ChildSwaps.Add( avfx ); - } + var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId); + if (avfx != null) + imc.ChildSwaps.Add(avfx); // IMC also controls sound, Example: Dodore Doublet, but unknown what it does? // IMC also controls some material animation, Example: The Howling Spirit and The Wailing Spirit, but unknown what it does. @@ -316,134 +318,135 @@ public static class EquipmentSwap } // Example: Crimson Standard Bracelet - public static FileSwap? CreateDecal( Func< Utf8GamePath, FullPath > redirections, byte decalId ) + public static FileSwap? CreateDecal(MetaFileManager manager, Func redirections, byte decalId) { - if( decalId == 0 ) - { + if (decalId == 0) return null; - } - var decalPath = GamePaths.Equipment.Decal.Path( decalId ); - return FileSwap.CreateSwap( ResourceType.Tex, redirections, decalPath, decalPath ); + var decalPath = GamePaths.Equipment.Decal.Path(decalId); + return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, decalPath, decalPath); } // Example: Abyssos Helm / Body - public static FileSwap? CreateAvfx( Func< Utf8GamePath, FullPath > redirections, SetId idFrom, SetId idTo, byte vfxId ) + public static FileSwap? CreateAvfx(MetaFileManager manager, Func redirections, SetId idFrom, SetId idTo, byte vfxId) { - if( vfxId == 0 ) - { + if (vfxId == 0) return null; - } - var vfxPathFrom = GamePaths.Equipment.Avfx.Path( idFrom.Value, vfxId ); - var vfxPathTo = GamePaths.Equipment.Avfx.Path( idTo.Value, vfxId ); - var avfx = FileSwap.CreateSwap( ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo ); + var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom.Value, vfxId); + var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo.Value, vfxId); + var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); - foreach( ref var filePath in avfx.AsAvfx()!.Textures.AsSpan() ) + foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan()) { - var atex = CreateAtex( redirections, ref filePath, ref avfx.DataWasChanged ); - avfx.ChildSwaps.Add( atex ); + var atex = CreateAtex(manager, redirections, ref filePath, ref avfx.DataWasChanged); + avfx.ChildSwaps.Add(atex); } return avfx; } - public static MetaSwap? CreateEqp( Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo ) + public static MetaSwap? CreateEqp(MetaFileManager manager, Func manips, EquipSlot slot, SetId idFrom, + SetId idTo) { - if( slot.IsAccessory() ) - { + if (slot.IsAccessory()) return null; - } - var eqpValueFrom = ExpandedEqpFile.GetDefault( idFrom.Value ); - var eqpValueTo = ExpandedEqpFile.GetDefault( idTo.Value ); - var eqpFrom = new EqpManipulation( eqpValueFrom, slot, idFrom.Value ); - var eqpTo = new EqpManipulation( eqpValueTo, slot, idFrom.Value ); - return new MetaSwap( manips, eqpFrom, eqpTo ); + var eqpValueFrom = ExpandedEqpFile.GetDefault(manager, idFrom.Value); + var eqpValueTo = ExpandedEqpFile.GetDefault(manager, idTo.Value); + var eqpFrom = new EqpManipulation(eqpValueFrom, slot, idFrom.Value); + var eqpTo = new EqpManipulation(eqpValueTo, slot, idFrom.Value); + return new MetaSwap(manips, eqpFrom, eqpTo); } - public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, SetId idFrom, SetId idTo, byte variantTo, ref string fileName, - ref bool dataWasChanged ) - => CreateMtrl( redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged ); + public static FileSwap? CreateMtrl(MetaFileManager manager, Func redirections, EquipSlot slot, SetId idFrom, + SetId idTo, byte variantTo, ref string fileName, + ref bool dataWasChanged) + => CreateMtrl(manager, redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged); - public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, byte variantTo, ref string fileName, - ref bool dataWasChanged ) + public static FileSwap? CreateMtrl(MetaFileManager manager, Func redirections, EquipSlot slotFrom, EquipSlot slotTo, + SetId idFrom, SetId idTo, byte variantTo, ref string fileName, + ref bool dataWasChanged) { var prefix = slotTo.IsAccessory() ? 'a' : 'e'; - if( !fileName.Contains( $"{prefix}{idTo.Value:D4}" ) ) - { + if (!fileName.Contains($"{prefix}{idTo.Value:D4}")) return null; - } - var folderTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idTo, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idTo, variantTo ); - var pathTo = $"{folderTo}{fileName}"; + var folderTo = slotTo.IsAccessory() + ? GamePaths.Accessory.Mtrl.FolderPath(idTo, variantTo) + : GamePaths.Equipment.Mtrl.FolderPath(idTo, variantTo); + var pathTo = $"{folderTo}{fileName}"; - var folderFrom = slotFrom.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idFrom, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idFrom, variantTo ); - var newFileName = ItemSwap.ReplaceId( fileName, prefix, idTo, idFrom ); - newFileName = ItemSwap.ReplaceSlot( newFileName, slotTo, slotFrom, slotTo != slotFrom ); - var pathFrom = $"{folderFrom}{newFileName}"; + var folderFrom = slotFrom.IsAccessory() + ? GamePaths.Accessory.Mtrl.FolderPath(idFrom, variantTo) + : GamePaths.Equipment.Mtrl.FolderPath(idFrom, variantTo); + var newFileName = ItemSwap.ReplaceId(fileName, prefix, idTo, idFrom); + newFileName = ItemSwap.ReplaceSlot(newFileName, slotTo, slotFrom, slotTo != slotFrom); + var pathFrom = $"{folderFrom}{newFileName}"; - if( newFileName != fileName ) + if (newFileName != fileName) { fileName = newFileName; dataWasChanged = true; } - var mtrl = FileSwap.CreateSwap( ResourceType.Mtrl, redirections, pathFrom, pathTo ); - var shpk = CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged ); - mtrl.ChildSwaps.Add( shpk ); + var mtrl = FileSwap.CreateSwap(manager, ResourceType.Mtrl, redirections, pathFrom, pathTo); + var shpk = CreateShader(manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged); + mtrl.ChildSwaps.Add(shpk); - foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() ) + foreach (ref var texture in mtrl.AsMtrl()!.Textures.AsSpan()) { - var tex = CreateTex( redirections, prefix, slotFrom, slotTo, idFrom, idTo, ref texture, ref mtrl.DataWasChanged ); - mtrl.ChildSwaps.Add( tex ); + var tex = CreateTex(manager, redirections, prefix, slotFrom, slotTo, idFrom, idTo, ref texture, ref mtrl.DataWasChanged); + mtrl.ChildSwaps.Add(tex); } return mtrl; } - public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, char prefix, SetId idFrom, SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged ) - => CreateTex( redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged ); + public static FileSwap CreateTex(MetaFileManager manager, Func redirections, char prefix, SetId idFrom, SetId idTo, + ref MtrlFile.Texture texture, ref bool dataWasChanged) + => CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged); - public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, char prefix, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged ) + public static FileSwap CreateTex(MetaFileManager manager, Func redirections, char prefix, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, + SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged) { var path = texture.Path; var addedDashes = false; - if( texture.DX11 ) + if (texture.DX11) { - var fileName = Path.GetFileName( path ); - if( !fileName.StartsWith( "--" ) ) + var fileName = Path.GetFileName(path); + if (!fileName.StartsWith("--")) { - path = path.Replace( fileName, $"--{fileName}" ); + path = path.Replace(fileName, $"--{fileName}"); addedDashes = true; } } - var newPath = ItemSwap.ReplaceAnyId( path, prefix, idFrom ); - newPath = ItemSwap.ReplaceSlot( newPath, slotTo, slotFrom, slotTo != slotFrom ); - newPath = ItemSwap.AddSuffix( newPath, ".tex", $"_{Path.GetFileName( texture.Path ).GetStableHashCode():x8}" ); - if( newPath != path ) + var newPath = ItemSwap.ReplaceAnyId(path, prefix, idFrom); + newPath = ItemSwap.ReplaceSlot(newPath, slotTo, slotFrom, slotTo != slotFrom); + newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}"); + if (newPath != path) { - texture.Path = addedDashes ? newPath.Replace( "--", string.Empty ) : newPath; + texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath; dataWasChanged = true; } - return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, path ); + return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path); } - public static FileSwap CreateShader( Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged ) + public static FileSwap CreateShader(MetaFileManager manager, Func redirections, ref string shaderName, ref bool dataWasChanged) { var path = $"shader/sm5/shpk/{shaderName}"; - return FileSwap.CreateSwap( ResourceType.Shpk, redirections, path, path ); + return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path); } - public static FileSwap CreateAtex( Func< Utf8GamePath, FullPath > redirections, ref string filePath, ref bool dataWasChanged ) + public static FileSwap CreateAtex(MetaFileManager manager, Func redirections, ref string filePath, ref bool dataWasChanged) { var oldPath = filePath; - filePath = ItemSwap.AddSuffix( filePath, ".atex", $"_{Path.GetFileName( filePath ).GetStableHashCode():x8}" ); + filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}"); dataWasChanged = true; - return FileSwap.CreateSwap( ResourceType.Atex, redirections, filePath, oldPath, oldPath ); + return FileSwap.CreateSwap(manager, ResourceType.Atex, redirections, filePath, oldPath, oldPath); } -} \ No newline at end of file +} diff --git a/Penumbra/Mods/ItemSwap/ItemSwap.cs b/Penumbra/Mods/ItemSwap/ItemSwap.cs index daefee5c..30159591 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwap.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwap.cs @@ -6,6 +6,7 @@ using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Services; @@ -27,7 +28,7 @@ public static class ItemSwap => Type = type; } - private static bool LoadFile( FullPath path, out byte[] data ) + private static bool LoadFile( MetaFileManager manager, FullPath path, out byte[] data ) { if( path.FullName.Length > 0 ) { @@ -39,7 +40,7 @@ public static class ItemSwap return true; } - var file = DalamudServices.SGameData.GetFile( path.InternalName.ToString() ); + var file = manager.GameData.GetFile( path.InternalName.ToString() ); if( file != null ) { data = file.Data; @@ -61,18 +62,18 @@ public static class ItemSwap public readonly byte[] Data; public bool Valid { get; } - public GenericFile( FullPath path ) - => Valid = LoadFile( path, out Data ); + public GenericFile( MetaFileManager manager, FullPath path ) + => Valid = LoadFile( manager, path, out Data ); public byte[] Write() => Data; - public static readonly GenericFile Invalid = new(FullPath.Empty); + public static readonly GenericFile Invalid = new(null!, FullPath.Empty); } - public static bool LoadFile( FullPath path, [NotNullWhen( true )] out GenericFile? file ) + public static bool LoadFile( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out GenericFile? file ) { - file = new GenericFile( path ); + file = new GenericFile( manager, path ); if( file.Valid ) { return true; @@ -82,11 +83,11 @@ public static class ItemSwap return false; } - public static bool LoadMdl( FullPath path, [NotNullWhen( true )] out MdlFile? file ) + public static bool LoadMdl( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MdlFile? file ) { try { - if( LoadFile( path, out byte[] data ) ) + if( LoadFile( manager, path, out byte[] data ) ) { file = new MdlFile( data ); return true; @@ -101,11 +102,11 @@ public static class ItemSwap return false; } - public static bool LoadMtrl( FullPath path, [NotNullWhen( true )] out MtrlFile? file ) + public static bool LoadMtrl(MetaFileManager manager, FullPath path, [NotNullWhen( true )] out MtrlFile? file ) { try { - if( LoadFile( path, out byte[] data ) ) + if( LoadFile( manager, path, out byte[] data ) ) { file = new MtrlFile( data ); return true; @@ -120,11 +121,11 @@ public static class ItemSwap return false; } - public static bool LoadAvfx( FullPath path, [NotNullWhen( true )] out AvfxFile? file ) + public static bool LoadAvfx( MetaFileManager manager, FullPath path, [NotNullWhen( true )] out AvfxFile? file ) { try { - if( LoadFile( path, out byte[] data ) ) + if( LoadFile( manager, path, out byte[] data ) ) { file = new AvfxFile( data ); return true; @@ -140,20 +141,20 @@ public static class ItemSwap } - public static FileSwap CreatePhyb( Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry ) + public static FileSwap CreatePhyb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry ) { var phybPath = GamePaths.Skeleton.Phyb.Path( race, EstManipulation.ToName( type ), estEntry ); - return FileSwap.CreateSwap( ResourceType.Phyb, redirections, phybPath, phybPath ); + return FileSwap.CreateSwap( manager, ResourceType.Phyb, redirections, phybPath, phybPath ); } - public static FileSwap CreateSklb( Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry ) + public static FileSwap CreateSklb(MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, EstManipulation.EstType type, GenderRace race, ushort estEntry ) { var sklbPath = GamePaths.Skeleton.Sklb.Path( race, EstManipulation.ToName( type ), estEntry ); - return FileSwap.CreateSwap( ResourceType.Sklb, redirections, sklbPath, sklbPath ); + return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath ); } /// metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. - public static MetaSwap? CreateEst( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EstManipulation.EstType type, + public static MetaSwap? CreateEst( MetaFileManager manager, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EstManipulation.EstType type, GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl ) { if( type == 0 ) @@ -162,15 +163,15 @@ public static class ItemSwap } var (gender, race) = genderRace.Split(); - var fromDefault = new EstManipulation( gender, race, type, idFrom.Value, EstFile.GetDefault( type, genderRace, idFrom.Value ) ); - var toDefault = new EstManipulation( gender, race, type, idTo.Value, EstFile.GetDefault( type, genderRace, idTo.Value ) ); + var fromDefault = new EstManipulation( gender, race, type, idFrom.Value, EstFile.GetDefault( manager, type, genderRace, idFrom.Value ) ); + var toDefault = new EstManipulation( gender, race, type, idTo.Value, EstFile.GetDefault( manager, type, genderRace, idTo.Value ) ); var est = new MetaSwap( manips, fromDefault, toDefault ); if( ownMdl && est.SwapApplied.Est.Entry >= 2 ) { - var phyb = CreatePhyb( redirections, type, genderRace, est.SwapApplied.Est.Entry ); + var phyb = CreatePhyb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry ); est.ChildSwaps.Add( phyb ); - var sklb = CreateSklb( redirections, type, genderRace, est.SwapApplied.Est.Entry ); + var sklb = CreateSklb( manager, redirections, type, genderRace, est.SwapApplied.Est.Entry ); est.ChildSwaps.Add( sklb ); } else if( est.SwapAppliedIsDefault ) diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index 2ca70cda..09283dc7 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -9,12 +9,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Penumbra.GameData; +using Penumbra.Meta; using Penumbra.Mods.Manager; namespace Penumbra.Mods.ItemSwap; public class ItemSwapContainer { + private readonly MetaFileManager _manager; private readonly IObjectIdentifier _identifier; private Dictionary< Utf8GamePath, FullPath > _modRedirections = new(); @@ -112,8 +114,9 @@ public class ItemSwapContainer } } - public ItemSwapContainer(IObjectIdentifier identifier) + public ItemSwapContainer(MetaFileManager manager, IObjectIdentifier identifier) { + _manager = manager; _identifier = identifier; LoadMod( null, null ); } @@ -133,7 +136,7 @@ public class ItemSwapContainer { Swaps.Clear(); Loaded = false; - var ret = EquipmentSwap.CreateItemSwap( _identifier, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing ); + var ret = EquipmentSwap.CreateItemSwap( _manager, _identifier, Swaps, PathResolver( collection ), MetaResolver( collection ), from, to, useRightRing, useLeftRing ); Loaded = true; return ret; } @@ -142,15 +145,15 @@ public class ItemSwapContainer { Swaps.Clear(); Loaded = false; - var ret = EquipmentSwap.CreateTypeSwap( _identifier, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to ); + var ret = EquipmentSwap.CreateTypeSwap( _manager, _identifier, Swaps, PathResolver( collection ), MetaResolver( collection ), slotFrom, from, slotTo, to ); Loaded = true; return ret; } - public bool LoadCustomization( BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null ) + public bool LoadCustomization( MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to, ModCollection? collection = null ) { var pathResolver = PathResolver( collection ); - var mdl = CustomizationSwap.CreateMdl( pathResolver, slot, race, from, to ); + var mdl = CustomizationSwap.CreateMdl( manager, pathResolver, slot, race, from, to ); var type = slot switch { BodySlot.Hair => EstManipulation.EstType.Hair, @@ -159,7 +162,7 @@ public class ItemSwapContainer }; var metaResolver = MetaResolver( collection ); - var est = ItemSwap.CreateEst( pathResolver, metaResolver, type, race, from, to, true ); + var est = ItemSwap.CreateEst( manager, pathResolver, metaResolver, type, race, from, to, true ); Swaps.Add( mdl ); if( est != null ) diff --git a/Penumbra/Mods/ItemSwap/Swaps.cs b/Penumbra/Mods/ItemSwap/Swaps.cs index 8249c189..d689c2cf 100644 --- a/Penumbra/Mods/ItemSwap/Swaps.cs +++ b/Penumbra/Mods/ItemSwap/Swaps.cs @@ -7,18 +7,19 @@ using System.IO; using System.Linq; using System.Security.Cryptography; using Penumbra.GameData.Enums; +using Penumbra.Meta; using static Penumbra.Mods.ItemSwap.ItemSwap; -using Penumbra.Services; - +using Penumbra.Services; + namespace Penumbra.Mods.ItemSwap; public class Swap { /// Any further swaps belonging specifically to this tree of changes. - public readonly List< Swap > ChildSwaps = new(); + public readonly List ChildSwaps = new(); - public IEnumerable< Swap > WithChildren() - => ChildSwaps.SelectMany( c => c.WithChildren() ).Prepend( this ); + public IEnumerable WithChildren() + => ChildSwaps.SelectMany(c => c.WithChildren()).Prepend(this); } public sealed class MetaSwap : Swap @@ -47,15 +48,15 @@ public sealed class MetaSwap : Swap /// A function that converts the given manipulation to the modded one. /// The original meta identifier with its default value. /// The target meta identifier with its default value. - public MetaSwap( Func< MetaManipulation, MetaManipulation > manipulations, MetaManipulation manipFrom, MetaManipulation manipTo ) + public MetaSwap(Func manipulations, MetaManipulation manipFrom, MetaManipulation manipTo) { SwapFrom = manipFrom; SwapToDefault = manipTo; - SwapToModded = manipulations( manipTo ); - SwapToIsDefault = manipTo.EntryEquals( SwapToModded ); - SwapApplied = SwapFrom.WithEntryOf( SwapToModded ); - SwapAppliedIsDefault = SwapApplied.EntryEquals( SwapFrom ); + SwapToModded = manipulations(manipTo); + SwapToIsDefault = manipTo.EntryEquals(SwapToModded); + SwapApplied = SwapFrom.WithEntryOf(SwapToModded); + SwapAppliedIsDefault = SwapApplied.EntryEquals(SwapFrom); } } @@ -95,8 +96,8 @@ public sealed class FileSwap : Swap /// Whether SwapFromPreChangePath equals SwapFromRequest. public bool SwapFromChanged; - public string GetNewPath( string newMod ) - => Path.Combine( newMod, new Utf8RelPath( SwapFromRequestPath ).ToString() ); + public string GetNewPath(string newMod) + => Path.Combine(newMod, new Utf8RelPath(SwapFromRequestPath).ToString()); public MdlFile? AsMdl() => FileData as MdlFile; @@ -116,8 +117,9 @@ public sealed class FileSwap : Swap /// The unmodded path to the file the game is supposed to load instead. /// A full swap container with the actual file in memory. /// True if everything could be read correctly, false otherwise. - public static FileSwap CreateSwap( ResourceType type, Func< Utf8GamePath, FullPath > redirections, string swapFromRequest, string swapToRequest, - string? swapFromPreChange = null ) + public static FileSwap CreateSwap(MetaFileManager manager, ResourceType type, Func redirections, + string swapFromRequest, string swapToRequest, + string? swapFromPreChange = null) { var swap = new FileSwap { @@ -131,49 +133,25 @@ public sealed class FileSwap : Swap SwapToModded = FullPath.Empty, }; - if( swapFromRequest.Length == 0 - || swapToRequest.Length == 0 - || !Utf8GamePath.FromString( swapToRequest, out swap.SwapToRequestPath ) - || !Utf8GamePath.FromString( swapFromRequest, out swap.SwapFromRequestPath ) ) - { - throw new Exception( $"Could not create UTF8 String for \"{swapFromRequest}\" or \"{swapToRequest}\"." ); - } + if (swapFromRequest.Length == 0 + || swapToRequest.Length == 0 + || !Utf8GamePath.FromString(swapToRequest, out swap.SwapToRequestPath) + || !Utf8GamePath.FromString(swapFromRequest, out swap.SwapFromRequestPath)) + throw new Exception($"Could not create UTF8 String for \"{swapFromRequest}\" or \"{swapToRequest}\"."); - swap.SwapToModded = redirections( swap.SwapToRequestPath ); - swap.SwapToModdedExistsInGame = !swap.SwapToModded.IsRooted && DalamudServices.SGameData.FileExists( swap.SwapToModded.InternalName.ToString() ); - swap.SwapToModdedEqualsOriginal = !swap.SwapToModded.IsRooted && swap.SwapToModded.InternalName.Equals( swap.SwapFromRequestPath.Path ); + swap.SwapToModded = redirections(swap.SwapToRequestPath); + swap.SwapToModdedExistsInGame = + !swap.SwapToModded.IsRooted && DalamudServices.SGameData.FileExists(swap.SwapToModded.InternalName.ToString()); + swap.SwapToModdedEqualsOriginal = !swap.SwapToModded.IsRooted && swap.SwapToModded.InternalName.Equals(swap.SwapFromRequestPath.Path); swap.FileData = type switch { - ResourceType.Mdl => LoadMdl( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ), - ResourceType.Mtrl => LoadMtrl( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ), - ResourceType.Avfx => LoadAvfx( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ), - _ => LoadFile( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ), + ResourceType.Mdl => LoadMdl(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded), + ResourceType.Mtrl => LoadMtrl(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded), + ResourceType.Avfx => LoadAvfx(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded), + _ => LoadFile(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded), }; return swap; } - - - /// - /// Convert a single file redirection to use the file name and extension given by type and the files SHA256 hash, if possible. - /// - /// The set of redirections that need to be considered. - /// The in- and output path for a file - /// Will be set to true if was changed. - /// Will be updated. - public static bool CreateShaRedirection( Func< Utf8GamePath, FullPath > redirections, ref string path, ref bool dataWasChanged, ref FileSwap swap ) - { - var oldFilename = Path.GetFileName( path ); - var hash = SHA256.HashData( swap.FileData.Write() ); - var name = - $"{( oldFilename.StartsWith( "--" ) ? "--" : string.Empty )}{string.Join( null, hash.Select( c => c.ToString( "x2" ) ) )}.{swap.Type.ToString().ToLowerInvariant()}"; - var newPath = path.Replace( oldFilename, name ); - var newSwap = CreateSwap( swap.Type, redirections, newPath, swap.SwapToRequestPath.ToString() ); - - path = newPath; - dataWasChanged = true; - swap = newSwap; - return true; - } -} \ No newline at end of file +} diff --git a/Penumbra/Mods/Mod.Files.cs b/Penumbra/Mods/Mod.Files.cs index 8326b4fb..8820dd7b 100644 --- a/Penumbra/Mods/Mod.Files.cs +++ b/Penumbra/Mods/Mod.Files.cs @@ -5,6 +5,7 @@ using System.Linq; using Newtonsoft.Json.Linq; using OtterGui; using Penumbra.Api.Enums; +using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; @@ -109,11 +110,11 @@ public partial class Mod } } - public void WriteAllTexToolsMeta() + public void WriteAllTexToolsMeta(MetaFileManager manager) { try { - _default.WriteTexToolsMeta(ModPath); + _default.WriteTexToolsMeta(manager, ModPath); foreach (var group in Groups) { var dir = ModCreator.NewOptionDirectory(ModPath, group.Name); @@ -126,7 +127,7 @@ public partial class Mod if (!optionDir.Exists) optionDir.Create(); - option.WriteTexToolsMeta(optionDir); + option.WriteTexToolsMeta(manager, optionDir); } } } diff --git a/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs b/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs index 8b5ba641..459d5ffb 100644 --- a/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs +++ b/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs @@ -5,12 +5,13 @@ using System.IO; using System.Linq; using Newtonsoft.Json.Linq; using Penumbra.Import; +using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; namespace Penumbra.Mods; -/// +/// /// A sub mod is a collection of /// - file replacements /// - file swaps @@ -18,14 +19,14 @@ namespace Penumbra.Mods; /// that can be used either as an option or as the default data for a mod. /// It can be loaded and reloaded from Json. /// Nothing is checked for existence or validity when loading. -/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides. +/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides. /// - public sealed class SubMod : ISubMod +public sealed class SubMod : ISubMod { public string Name { get; set; } = "Default"; public string FullName - => GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}"; + => GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[GroupIdx].Name}: {Name}"; public string Description { get; set; } = string.Empty; @@ -36,180 +37,165 @@ namespace Penumbra.Mods; public bool IsDefault => GroupIdx < 0; - public Dictionary< Utf8GamePath, FullPath > FileData = new(); - public Dictionary< Utf8GamePath, FullPath > FileSwapData = new(); - public HashSet< MetaManipulation > ManipulationData = new(); + public Dictionary FileData = new(); + public Dictionary FileSwapData = new(); + public HashSet ManipulationData = new(); - public SubMod( IMod parentMod ) + public SubMod(IMod parentMod) => ParentMod = parentMod; - public IReadOnlyDictionary< Utf8GamePath, FullPath > Files + public IReadOnlyDictionary Files => FileData; - public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps + public IReadOnlyDictionary FileSwaps => FileSwapData; - public IReadOnlySet< MetaManipulation > Manipulations + public IReadOnlySet Manipulations => ManipulationData; - public void SetPosition( int groupIdx, int optionIdx ) + public void SetPosition(int groupIdx, int optionIdx) { GroupIdx = groupIdx; OptionIdx = optionIdx; } - public void Load( DirectoryInfo basePath, JToken json, out int priority ) + public void Load(DirectoryInfo basePath, JToken json, out int priority) { FileData.Clear(); FileSwapData.Clear(); ManipulationData.Clear(); // Every option has a name, but priorities are only relevant for multi group options. - Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty; - Description = json[ nameof( ISubMod.Description ) ]?.ToObject< string >() ?? string.Empty; - priority = json[ nameof( IModGroup.Priority ) ]?.ToObject< int >() ?? 0; + Name = json[nameof(ISubMod.Name)]?.ToObject() ?? string.Empty; + Description = json[nameof(ISubMod.Description)]?.ToObject() ?? string.Empty; + priority = json[nameof(IModGroup.Priority)]?.ToObject() ?? 0; - var files = ( JObject? )json[ nameof( Files ) ]; - if( files != null ) - { - foreach( var property in files.Properties() ) + var files = (JObject?)json[nameof(Files)]; + if (files != null) + foreach (var property in files.Properties()) { - if( Utf8GamePath.FromString( property.Name, out var p, true ) ) - { - FileData.TryAdd( p, new FullPath( basePath, property.Value.ToObject< Utf8RelPath >() ) ); - } + if (Utf8GamePath.FromString(property.Name, out var p, true)) + FileData.TryAdd(p, new FullPath(basePath, property.Value.ToObject())); } - } - var swaps = ( JObject? )json[ nameof( FileSwaps ) ]; - if( swaps != null ) - { - foreach( var property in swaps.Properties() ) + var swaps = (JObject?)json[nameof(FileSwaps)]; + if (swaps != null) + foreach (var property in swaps.Properties()) { - if( Utf8GamePath.FromString( property.Name, out var p, true ) ) - { - FileSwapData.TryAdd( p, new FullPath( property.Value.ToObject< string >()! ) ); - } + if (Utf8GamePath.FromString(property.Name, out var p, true)) + FileSwapData.TryAdd(p, new FullPath(property.Value.ToObject()!)); } - } - var manips = json[ nameof( Manipulations ) ]; - if( manips != null ) - { - foreach( var s in manips.Children().Select( c => c.ToObject< MetaManipulation >() ).Where( m => m.ManipulationType != MetaManipulation.Type.Unknown ) ) - { - ManipulationData.Add( s ); - } - } + var manips = json[nameof(Manipulations)]; + if (manips != null) + foreach (var s in manips.Children().Select(c => c.ToObject()) + .Where(m => m.ManipulationType != MetaManipulation.Type.Unknown)) + 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< string > DeleteList) IncorporateMetaChanges( DirectoryInfo basePath, bool delete ) + public (bool Changes, List DeleteList) IncorporateMetaChanges(DirectoryInfo basePath, bool delete) { - var deleteList = new List< string >(); + var deleteList = new List(); var oldSize = ManipulationData.Count; var deleteString = delete ? "with deletion." : "without deletion."; - foreach( var (key, file) in Files.ToList() ) + foreach (var (key, file) in Files.ToList()) { var ext1 = key.Extension().AsciiToLower().ToString(); var ext2 = file.Extension.ToLowerInvariant(); try { - if( ext1 == ".meta" || ext2 == ".meta" ) + if (ext1 == ".meta" || ext2 == ".meta") { - FileData.Remove( key ); - if( !file.Exists ) - { + FileData.Remove(key); + if (!file.Exists) continue; - } - var meta = new TexToolsMeta( 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 ); + 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" ) + else if (ext1 == ".rgsp" || ext2 == ".rgsp") { - FileData.Remove( key ); - if( !file.Exists ) - { + FileData.Remove(key); + if (!file.Exists) continue; - } - var rgsp = TexToolsMeta.FromRgspFile( 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 ); + 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 ); + ManipulationData.UnionWith(rgsp.MetaManipulations); } } - catch( Exception e ) + catch (Exception e) { - Penumbra.Log.Error( $"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{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 ); + DeleteDeleteList(deleteList, delete); + return (oldSize < ManipulationData.Count, deleteList); } - internal static void DeleteDeleteList( IEnumerable< string > deleteList, bool delete ) + internal static void DeleteDeleteList(IEnumerable deleteList, bool delete) { - if( !delete ) - { + if (!delete) return; - } - foreach( var file in deleteList ) + foreach (var file in deleteList) { try { - File.Delete( file ); + File.Delete(file); } - catch( Exception e ) + catch (Exception e) { - Penumbra.Log.Error( $"Could not delete incorporated meta file {file}:\n{e}" ); + Penumbra.Log.Error($"Could not delete incorporated meta file {file}:\n{e}"); } } } - public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false ) + public void WriteTexToolsMeta(MetaFileManager manager, DirectoryInfo basePath, bool test = false) { - var files = TexToolsMeta.ConvertToTexTools( Manipulations ); + var files = TexToolsMeta.ConvertToTexTools(manager, Manipulations); - foreach( var (file, data) in files ) + foreach (var (file, data) in files) { - var path = Path.Combine( basePath.FullName, file ); + var path = Path.Combine(basePath.FullName, file); try { - Directory.CreateDirectory( Path.GetDirectoryName( path )! ); - File.WriteAllBytes( path, data ); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllBytes(path, data); } - catch( Exception e ) + catch (Exception e) { - Penumbra.Log.Error( $"Could not write meta file {path}:\n{e}" ); + Penumbra.Log.Error($"Could not write meta file {path}:\n{e}"); } } - if( test ) - { - TestMetaWriting( files ); - } + if (test) + TestMetaWriting(manager, files); } - [Conditional("DEBUG" )] - private void TestMetaWriting( Dictionary< string, byte[] > files ) + [Conditional("DEBUG")] + private void TestMetaWriting(MetaFileManager manager, Dictionary files) { - var meta = new HashSet< MetaManipulation >( Manipulations.Count ); - foreach( var (file, data) in files ) + var meta = new HashSet(Manipulations.Count); + foreach (var (file, data) in files) { try { - var x = file.EndsWith( "rgsp" ) - ? TexToolsMeta.FromRgspFile( file, data, Penumbra.Config.KeepDefaultMetaChanges ) - : new TexToolsMeta( Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges ); - meta.UnionWith( x.MetaManipulations ); + 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 { @@ -217,27 +203,21 @@ namespace Penumbra.Mods; } } - if( !Manipulations.SetEquals( meta ) ) + 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()}" ); - } + 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 Manipulations.Skip(meta.Count)) + Penumbra.Log.Information($"{m} {m.EntryToString()} "); - foreach( var m in meta.Skip( Manipulations.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." ); + Penumbra.Log.Information("Meta Sets are equal."); } } -} \ No newline at end of file +} diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 24f13749..150fd9c9 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -28,7 +28,8 @@ using Penumbra.Interop.Services; using Penumbra.Mods.Manager; using Penumbra.Collections.Manager; using Penumbra.Mods; - +using Penumbra.Meta; + namespace Penumbra; public class Penumbra : IDalamudPlugin diff --git a/Penumbra/PenumbraNew.cs b/Penumbra/PenumbraNew.cs index d1227472..94305567 100644 --- a/Penumbra/PenumbraNew.cs +++ b/Penumbra/PenumbraNew.cs @@ -23,6 +23,7 @@ using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; using ModFileSystemSelector = Penumbra.UI.ModsTab.ModFileSystemSelector; using Penumbra.Mods.Manager; using Penumbra.Collections.Cache; +using Penumbra.Meta; namespace Penumbra; @@ -66,7 +67,6 @@ public class PenumbraNew // Add Game Services services.AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -78,10 +78,6 @@ public class PenumbraNew .AddSingleton() .AddSingleton(); - // Add PathResolver - services.AddSingleton() - .AddSingleton(); - // Add Configuration services.AddTransient() .AddSingleton(); @@ -109,7 +105,8 @@ public class PenumbraNew // Add Resource services services.AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); // Add Path Resolver services.AddSingleton() diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 1721480b..7f2966ff 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -12,9 +12,10 @@ using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections; -using Penumbra.Collections.Manager; +using Penumbra.Collections.Manager; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.Meta; using Penumbra.Mods; using Penumbra.Mods.ItemSwap; using Penumbra.Mods.Manager; @@ -25,21 +26,23 @@ namespace Penumbra.UI.AdvancedWindow; public class ItemSwapTab : IDisposable, ITab { - private readonly CommunicatorService _communicator; - private readonly ItemService _itemService; - private readonly CollectionManager _collectionManager; - private readonly ModManager _modManager; - private readonly Configuration _config; + private readonly CommunicatorService _communicator; + private readonly ItemService _itemService; + private readonly CollectionManager _collectionManager; + private readonly ModManager _modManager; + private readonly Configuration _config; + private readonly MetaFileManager _metaFileManager; public ItemSwapTab(CommunicatorService communicator, ItemService itemService, CollectionManager collectionManager, - ModManager modManager, Configuration config, IdentifierService identifier) + ModManager modManager, Configuration config, IdentifierService identifier, MetaFileManager metaFileManager) { _communicator = communicator; _itemService = itemService; _collectionManager = collectionManager; _modManager = modManager; _config = config; - _swapData = new ItemSwapContainer(identifier.AwaitedService); + _metaFileManager = metaFileManager; + _swapData = new ItemSwapContainer(metaFileManager, identifier.AwaitedService); _selectors = new Dictionary { @@ -215,22 +218,22 @@ public class ItemSwapTab : IDisposable, ITab _useCurrentCollection ? _collectionManager.Active.Current : null); break; case SwapType.Hair when _targetId > 0 && _sourceId > 0: - _swapData.LoadCustomization(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(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(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(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; @@ -312,7 +315,8 @@ public class ItemSwapTab : IDisposable, ITab optionCreated = true; optionFolderName = Directory.CreateDirectory(optionFolderName.FullName); dirCreated = true; - if (!_swapData.WriteMod(_modManager, _mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps, + if (!_swapData.WriteMod(_modManager, _mod, + _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps, optionFolderName, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1)) throw new Exception("Failure writing files for mod swap."); @@ -751,7 +755,6 @@ public class ItemSwapTab : IDisposable, ITab private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited) { - if (collection != _collectionManager.Active.Current || mod != _mod) return; @@ -762,7 +765,7 @@ public class ItemSwapTab : IDisposable, ITab private void OnInheritanceChange(ModCollection collection, bool _) { if (collection != _collectionManager.Active.Current || _mod == null) - return; + return; UpdateMod(_mod, collection[_mod.Index].Settings); _swapData.LoadMod(_mod, _modSettings); @@ -772,8 +775,9 @@ public class ItemSwapTab : IDisposable, ITab private void OnModOptionChange(ModOptionChangeType type, Mod mod, int a, int b, int c) { if (type is ModOptionChangeType.PrepareChange or ModOptionChangeType.GroupAdded or ModOptionChangeType.OptionAdded || mod != _mod) - return; - _swapData.LoadMod(_mod, _modSettings); + return; + + _swapData.LoadMod(_mod, _modSettings); UpdateOption(); _dirty = true; } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index 242c49dc..1b92463e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -9,6 +9,7 @@ using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; +using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Mods; @@ -62,7 +63,7 @@ public partial class ModEditWindow CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor.Recombine()); ImGui.SameLine(); if (ImGui.Button("Write as TexTools Files")) - _mod!.WriteAllTexToolsMeta(); + _mod!.WriteAllTexToolsMeta(_metaFileManager); using var child = ImRaii.Child("##meta", -Vector2.One, true); if (!child) @@ -77,9 +78,9 @@ public partial class ModEditWindow } - // The headers for the different meta changes all have basically the same structure for different types. - private void DrawEditHeader(IReadOnlyCollection items, string label, int numColumns, Action draw, - Action drawNew) + /// The headers for the different meta changes all have basically the same structure for different types. + private void DrawEditHeader(IReadOnlyCollection items, string label, int numColumns, Action draw, + Action drawNew) { const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersInnerV; if (!ImGui.CollapsingHeader($"{items.Count} {label}")) @@ -89,11 +90,11 @@ public partial class ModEditWindow { if (table) { - drawNew(_editor, _iconSize); + drawNew(_metaFileManager, _editor, _iconSize); foreach (var (item, index) in items.ToArray().WithIndex()) { using var id = ImRaii.PushId(index); - draw(item, _editor, _iconSize); + draw(_metaFileManager, item, _editor, _iconSize); } } } @@ -108,7 +109,7 @@ public partial class ModEditWindow private static float IdWidth => 100 * UiHelpers.Scale; - public static void DrawNew(ModEditor editor, Vector2 iconSize) + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current EQP manipulations to clipboard.", iconSize, @@ -116,20 +117,20 @@ public partial class ModEditWindow ImGui.TableNextColumn(); var canAdd = editor.MetaEditor.CanAdd(_new); var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = ExpandedEqpFile.GetDefault(_new.SetId); + var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, _new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); // Identifier ImGui.TableNextColumn(); if (IdInput("##eqpId", IdWidth, _new.SetId, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) - _new = new EqpManipulation(ExpandedEqpFile.GetDefault(setId), _new.Slot, setId); + _new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), _new.Slot, setId); ImGuiUtil.HoverTooltip(ModelSetIdTooltip); ImGui.TableNextColumn(); if (Combos.EqpEquipSlot("##eqpSlot", 100, _new.Slot, out var slot)) - _new = new EqpManipulation(ExpandedEqpFile.GetDefault(setId), slot, _new.SetId); + _new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), slot, _new.SetId); ImGuiUtil.HoverTooltip(EquipSlotTooltip); @@ -148,7 +149,7 @@ public partial class ModEditWindow ImGui.NewLine(); } - public static void Draw(EqpManipulation meta, ModEditor editor, Vector2 iconSize) + public static void Draw(MetaFileManager metaFileManager, EqpManipulation meta, ModEditor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); @@ -157,7 +158,7 @@ public partial class ModEditWindow ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); ImGui.TextUnformatted(meta.SetId.ToString()); ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort); - var defaultEntry = ExpandedEqpFile.GetDefault(meta.SetId); + var defaultEntry = ExpandedEqpFile.GetDefault(metaFileManager, meta.SetId); ImGui.TableNextColumn(); ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); @@ -192,7 +193,7 @@ public partial class ModEditWindow private static float IdWidth => 100 * UiHelpers.Scale; - public static void DrawNew(ModEditor editor, Vector2 iconSize) + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current EQDP manipulations to clipboard.", iconSize, @@ -204,7 +205,7 @@ public partial class ModEditWindow var tt = canAdd ? "Stage this edit." : validRaceCode ? "This entry is already edited." : "This combination of race and gender can not be used."; var defaultEntry = validRaceCode - ? ExpandedEqdpFile.GetDefault(Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId) + ? ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId) : 0; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); @@ -213,7 +214,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (IdInput("##eqdpId", IdWidth, _new.SetId, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), setId); + var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), _new.Slot.IsAccessory(), setId); _new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, _new.Race, setId); } @@ -222,7 +223,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (Combos.Race("##eqdpRace", _new.Race, out var race)) { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(Names.CombinedRace(_new.Gender, race), _new.Slot.IsAccessory(), _new.SetId); + var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, race), _new.Slot.IsAccessory(), _new.SetId); _new = new EqdpManipulation(newDefaultEntry, _new.Slot, _new.Gender, race, _new.SetId); } @@ -231,7 +232,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (Combos.Gender("##eqdpGender", _new.Gender, out var gender)) { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(Names.CombinedRace(gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId); + var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(gender, _new.Race), _new.Slot.IsAccessory(), _new.SetId); _new = new EqdpManipulation(newDefaultEntry, _new.Slot, gender, _new.Race, _new.SetId); } @@ -240,7 +241,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (Combos.EqdpEquipSlot("##eqdpSlot", _new.Slot, out var slot)) { - var newDefaultEntry = ExpandedEqdpFile.GetDefault(Names.CombinedRace(_new.Gender, _new.Race), slot.IsAccessory(), _new.SetId); + var newDefaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(_new.Gender, _new.Race), slot.IsAccessory(), _new.SetId); _new = new EqdpManipulation(newDefaultEntry, slot, _new.Gender, _new.Race, _new.SetId); } @@ -255,7 +256,7 @@ public partial class ModEditWindow Checkmark("Model##eqdpCheck2", string.Empty, bit2, bit2, out _); } - public static void Draw(EqdpManipulation meta, ModEditor editor, Vector2 iconSize) + public static void Draw(MetaFileManager metaFileManager, EqdpManipulation meta, ModEditor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); @@ -278,7 +279,7 @@ public partial class ModEditWindow ImGuiUtil.HoverTooltip(EquipSlotTooltip); // Values - var defaultEntry = ExpandedEqdpFile.GetDefault(Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(), meta.SetId); + var defaultEntry = ExpandedEqdpFile.GetDefault(metaFileManager, Names.CombinedRace(meta.Gender, meta.Race), meta.Slot.IsAccessory(), meta.SetId); var (defaultBit1, defaultBit2) = defaultEntry.ToBits(meta.Slot); var (bit1, bit2) = meta.Entry.ToBits(meta.Slot); ImGui.TableNextColumn(); @@ -301,12 +302,12 @@ public partial class ModEditWindow private static float SmallIdWidth => 45 * UiHelpers.Scale; - // Convert throwing to null-return if the file does not exist. - private static ImcEntry? GetDefault(ImcManipulation imc) + /// Convert throwing to null-return if the file does not exist. + private static ImcEntry? GetDefault(MetaFileManager metaFileManager, ImcManipulation imc) { try { - return ImcFile.GetDefault(imc.GamePath(), imc.EquipSlot, imc.Variant, out _); + return ImcFile.GetDefault(metaFileManager, imc.GamePath(), imc.EquipSlot, imc.Variant, out _); } catch { @@ -314,13 +315,13 @@ public partial class ModEditWindow } } - public static void DrawNew(ModEditor editor, Vector2 iconSize) + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current IMC manipulations to clipboard.", iconSize, editor.MetaEditor.Imc.Select(m => (MetaManipulation)m)); ImGui.TableNextColumn(); - var defaultEntry = GetDefault(_new); + var defaultEntry = GetDefault(metaFileManager, _new); var canAdd = defaultEntry != null && editor.MetaEditor.CanAdd(_new); var tt = canAdd ? "Stage this edit." : defaultEntry == null ? "This IMC file does not exist." : "This entry is already edited."; defaultEntry ??= new ImcEntry(); @@ -347,7 +348,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (IdInput("##imcId", IdWidth, _new.PrimaryId, out var setId, 0, ushort.MaxValue, _new.PrimaryId <= 1)) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, setId, _new.SecondaryId, _new.Variant, _new.EquipSlot, _new.Entry) - .Copy(GetDefault(_new) + .Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); ImGuiUtil.HoverTooltip(PrimaryIdTooltip); @@ -361,7 +362,7 @@ public partial class ModEditWindow { if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot)) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant, slot, _new.Entry) - .Copy(GetDefault(_new) + .Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); ImGuiUtil.HoverTooltip(EquipSlotTooltip); @@ -370,7 +371,7 @@ public partial class ModEditWindow { if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot)) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant, slot, _new.Entry) - .Copy(GetDefault(_new) + .Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); ImGuiUtil.HoverTooltip(EquipSlotTooltip); @@ -379,7 +380,7 @@ public partial class ModEditWindow { if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId, out var setId2, 0, ushort.MaxValue, false)) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant, _new.EquipSlot, _new.Entry) - .Copy(GetDefault(_new) + .Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); ImGuiUtil.HoverTooltip(SecondaryIdTooltip); @@ -388,7 +389,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (IdInput("##imcVariant", SmallIdWidth, _new.Variant, out var variant, 0, byte.MaxValue, false)) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, variant, _new.EquipSlot, - _new.Entry).Copy(GetDefault(_new) + _new.Entry).Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); ImGui.TableNextColumn(); @@ -396,7 +397,7 @@ public partial class ModEditWindow { if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot)) _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant, slot, _new.Entry) - .Copy(GetDefault(_new) + .Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); ImGuiUtil.HoverTooltip(EquipSlotTooltip); @@ -438,7 +439,7 @@ public partial class ModEditWindow ImGui.NewLine(); } - public static void Draw(ImcManipulation meta, ModEditor editor, Vector2 iconSize) + public static void Draw(MetaFileManager metaFileManager, ImcManipulation meta, ModEditor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); @@ -479,7 +480,7 @@ public partial class ModEditWindow using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); ImGui.TableNextColumn(); - var defaultEntry = GetDefault(meta) ?? new ImcEntry(); + var defaultEntry = GetDefault(metaFileManager, meta) ?? new ImcEntry(); if (IntDragInput("##imcMaterialId", $"Material ID\nDefault Value: {defaultEntry.MaterialId}", SmallIdWidth, meta.Entry.MaterialId, defaultEntry.MaterialId, out var materialId, 1, byte.MaxValue, 0.01f)) editor.MetaEditor.Change(meta.Copy(meta.Entry with { MaterialId = (byte)materialId })); @@ -530,7 +531,7 @@ public partial class ModEditWindow private static float IdWidth => 100 * UiHelpers.Scale; - public static void DrawNew(ModEditor editor, Vector2 iconSize) + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current EST manipulations to clipboard.", iconSize, @@ -538,7 +539,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); var canAdd = editor.MetaEditor.CanAdd(_new); var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = EstFile.GetDefault(_new.Slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); + var defaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); @@ -546,7 +547,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (IdInput("##estId", IdWidth, _new.SetId, out var setId, 0, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) { - var newDefaultEntry = EstFile.GetDefault(_new.Slot, Names.CombinedRace(_new.Gender, _new.Race), setId); + var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, _new.Race), setId); _new = new EstManipulation(_new.Gender, _new.Race, _new.Slot, setId, newDefaultEntry); } @@ -555,7 +556,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (Combos.Race("##estRace", _new.Race, out var race)) { - var newDefaultEntry = EstFile.GetDefault(_new.Slot, Names.CombinedRace(_new.Gender, race), _new.SetId); + var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(_new.Gender, race), _new.SetId); _new = new EstManipulation(_new.Gender, race, _new.Slot, _new.SetId, newDefaultEntry); } @@ -564,7 +565,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (Combos.Gender("##estGender", _new.Gender, out var gender)) { - var newDefaultEntry = EstFile.GetDefault(_new.Slot, Names.CombinedRace(gender, _new.Race), _new.SetId); + var newDefaultEntry = EstFile.GetDefault(metaFileManager, _new.Slot, Names.CombinedRace(gender, _new.Race), _new.SetId); _new = new EstManipulation(gender, _new.Race, _new.Slot, _new.SetId, newDefaultEntry); } @@ -573,7 +574,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (Combos.EstSlot("##estSlot", _new.Slot, out var slot)) { - var newDefaultEntry = EstFile.GetDefault(slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); + var newDefaultEntry = EstFile.GetDefault(metaFileManager, slot, Names.CombinedRace(_new.Gender, _new.Race), _new.SetId); _new = new EstManipulation(_new.Gender, _new.Race, slot, _new.SetId, newDefaultEntry); } @@ -585,7 +586,7 @@ public partial class ModEditWindow IntDragInput("##estSkeleton", "Skeleton Index", IdWidth, _new.Entry, defaultEntry, out _, 0, ushort.MaxValue, 0.05f); } - public static void Draw(EstManipulation meta, ModEditor editor, Vector2 iconSize) + public static void Draw(MetaFileManager metaFileManager, EstManipulation meta, ModEditor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); @@ -608,7 +609,7 @@ public partial class ModEditWindow ImGuiUtil.HoverTooltip(EstTypeTooltip); // Values - var defaultEntry = EstFile.GetDefault(meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId); + var defaultEntry = EstFile.GetDefault(metaFileManager, meta.Slot, Names.CombinedRace(meta.Gender, meta.Race), meta.SetId); ImGui.TableNextColumn(); if (IntDragInput("##estSkeleton", $"Skeleton Index\nDefault Value: {defaultEntry}", IdWidth, meta.Entry, defaultEntry, out var entry, 0, ushort.MaxValue, 0.05f)) @@ -629,7 +630,7 @@ public partial class ModEditWindow private static float IdWidth => 100 * UiHelpers.Scale; - public static void DrawNew(ModEditor editor, Vector2 iconSize) + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current GMP manipulations to clipboard.", iconSize, @@ -637,14 +638,14 @@ public partial class ModEditWindow ImGui.TableNextColumn(); var canAdd = editor.MetaEditor.CanAdd(_new); var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = ExpandedGmpFile.GetDefault(_new.SetId); + var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, _new.SetId); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); // Identifier ImGui.TableNextColumn(); if (IdInput("##gmpId", IdWidth, _new.SetId, out var setId, 1, ExpandedEqpGmpBase.Count - 1, _new.SetId <= 1)) - _new = new GmpManipulation(ExpandedGmpFile.GetDefault(setId), setId); + _new = new GmpManipulation(ExpandedGmpFile.GetDefault(metaFileManager, setId), setId); ImGuiUtil.HoverTooltip(ModelSetIdTooltip); @@ -669,7 +670,7 @@ public partial class ModEditWindow IntDragInput("##gmpUnkB", "Animation Type B?", UnkWidth, defaultEntry.UnknownB, defaultEntry.UnknownB, out _, 0, 15, 0f); } - public static void Draw(GmpManipulation meta, ModEditor editor, Vector2 iconSize) + public static void Draw(MetaFileManager metaFileManager, GmpManipulation meta, ModEditor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); @@ -680,7 +681,7 @@ public partial class ModEditWindow ImGuiUtil.HoverTooltip(ModelSetIdTooltipShort); // Values - var defaultEntry = ExpandedGmpFile.GetDefault(meta.SetId); + var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, meta.SetId); ImGui.TableNextColumn(); if (Checkmark("##gmpEnabled", "Gimmick Enabled", meta.Entry.Enabled, defaultEntry.Enabled, out var enabled)) editor.MetaEditor.Change(meta.Copy(meta.Entry with { Enabled = enabled })); @@ -723,7 +724,7 @@ public partial class ModEditWindow private static float FloatWidth => 150 * UiHelpers.Scale; - public static void DrawNew(ModEditor editor, Vector2 iconSize) + public static void DrawNew(MetaFileManager metaFileManager, ModEditor editor, Vector2 iconSize) { ImGui.TableNextColumn(); CopyToClipboardButton("Copy all current RSP manipulations to clipboard.", iconSize, @@ -731,20 +732,20 @@ public partial class ModEditWindow ImGui.TableNextColumn(); var canAdd = editor.MetaEditor.CanAdd(_new); var tt = canAdd ? "Stage this edit." : "This entry is already edited."; - var defaultEntry = CmpFile.GetDefault(_new.SubRace, _new.Attribute); + var defaultEntry = CmpFile.GetDefault(metaFileManager, _new.SubRace, _new.Attribute); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), iconSize, tt, !canAdd, true)) editor.MetaEditor.Add(_new.Copy(defaultEntry)); // Identifier ImGui.TableNextColumn(); if (Combos.SubRace("##rspSubRace", _new.SubRace, out var subRace)) - _new = new RspManipulation(subRace, _new.Attribute, CmpFile.GetDefault(subRace, _new.Attribute)); + _new = new RspManipulation(subRace, _new.Attribute, CmpFile.GetDefault(metaFileManager, subRace, _new.Attribute)); ImGuiUtil.HoverTooltip(RacialTribeTooltip); ImGui.TableNextColumn(); if (Combos.RspAttribute("##rspAttribute", _new.Attribute, out var attribute)) - _new = new RspManipulation(_new.SubRace, attribute, CmpFile.GetDefault(subRace, attribute)); + _new = new RspManipulation(_new.SubRace, attribute, CmpFile.GetDefault(metaFileManager, subRace, attribute)); ImGuiUtil.HoverTooltip(ScalingTypeTooltip); @@ -755,7 +756,7 @@ public partial class ModEditWindow ImGui.DragFloat("##rspValue", ref defaultEntry, 0f); } - public static void Draw(RspManipulation meta, ModEditor editor, Vector2 iconSize) + public static void Draw(MetaFileManager metaFileManager, RspManipulation meta, ModEditor editor, Vector2 iconSize) { DrawMetaButtons(meta, editor, iconSize); @@ -771,7 +772,7 @@ public partial class ModEditWindow ImGui.TableNextColumn(); // Values - var def = CmpFile.GetDefault(meta.SubRace, meta.Attribute); + var def = CmpFile.GetDefault(metaFileManager, meta.SubRace, meta.Attribute); var value = meta.Entry; ImGui.SetNextItemWidth(FloatWidth); using var color = ImRaii.PushColor(ImGuiCol.FrameBg, diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 68265a43..31fc2a49 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.Import.Textures; using Penumbra.Interop.ResourceTree; +using Penumbra.Meta; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.String.Classes; @@ -31,6 +32,7 @@ public partial class ModEditWindow : Window, IDisposable private readonly Configuration _config; private readonly ItemSwapTab _itemSwapTab; private readonly DataManager _gameData; + private readonly MetaFileManager _metaFileManager; private Mod? _mod; private Vector2 _iconSize = Vector2.Zero; @@ -493,16 +495,17 @@ public partial class ModEditWindow : Window, IDisposable } public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, DataManager gameData, - Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches) + Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches, MetaFileManager metaFileManager) : base(WindowBaseLabel) { - _performance = performance; - _itemSwapTab = itemSwapTab; - _config = config; - _editor = editor; - _modCaches = modCaches; - _gameData = gameData; - _fileDialog = fileDialog; + _performance = performance; + _itemSwapTab = itemSwapTab; + _config = config; + _editor = editor; + _modCaches = modCaches; + _metaFileManager = metaFileManager; + _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)));