Meta stuff is terrible.

This commit is contained in:
Ottermandias 2023-04-16 13:18:43 +02:00
parent 0186f176d0
commit 1d82e882ed
35 changed files with 1265 additions and 1247 deletions

View file

@ -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,28 +18,36 @@ public struct CmpCache : IDisposable
public CmpCache()
{ }
public void SetFiles(CollectionCacheManager manager)
public void SetFiles(MetaFileManager manager)
=> manager.SetFile(_cmpFile, MetaIndex.HumanCmp);
public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(CollectionCacheManager manager)
public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
=> manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp);
public bool ApplyMod( CollectionCacheManager manager, RspManipulation manip )
public void Reset()
{
if (_cmpFile == null)
return;
_cmpFile.Reset(_cmpManipulations.Select(m => (m.SubRace, m.Attribute)));
_cmpManipulations.Clear();
}
public bool ApplyMod(MetaFileManager manager, RspManipulation manip)
{
_cmpManipulations.AddOrReplace(manip);
_cmpFile ??= new CmpFile();
_cmpFile ??= new CmpFile(manager);
return manip.Apply(_cmpFile);
}
public bool RevertMod( CollectionCacheManager manager, RspManipulation manip )
public bool RevertMod(MetaFileManager manager, RspManipulation manip)
{
if (!_cmpManipulations.Remove(manip))
return false;
var def = CmpFile.GetDefault( manip.SubRace, manip.Attribute );
var def = CmpFile.GetDefault(manager, manip.SubRace, manip.Attribute);
manip = new RspManipulation(manip.SubRace, manip.Attribute, def);
return manip.Apply(_cmpFile!);
}
public void Dispose()

View file

@ -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;
}
/// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary>
internal void ForceFile(Utf8GamePath path, FullPath fullPath)
{
if (CheckFullPath(path, fullPath))
ResolvedFiles[path] = new ModPath(Mod.ForcedFiles, fullPath);
}
/// <summary> Force a file resolve to be removed. </summary>
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;
}
}

View file

@ -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<ModCollection, CollectionCache> _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;
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;
}
/// <summary> Only creates a new cache, does not update an existing one. </summary>
@ -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 _)

View file

@ -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<ExpandedEqdpFile>())
{
@ -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);

View file

@ -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! );

View file

@ -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;
@ -21,7 +22,7 @@ public struct EstCache : IDisposable
public EstCache()
{ }
public void SetFiles(CollectionCacheManager manager)
public void SetFiles(MetaFileManager manager)
{
manager.SetFile(_estFaceFile, MetaIndex.FaceEst);
manager.SetFile(_estHairFile, MetaIndex.HairEst);
@ -29,7 +30,26 @@ public struct EstCache : IDisposable
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
{
@ -40,7 +60,7 @@ public struct EstCache : IDisposable
_ => (null, 0),
};
return idx != 0 ? manager.TemporarilySetFile( file, idx ) : null;
return manager.TemporarilySetFile(file, idx);
}
public void Reset()
@ -52,26 +72,26 @@ public struct EstCache : IDisposable
_estManipulations.Clear();
}
public bool ApplyMod( CollectionCacheManager manager, EstManipulation m )
public bool ApplyMod(MetaFileManager manager, EstManipulation 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);
}
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 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
{
@ -82,7 +102,6 @@ public struct EstCache : IDisposable
_ => throw new ArgumentOutOfRangeException(),
};
return manip.Apply(file);
}
public void Dispose()

View file

@ -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! );
}

View file

@ -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;

View file

@ -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<KeyValuePair<MetaManipulation, IMod>>
public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation, IMod>>
{
private readonly CollectionCacheManager _manager;
private readonly MetaFileManager _manager;
private readonly ModCollection _collection;
private readonly Dictionary<MetaManipulation, IMod> _manipulations = new();
private EqpCache _eqpCache = new();
@ -22,7 +22,7 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
private EstCache _estCache = new();
private GmpCache _gmpCache = new();
private CmpCache _cmpCache = new();
private readonly ImcCache _imcCache;
private readonly ImcCache _imcCache = new();
public bool TryGetValue(MetaManipulation manip, [NotNullWhen(true)] out IMod? mod)
=> _manipulations.TryGetValue(manip, out mod);
@ -39,10 +39,10 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public MetaCache(CollectionCacheManager manager, ModCollection collection)
public MetaCache(MetaFileManager manager, ModCollection collection)
{
_manager = manager;
_imcCache = new ImcCache(collection);
_collection = collection;
if (!_manager.CharacterUtility.Ready)
_manager.CharacterUtility.LoadingFinished += ApplyStoredManipulations;
}
@ -54,17 +54,17 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
_estCache.SetFiles(_manager);
_gmpCache.SetFiles(_manager);
_cmpCache.SetFiles(_manager);
_imcCache.SetFiles(_manager, _collection);
_imcCache.SetFiles(_collection);
}
public void Reset()
{
_eqpCache.Reset(_manager);
_eqdpCache.Reset(_manager);
_estCache.Reset(_manager);
_gmpCache.Reset(_manager);
_cmpCache.Reset(_manager);
_imcCache.Reset(_manager, _collection);
_eqpCache.Reset();
_eqdpCache.Reset();
_estCache.Reset();
_gmpCache.Reset();
_cmpCache.Reset();
_imcCache.Reset(_collection);
_manipulations.Clear();
}
@ -80,6 +80,9 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
_manipulations.Clear();
}
~MetaCache()
=> Dispose();
public bool ApplyMod(MetaManipulation manip, IMod mod)
{
if (_manipulations.ContainsKey(manip))
@ -126,10 +129,60 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
};
}
// Use this when CharacterUtility becomes ready.
/// <summary> Set a single file. </summary>
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;
}
}
/// <summary> Set the currently relevant IMC files for the collection cache. </summary>
public void SetImcFiles()
=> _imcCache.SetFiles(_collection);
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);
/// <summary> Try to obtain a manipulated IMC file. </summary>
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
=> _imcCache.GetImcFile(path, out file);
/// <summary> Use this when CharacterUtility becomes ready. </summary>
private void ApplyStoredManipulations()
{
if (!Penumbra.CharacterUtility.Ready)
if (!_manager.CharacterUtility.Ready)
return;
var loaded = 0;
@ -150,28 +203,8 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
: 0;
}
if (_manager.IsDefault(_collection))
{
SetFiles();
_manager.ResidentResources.Reload();
}
_manager.ApplyDefaultFiles(_collection);
_manager.CharacterUtility.LoadingFinished -= ApplyStoredManipulations;
Penumbra.Log.Debug($"{_collection.AnonymizedName}: Loaded {loaded} delayed meta manipulations.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public unsafe void SetFile(MetaBaseFile? file, MetaIndex metaIndex)
{
if (file == null)
_manager.CharacterUtility.ResetResource(metaIndex);
else
_manager.CharacterUtility.SetResource(metaIndex, (IntPtr)file.Data, file.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public unsafe CharacterUtility.MetaList.MetaReverter TemporarilySetFile(MetaBaseFile? file, MetaIndex metaIndex)
=> file == null
? _manager.CharacterUtility.TemporarilyResetResource(metaIndex)
: _manager.CharacterUtility.TemporarilySetResource(metaIndex, (IntPtr)file.Data, file.Length);
}

View file

@ -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
{
}

View file

@ -16,26 +16,21 @@ public partial class TexToolsMeta
{
// Eqp can only be valid for equipment.
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 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)
{
if (data == null)
{
return;
}
var num = data.Length / 5;
using var reader = new BinaryReader(new MemoryStream(data));
@ -45,47 +40,38 @@ public partial class TexToolsMeta
var gr = (GenderRace)reader.ReadUInt32();
var byteValue = reader.ReadByte();
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 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);
}
}
}
// Deserialize and check Gmp Entries and add them to the list if they are non-default.
private void DeserializeGmpEntry(MetaFileInfo metaFileInfo, byte[]? data)
{
if (data == null)
{
return;
}
using var reader = new BinaryReader(new MemoryStream(data));
var value = (GmpEntry)reader.ReadUInt32();
value.UnknownTotal = reader.ReadByte();
var def = ExpandedGmpFile.GetDefault( 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)
{
if (data == null)
{
return;
}
var num = data.Length / 6;
using var reader = new BinaryReader(new MemoryStream(data));
@ -103,17 +89,13 @@ public partial class TexToolsMeta
_ => (EstManipulation.EstType)0,
};
if (!gr.IsValid() || type == 0)
{
continue;
}
var def = EstFile.GetDefault( type, gr, id );
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.
@ -121,9 +103,7 @@ public partial class TexToolsMeta
private void DeserializeImcEntries(MetaFileInfo metaFileInfo, byte[]? data)
{
if (data == null)
{
return;
}
var num = data.Length / 6;
using var reader = new BinaryReader(new MemoryStream(data));
@ -131,20 +111,20 @@ public partial class TexToolsMeta
ushort i = 0;
try
{
var manip = new ImcManipulation( metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId, metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot,
var manip = new ImcManipulation(metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId,
metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot,
new ImcEntry());
var def = new ImcFile( manip );
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)))
{
var imc = new ImcManipulation( manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, value );
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
value);
if (imc.Valid)
{
MetaManipulations.Add(imc);
}
}
++i;
}

View file

@ -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 );

View file

@ -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 ) );

View file

@ -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.
/// <summary>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.</summary>
public partial class TexToolsMeta
{
// An empty TexToolsMeta.
public static readonly TexToolsMeta Invalid = new(string.Empty, 0);
/// <summary> An empty TexToolsMeta. </summary>
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;
public TexToolsMeta( IGamePathParser parser, byte[] data, bool keepDefault )
private readonly MetaFileManager _metaFileManager;
public TexToolsMeta( MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault )
{
_metaFileManager = metaFileManager;
_keepDefault = keepDefault;
try
{
@ -80,8 +83,9 @@ public partial class TexToolsMeta
}
}
private TexToolsMeta( string filePath, uint version )
private TexToolsMeta( MetaFileManager metaFileManager, string filePath, uint version )
{
_metaFileManager = metaFileManager;
FilePath = filePath;
Version = version;
}

View file

@ -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<IMemorySpace*>)_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);
}

View file

@ -8,8 +8,10 @@ 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.
/// <summary>
/// The human.cmp file contains many character-relevant parameters like color sets.
/// We only support manipulating the racial scaling parameters at the moment.
/// </summary>
public sealed unsafe class CmpFile : MetaBaseFile
{
public static readonly CharacterUtility.InternalIndex InternalIndex =
@ -29,21 +31,19 @@ public sealed unsafe class CmpFile : MetaBaseFile
public void Reset(IEnumerable<(SubRace, RspAttribute)> entries)
{
foreach (var (r, a) in entries)
{
this[ r, a ] = GetDefault( r, a );
}
this[r, a] = GetDefault(Manager, r, a);
}
public CmpFile()
: base( MetaIndex.HumanCmp )
public CmpFile(MetaFileManager manager)
: base(manager, MetaIndex.HumanCmp)
{
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;
var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address;
return *(float*)(data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4);
}

View file

@ -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.
/// <summary>
/// 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.
/// </summary>
public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
{
private const ushort BlockHeaderSize = 2;
@ -44,18 +45,14 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
get
{
if (idx >= Count || idx < 0)
{
throw new IndexOutOfRangeException();
}
return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * idx));
}
set
{
if (idx >= Count || idx < 0)
{
throw new IndexOutOfRangeException();
}
*(ushort*)(Data + DataOffset + EqdpEntrySize * idx) = (ushort)value;
}
@ -73,13 +70,9 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
for (var i = 0; i < BlockCount; ++i)
{
if (controlPtr[i] == CollapsedBlock)
{
MemoryUtility.MemSet(myDataPtr, 0, BlockSize * EqdpEntrySize);
}
else
{
MemoryUtility.MemCpyUnchecked(myDataPtr, dataBasePtr + controlPtr[i], BlockSize * EqdpEntrySize);
}
myControlPtr[i] = (ushort)(i * BlockSize);
myDataPtr += BlockSize;
@ -91,13 +84,11 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public void Reset(IEnumerable<int> entries)
{
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);
@ -113,10 +104,10 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
}
public EqdpEntry GetDefault(int setIdx)
=> GetDefault( Index, 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)
{
@ -125,20 +116,16 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
var blockIdx = setIdx / blockSize;
if (blockIdx >= totalBlockCount)
{
return 0;
}
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));
}
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )
=> GetDefault( CharacterUtility.ReverseIndices[ ( int )CharacterUtilityData.EqdpIdx( raceCode, accessory ) ], setIdx );
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, int setIdx)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setIdx);
}

View file

@ -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.
/// <summary>
/// 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.
/// </summary>
public unsafe class ExpandedEqpGmpBase : MetaBaseFile
{
protected const int BlockSize = 160;
@ -63,7 +65,8 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
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
@ -75,33 +78,27 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
*(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);
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;
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
if (setIdx == 0)
{
setIdx = 1;
}
var blockIdx = setIdx / BlockSize;
if (blockIdx >= NumBlocks)
{
return def;
}
var control = *(ulong*)data;
var blockBit = 1ul << blockIdx;
if ((control & blockBit) == 0)
{
return def;
}
var count = BitOperations.PopCount(control & (blockBit - 1));
var idx = setIdx % BlockSize;
@ -115,8 +112,8 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
public static readonly CharacterUtility.InternalIndex InternalIndex =
CharacterUtility.ReverseIndices[(int)MetaIndex.Eqp];
public ExpandedEqpFile()
: base( false )
public ExpandedEqpFile(MetaFileManager manager)
: base(manager, false)
{ }
public EqpEntry this[int idx]
@ -126,25 +123,21 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
}
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)
{
var blockPtr = (ulong*)(Data + idx * BlockSize * EntrySize);
var endPtr = blockPtr + BlockSize;
for (var ptr = blockPtr; ptr < endPtr; ++ptr)
{
*ptr = (ulong)Eqp.DefaultEntry;
}
}
public void Reset(IEnumerable<int> entries)
{
foreach (var entry in entries)
{
this[ entry ] = GetDefault( entry );
}
this[entry] = GetDefault(Manager, entry);
}
public IEnumerator<EqpEntry> GetEnumerator()
@ -162,8 +155,8 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
public static readonly CharacterUtility.InternalIndex InternalIndex =
CharacterUtility.ReverseIndices[(int)MetaIndex.Gmp];
public ExpandedGmpFile()
: base( true )
public ExpandedGmpFile(MetaFileManager manager)
: base(manager, true)
{ }
public GmpEntry this[int idx]
@ -172,15 +165,13 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
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)
{
foreach (var entry in entries)
{
this[ entry ] = GetDefault( entry );
}
this[entry] = GetDefault(Manager, entry);
}
public IEnumerator<GmpEntry> GetEnumerator()

View file

@ -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]
/// <summary>
/// EST Structure:
/// 1x [NumEntries : UInt32]
/// Apparently entries need to be sorted.
/// #NumEntries x [SetId : UInt16] [RaceId : UInt16]
/// #NumEntries x [SkeletonId : UInt16]
/// </summary>
public sealed unsafe class EstFile : MetaBaseFile
{
private const ushort EntryDescSize = 4;
@ -39,9 +41,7 @@ public sealed unsafe class EstFile : MetaBaseFile
{
var (idx, exists) = FindEntry(genderRace, setId);
if (!exists)
{
return 0;
}
return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
}
@ -51,29 +51,21 @@ public sealed unsafe class EstFile : MetaBaseFile
private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId)
{
if (Length < Size + EntryDescSize + EntrySize)
{
ResizeResources(Length + IncreaseSize);
}
var control = (Info*)(Data + 4);
var entries = (ushort*)(control + Count);
for (var i = Count - 1; i >= idx; --i)
{
entries[i + 3] = entries[i];
}
entries[idx + 2] = skeletonId;
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];
}
control[idx] = new Info(genderRace, setId);
@ -86,19 +78,13 @@ public sealed unsafe class EstFile : MetaBaseFile
var entries = (ushort*)(control + Count);
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 = idx; i < Count - 1; ++i)
{
entries[i - 2] = entries[i + 1];
}
entries[Count - 3] = 0;
entries[Count - 2] = 0;
@ -144,9 +130,7 @@ public sealed unsafe class EstFile : MetaBaseFile
{
var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx);
if (value == skeletonId)
{
return EstEntryChange.Unchanged;
}
if (skeletonId == 0)
{
@ -159,9 +143,7 @@ public sealed unsafe class EstFile : MetaBaseFile
}
if (skeletonId == 0)
{
return EstEntryChange.Unchanged;
}
InsertEntry(idx, genderRace, setId, skeletonId);
return EstEntryChange.Added;
@ -175,8 +157,8 @@ public sealed unsafe class EstFile : MetaBaseFile
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);
@ -184,25 +166,23 @@ public sealed unsafe class EstFile : MetaBaseFile
}
public ushort GetDefault(GenderRace genderRace, ushort setId)
=> GetDefault( Index, genderRace, 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 data = (byte*)manager.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)
{
return 0;
}
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 );
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, ushort setId)
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId);
}

View file

@ -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.
/// <summary>
/// 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.
/// </summary>
public unsafe class EvpFile : MetaBaseFile
{
public const int FlagArraySize = 512;
@ -40,23 +41,17 @@ public unsafe class EvpFile : MetaBaseFile
public EvpFlag Flag(ushort modelSet, int arrayIndex)
{
if (arrayIndex is >= FlagArraySize or < 0)
{
return EvpFlag.None;
}
var ids = ModelSetIds;
for (var i = 0; i < ids.Length; ++i)
{
var model = ids[i];
if (model < modelSet)
{
continue;
}
if (model > modelSet)
{
break;
}
return Flags(i)[arrayIndex];
}
@ -64,7 +59,7 @@ public unsafe class EvpFile : MetaBaseFile
return EvpFlag.None;
}
public EvpFile()
: base( ( MetaIndex )1 ) // TODO: Name
public EvpFile(MetaFileManager manager)
: base(manager, (MetaIndex)1) // TODO: Name
{ }
}

View file

@ -1,6 +1,5 @@
using System;
using System.Numerics;
using Newtonsoft.Json;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
@ -55,9 +54,7 @@ public unsafe class ImcFile : MetaBaseFile
{
var flag = 1 << partIdx;
if ((PartMask(data) & flag) == 0 || variantIdx > CountInternal(data))
{
return null;
}
var numParts = BitOperations.PopCount(PartMask(data));
var ptr = (ImcEntry*)(data + PreambleSize);
@ -90,9 +87,7 @@ public unsafe class ImcFile : MetaBaseFile
public bool EnsureVariantCount(int numVariants)
{
if (numVariants <= Count)
{
return true;
}
var oldCount = Count;
*(ushort*)Data = (ushort)numVariants;
@ -105,9 +100,7 @@ public unsafe class ImcFile : MetaBaseFile
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.");
return true;
@ -116,9 +109,7 @@ public unsafe class ImcFile : MetaBaseFile
public bool SetEntry(int partIdx, int variantIdx, ImcEntry entry)
{
if (partIdx >= NumParts)
{
return false;
}
EnsureVariantCount(variantIdx);
@ -130,9 +121,7 @@ public unsafe class ImcFile : MetaBaseFile
}
if (variantPtr->Equals(entry))
{
return false;
}
*variantPtr = entry;
return true;
@ -149,15 +138,13 @@ public unsafe class ImcFile : MetaBaseFile
}
}
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() );
var file = manager.GameData.GetFile(Path.ToString());
if (file == null)
{
throw new ImcException(manip, Path);
}
fixed (byte* ptr = file.Data)
{
@ -167,29 +154,25 @@ public unsafe class ImcFile : MetaBaseFile
}
}
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)
{
throw new Exception();
}
fixed (byte* ptr = file.Data)
{
var entry = VariantPtr(ptr, PartIndex(slot), variantIdx);
if( entry != null )
{
if (entry == null)
return new ImcEntry();
exists = true;
return *entry;
}
return new ImcEntry();
}
}
public void Replace(ResourceHandle* resource)

View file

@ -8,54 +8,53 @@ namespace Penumbra.Meta.Files;
public unsafe class MetaBaseFile : IDisposable
{
protected readonly MetaFileManager Manager;
public byte* Data { get; private set; }
public int Length { get; private set; }
public CharacterUtility.InternalIndex Index { get; }
public MetaBaseFile( MetaIndex idx )
=> Index = CharacterUtility.ReverseIndices[ ( int )idx ];
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.
/// <summary> Reset to default values. </summary>
public virtual void Reset()
{ }
// Obtain memory.
/// <summary> Obtain memory. </summary>
protected void AllocateData(int length)
{
Length = length;
Data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( length );
Data = (byte*)Manager.AllocateFileMemory(length);
if (length > 0)
{
GC.AddMemoryPressure(length);
}
}
// Free memory.
/// <summary> Free memory. </summary>
protected void ReleaseUnmanagedResources()
{
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.
/// <summary> Resize memory while retaining data. </summary>
protected void ResizeResources(int newLength)
{
if (newLength == Length)
{
return;
}
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
var data = (byte*)Manager.AllocateFileMemory((ulong)newLength);
if (newLength > Length)
{
MemoryUtility.MemCpyUnchecked(data, Data, Length);
@ -72,7 +71,7 @@ public unsafe class MetaBaseFile : IDisposable
Length = newLength;
}
// Manually free memory.
/// <summary> Manually free memory. </summary>
public void Dispose()
{
ReleaseUnmanagedResources();

View file

@ -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();
}
/// <summary>
/// Allocate in the games space for file storage.
/// We only need this if using any meta file.
/// </summary>
[Signature(Sigs.GetFileSpace)]
private readonly nint _getFileSpaceAddress = nint.Zero;
public IMemorySpace* GetFileSpace()
=> ((delegate* unmanaged<IMemorySpace*>)_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);
}

View file

@ -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 );
}
}

View file

@ -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;
@ -21,32 +22,42 @@ public static class EquipmentSwap
private static EquipSlot[] ConvertSlots(EquipSlot slot, bool rFinger, bool lFinger)
{
if (slot != EquipSlot.RFinger)
return new[]
{
return new[] { slot };
}
slot,
};
return rFinger
? lFinger
? new[] { EquipSlot.RFinger, EquipSlot.LFinger }
: new[] { EquipSlot.RFinger }
? new[]
{
EquipSlot.RFinger,
EquipSlot.LFinger,
}
: new[]
{
EquipSlot.RFinger,
}
: lFinger
? new[] { EquipSlot.LFinger }
? new[]
{
EquipSlot.LFinger,
}
: Array.Empty<EquipSlot>();
}
public static Item[] CreateTypeSwap( IObjectIdentifier identifier, List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips,
public static Item[] CreateTypeSwap(MetaFileManager manager, IObjectIdentifier identifier, List<Swap> swaps,
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> 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())
{
throw new ItemSwap.InvalidItemTypeException();
}
var ( imcFileFrom, variants, affectedItems ) = GetVariants( identifier, slotFrom, idFrom, idTo, variantFrom );
var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom);
var imcManip = new ImcManipulation(slotTo, variantTo, idTo.Value, default);
var imcFileTo = new ImcFile( imcManip );
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;
@ -61,18 +72,14 @@ public static class EquipmentSwap
}
if (CharacterUtilityData.EqdpIdx(gr, true) < 0)
{
continue;
}
try
{
var eqdp = CreateEqdp( redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo );
var eqdp = CreateEqdp(manager, redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo);
if (eqdp != null)
{
swaps.Add(eqdp);
}
}
catch (ItemSwap.MissingFileException e)
{
switch (gr)
@ -90,42 +97,37 @@ public static class EquipmentSwap
foreach (var variant in variants)
{
var imc = CreateImc( redirections, manips, slotFrom, slotTo, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo );
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,
public static Item[] CreateItemSwap(MetaFileManager manager, IObjectIdentifier identifier, List<Swap> swaps,
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> 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)
{
throw new ItemSwap.InvalidItemTypeException();
}
var eqp = CreateEqp( manips, slotFrom, idFrom, idTo );
var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo);
if (eqp != null)
{
swaps.Add(eqp);
}
var gmp = CreateGmp( manips, slotFrom, idFrom, idTo );
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 imcFileFrom, var variants, affectedItems ) = GetVariants( identifier, slot, idFrom, idTo, variantFrom );
(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( imcManip );
var imcFileTo = new ImcFile(manager, imcManip);
var isAccessory = slot.IsAccessory();
var estType = slot switch
@ -149,26 +151,20 @@ public static class EquipmentSwap
}
if (CharacterUtilityData.EqdpIdx(gr, isAccessory) < 0)
{
continue;
}
try
{
var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo );
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 );
var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl);
if (est != null)
{
swaps.Add(est);
}
}
catch (ItemSwap.MissingFileException e)
{
switch (gr)
@ -186,7 +182,7 @@ public static class EquipmentSwap
foreach (var variant in variants)
{
var imc = CreateImc( redirections, manips, slot, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo );
var imc = CreateImc(manager, redirections, manips, slot, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo);
swaps.Add(imc);
}
}
@ -194,20 +190,25 @@ public static class EquipmentSwap
return affectedItems;
}
public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, GenderRace gr, SetId idFrom,
public static MetaSwap? CreateEqdp(MetaFileManager manager, 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,
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
Func<MetaManipulation, MetaManipulation> 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 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 );
var mdl = CreateMdl(manager, redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo);
meta.ChildSwaps.Add(mdl);
}
else if (!ownMtrl && meta.SwapAppliedIsDefault)
@ -218,23 +219,25 @@ 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<Utf8GamePath, FullPath> 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<Utf8GamePath, FullPath> 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 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 mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo);
foreach (ref var fileName in mdl.AsMdl()!.Materials.AsSpan())
{
var mtrl = CreateMtrl( redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged );
var mtrl = CreateMtrl(manager, redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged);
if (mtrl != null)
{
mdl.ChildSwaps.Add(mtrl);
}
}
return mdl;
}
@ -243,24 +246,26 @@ public static class EquipmentSwap
{
slot = ((EquipSlot)i.EquipSlotCategory.Row).ToSlot();
if (!slot.IsEquipmentPiece())
{
throw new ItemSwap.InvalidItemTypeException();
}
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 imc = new ImcFile(manager, entry);
Item[] items;
byte[] variants;
if (idFrom.Value == idTo.Value)
{
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
variants = new[] { variantFrom };
variants = new[]
{
variantFrom,
};
}
else
{
@ -273,23 +278,24 @@ public static class EquipmentSwap
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<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
SetId idTo)
{
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 );
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,
public static MetaSwap CreateImc(MetaFileManager manager, 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 );
=> 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,
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> 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);
@ -298,17 +304,13 @@ public static class EquipmentSwap
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 );
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 );
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,70 +318,69 @@ 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<Utf8GamePath, FullPath> redirections, byte decalId)
{
if (decalId == 0)
{
return null;
}
var decalPath = GamePaths.Equipment.Decal.Path(decalId);
return FileSwap.CreateSwap( ResourceType.Tex, redirections, decalPath, decalPath );
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<Utf8GamePath, FullPath> redirections, SetId idFrom, SetId idTo, byte vfxId)
{
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 avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo);
foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan())
{
var atex = CreateAtex( redirections, ref filePath, ref avfx.DataWasChanged );
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<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
SetId idTo)
{
if (slot.IsAccessory())
{
return null;
}
var eqpValueFrom = ExpandedEqpFile.GetDefault( idFrom.Value );
var eqpValueTo = ExpandedEqpFile.GetDefault( idTo.Value );
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,
public static FileSwap? CreateMtrl(MetaFileManager manager, 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 );
=> 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,
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> 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}"))
{
return null;
}
var folderTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idTo, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idTo, variantTo );
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 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}";
@ -390,23 +391,25 @@ public static class EquipmentSwap
dataWasChanged = true;
}
var mtrl = FileSwap.CreateSwap( ResourceType.Mtrl, redirections, pathFrom, pathTo );
var shpk = CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged );
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())
{
var tex = CreateTex( redirections, prefix, slotFrom, slotTo, idFrom, idTo, ref texture, ref mtrl.DataWasChanged );
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<Utf8GamePath, FullPath> 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<Utf8GamePath, FullPath> 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;
@ -429,21 +432,21 @@ public static class EquipmentSwap
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);
}
public static FileSwap CreateAtex( Func< Utf8GamePath, FullPath > redirections, ref string filePath, ref bool dataWasChanged )
public static FileSwap CreateAtex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, ref string filePath, ref bool dataWasChanged)
{
var oldPath = filePath;
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);
}
}

View file

@ -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 );
}
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
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 )

View file

@ -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 )

View file

@ -7,6 +7,7 @@ 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;
@ -116,7 +117,8 @@ public sealed class FileSwap : Swap
/// <param name="swapToRequest">The unmodded path to the file the game is supposed to load instead.</param>
/// <param name="swap">A full swap container with the actual file in memory.</param>
/// <returns>True if everything could be read correctly, false otherwise.</returns>
public static FileSwap CreateSwap( ResourceType type, Func< Utf8GamePath, FullPath > redirections, string swapFromRequest, string swapToRequest,
public static FileSwap CreateSwap(MetaFileManager manager, ResourceType type, Func<Utf8GamePath, FullPath> redirections,
string swapFromRequest, string swapToRequest,
string? swapFromPreChange = null)
{
var swap = new FileSwap
@ -135,45 +137,21 @@ public sealed class FileSwap : Swap
|| 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.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;
}
/// <summary>
/// Convert a single file redirection to use the file name and extension given by type and the files SHA256 hash, if possible.
/// </summary>
/// <param name="redirections">The set of redirections that need to be considered.</param>
/// <param name="path">The in- and output path for a file</param>
/// <param name="dataWasChanged">Will be set to true if <paramref name="path"/> was changed.</param>
/// <param name="swap">Will be updated.</param>
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;
}
}

View file

@ -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);
}
}
}

View file

@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using Penumbra.Import;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes;
@ -71,37 +72,26 @@ namespace Penumbra.Mods;
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>()));
}
}
}
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>()!));
}
}
}
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 ) )
{
foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
.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.
@ -120,12 +110,12 @@ namespace Penumbra.Mods;
{
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}" );
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);
}
@ -133,12 +123,12 @@ namespace Penumbra.Mods;
{
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}" );
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);
@ -157,9 +147,7 @@ namespace Penumbra.Mods;
internal static void DeleteDeleteList(IEnumerable<string> deleteList, bool delete)
{
if (!delete)
{
return;
}
foreach (var file in deleteList)
{
@ -174,9 +162,9 @@ namespace Penumbra.Mods;
}
}
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)
{
@ -193,13 +181,11 @@ namespace Penumbra.Mods;
}
if (test)
{
TestMetaWriting( files );
}
TestMetaWriting(manager, files);
}
[Conditional("DEBUG")]
private void TestMetaWriting( Dictionary< string, byte[] > files )
private void TestMetaWriting(MetaFileManager manager, Dictionary<string, byte[]> files)
{
var meta = new HashSet<MetaManipulation>(Manipulations.Count);
foreach (var (file, data) in files)
@ -207,8 +193,8 @@ namespace Penumbra.Mods;
try
{
var x = file.EndsWith("rgsp")
? TexToolsMeta.FromRgspFile( file, data, Penumbra.Config.KeepDefaultMetaChanges )
: new TexToolsMeta( Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges );
? TexToolsMeta.FromRgspFile(manager, file, data, Penumbra.Config.KeepDefaultMetaChanges)
: new TexToolsMeta(manager, Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges);
meta.UnionWith(x.MetaManipulations);
}
catch
@ -221,20 +207,14 @@ namespace Penumbra.Mods;
{
Penumbra.Log.Information("Meta Sets do not equal.");
foreach (var (m1, m2) in Manipulations.Zip(meta))
{
Penumbra.Log.Information($"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}");
}
foreach (var m in Manipulations.Skip(meta.Count))
{
Penumbra.Log.Information($"{m} {m.EntryToString()} ");
}
foreach (var m in meta.Skip(Manipulations.Count))
{
Penumbra.Log.Information($"{m} {m.EntryToString()} ");
}
}
else
{
Penumbra.Log.Information("Meta Sets are equal.");

View file

@ -28,6 +28,7 @@ using Penumbra.Interop.Services;
using Penumbra.Mods.Manager;
using Penumbra.Collections.Manager;
using Penumbra.Mods;
using Penumbra.Meta;
namespace Penumbra;

View file

@ -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<GameEventManager>()
.AddSingleton<FrameworkManager>()
.AddSingleton<MetaFileManager>()
.AddSingleton<CutsceneService>()
.AddSingleton<CharacterUtility>()
.AddSingleton<ResourceManagerService>()
@ -78,10 +78,6 @@ public class PenumbraNew
.AddSingleton<FontReloader>()
.AddSingleton<RedrawService>();
// Add PathResolver
services.AddSingleton<CutsceneService>()
.AddSingleton<IdentifiedCollectionCache>();
// Add Configuration
services.AddTransient<ConfigMigrationService>()
.AddSingleton<Configuration>();
@ -109,7 +105,8 @@ public class PenumbraNew
// Add Resource services
services.AddSingleton<ResourceLoader>()
.AddSingleton<ResourceWatcher>()
.AddSingleton<ResourceTreeFactory>();
.AddSingleton<ResourceTreeFactory>()
.AddSingleton<MetaFileManager>();
// Add Path Resolver
services.AddSingleton<AnimationHookService>()

View file

@ -15,6 +15,7 @@ using Penumbra.Collections;
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;
@ -30,16 +31,18 @@ public class ItemSwapTab : IDisposable, ITab
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<SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo)>
{
@ -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;
@ -773,6 +776,7 @@ public class ItemSwapTab : IDisposable, ITab
{
if (type is ModOptionChangeType.PrepareChange or ModOptionChangeType.GroupAdded or ModOptionChangeType.OptionAdded || mod != _mod)
return;
_swapData.LoadMod(_mod, _modSettings);
UpdateOption();
_dirty = true;

View file

@ -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<T>(IReadOnlyCollection<T> items, string label, int numColumns, Action<T, ModEditor, Vector2> draw,
Action<ModEditor, Vector2> drawNew)
/// <summary> The headers for the different meta changes all have basically the same structure for different types.</summary>
private void DrawEditHeader<T>(IReadOnlyCollection<T> items, string label, int numColumns, Action<MetaFileManager, T, ModEditor, Vector2> draw,
Action<MetaFileManager, ModEditor, Vector2> 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)
/// <summary> Convert throwing to null-return if the file does not exist. </summary>
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,

View file

@ -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,7 +495,7 @@ 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;
@ -501,6 +503,7 @@ public partial class ModEditWindow : Window, IDisposable
_config = config;
_editor = editor;
_modCaches = modCaches;
_metaFileManager = metaFileManager;
_gameData = gameData;
_fileDialog = fileDialog;
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _fileDialog, "Materials", ".mtrl",