mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Meta stuff is terrible.
This commit is contained in:
parent
0186f176d0
commit
1d82e882ed
35 changed files with 1265 additions and 1247 deletions
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
|
|
@ -10,34 +12,42 @@ namespace Penumbra.Collections.Cache;
|
|||
|
||||
public struct CmpCache : IDisposable
|
||||
{
|
||||
private CmpFile? _cmpFile = null;
|
||||
private readonly List< RspManipulation > _cmpManipulations = new();
|
||||
|
||||
private CmpFile? _cmpFile = null;
|
||||
private readonly List<RspManipulation> _cmpManipulations = new();
|
||||
|
||||
public CmpCache()
|
||||
{}
|
||||
{ }
|
||||
|
||||
public void SetFiles(CollectionCacheManager manager)
|
||||
=> manager.SetFile( _cmpFile, MetaIndex.HumanCmp );
|
||||
public void SetFiles(MetaFileManager manager)
|
||||
=> manager.SetFile(_cmpFile, MetaIndex.HumanCmp);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(CollectionCacheManager manager)
|
||||
=> manager.TemporarilySetFile( _cmpFile, MetaIndex.HumanCmp );
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager)
|
||||
=> manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp);
|
||||
|
||||
public bool ApplyMod( CollectionCacheManager manager, RspManipulation manip )
|
||||
public void Reset()
|
||||
{
|
||||
_cmpManipulations.AddOrReplace( manip );
|
||||
_cmpFile ??= new CmpFile();
|
||||
return manip.Apply( _cmpFile );
|
||||
if (_cmpFile == null)
|
||||
return;
|
||||
|
||||
_cmpFile.Reset(_cmpManipulations.Select(m => (m.SubRace, m.Attribute)));
|
||||
_cmpManipulations.Clear();
|
||||
}
|
||||
|
||||
public bool RevertMod( CollectionCacheManager manager, RspManipulation manip )
|
||||
public bool ApplyMod(MetaFileManager manager, RspManipulation manip)
|
||||
{
|
||||
_cmpManipulations.AddOrReplace(manip);
|
||||
_cmpFile ??= new CmpFile(manager);
|
||||
return manip.Apply(_cmpFile);
|
||||
}
|
||||
|
||||
public bool RevertMod(MetaFileManager manager, RspManipulation manip)
|
||||
{
|
||||
if (!_cmpManipulations.Remove(manip))
|
||||
return false;
|
||||
|
||||
var def = CmpFile.GetDefault( manip.SubRace, manip.Attribute );
|
||||
manip = new RspManipulation( manip.SubRace, manip.Attribute, def );
|
||||
return manip.Apply( _cmpFile! );
|
||||
|
||||
var def = CmpFile.GetDefault(manager, manip.SubRace, manip.Attribute);
|
||||
manip = new RspManipulation(manip.SubRace, manip.Attribute, def);
|
||||
return manip.Apply(_cmpFile!);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -46,4 +56,4 @@ public struct CmpCache : IDisposable
|
|||
_cmpFile = null;
|
||||
_cmpManipulations.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
_framework = framework;
|
||||
_communicator = communicator;
|
||||
_tempMods = tempMods;
|
||||
_modStorage = modStorage;
|
||||
MetaFileManager = metaFileManager;
|
||||
_active = active;
|
||||
|
||||
_communicator.CollectionChange.Subscribe(OnCollectionChange);
|
||||
_communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100);
|
||||
|
|
@ -61,8 +52,8 @@ public class CollectionCacheManager : IDisposable
|
|||
_communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange);
|
||||
CreateNecessaryCaches();
|
||||
|
||||
if (!CharacterUtility.Ready)
|
||||
CharacterUtility.LoadingFinished += IncrementCounters;
|
||||
if (!MetaFileManager.CharacterUtility.Ready)
|
||||
MetaFileManager.CharacterUtility.LoadingFinished += IncrementCounters;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -74,7 +65,7 @@ public class CollectionCacheManager : IDisposable
|
|||
_communicator.ModOptionChanged.Unsubscribe(OnModOptionChange);
|
||||
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
|
||||
_communicator.CollectionInheritanceChanged.Unsubscribe(OnCollectionInheritanceChange);
|
||||
CharacterUtility.LoadingFinished -= IncrementCounters;
|
||||
MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters;
|
||||
}
|
||||
|
||||
/// <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 _)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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! );
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using OtterGui.Filesystem;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
|
|
@ -16,31 +17,50 @@ public struct EstCache : IDisposable
|
|||
private EstFile? _estBodyFile = null;
|
||||
private EstFile? _estHeadFile = null;
|
||||
|
||||
private readonly List< EstManipulation > _estManipulations = new();
|
||||
|
||||
public EstCache()
|
||||
{}
|
||||
private readonly List<EstManipulation> _estManipulations = new();
|
||||
|
||||
public void SetFiles(CollectionCacheManager manager)
|
||||
public EstCache()
|
||||
{ }
|
||||
|
||||
public void SetFiles(MetaFileManager manager)
|
||||
{
|
||||
manager.SetFile( _estFaceFile, MetaIndex.FaceEst );
|
||||
manager.SetFile( _estHairFile, MetaIndex.HairEst );
|
||||
manager.SetFile( _estBodyFile, MetaIndex.BodyEst );
|
||||
manager.SetFile( _estHeadFile, MetaIndex.HeadEst );
|
||||
manager.SetFile(_estFaceFile, MetaIndex.FaceEst);
|
||||
manager.SetFile(_estHairFile, MetaIndex.HairEst);
|
||||
manager.SetFile(_estBodyFile, MetaIndex.BodyEst);
|
||||
manager.SetFile(_estHeadFile, MetaIndex.HeadEst);
|
||||
}
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter? TemporarilySetFiles(CollectionCacheManager manager, EstManipulation.EstType type)
|
||||
public void SetFile(MetaFileManager manager, MetaIndex index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case MetaIndex.FaceEst:
|
||||
manager.SetFile(_estFaceFile, MetaIndex.FaceEst);
|
||||
break;
|
||||
case MetaIndex.HairEst:
|
||||
manager.SetFile(_estHairFile, MetaIndex.HairEst);
|
||||
break;
|
||||
case MetaIndex.BodyEst:
|
||||
manager.SetFile(_estBodyFile, MetaIndex.BodyEst);
|
||||
break;
|
||||
case MetaIndex.HeadEst:
|
||||
manager.SetFile(_estHeadFile, MetaIndex.HeadEst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetFiles(MetaFileManager manager, EstManipulation.EstType type)
|
||||
{
|
||||
var (file, idx) = type switch
|
||||
{
|
||||
EstManipulation.EstType.Face => ( _estFaceFile, MetaIndex.FaceEst ),
|
||||
EstManipulation.EstType.Hair => ( _estHairFile, MetaIndex.HairEst ),
|
||||
EstManipulation.EstType.Body => ( _estBodyFile, MetaIndex.BodyEst ),
|
||||
EstManipulation.EstType.Head => ( _estHeadFile, MetaIndex.HeadEst ),
|
||||
_ => ( null, 0 ),
|
||||
EstManipulation.EstType.Face => (_estFaceFile, MetaIndex.FaceEst),
|
||||
EstManipulation.EstType.Hair => (_estHairFile, MetaIndex.HairEst),
|
||||
EstManipulation.EstType.Body => (_estBodyFile, MetaIndex.BodyEst),
|
||||
EstManipulation.EstType.Head => (_estHeadFile, MetaIndex.HeadEst),
|
||||
_ => (null, 0),
|
||||
};
|
||||
|
||||
return idx != 0 ? manager.TemporarilySetFile( file, idx ) : null;
|
||||
|
||||
return manager.TemporarilySetFile(file, idx);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
|
|
@ -52,27 +72,27 @@ public struct EstCache : IDisposable
|
|||
_estManipulations.Clear();
|
||||
}
|
||||
|
||||
public bool ApplyMod( CollectionCacheManager manager, EstManipulation m )
|
||||
public bool ApplyMod(MetaFileManager manager, EstManipulation m)
|
||||
{
|
||||
_estManipulations.AddOrReplace( m );
|
||||
_estManipulations.AddOrReplace(m);
|
||||
var file = m.Slot switch
|
||||
{
|
||||
EstManipulation.EstType.Hair => _estHairFile ??= new EstFile( EstManipulation.EstType.Hair ),
|
||||
EstManipulation.EstType.Face => _estFaceFile ??= new EstFile( EstManipulation.EstType.Face ),
|
||||
EstManipulation.EstType.Body => _estBodyFile ??= new EstFile( EstManipulation.EstType.Body ),
|
||||
EstManipulation.EstType.Head => _estHeadFile ??= new EstFile( EstManipulation.EstType.Head ),
|
||||
EstManipulation.EstType.Hair => _estHairFile ??= new EstFile(manager, EstManipulation.EstType.Hair),
|
||||
EstManipulation.EstType.Face => _estFaceFile ??= new EstFile(manager, EstManipulation.EstType.Face),
|
||||
EstManipulation.EstType.Body => _estBodyFile ??= new EstFile(manager, EstManipulation.EstType.Body),
|
||||
EstManipulation.EstType.Head => _estHeadFile ??= new EstFile(manager, EstManipulation.EstType.Head),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
return m.Apply( file );
|
||||
return m.Apply(file);
|
||||
}
|
||||
|
||||
public bool RevertMod( CollectionCacheManager manager, EstManipulation m )
|
||||
public bool RevertMod(MetaFileManager manager, EstManipulation m)
|
||||
{
|
||||
if (!_estManipulations.Remove(m))
|
||||
return false;
|
||||
|
||||
var def = EstFile.GetDefault( m.Slot, Names.CombinedRace( m.Gender, m.Race ), m.SetId );
|
||||
var manip = new EstManipulation( m.Gender, m.Race, m.Slot, m.SetId, def );
|
||||
var def = EstFile.GetDefault(manager, m.Slot, Names.CombinedRace(m.Gender, m.Race), m.SetId);
|
||||
var manip = new EstManipulation(m.Gender, m.Race, m.Slot, m.SetId, def);
|
||||
var file = m.Slot switch
|
||||
{
|
||||
EstManipulation.EstType.Hair => _estHairFile!,
|
||||
|
|
@ -81,8 +101,7 @@ public struct EstCache : IDisposable
|
|||
EstManipulation.EstType.Head => _estHeadFile!,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
return manip.Apply( file );
|
||||
|
||||
return manip.Apply(file);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -97,4 +116,4 @@ public struct EstCache : IDisposable
|
|||
_estHeadFile = null;
|
||||
_estManipulations.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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! );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
_manager = manager;
|
||||
_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))
|
||||
|
|
@ -125,11 +128,61 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
|
|||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
// Use this when CharacterUtility becomes ready.
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetEqpFile()
|
||||
=> _eqpCache.TemporarilySetFiles(_manager);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetEqdpFile(GenderRace genderRace, bool accessory)
|
||||
=> _eqdpCache.TemporarilySetFiles(_manager, genderRace, accessory);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetGmpFile()
|
||||
=> _gmpCache.TemporarilySetFiles(_manager);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetCmpFile()
|
||||
=> _cmpCache.TemporarilySetFiles(_manager);
|
||||
|
||||
public CharacterUtility.MetaList.MetaReverter TemporarilySetEstFile(EstManipulation.EstType type)
|
||||
=> _estCache.TemporarilySetFiles(_manager, type);
|
||||
|
||||
|
||||
/// <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;
|
||||
|
|
@ -149,29 +202,9 @@ public struct MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation
|
|||
? 1
|
||||
: 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ using System.IO;
|
|||
using Lumina.Extensions;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
|
|
@ -12,148 +12,128 @@ namespace Penumbra.Import;
|
|||
public partial class TexToolsMeta
|
||||
{
|
||||
// Deserialize and check Eqp Entries and add them to the list if they are non-default.
|
||||
private void DeserializeEqpEntry( MetaFileInfo metaFileInfo, byte[]? data )
|
||||
private void DeserializeEqpEntry(MetaFileInfo metaFileInfo, byte[]? data)
|
||||
{
|
||||
// Eqp can only be valid for equipment.
|
||||
if( data == null || !metaFileInfo.EquipSlot.IsEquipment() )
|
||||
{
|
||||
if (data == null || !metaFileInfo.EquipSlot.IsEquipment())
|
||||
return;
|
||||
}
|
||||
|
||||
var value = Eqp.FromSlotAndBytes( metaFileInfo.EquipSlot, data );
|
||||
var def = new EqpManipulation( ExpandedEqpFile.GetDefault( metaFileInfo.PrimaryId ), metaFileInfo.EquipSlot, metaFileInfo.PrimaryId );
|
||||
var manip = new EqpManipulation( value, metaFileInfo.EquipSlot, metaFileInfo.PrimaryId );
|
||||
if( _keepDefault || def.Entry != manip.Entry )
|
||||
{
|
||||
MetaManipulations.Add( manip );
|
||||
}
|
||||
var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data);
|
||||
var def = new EqpManipulation(ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId), metaFileInfo.EquipSlot,
|
||||
metaFileInfo.PrimaryId);
|
||||
var manip = new EqpManipulation(value, metaFileInfo.EquipSlot, metaFileInfo.PrimaryId);
|
||||
if (_keepDefault || def.Entry != manip.Entry)
|
||||
MetaManipulations.Add(manip);
|
||||
}
|
||||
|
||||
// Deserialize and check Eqdp Entries and add them to the list if they are non-default.
|
||||
private void DeserializeEqdpEntries( MetaFileInfo metaFileInfo, byte[]? data )
|
||||
private void DeserializeEqdpEntries(MetaFileInfo metaFileInfo, byte[]? data)
|
||||
{
|
||||
if( data == null )
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var num = data.Length / 5;
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
for( var i = 0; i < num; ++i )
|
||||
using var reader = new BinaryReader(new MemoryStream(data));
|
||||
for (var i = 0; i < num; ++i)
|
||||
{
|
||||
// Use the SE gender/race code.
|
||||
var gr = ( GenderRace )reader.ReadUInt32();
|
||||
var gr = (GenderRace)reader.ReadUInt32();
|
||||
var byteValue = reader.ReadByte();
|
||||
if( !gr.IsValid() || !metaFileInfo.EquipSlot.IsEquipment() && !metaFileInfo.EquipSlot.IsAccessory() )
|
||||
{
|
||||
if (!gr.IsValid() || !metaFileInfo.EquipSlot.IsEquipment() && !metaFileInfo.EquipSlot.IsAccessory())
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = Eqdp.FromSlotAndBits( metaFileInfo.EquipSlot, ( byteValue & 1 ) == 1, ( byteValue & 2 ) == 2 );
|
||||
var def = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId ),
|
||||
var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2);
|
||||
var def = new EqdpManipulation(
|
||||
ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId),
|
||||
metaFileInfo.EquipSlot,
|
||||
gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId );
|
||||
var manip = new EqdpManipulation( value, metaFileInfo.EquipSlot, gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId );
|
||||
if( _keepDefault || def.Entry != manip.Entry )
|
||||
{
|
||||
MetaManipulations.Add( manip );
|
||||
}
|
||||
gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId);
|
||||
var manip = new EqdpManipulation(value, metaFileInfo.EquipSlot, gr.Split().Item1, gr.Split().Item2, metaFileInfo.PrimaryId);
|
||||
if (_keepDefault || def.Entry != manip.Entry)
|
||||
MetaManipulations.Add(manip);
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize and check Gmp Entries and add them to the list if they are non-default.
|
||||
private void DeserializeGmpEntry( MetaFileInfo metaFileInfo, byte[]? data )
|
||||
private void DeserializeGmpEntry(MetaFileInfo metaFileInfo, byte[]? data)
|
||||
{
|
||||
if( data == null )
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
}
|
||||
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
var value = ( GmpEntry )reader.ReadUInt32();
|
||||
using var reader = new BinaryReader(new MemoryStream(data));
|
||||
var value = (GmpEntry)reader.ReadUInt32();
|
||||
value.UnknownTotal = reader.ReadByte();
|
||||
var def = ExpandedGmpFile.GetDefault( metaFileInfo.PrimaryId );
|
||||
if( _keepDefault || value != def )
|
||||
{
|
||||
MetaManipulations.Add( new GmpManipulation( value, metaFileInfo.PrimaryId ) );
|
||||
}
|
||||
var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId);
|
||||
if (_keepDefault || value != def)
|
||||
MetaManipulations.Add(new GmpManipulation(value, metaFileInfo.PrimaryId));
|
||||
}
|
||||
|
||||
// Deserialize and check Est Entries and add them to the list if they are non-default.
|
||||
private void DeserializeEstEntries( MetaFileInfo metaFileInfo, byte[]? data )
|
||||
private void DeserializeEstEntries(MetaFileInfo metaFileInfo, byte[]? data)
|
||||
{
|
||||
if( data == null )
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var num = data.Length / 6;
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
for( var i = 0; i < num; ++i )
|
||||
using var reader = new BinaryReader(new MemoryStream(data));
|
||||
for (var i = 0; i < num; ++i)
|
||||
{
|
||||
var gr = ( GenderRace )reader.ReadUInt16();
|
||||
var gr = (GenderRace)reader.ReadUInt16();
|
||||
var id = reader.ReadUInt16();
|
||||
var value = reader.ReadUInt16();
|
||||
var type = ( metaFileInfo.SecondaryType, metaFileInfo.EquipSlot ) switch
|
||||
var type = (metaFileInfo.SecondaryType, metaFileInfo.EquipSlot) switch
|
||||
{
|
||||
(BodySlot.Face, _) => EstManipulation.EstType.Face,
|
||||
(BodySlot.Hair, _) => EstManipulation.EstType.Hair,
|
||||
(_, EquipSlot.Head) => EstManipulation.EstType.Head,
|
||||
(_, EquipSlot.Body) => EstManipulation.EstType.Body,
|
||||
_ => ( EstManipulation.EstType )0,
|
||||
_ => (EstManipulation.EstType)0,
|
||||
};
|
||||
if( !gr.IsValid() || type == 0 )
|
||||
{
|
||||
if (!gr.IsValid() || type == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
var def = EstFile.GetDefault( type, gr, id );
|
||||
if( _keepDefault || def != value )
|
||||
{
|
||||
MetaManipulations.Add( new EstManipulation( gr.Split().Item1, gr.Split().Item2, type, id, value ) );
|
||||
}
|
||||
var def = EstFile.GetDefault(_metaFileManager, type, gr, id);
|
||||
if (_keepDefault || def != value)
|
||||
MetaManipulations.Add(new EstManipulation(gr.Split().Item1, gr.Split().Item2, type, id, value));
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize and check IMC Entries and add them to the list if they are non-default.
|
||||
// This requires requesting a file from Lumina, which may fail due to TexTools corruption or just not existing.
|
||||
// TexTools creates IMC files for off-hand weapon models which may not exist in the game files.
|
||||
private void DeserializeImcEntries( MetaFileInfo metaFileInfo, byte[]? data )
|
||||
private void DeserializeImcEntries(MetaFileInfo metaFileInfo, byte[]? data)
|
||||
{
|
||||
if( data == null )
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var num = data.Length / 6;
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
var values = reader.ReadStructures< ImcEntry >( num );
|
||||
using var reader = new BinaryReader(new MemoryStream(data));
|
||||
var values = reader.ReadStructures<ImcEntry>(num);
|
||||
ushort i = 0;
|
||||
try
|
||||
{
|
||||
var manip = new ImcManipulation( metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId, metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot,
|
||||
new ImcEntry() );
|
||||
var def = new ImcFile( manip );
|
||||
var partIdx = ImcFile.PartIndex( manip.EquipSlot ); // Gets turned to unknown for things without equip, and unknown turns to 0.
|
||||
foreach( var value in values )
|
||||
var manip = new ImcManipulation(metaFileInfo.PrimaryType, metaFileInfo.SecondaryType, metaFileInfo.PrimaryId,
|
||||
metaFileInfo.SecondaryId, i, metaFileInfo.EquipSlot,
|
||||
new ImcEntry());
|
||||
var def = new ImcFile(_metaFileManager, manip);
|
||||
var partIdx = ImcFile.PartIndex(manip.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0.
|
||||
foreach (var value in values)
|
||||
{
|
||||
if( _keepDefault || !value.Equals( def.GetEntry( partIdx, i ) ) )
|
||||
if (_keepDefault || !value.Equals(def.GetEntry(partIdx, i)))
|
||||
{
|
||||
var imc = new ImcManipulation( manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, value );
|
||||
if( imc.Valid )
|
||||
{
|
||||
MetaManipulations.Add( imc );
|
||||
}
|
||||
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
|
||||
value);
|
||||
if (imc.Valid)
|
||||
MetaManipulations.Add(imc);
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Warning(
|
||||
$"Could not compute IMC manipulation for {metaFileInfo.PrimaryType} {metaFileInfo.PrimaryId}. This is in all likelihood due to TexTools corrupting your index files.\n"
|
||||
+ $"If the following error looks like Lumina is having trouble to read an IMC file, please do a do-over in TexTools:\n{e}" );
|
||||
+ $"If the following error looks like Lumina is having trouble to read an IMC file, please do a do-over in TexTools:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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 ) );
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
|
||||
public TexToolsMeta( IGamePathParser parser, byte[] data, bool keepDefault )
|
||||
{
|
||||
public TexToolsMeta( MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault )
|
||||
{
|
||||
_metaFileManager = metaFileManager;
|
||||
_keepDefault = keepDefault;
|
||||
try
|
||||
{
|
||||
|
|
@ -80,10 +83,11 @@ public partial class TexToolsMeta
|
|||
}
|
||||
}
|
||||
|
||||
private TexToolsMeta( string filePath, uint version )
|
||||
private TexToolsMeta( MetaFileManager metaFileManager, string filePath, uint version )
|
||||
{
|
||||
FilePath = filePath;
|
||||
Version = version;
|
||||
_metaFileManager = metaFileManager;
|
||||
FilePath = filePath;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
// Read a null terminated string from a binary reader.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -8,46 +8,46 @@ using Penumbra.String.Functions;
|
|||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
// The human.cmp file contains many character-relevant parameters like color sets.
|
||||
// We only support manipulating the racial scaling parameters at the moment.
|
||||
/// <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 =
|
||||
CharacterUtility.ReverseIndices[ ( int )MetaIndex.HumanCmp ];
|
||||
CharacterUtility.ReverseIndices[(int)MetaIndex.HumanCmp];
|
||||
|
||||
private const int RacialScalingStart = 0x2A800;
|
||||
|
||||
public float this[ SubRace subRace, RspAttribute attribute ]
|
||||
public float this[SubRace subRace, RspAttribute attribute]
|
||||
{
|
||||
get => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 );
|
||||
set => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ) = value;
|
||||
get => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4);
|
||||
set => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4) = value;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
=> MemoryUtility.MemCpyUnchecked( Data, ( byte* )DefaultData.Data, DefaultData.Length );
|
||||
=> MemoryUtility.MemCpyUnchecked(Data, (byte*)DefaultData.Data, DefaultData.Length);
|
||||
|
||||
public void Reset( IEnumerable< (SubRace, RspAttribute) > entries )
|
||||
public void Reset(IEnumerable<(SubRace, RspAttribute)> entries)
|
||||
{
|
||||
foreach( var (r, a) in entries )
|
||||
{
|
||||
this[ r, a ] = GetDefault( r, a );
|
||||
}
|
||||
foreach (var (r, a) in entries)
|
||||
this[r, a] = GetDefault(Manager, r, a);
|
||||
}
|
||||
|
||||
public CmpFile()
|
||||
: base( MetaIndex.HumanCmp )
|
||||
public CmpFile(MetaFileManager manager)
|
||||
: base(manager, MetaIndex.HumanCmp)
|
||||
{
|
||||
AllocateData( DefaultData.Length );
|
||||
AllocateData(DefaultData.Length);
|
||||
Reset();
|
||||
}
|
||||
|
||||
public static float GetDefault( SubRace subRace, RspAttribute attribute )
|
||||
public static float GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute)
|
||||
{
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( InternalIndex ).Address;
|
||||
return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 );
|
||||
var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address;
|
||||
return *(float*)(data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4);
|
||||
}
|
||||
|
||||
private static int ToRspIndex( SubRace subRace )
|
||||
private static int ToRspIndex(SubRace subRace)
|
||||
=> subRace switch
|
||||
{
|
||||
SubRace.Midlander => 0,
|
||||
|
|
@ -67,6 +67,6 @@ public sealed unsafe class CmpFile : MetaBaseFile
|
|||
SubRace.Rava => 70,
|
||||
SubRace.Veena => 71,
|
||||
SubRace.Unknown => 0,
|
||||
_ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(subRace), subRace, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -28,117 +29,103 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
|||
public readonly int DataOffset;
|
||||
|
||||
public ushort Identifier
|
||||
=> *( ushort* )Data;
|
||||
=> *(ushort*)Data;
|
||||
|
||||
public ushort BlockSize
|
||||
=> *( ushort* )( Data + 2 );
|
||||
=> *(ushort*)(Data + 2);
|
||||
|
||||
public ushort BlockCount
|
||||
=> *( ushort* )( Data + 4 );
|
||||
=> *(ushort*)(Data + 4);
|
||||
|
||||
public int Count
|
||||
=> ( Length - DataOffset ) / EqdpEntrySize;
|
||||
=> (Length - DataOffset) / EqdpEntrySize;
|
||||
|
||||
public EqdpEntry this[ int idx ]
|
||||
public EqdpEntry this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
if( idx >= Count || idx < 0 )
|
||||
{
|
||||
if (idx >= Count || idx < 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
return ( EqdpEntry )( *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) );
|
||||
return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * idx));
|
||||
}
|
||||
set
|
||||
{
|
||||
if( idx >= Count || idx < 0 )
|
||||
{
|
||||
if (idx >= Count || idx < 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
*( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) = ( ushort )value;
|
||||
*(ushort*)(Data + DataOffset + EqdpEntrySize * idx) = (ushort)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
var def = ( byte* )DefaultData.Data;
|
||||
MemoryUtility.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
|
||||
var def = (byte*)DefaultData.Data;
|
||||
MemoryUtility.MemCpyUnchecked(Data, def, IdentifierSize + PreambleSize);
|
||||
|
||||
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
|
||||
var controlPtr = (ushort*)(def + IdentifierSize + PreambleSize);
|
||||
var dataBasePtr = controlPtr + BlockCount;
|
||||
var myDataPtr = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount );
|
||||
var myControlPtr = ( ushort* )( Data + IdentifierSize + PreambleSize );
|
||||
for( var i = 0; i < BlockCount; ++i )
|
||||
var myDataPtr = (ushort*)(Data + IdentifierSize + PreambleSize + 2 * BlockCount);
|
||||
var myControlPtr = (ushort*)(Data + IdentifierSize + PreambleSize);
|
||||
for (var i = 0; i < BlockCount; ++i)
|
||||
{
|
||||
if( controlPtr[ i ] == CollapsedBlock )
|
||||
{
|
||||
MemoryUtility.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
|
||||
}
|
||||
if (controlPtr[i] == CollapsedBlock)
|
||||
MemoryUtility.MemSet(myDataPtr, 0, BlockSize * EqdpEntrySize);
|
||||
else
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
|
||||
}
|
||||
MemoryUtility.MemCpyUnchecked(myDataPtr, dataBasePtr + controlPtr[i], BlockSize * EqdpEntrySize);
|
||||
|
||||
myControlPtr[ i ] = ( ushort )( i * BlockSize );
|
||||
myDataPtr += BlockSize;
|
||||
myControlPtr[i] = (ushort)(i * BlockSize);
|
||||
myDataPtr += BlockSize;
|
||||
}
|
||||
|
||||
MemoryUtility.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) );
|
||||
MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data));
|
||||
}
|
||||
|
||||
public void Reset( IEnumerable< int > entries )
|
||||
public void Reset(IEnumerable<int> entries)
|
||||
{
|
||||
foreach( var entry in entries )
|
||||
{
|
||||
this[ entry ] = GetDefault( entry );
|
||||
}
|
||||
foreach (var entry in entries)
|
||||
this[entry] = GetDefault(entry);
|
||||
}
|
||||
|
||||
public ExpandedEqdpFile( GenderRace raceCode, bool accessory )
|
||||
: base( CharacterUtilityData.EqdpIdx( raceCode, accessory ) )
|
||||
public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory)
|
||||
: base(manager, CharacterUtilityData.EqdpIdx(raceCode, accessory))
|
||||
{
|
||||
var def = ( byte* )DefaultData.Data;
|
||||
var blockSize = *( ushort* )( def + IdentifierSize );
|
||||
var totalBlockCount = *( ushort* )( def + IdentifierSize + 2 );
|
||||
var def = (byte*)DefaultData.Data;
|
||||
var blockSize = *(ushort*)(def + IdentifierSize);
|
||||
var totalBlockCount = *(ushort*)(def + IdentifierSize + 2);
|
||||
var totalBlockSize = blockSize * EqdpEntrySize;
|
||||
|
||||
DataOffset = IdentifierSize + PreambleSize + totalBlockCount * BlockHeaderSize;
|
||||
|
||||
var fullLength = DataOffset + totalBlockCount * totalBlockSize;
|
||||
fullLength += ( FileAlignment - ( fullLength & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 );
|
||||
AllocateData( fullLength );
|
||||
var fullLength = DataOffset + totalBlockCount * totalBlockSize;
|
||||
fullLength += (FileAlignment - (fullLength & (FileAlignment - 1))) & (FileAlignment - 1);
|
||||
AllocateData(fullLength);
|
||||
Reset();
|
||||
}
|
||||
|
||||
public EqdpEntry GetDefault( int setIdx )
|
||||
=> GetDefault( Index, setIdx );
|
||||
public EqdpEntry GetDefault(int setIdx)
|
||||
=> GetDefault(Manager, Index, setIdx);
|
||||
|
||||
public static EqdpEntry GetDefault( CharacterUtility.InternalIndex idx, int setIdx )
|
||||
=> GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( idx ).Address, setIdx );
|
||||
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, int setIdx)
|
||||
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setIdx);
|
||||
|
||||
public static EqdpEntry GetDefault( byte* data, int setIdx )
|
||||
public static EqdpEntry GetDefault(byte* data, int setIdx)
|
||||
{
|
||||
var blockSize = *( ushort* )( data + IdentifierSize );
|
||||
var totalBlockCount = *( ushort* )( data + IdentifierSize + 2 );
|
||||
var blockSize = *(ushort*)(data + IdentifierSize);
|
||||
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
|
||||
|
||||
var blockIdx = setIdx / blockSize;
|
||||
if( blockIdx >= totalBlockCount )
|
||||
{
|
||||
if (blockIdx >= totalBlockCount)
|
||||
return 0;
|
||||
}
|
||||
|
||||
var block = ( ( ushort* )( data + IdentifierSize + PreambleSize ) )[ blockIdx ];
|
||||
if( block == CollapsedBlock )
|
||||
{
|
||||
var block = ((ushort*)(data + IdentifierSize + PreambleSize))[blockIdx];
|
||||
if (block == CollapsedBlock)
|
||||
return 0;
|
||||
}
|
||||
|
||||
var blockData = ( ushort* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2 );
|
||||
return ( EqdpEntry )( *( blockData + setIdx % blockSize ) );
|
||||
var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2);
|
||||
return (EqdpEntry)(*(blockData + setIdx % blockSize));
|
||||
}
|
||||
|
||||
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )
|
||||
=> GetDefault( CharacterUtility.ReverseIndices[ ( int )CharacterUtilityData.EqdpIdx( raceCode, accessory ) ], setIdx );
|
||||
}
|
||||
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, int setIdx)
|
||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setIdx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -24,19 +26,19 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
|||
public const int Count = BlockSize * NumBlocks;
|
||||
|
||||
public ulong ControlBlock
|
||||
=> *( ulong* )Data;
|
||||
=> *(ulong*)Data;
|
||||
|
||||
protected ulong GetInternal( int idx )
|
||||
protected ulong GetInternal(int idx)
|
||||
{
|
||||
return idx switch
|
||||
{
|
||||
>= Count => throw new IndexOutOfRangeException(),
|
||||
<= 1 => *( ( ulong* )Data + 1 ),
|
||||
_ => *( ( ulong* )Data + idx ),
|
||||
<= 1 => *((ulong*)Data + 1),
|
||||
_ => *((ulong*)Data + idx),
|
||||
};
|
||||
}
|
||||
|
||||
protected void SetInternal( int idx, ulong value )
|
||||
protected void SetInternal(int idx, ulong value)
|
||||
{
|
||||
idx = idx switch
|
||||
{
|
||||
|
|
@ -45,67 +47,62 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
|||
_ => idx,
|
||||
};
|
||||
|
||||
*( ( ulong* )Data + idx ) = value;
|
||||
*((ulong*)Data + idx) = value;
|
||||
}
|
||||
|
||||
protected virtual void SetEmptyBlock( int idx )
|
||||
protected virtual void SetEmptyBlock(int idx)
|
||||
{
|
||||
MemoryUtility.MemSet( Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize );
|
||||
MemoryUtility.MemSet(Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize);
|
||||
}
|
||||
|
||||
public sealed override void Reset()
|
||||
{
|
||||
var ptr = ( byte* )DefaultData.Data;
|
||||
var controlBlock = *( ulong* )ptr;
|
||||
var ptr = (byte*)DefaultData.Data;
|
||||
var controlBlock = *(ulong*)ptr;
|
||||
var expandedBlocks = 0;
|
||||
for( var i = 0; i < NumBlocks; ++i )
|
||||
for (var i = 0; i < NumBlocks; ++i)
|
||||
{
|
||||
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
|
||||
if( !collapsed )
|
||||
var collapsed = ((controlBlock >> i) & 1) == 0;
|
||||
if (!collapsed)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize );
|
||||
MemoryUtility.MemCpyUnchecked(Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize,
|
||||
BlockSize * EntrySize);
|
||||
expandedBlocks++;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetEmptyBlock( i );
|
||||
SetEmptyBlock(i);
|
||||
}
|
||||
}
|
||||
|
||||
*( ulong* )Data = ulong.MaxValue;
|
||||
*(ulong*)Data = ulong.MaxValue;
|
||||
}
|
||||
|
||||
public ExpandedEqpGmpBase( bool gmp )
|
||||
: base( gmp ? MetaIndex.Gmp : MetaIndex.Eqp )
|
||||
public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp)
|
||||
: base(manager, gmp ? MetaIndex.Gmp : MetaIndex.Eqp)
|
||||
{
|
||||
AllocateData( MaxSize );
|
||||
AllocateData(MaxSize);
|
||||
Reset();
|
||||
}
|
||||
|
||||
protected static ulong GetDefaultInternal( CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def )
|
||||
protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def)
|
||||
{
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource(fileIndex).Address;
|
||||
if( setIdx == 0 )
|
||||
{
|
||||
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
|
||||
if (setIdx == 0)
|
||||
setIdx = 1;
|
||||
}
|
||||
|
||||
var blockIdx = setIdx / BlockSize;
|
||||
if( blockIdx >= NumBlocks )
|
||||
{
|
||||
if (blockIdx >= NumBlocks)
|
||||
return def;
|
||||
}
|
||||
|
||||
var control = *( ulong* )data;
|
||||
var control = *(ulong*)data;
|
||||
var blockBit = 1ul << blockIdx;
|
||||
if( ( control & blockBit ) == 0 )
|
||||
{
|
||||
if ((control & blockBit) == 0)
|
||||
return def;
|
||||
}
|
||||
|
||||
var count = BitOperations.PopCount( control & ( blockBit - 1 ) );
|
||||
var count = BitOperations.PopCount(control & (blockBit - 1));
|
||||
var idx = setIdx % BlockSize;
|
||||
var ptr = ( ulong* )data + BlockSize * count + idx;
|
||||
var ptr = (ulong*)data + BlockSize * count + idx;
|
||||
return *ptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -113,44 +110,40 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
|||
public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
|
||||
{
|
||||
public static readonly CharacterUtility.InternalIndex InternalIndex =
|
||||
CharacterUtility.ReverseIndices[ (int) MetaIndex.Eqp ];
|
||||
CharacterUtility.ReverseIndices[(int)MetaIndex.Eqp];
|
||||
|
||||
public ExpandedEqpFile()
|
||||
: base( false )
|
||||
public ExpandedEqpFile(MetaFileManager manager)
|
||||
: base(manager, false)
|
||||
{ }
|
||||
|
||||
public EqpEntry this[ int idx ]
|
||||
public EqpEntry this[int idx]
|
||||
{
|
||||
get => ( EqpEntry )GetInternal( idx );
|
||||
set => SetInternal( idx, ( ulong )value );
|
||||
get => (EqpEntry)GetInternal(idx);
|
||||
set => SetInternal(idx, (ulong)value);
|
||||
}
|
||||
|
||||
|
||||
public static EqpEntry GetDefault( int setIdx )
|
||||
=> ( EqpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )Eqp.DefaultEntry );
|
||||
public static EqpEntry GetDefault(MetaFileManager manager, int setIdx)
|
||||
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry);
|
||||
|
||||
protected override unsafe void SetEmptyBlock( int idx )
|
||||
protected override unsafe void SetEmptyBlock(int idx)
|
||||
{
|
||||
var blockPtr = ( ulong* )( Data + idx * BlockSize * EntrySize );
|
||||
var blockPtr = (ulong*)(Data + idx * BlockSize * EntrySize);
|
||||
var endPtr = blockPtr + BlockSize;
|
||||
for( var ptr = blockPtr; ptr < endPtr; ++ptr )
|
||||
{
|
||||
*ptr = ( ulong )Eqp.DefaultEntry;
|
||||
}
|
||||
for (var ptr = blockPtr; ptr < endPtr; ++ptr)
|
||||
*ptr = (ulong)Eqp.DefaultEntry;
|
||||
}
|
||||
|
||||
public void Reset( IEnumerable< int > entries )
|
||||
public void Reset(IEnumerable<int> entries)
|
||||
{
|
||||
foreach( var entry in entries )
|
||||
{
|
||||
this[ entry ] = GetDefault( entry );
|
||||
}
|
||||
foreach (var entry in entries)
|
||||
this[entry] = GetDefault(Manager, entry);
|
||||
}
|
||||
|
||||
public IEnumerator< EqpEntry > GetEnumerator()
|
||||
public IEnumerator<EqpEntry> GetEnumerator()
|
||||
{
|
||||
for( var idx = 1; idx < Count; ++idx )
|
||||
yield return this[ idx ];
|
||||
for (var idx = 1; idx < Count; ++idx)
|
||||
yield return this[idx];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
|
|
@ -160,35 +153,33 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
|
|||
public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
|
||||
{
|
||||
public static readonly CharacterUtility.InternalIndex InternalIndex =
|
||||
CharacterUtility.ReverseIndices[( int )MetaIndex.Gmp];
|
||||
CharacterUtility.ReverseIndices[(int)MetaIndex.Gmp];
|
||||
|
||||
public ExpandedGmpFile()
|
||||
: base( true )
|
||||
public ExpandedGmpFile(MetaFileManager manager)
|
||||
: base(manager, true)
|
||||
{ }
|
||||
|
||||
public GmpEntry this[ int idx ]
|
||||
public GmpEntry this[int idx]
|
||||
{
|
||||
get => ( GmpEntry )GetInternal( idx );
|
||||
set => SetInternal( idx, ( ulong )value );
|
||||
get => (GmpEntry)GetInternal(idx);
|
||||
set => SetInternal(idx, (ulong)value);
|
||||
}
|
||||
|
||||
public static GmpEntry GetDefault( int setIdx )
|
||||
=> ( GmpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )GmpEntry.Default );
|
||||
public static GmpEntry GetDefault(MetaFileManager manager, int setIdx)
|
||||
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default);
|
||||
|
||||
public void Reset( IEnumerable< int > entries )
|
||||
public void Reset(IEnumerable<int> entries)
|
||||
{
|
||||
foreach( var entry in entries )
|
||||
{
|
||||
this[ entry ] = GetDefault( entry );
|
||||
}
|
||||
foreach (var entry in entries)
|
||||
this[entry] = GetDefault(Manager, entry);
|
||||
}
|
||||
|
||||
public IEnumerator<GmpEntry> GetEnumerator()
|
||||
{
|
||||
for( var idx = 1; idx < Count; ++idx )
|
||||
for (var idx = 1; idx < Count; ++idx)
|
||||
yield return this[idx];
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -20,10 +22,10 @@ public sealed unsafe class EstFile : MetaBaseFile
|
|||
private const int IncreaseSize = 512;
|
||||
|
||||
public int Count
|
||||
=> *( int* )Data;
|
||||
=> *(int*)Data;
|
||||
|
||||
private int Size
|
||||
=> 4 + Count * ( EntryDescSize + EntrySize );
|
||||
=> 4 + Count * (EntryDescSize + EntrySize);
|
||||
|
||||
public enum EstEntryChange
|
||||
{
|
||||
|
|
@ -33,176 +35,154 @@ public sealed unsafe class EstFile : MetaBaseFile
|
|||
Removed,
|
||||
}
|
||||
|
||||
public ushort this[ GenderRace genderRace, ushort setId ]
|
||||
public ushort this[GenderRace genderRace, ushort setId]
|
||||
{
|
||||
get
|
||||
{
|
||||
var (idx, exists) = FindEntry( genderRace, setId );
|
||||
if( !exists )
|
||||
{
|
||||
var (idx, exists) = FindEntry(genderRace, setId);
|
||||
if (!exists)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return *( ushort* )( Data + EntryDescSize * ( Count + 1 ) + EntrySize * idx );
|
||||
return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
|
||||
}
|
||||
set => SetEntry( genderRace, setId, value );
|
||||
set => SetEntry(genderRace, setId, value);
|
||||
}
|
||||
|
||||
private void InsertEntry( int idx, GenderRace genderRace, ushort setId, ushort skeletonId )
|
||||
private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId)
|
||||
{
|
||||
if( Length < Size + EntryDescSize + EntrySize )
|
||||
{
|
||||
ResizeResources( Length + IncreaseSize );
|
||||
}
|
||||
if (Length < Size + EntryDescSize + EntrySize)
|
||||
ResizeResources(Length + IncreaseSize);
|
||||
|
||||
var control = ( Info* )( Data + 4 );
|
||||
var entries = ( ushort* )( control + Count );
|
||||
var control = (Info*)(Data + 4);
|
||||
var entries = (ushort*)(control + Count);
|
||||
|
||||
for( var i = Count - 1; i >= idx; --i )
|
||||
{
|
||||
entries[ i + 3 ] = entries[ i ];
|
||||
}
|
||||
for (var i = Count - 1; i >= idx; --i)
|
||||
entries[i + 3] = entries[i];
|
||||
|
||||
entries[ idx + 2 ] = skeletonId;
|
||||
entries[idx + 2] = skeletonId;
|
||||
|
||||
for( var i = idx - 1; i >= 0; --i )
|
||||
{
|
||||
entries[ i + 2 ] = entries[ i ];
|
||||
}
|
||||
for (var i = idx - 1; i >= 0; --i)
|
||||
entries[i + 2] = entries[i];
|
||||
|
||||
for( var i = Count - 1; i >= idx; --i )
|
||||
{
|
||||
control[ i + 1 ] = control[ i ];
|
||||
}
|
||||
for (var i = Count - 1; i >= idx; --i)
|
||||
control[i + 1] = control[i];
|
||||
|
||||
control[ idx ] = new Info( genderRace, setId );
|
||||
control[idx] = new Info(genderRace, setId);
|
||||
|
||||
*( int* )Data = Count + 1;
|
||||
*(int*)Data = Count + 1;
|
||||
}
|
||||
|
||||
private void RemoveEntry( int idx )
|
||||
private void RemoveEntry(int idx)
|
||||
{
|
||||
var control = ( Info* )( Data + 4 );
|
||||
var entries = ( ushort* )( control + Count );
|
||||
var control = (Info*)(Data + 4);
|
||||
var entries = (ushort*)(control + Count);
|
||||
|
||||
for( var i = idx; i < Count; ++i )
|
||||
{
|
||||
control[ i ] = control[ i + 1 ];
|
||||
}
|
||||
for (var i = idx; i < Count; ++i)
|
||||
control[i] = control[i + 1];
|
||||
|
||||
for( var i = 0; i < idx; ++i )
|
||||
{
|
||||
entries[ i - 2 ] = entries[ i ];
|
||||
}
|
||||
for (var i = 0; i < idx; ++i)
|
||||
entries[i - 2] = entries[i];
|
||||
|
||||
for( var i = idx; i < Count - 1; ++i )
|
||||
{
|
||||
entries[ i - 2 ] = entries[ i + 1 ];
|
||||
}
|
||||
for (var i = idx; i < Count - 1; ++i)
|
||||
entries[i - 2] = entries[i + 1];
|
||||
|
||||
entries[ Count - 3 ] = 0;
|
||||
entries[ Count - 2 ] = 0;
|
||||
entries[ Count - 1 ] = 0;
|
||||
*( int* )Data = Count - 1;
|
||||
entries[Count - 3] = 0;
|
||||
entries[Count - 2] = 0;
|
||||
entries[Count - 1] = 0;
|
||||
*(int*)Data = Count - 1;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Size = 4 )]
|
||||
private struct Info : IComparable< Info >
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
||||
private struct Info : IComparable<Info>
|
||||
{
|
||||
public readonly ushort SetId;
|
||||
public readonly GenderRace GenderRace;
|
||||
|
||||
public Info( GenderRace gr, ushort setId )
|
||||
public Info(GenderRace gr, ushort setId)
|
||||
{
|
||||
GenderRace = gr;
|
||||
SetId = setId;
|
||||
}
|
||||
|
||||
public int CompareTo( Info other )
|
||||
public int CompareTo(Info other)
|
||||
{
|
||||
var genderRaceComparison = GenderRace.CompareTo( other.GenderRace );
|
||||
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo( other.SetId );
|
||||
var genderRaceComparison = GenderRace.CompareTo(other.GenderRace);
|
||||
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo(other.SetId);
|
||||
}
|
||||
}
|
||||
|
||||
private static (int, bool) FindEntry( ReadOnlySpan< Info > data, GenderRace genderRace, ushort setId )
|
||||
private static (int, bool) FindEntry(ReadOnlySpan<Info> data, GenderRace genderRace, ushort setId)
|
||||
{
|
||||
var idx = data.BinarySearch( new Info( genderRace, setId ) );
|
||||
return idx < 0 ? ( ~idx, false ) : ( idx, true );
|
||||
var idx = data.BinarySearch(new Info(genderRace, setId));
|
||||
return idx < 0 ? (~idx, false) : (idx, true);
|
||||
}
|
||||
|
||||
private (int, bool) FindEntry( GenderRace genderRace, ushort setId )
|
||||
private (int, bool) FindEntry(GenderRace genderRace, ushort setId)
|
||||
{
|
||||
var span = new ReadOnlySpan< Info >( Data + 4, Count );
|
||||
return FindEntry( span, genderRace, setId );
|
||||
var span = new ReadOnlySpan<Info>(Data + 4, Count);
|
||||
return FindEntry(span, genderRace, setId);
|
||||
}
|
||||
|
||||
public EstEntryChange SetEntry( GenderRace genderRace, ushort setId, ushort skeletonId )
|
||||
public EstEntryChange SetEntry(GenderRace genderRace, ushort setId, ushort skeletonId)
|
||||
{
|
||||
var (idx, exists) = FindEntry( genderRace, setId );
|
||||
if( exists )
|
||||
var (idx, exists) = FindEntry(genderRace, setId);
|
||||
if (exists)
|
||||
{
|
||||
var value = *( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx );
|
||||
if( value == skeletonId )
|
||||
{
|
||||
var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx);
|
||||
if (value == skeletonId)
|
||||
return EstEntryChange.Unchanged;
|
||||
}
|
||||
|
||||
if( skeletonId == 0 )
|
||||
if (skeletonId == 0)
|
||||
{
|
||||
RemoveEntry( idx );
|
||||
RemoveEntry(idx);
|
||||
return EstEntryChange.Removed;
|
||||
}
|
||||
|
||||
*( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx ) = skeletonId;
|
||||
*(ushort*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
|
||||
return EstEntryChange.Changed;
|
||||
}
|
||||
|
||||
if( skeletonId == 0 )
|
||||
{
|
||||
if (skeletonId == 0)
|
||||
return EstEntryChange.Unchanged;
|
||||
}
|
||||
|
||||
InsertEntry( idx, genderRace, setId, skeletonId );
|
||||
InsertEntry(idx, genderRace, setId, skeletonId);
|
||||
return EstEntryChange.Added;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
var (d, length) = DefaultData;
|
||||
var data = ( byte* )d;
|
||||
MemoryUtility.MemCpyUnchecked( Data, data, length );
|
||||
MemoryUtility.MemSet( Data + length, 0, Length - length );
|
||||
var data = (byte*)d;
|
||||
MemoryUtility.MemCpyUnchecked(Data, data, length);
|
||||
MemoryUtility.MemSet(Data + length, 0, Length - length);
|
||||
}
|
||||
|
||||
public EstFile( EstManipulation.EstType estType )
|
||||
: base( ( MetaIndex )estType )
|
||||
public EstFile(MetaFileManager manager, EstManipulation.EstType estType)
|
||||
: base(manager, (MetaIndex)estType)
|
||||
{
|
||||
var length = DefaultData.Length;
|
||||
AllocateData( length + IncreaseSize );
|
||||
AllocateData(length + IncreaseSize);
|
||||
Reset();
|
||||
}
|
||||
|
||||
public ushort GetDefault( GenderRace genderRace, ushort setId )
|
||||
=> GetDefault( Index, genderRace, setId );
|
||||
public ushort GetDefault(GenderRace genderRace, ushort setId)
|
||||
=> GetDefault(Manager, Index, genderRace, setId);
|
||||
|
||||
public static ushort GetDefault( CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId )
|
||||
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId)
|
||||
{
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( index ).Address;
|
||||
var count = *( int* )data;
|
||||
var span = new ReadOnlySpan< Info >( data + 4, count );
|
||||
var (idx, found) = FindEntry( span, genderRace, setId );
|
||||
if( !found )
|
||||
{
|
||||
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
|
||||
var count = *(int*)data;
|
||||
var span = new ReadOnlySpan<Info>(data + 4, count);
|
||||
var (idx, found) = FindEntry(span, genderRace, setId);
|
||||
if (!found)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize );
|
||||
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
|
||||
}
|
||||
|
||||
public static ushort GetDefault( MetaIndex metaIndex, GenderRace genderRace, ushort setId )
|
||||
=> GetDefault( CharacterUtility.ReverseIndices[ ( int )metaIndex ], genderRace, setId );
|
||||
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, ushort setId)
|
||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId);
|
||||
|
||||
public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId )
|
||||
=> GetDefault( ( MetaIndex )estType, genderRace, setId );
|
||||
}
|
||||
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, ushort setId)
|
||||
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -26,45 +27,39 @@ public unsafe class EvpFile : MetaBaseFile
|
|||
}
|
||||
|
||||
public int NumModels
|
||||
=> Data[ 3 ];
|
||||
=> Data[3];
|
||||
|
||||
public ReadOnlySpan< ushort > ModelSetIds
|
||||
public ReadOnlySpan<ushort> ModelSetIds
|
||||
=> new(Data + 4, NumModels);
|
||||
|
||||
public ushort ModelSetId( int idx )
|
||||
=> idx >= 0 && idx < NumModels ? ( ( ushort* )( Data + 4 ) )[ idx ] : ushort.MaxValue;
|
||||
public ushort ModelSetId(int idx)
|
||||
=> idx >= 0 && idx < NumModels ? ((ushort*)(Data + 4))[idx] : ushort.MaxValue;
|
||||
|
||||
public ReadOnlySpan< EvpFlag > Flags( int idx )
|
||||
public ReadOnlySpan<EvpFlag> Flags(int idx)
|
||||
=> new(Data + 4 + idx * FlagArraySize, FlagArraySize);
|
||||
|
||||
public EvpFlag Flag( ushort modelSet, int arrayIndex )
|
||||
public EvpFlag Flag(ushort modelSet, int arrayIndex)
|
||||
{
|
||||
if( arrayIndex is >= FlagArraySize or < 0 )
|
||||
{
|
||||
if (arrayIndex is >= FlagArraySize or < 0)
|
||||
return EvpFlag.None;
|
||||
}
|
||||
|
||||
var ids = ModelSetIds;
|
||||
for( var i = 0; i < ids.Length; ++i )
|
||||
for (var i = 0; i < ids.Length; ++i)
|
||||
{
|
||||
var model = ids[ i ];
|
||||
if( model < modelSet )
|
||||
{
|
||||
var model = ids[i];
|
||||
if (model < modelSet)
|
||||
continue;
|
||||
}
|
||||
|
||||
if( model > modelSet )
|
||||
{
|
||||
if (model > modelSet)
|
||||
break;
|
||||
}
|
||||
|
||||
return Flags( i )[ arrayIndex ];
|
||||
return Flags(i)[arrayIndex];
|
||||
}
|
||||
|
||||
return EvpFlag.None;
|
||||
}
|
||||
|
||||
public EvpFile()
|
||||
: base( ( MetaIndex )1 ) // TODO: Name
|
||||
public EvpFile(MetaFileManager manager)
|
||||
: base(manager, (MetaIndex)1) // TODO: Name
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
|
|
@ -16,7 +15,7 @@ public class ImcException : Exception
|
|||
public readonly ImcManipulation Manipulation;
|
||||
public readonly string GamePath;
|
||||
|
||||
public ImcException( ImcManipulation manip, Utf8GamePath path )
|
||||
public ImcException(ImcManipulation manip, Utf8GamePath path)
|
||||
{
|
||||
Manipulation = manip;
|
||||
GamePath = path.ToString();
|
||||
|
|
@ -34,44 +33,42 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
private const int PreambleSize = 4;
|
||||
|
||||
public int ActualLength
|
||||
=> NumParts * sizeof( ImcEntry ) * ( Count + 1 ) + PreambleSize;
|
||||
=> NumParts * sizeof(ImcEntry) * (Count + 1) + PreambleSize;
|
||||
|
||||
public int Count
|
||||
=> CountInternal( Data );
|
||||
=> CountInternal(Data);
|
||||
|
||||
public readonly Utf8GamePath Path;
|
||||
public readonly int NumParts;
|
||||
|
||||
public ReadOnlySpan< ImcEntry > Span
|
||||
=> new(( ImcEntry* )( Data + PreambleSize ), ( Length - PreambleSize ) / sizeof( ImcEntry ));
|
||||
public ReadOnlySpan<ImcEntry> Span
|
||||
=> new((ImcEntry*)(Data + PreambleSize), (Length - PreambleSize) / sizeof(ImcEntry));
|
||||
|
||||
private static int CountInternal( byte* data )
|
||||
=> *( ushort* )data;
|
||||
private static int CountInternal(byte* data)
|
||||
=> *(ushort*)data;
|
||||
|
||||
private static ushort PartMask( byte* data )
|
||||
=> *( ushort* )( data + 2 );
|
||||
private static ushort PartMask(byte* data)
|
||||
=> *(ushort*)(data + 2);
|
||||
|
||||
private static ImcEntry* VariantPtr( byte* data, int partIdx, int variantIdx )
|
||||
private static ImcEntry* VariantPtr(byte* data, int partIdx, int variantIdx)
|
||||
{
|
||||
var flag = 1 << partIdx;
|
||||
if( ( PartMask( data ) & flag ) == 0 || variantIdx > CountInternal( data ) )
|
||||
{
|
||||
if ((PartMask(data) & flag) == 0 || variantIdx > CountInternal(data))
|
||||
return null;
|
||||
}
|
||||
|
||||
var numParts = BitOperations.PopCount( PartMask( data ) );
|
||||
var ptr = ( ImcEntry* )( data + PreambleSize );
|
||||
var numParts = BitOperations.PopCount(PartMask(data));
|
||||
var ptr = (ImcEntry*)(data + PreambleSize);
|
||||
ptr += variantIdx * numParts + partIdx;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public ImcEntry GetEntry( int partIdx, int variantIdx )
|
||||
public ImcEntry GetEntry(int partIdx, int variantIdx)
|
||||
{
|
||||
var ptr = VariantPtr( Data, partIdx, variantIdx );
|
||||
var ptr = VariantPtr(Data, partIdx, variantIdx);
|
||||
return ptr == null ? new ImcEntry() : *ptr;
|
||||
}
|
||||
|
||||
public static int PartIndex( EquipSlot slot )
|
||||
public static int PartIndex(EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
|
|
@ -87,52 +84,44 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
_ => 0,
|
||||
};
|
||||
|
||||
public bool EnsureVariantCount( int numVariants )
|
||||
public bool EnsureVariantCount(int numVariants)
|
||||
{
|
||||
if( numVariants <= Count )
|
||||
{
|
||||
if (numVariants <= Count)
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldCount = Count;
|
||||
*( ushort* )Data = ( ushort )numVariants;
|
||||
if( ActualLength > Length )
|
||||
*(ushort*)Data = (ushort)numVariants;
|
||||
if (ActualLength > Length)
|
||||
{
|
||||
var newLength = ( ( ( ActualLength - 1 ) >> 7 ) + 1 ) << 7;
|
||||
Penumbra.Log.Verbose( $"Resized IMC {Path} from {Length} to {newLength}." );
|
||||
ResizeResources( newLength );
|
||||
var newLength = (((ActualLength - 1) >> 7) + 1) << 7;
|
||||
Penumbra.Log.Verbose($"Resized IMC {Path} from {Length} to {newLength}.");
|
||||
ResizeResources(newLength);
|
||||
}
|
||||
|
||||
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
|
||||
for( var i = oldCount + 1; i < numVariants + 1; ++i )
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof( ImcEntry ) );
|
||||
}
|
||||
var defaultPtr = (ImcEntry*)(Data + PreambleSize);
|
||||
for (var i = oldCount + 1; i < numVariants + 1; ++i)
|
||||
MemoryUtility.MemCpyUnchecked(defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof(ImcEntry));
|
||||
|
||||
Penumbra.Log.Verbose( $"Expanded IMC {Path} from {oldCount} to {numVariants} variants." );
|
||||
Penumbra.Log.Verbose($"Expanded IMC {Path} from {oldCount} to {numVariants} variants.");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetEntry( int partIdx, int variantIdx, ImcEntry entry )
|
||||
public bool SetEntry(int partIdx, int variantIdx, ImcEntry entry)
|
||||
{
|
||||
if( partIdx >= NumParts )
|
||||
if (partIdx >= NumParts)
|
||||
return false;
|
||||
|
||||
EnsureVariantCount(variantIdx);
|
||||
|
||||
var variantPtr = VariantPtr(Data, partIdx, variantIdx);
|
||||
if (variantPtr == null)
|
||||
{
|
||||
Penumbra.Log.Error("Error during expansion of imc file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureVariantCount( variantIdx );
|
||||
|
||||
var variantPtr = VariantPtr( Data, partIdx, variantIdx );
|
||||
if( variantPtr == null )
|
||||
{
|
||||
Penumbra.Log.Error( "Error during expansion of imc file." );
|
||||
if (variantPtr->Equals(entry))
|
||||
return false;
|
||||
}
|
||||
|
||||
if( variantPtr->Equals( entry ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*variantPtr = entry;
|
||||
return true;
|
||||
|
|
@ -141,70 +130,64 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
|
||||
public override void Reset()
|
||||
{
|
||||
var file = DalamudServices.SGameData.GetFile( Path.ToString() );
|
||||
fixed( byte* ptr = file!.Data )
|
||||
var file = DalamudServices.SGameData.GetFile(Path.ToString());
|
||||
fixed (byte* ptr = file!.Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||
MemoryUtility.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
|
||||
MemoryUtility.MemCpyUnchecked(Data, ptr, file.Data.Length);
|
||||
MemoryUtility.MemSet(Data + file.Data.Length, 0, Length - file.Data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public ImcFile( ImcManipulation manip )
|
||||
: base( 0 )
|
||||
public ImcFile(MetaFileManager manager, ImcManipulation manip)
|
||||
: base(manager, 0)
|
||||
{
|
||||
Path = manip.GamePath();
|
||||
var file = DalamudServices.SGameData.GetFile( Path.ToString() );
|
||||
if( file == null )
|
||||
{
|
||||
throw new ImcException( manip, Path );
|
||||
}
|
||||
var file = manager.GameData.GetFile(Path.ToString());
|
||||
if (file == null)
|
||||
throw new ImcException(manip, Path);
|
||||
|
||||
fixed( byte* ptr = file.Data )
|
||||
fixed (byte* ptr = file.Data)
|
||||
{
|
||||
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
|
||||
AllocateData( file.Data.Length );
|
||||
MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||
NumParts = BitOperations.PopCount(*(ushort*)(ptr + 2));
|
||||
AllocateData(file.Data.Length);
|
||||
MemoryUtility.MemCpyUnchecked(Data, ptr, file.Data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public static ImcEntry GetDefault( Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists )
|
||||
=> GetDefault( path.ToString(), slot, variantIdx, out exists );
|
||||
public static ImcEntry GetDefault(MetaFileManager manager, Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists)
|
||||
=> GetDefault(manager, path.ToString(), slot, variantIdx, out exists);
|
||||
|
||||
public static ImcEntry GetDefault( string path, EquipSlot slot, int variantIdx, out bool exists )
|
||||
public static ImcEntry GetDefault(MetaFileManager manager, string path, EquipSlot slot, int variantIdx, out bool exists)
|
||||
{
|
||||
var file = DalamudServices.SGameData.GetFile( path );
|
||||
var file = manager.GameData.GetFile(path);
|
||||
exists = false;
|
||||
if( file == null )
|
||||
{
|
||||
if (file == null)
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
fixed( byte* ptr = file.Data )
|
||||
fixed (byte* ptr = file.Data)
|
||||
{
|
||||
var entry = VariantPtr( ptr, PartIndex( slot ), variantIdx );
|
||||
if( entry != null )
|
||||
{
|
||||
exists = true;
|
||||
return *entry;
|
||||
}
|
||||
var entry = VariantPtr(ptr, PartIndex(slot), variantIdx);
|
||||
if (entry == null)
|
||||
return new ImcEntry();
|
||||
|
||||
return new ImcEntry();
|
||||
exists = true;
|
||||
return *entry;
|
||||
}
|
||||
}
|
||||
|
||||
public void Replace( ResourceHandle* resource )
|
||||
public void Replace(ResourceHandle* resource)
|
||||
{
|
||||
var (data, length) = resource->GetData();
|
||||
var newData = Penumbra.MetaFileManager.AllocateDefaultMemory( ActualLength, 8 );
|
||||
if( newData == null )
|
||||
var newData = Penumbra.MetaFileManager.AllocateDefaultMemory(ActualLength, 8);
|
||||
if (newData == null)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not replace loaded IMC data at 0x{( ulong )resource:X}, allocation failed." );
|
||||
Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
MemoryUtility.MemCpyUnchecked( newData, Data, ActualLength );
|
||||
MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength);
|
||||
|
||||
Penumbra.MetaFileManager.Free( data, length );
|
||||
resource->SetData( ( IntPtr )newData, ActualLength );
|
||||
Penumbra.MetaFileManager.Free(data, length);
|
||||
resource->SetData((IntPtr)newData, ActualLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,79 +8,78 @@ namespace Penumbra.Meta.Files;
|
|||
|
||||
public unsafe class MetaBaseFile : IDisposable
|
||||
{
|
||||
public byte* Data { get; private set; }
|
||||
public int Length { get; private set; }
|
||||
public CharacterUtility.InternalIndex Index { get; }
|
||||
protected readonly MetaFileManager Manager;
|
||||
|
||||
public MetaBaseFile( MetaIndex idx )
|
||||
=> Index = CharacterUtility.ReverseIndices[ ( int )idx ];
|
||||
public byte* Data { get; private set; }
|
||||
public int Length { get; private set; }
|
||||
public CharacterUtility.InternalIndex Index { get; }
|
||||
|
||||
public MetaBaseFile(MetaFileManager manager, MetaIndex idx)
|
||||
{
|
||||
Manager = manager;
|
||||
Index = CharacterUtility.ReverseIndices[(int)idx];
|
||||
}
|
||||
|
||||
protected (IntPtr Data, int Length) DefaultData
|
||||
=> Penumbra.CharacterUtility.DefaultResource( Index );
|
||||
=> Manager.CharacterUtility.DefaultResource(Index);
|
||||
|
||||
// Reset to default values.
|
||||
/// <summary> Reset to default values. </summary>
|
||||
public virtual void Reset()
|
||||
{ }
|
||||
|
||||
// Obtain memory.
|
||||
protected void AllocateData( int length )
|
||||
/// <summary> Obtain memory. </summary>
|
||||
protected void AllocateData(int length)
|
||||
{
|
||||
Length = length;
|
||||
Data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( length );
|
||||
if( length > 0 )
|
||||
{
|
||||
GC.AddMemoryPressure( length );
|
||||
}
|
||||
Data = (byte*)Manager.AllocateFileMemory(length);
|
||||
if (length > 0)
|
||||
GC.AddMemoryPressure(length);
|
||||
}
|
||||
|
||||
// Free memory.
|
||||
/// <summary> Free memory. </summary>
|
||||
protected void ReleaseUnmanagedResources()
|
||||
{
|
||||
var ptr = ( IntPtr )Data;
|
||||
MemoryHelper.GameFree( ref ptr, ( ulong )Length );
|
||||
if( Length > 0 )
|
||||
{
|
||||
GC.RemoveMemoryPressure( Length );
|
||||
}
|
||||
var ptr = (IntPtr)Data;
|
||||
MemoryHelper.GameFree(ref ptr, (ulong)Length);
|
||||
if (Length > 0)
|
||||
GC.RemoveMemoryPressure(Length);
|
||||
|
||||
Length = 0;
|
||||
Data = null;
|
||||
}
|
||||
|
||||
// Resize memory while retaining data.
|
||||
protected void ResizeResources( int newLength )
|
||||
/// <summary> Resize memory while retaining data. </summary>
|
||||
protected void ResizeResources(int newLength)
|
||||
{
|
||||
if( newLength == Length )
|
||||
{
|
||||
if (newLength == Length)
|
||||
return;
|
||||
}
|
||||
|
||||
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
|
||||
if( newLength > Length )
|
||||
var data = (byte*)Manager.AllocateFileMemory((ulong)newLength);
|
||||
if (newLength > Length)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( data, Data, Length );
|
||||
MemoryUtility.MemSet( data + Length, 0, newLength - Length );
|
||||
MemoryUtility.MemCpyUnchecked(data, Data, Length);
|
||||
MemoryUtility.MemSet(data + Length, 0, newLength - Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked( data, Data, newLength );
|
||||
MemoryUtility.MemCpyUnchecked(data, Data, newLength);
|
||||
}
|
||||
|
||||
ReleaseUnmanagedResources();
|
||||
GC.AddMemoryPressure( newLength );
|
||||
GC.AddMemoryPressure(newLength);
|
||||
Data = data;
|
||||
Length = newLength;
|
||||
}
|
||||
|
||||
// Manually free memory.
|
||||
/// <summary> Manually free memory. </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize( this );
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~MetaBaseFile()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
85
Penumbra/Meta/MetaFileManager.cs
Normal file
85
Penumbra/Meta/MetaFileManager.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
@ -18,41 +19,51 @@ namespace Penumbra.Mods.ItemSwap;
|
|||
|
||||
public static class EquipmentSwap
|
||||
{
|
||||
private static EquipSlot[] ConvertSlots( EquipSlot slot, bool rFinger, bool lFinger )
|
||||
private static EquipSlot[] ConvertSlots(EquipSlot slot, bool rFinger, bool lFinger)
|
||||
{
|
||||
if( slot != EquipSlot.RFinger )
|
||||
{
|
||||
return new[] { slot };
|
||||
}
|
||||
if (slot != EquipSlot.RFinger)
|
||||
return new[]
|
||||
{
|
||||
slot,
|
||||
};
|
||||
|
||||
return rFinger
|
||||
? lFinger
|
||||
? new[] { EquipSlot.RFinger, EquipSlot.LFinger }
|
||||
: new[] { EquipSlot.RFinger }
|
||||
? new[]
|
||||
{
|
||||
EquipSlot.RFinger,
|
||||
EquipSlot.LFinger,
|
||||
}
|
||||
: new[]
|
||||
{
|
||||
EquipSlot.RFinger,
|
||||
}
|
||||
: lFinger
|
||||
? new[] { EquipSlot.LFinger }
|
||||
: Array.Empty< EquipSlot >();
|
||||
? new[]
|
||||
{
|
||||
EquipSlot.LFinger,
|
||||
}
|
||||
: Array.Empty<EquipSlot>();
|
||||
}
|
||||
|
||||
public static Item[] CreateTypeSwap( IObjectIdentifier identifier, List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips,
|
||||
EquipSlot slotFrom, Item itemFrom, EquipSlot slotTo, Item itemTo )
|
||||
public static Item[] CreateTypeSwap(MetaFileManager manager, IObjectIdentifier identifier, List<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() )
|
||||
{
|
||||
LookupItem(itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom);
|
||||
LookupItem(itemTo, out var actualSlotTo, out var idTo, out var variantTo);
|
||||
if (actualSlotFrom != slotFrom.ToSlot() || actualSlotTo != slotTo.ToSlot())
|
||||
throw new ItemSwap.InvalidItemTypeException();
|
||||
}
|
||||
|
||||
var ( imcFileFrom, variants, affectedItems ) = GetVariants( identifier, slotFrom, idFrom, idTo, variantFrom );
|
||||
var imcManip = new ImcManipulation( slotTo, variantTo, idTo.Value, default );
|
||||
var imcFileTo = new ImcFile( imcManip );
|
||||
var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom);
|
||||
var imcManip = new ImcManipulation(slotTo, variantTo, idTo.Value, default);
|
||||
var imcFileTo = new ImcFile(manager, imcManip);
|
||||
var skipFemale = false;
|
||||
var skipMale = false;
|
||||
var mtrlVariantTo = manips( imcManip.Copy( imcFileTo.GetEntry( ImcFile.PartIndex( slotTo ), variantTo ) ) ).Imc.Entry.MaterialId;
|
||||
foreach( var gr in Enum.GetValues< GenderRace >() )
|
||||
var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo))).Imc.Entry.MaterialId;
|
||||
foreach (var gr in Enum.GetValues<GenderRace>())
|
||||
{
|
||||
switch( gr.Split().Item1 )
|
||||
switch (gr.Split().Item1)
|
||||
{
|
||||
case Gender.Male when skipMale: continue;
|
||||
case Gender.Female when skipFemale: continue;
|
||||
|
|
@ -60,22 +71,18 @@ public static class EquipmentSwap
|
|||
case Gender.FemaleNpc when skipFemale: continue;
|
||||
}
|
||||
|
||||
if( CharacterUtilityData.EqdpIdx( gr, true ) < 0 )
|
||||
{
|
||||
if (CharacterUtilityData.EqdpIdx(gr, true) < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var eqdp = CreateEqdp( redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo );
|
||||
if( eqdp != null )
|
||||
{
|
||||
swaps.Add( eqdp );
|
||||
}
|
||||
var eqdp = CreateEqdp(manager, redirections, manips, slotFrom, slotTo, gr, idFrom, idTo, mtrlVariantTo);
|
||||
if (eqdp != null)
|
||||
swaps.Add(eqdp);
|
||||
}
|
||||
catch( ItemSwap.MissingFileException e )
|
||||
catch (ItemSwap.MissingFileException e)
|
||||
{
|
||||
switch( gr )
|
||||
switch (gr)
|
||||
{
|
||||
case GenderRace.MidlanderMale when e.Type == ResourceType.Mdl:
|
||||
skipMale = true;
|
||||
|
|
@ -88,59 +95,54 @@ public static class EquipmentSwap
|
|||
}
|
||||
}
|
||||
|
||||
foreach( var variant in variants )
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
var imc = CreateImc( redirections, manips, slotFrom, slotTo, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo );
|
||||
swaps.Add( imc );
|
||||
var imc = CreateImc(manager, redirections, manips, slotFrom, slotTo, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo);
|
||||
swaps.Add(imc);
|
||||
}
|
||||
|
||||
return affectedItems;
|
||||
}
|
||||
|
||||
public static Item[] CreateItemSwap( IObjectIdentifier identifier, List< Swap > swaps, Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, Item itemFrom,
|
||||
Item itemTo, bool rFinger = true, bool lFinger = true )
|
||||
public static Item[] CreateItemSwap(MetaFileManager manager, IObjectIdentifier identifier, List<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 )
|
||||
{
|
||||
LookupItem(itemFrom, out var slotFrom, out var idFrom, out var variantFrom);
|
||||
LookupItem(itemTo, out var slotTo, out var idTo, out var variantTo);
|
||||
if (slotFrom != slotTo)
|
||||
throw new ItemSwap.InvalidItemTypeException();
|
||||
}
|
||||
|
||||
var eqp = CreateEqp( manips, slotFrom, idFrom, idTo );
|
||||
if( eqp != null )
|
||||
{
|
||||
swaps.Add( eqp );
|
||||
}
|
||||
var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo);
|
||||
if (eqp != null)
|
||||
swaps.Add(eqp);
|
||||
|
||||
var gmp = CreateGmp( manips, slotFrom, idFrom, idTo );
|
||||
if( gmp != null )
|
||||
{
|
||||
swaps.Add( gmp );
|
||||
}
|
||||
var gmp = CreateGmp(manager, manips, slotFrom, idFrom, idTo);
|
||||
if (gmp != null)
|
||||
swaps.Add(gmp);
|
||||
|
||||
var affectedItems = Array.Empty< Item >();
|
||||
foreach( var slot in ConvertSlots( slotFrom, rFinger, lFinger ) )
|
||||
var affectedItems = Array.Empty<Item>();
|
||||
foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger))
|
||||
{
|
||||
( var imcFileFrom, var variants, affectedItems ) = GetVariants( identifier, slot, idFrom, idTo, variantFrom );
|
||||
var imcManip = new ImcManipulation( slot, variantTo, idTo.Value, default );
|
||||
var imcFileTo = new ImcFile( imcManip );
|
||||
(var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom);
|
||||
var imcManip = new ImcManipulation(slot, variantTo, idTo.Value, default);
|
||||
var imcFileTo = new ImcFile(manager, imcManip);
|
||||
|
||||
var isAccessory = slot.IsAccessory();
|
||||
var estType = slot switch
|
||||
{
|
||||
EquipSlot.Head => EstManipulation.EstType.Head,
|
||||
EquipSlot.Body => EstManipulation.EstType.Body,
|
||||
_ => ( EstManipulation.EstType )0,
|
||||
_ => (EstManipulation.EstType)0,
|
||||
};
|
||||
|
||||
var skipFemale = false;
|
||||
var skipMale = false;
|
||||
var mtrlVariantTo = manips( imcManip.Copy( imcFileTo.GetEntry( ImcFile.PartIndex( slot ), variantTo ) ) ).Imc.Entry.MaterialId;
|
||||
foreach( var gr in Enum.GetValues< GenderRace >() )
|
||||
var mtrlVariantTo = manips(imcManip.Copy(imcFileTo.GetEntry(ImcFile.PartIndex(slot), variantTo))).Imc.Entry.MaterialId;
|
||||
foreach (var gr in Enum.GetValues<GenderRace>())
|
||||
{
|
||||
switch( gr.Split().Item1 )
|
||||
switch (gr.Split().Item1)
|
||||
{
|
||||
case Gender.Male when skipMale: continue;
|
||||
case Gender.Female when skipFemale: continue;
|
||||
|
|
@ -148,30 +150,24 @@ public static class EquipmentSwap
|
|||
case Gender.FemaleNpc when skipFemale: continue;
|
||||
}
|
||||
|
||||
if( CharacterUtilityData.EqdpIdx( gr, isAccessory ) < 0 )
|
||||
{
|
||||
if (CharacterUtilityData.EqdpIdx(gr, isAccessory) < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var eqdp = CreateEqdp( redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo );
|
||||
if( eqdp != null )
|
||||
{
|
||||
swaps.Add( eqdp );
|
||||
}
|
||||
var eqdp = CreateEqdp(manager, redirections, manips, slot, gr, idFrom, idTo, mtrlVariantTo);
|
||||
if (eqdp != null)
|
||||
swaps.Add(eqdp);
|
||||
|
||||
var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits( slot ).Item2 ?? false;
|
||||
var est = ItemSwap.CreateEst( redirections, manips, estType, gr, idFrom, idTo, ownMdl );
|
||||
if( est != null )
|
||||
{
|
||||
swaps.Add( est );
|
||||
}
|
||||
var ownMdl = eqdp?.SwapApplied.Eqdp.Entry.ToBits(slot).Item2 ?? false;
|
||||
var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl);
|
||||
if (est != null)
|
||||
swaps.Add(est);
|
||||
}
|
||||
catch( ItemSwap.MissingFileException e )
|
||||
catch (ItemSwap.MissingFileException e)
|
||||
{
|
||||
switch( gr )
|
||||
switch (gr)
|
||||
{
|
||||
case GenderRace.MidlanderMale when e.Type == ResourceType.Mdl:
|
||||
skipMale = true;
|
||||
|
|
@ -184,33 +180,38 @@ public static class EquipmentSwap
|
|||
}
|
||||
}
|
||||
|
||||
foreach( var variant in variants )
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
var imc = CreateImc( redirections, manips, slot, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo );
|
||||
swaps.Add( imc );
|
||||
var imc = CreateImc(manager, redirections, manips, slot, idFrom, idTo, variant, variantTo, imcFileFrom, imcFileTo);
|
||||
swaps.Add(imc);
|
||||
}
|
||||
}
|
||||
|
||||
return affectedItems;
|
||||
}
|
||||
|
||||
public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, GenderRace gr, SetId idFrom,
|
||||
SetId idTo, byte mtrlTo )
|
||||
=> CreateEqdp( redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo );
|
||||
public static MetaSwap? CreateEqdp( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom,
|
||||
SetId idTo, byte mtrlTo )
|
||||
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, SetId idFrom,
|
||||
SetId idTo, byte mtrlTo)
|
||||
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
|
||||
|
||||
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<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 meta = new MetaSwap( manips, eqdpFrom, eqdpTo );
|
||||
var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits( slotFrom );
|
||||
if( ownMdl )
|
||||
var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom.Value), slotFrom, gender,
|
||||
race, idFrom.Value);
|
||||
var eqdpTo = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotTo.IsAccessory(), idTo.Value), slotTo, gender, race,
|
||||
idTo.Value);
|
||||
var meta = new MetaSwap(manips, eqdpFrom, eqdpTo);
|
||||
var (ownMtrl, ownMdl) = meta.SwapApplied.Eqdp.Entry.ToBits(slotFrom);
|
||||
if (ownMdl)
|
||||
{
|
||||
var mdl = CreateMdl( redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo );
|
||||
meta.ChildSwaps.Add( mdl );
|
||||
var mdl = CreateMdl(manager, redirections, slotFrom, slotTo, gr, idFrom, idTo, mtrlTo);
|
||||
meta.ChildSwaps.Add(mdl);
|
||||
}
|
||||
else if( !ownMtrl && meta.SwapAppliedIsDefault )
|
||||
else if (!ownMtrl && meta.SwapAppliedIsDefault)
|
||||
{
|
||||
meta = null;
|
||||
}
|
||||
|
|
@ -218,97 +219,98 @@ public static class EquipmentSwap
|
|||
return meta;
|
||||
}
|
||||
|
||||
public static FileSwap CreateMdl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo )
|
||||
=> CreateMdl( redirections, slot, slot, gr, idFrom, idTo, mtrlTo );
|
||||
public static FileSwap CreateMdl(MetaFileManager manager, Func<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 mdlPathTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mdl.Path( idTo, gr, slotTo ) : GamePaths.Equipment.Mdl.Path( idTo, gr, slotTo );
|
||||
var mdl = FileSwap.CreateSwap( ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo );
|
||||
var mdlPathFrom = slotFrom.IsAccessory()
|
||||
? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom)
|
||||
: GamePaths.Equipment.Mdl.Path(idFrom, gr, slotFrom);
|
||||
var mdlPathTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mdl.Path(idTo, gr, slotTo) : GamePaths.Equipment.Mdl.Path(idTo, gr, slotTo);
|
||||
var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo);
|
||||
|
||||
foreach( ref var fileName in mdl.AsMdl()!.Materials.AsSpan() )
|
||||
foreach (ref var fileName in mdl.AsMdl()!.Materials.AsSpan())
|
||||
{
|
||||
var mtrl = CreateMtrl( redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged );
|
||||
if( mtrl != null )
|
||||
{
|
||||
mdl.ChildSwaps.Add( mtrl );
|
||||
}
|
||||
var mtrl = CreateMtrl(manager, redirections, slotFrom, slotTo, idFrom, idTo, mtrlTo, ref fileName, ref mdl.DataWasChanged);
|
||||
if (mtrl != null)
|
||||
mdl.ChildSwaps.Add(mtrl);
|
||||
}
|
||||
|
||||
return mdl;
|
||||
}
|
||||
|
||||
private static void LookupItem( Item i, out EquipSlot slot, out SetId modelId, out byte variant )
|
||||
private static void LookupItem(Item i, out EquipSlot slot, out SetId modelId, out byte variant)
|
||||
{
|
||||
slot = ( ( EquipSlot )i.EquipSlotCategory.Row ).ToSlot();
|
||||
if( !slot.IsEquipmentPiece() )
|
||||
{
|
||||
slot = ((EquipSlot)i.EquipSlotCategory.Row).ToSlot();
|
||||
if (!slot.IsEquipmentPiece())
|
||||
throw new ItemSwap.InvalidItemTypeException();
|
||||
}
|
||||
|
||||
modelId = ( ( Quad )i.ModelMain ).A;
|
||||
variant = ( byte )( ( Quad )i.ModelMain ).B;
|
||||
modelId = ((Quad)i.ModelMain).A;
|
||||
variant = (byte)((Quad)i.ModelMain).B;
|
||||
}
|
||||
|
||||
private static (ImcFile, byte[], Item[]) GetVariants( IObjectIdentifier identifier, EquipSlot slotFrom, SetId idFrom, SetId idTo, byte variantFrom )
|
||||
private static (ImcFile, byte[], Item[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
|
||||
SetId idFrom, SetId idTo, byte variantFrom)
|
||||
{
|
||||
var entry = new ImcManipulation( slotFrom, variantFrom, idFrom.Value, default );
|
||||
var imc = new ImcFile( entry );
|
||||
var entry = new ImcManipulation(slotFrom, variantFrom, idFrom.Value, default);
|
||||
var imc = new ImcFile(manager, entry);
|
||||
Item[] items;
|
||||
byte[] variants;
|
||||
if( idFrom.Value == idTo.Value )
|
||||
if (idFrom.Value == idTo.Value)
|
||||
{
|
||||
items = identifier.Identify( idFrom, variantFrom, slotFrom ).ToArray();
|
||||
variants = new[] { variantFrom };
|
||||
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
|
||||
variants = new[]
|
||||
{
|
||||
variantFrom,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
items = identifier.Identify( slotFrom.IsEquipment()
|
||||
? GamePaths.Equipment.Mdl.Path( idFrom, GenderRace.MidlanderMale, slotFrom )
|
||||
: GamePaths.Accessory.Mdl.Path( idFrom, GenderRace.MidlanderMale, slotFrom ) ).Select( kvp => kvp.Value ).OfType< Item >().ToArray();
|
||||
variants = Enumerable.Range( 0, imc.Count + 1 ).Select( i => ( byte )i ).ToArray();
|
||||
items = identifier.Identify(slotFrom.IsEquipment()
|
||||
? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)
|
||||
: GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)).Select(kvp => kvp.Value).OfType<Item>().ToArray();
|
||||
variants = Enumerable.Range(0, imc.Count + 1).Select(i => (byte)i).ToArray();
|
||||
}
|
||||
|
||||
return ( imc, variants, items );
|
||||
return (imc, variants, items);
|
||||
}
|
||||
|
||||
public static MetaSwap? CreateGmp( Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo )
|
||||
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
|
||||
SetId idTo)
|
||||
{
|
||||
if( slot is not EquipSlot.Head )
|
||||
{
|
||||
if (slot is not EquipSlot.Head)
|
||||
return null;
|
||||
}
|
||||
|
||||
var manipFrom = new GmpManipulation( ExpandedGmpFile.GetDefault( idFrom.Value ), idFrom.Value );
|
||||
var manipTo = new GmpManipulation( ExpandedGmpFile.GetDefault( idTo.Value ), idTo.Value );
|
||||
return new MetaSwap( manips, manipFrom, manipTo );
|
||||
var manipFrom = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idFrom.Value), idFrom.Value);
|
||||
var manipTo = new GmpManipulation(ExpandedGmpFile.GetDefault(manager, idTo.Value), idTo.Value);
|
||||
return new MetaSwap(manips, manipFrom, manipTo);
|
||||
}
|
||||
|
||||
public static MetaSwap CreateImc( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo,
|
||||
byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo )
|
||||
=> CreateImc( redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo );
|
||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
|
||||
SetId idFrom, SetId idTo,
|
||||
byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
|
||||
|
||||
public static MetaSwap CreateImc( Func< Utf8GamePath, FullPath > redirections, Func< MetaManipulation, MetaManipulation > manips, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
|
||||
byte variantFrom, byte variantTo, ImcFile imcFileFrom, ImcFile imcFileTo )
|
||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<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 );
|
||||
var entryTo = imcFileTo.GetEntry( ImcFile.PartIndex( slotTo ), variantTo );
|
||||
var manipulationFrom = new ImcManipulation( slotFrom, variantFrom, idFrom.Value, entryFrom );
|
||||
var manipulationTo = new ImcManipulation( slotTo, variantTo, idTo.Value, entryTo );
|
||||
var imc = new MetaSwap( manips, manipulationFrom, manipulationTo );
|
||||
var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
|
||||
var entryTo = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo);
|
||||
var manipulationFrom = new ImcManipulation(slotFrom, variantFrom, idFrom.Value, entryFrom);
|
||||
var manipulationTo = new ImcManipulation(slotTo, variantTo, idTo.Value, entryTo);
|
||||
var imc = new MetaSwap(manips, manipulationFrom, manipulationTo);
|
||||
|
||||
var decal = CreateDecal( redirections, imc.SwapToModded.Imc.Entry.DecalId );
|
||||
if( decal != null )
|
||||
{
|
||||
imc.ChildSwaps.Add( decal );
|
||||
}
|
||||
var decal = CreateDecal(manager, redirections, imc.SwapToModded.Imc.Entry.DecalId);
|
||||
if (decal != null)
|
||||
imc.ChildSwaps.Add(decal);
|
||||
|
||||
var avfx = CreateAvfx( redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId );
|
||||
if( avfx != null )
|
||||
{
|
||||
imc.ChildSwaps.Add( avfx );
|
||||
}
|
||||
var avfx = CreateAvfx(manager, redirections, idFrom, idTo, imc.SwapToModded.Imc.Entry.VfxId);
|
||||
if (avfx != null)
|
||||
imc.ChildSwaps.Add(avfx);
|
||||
|
||||
// IMC also controls sound, Example: Dodore Doublet, but unknown what it does?
|
||||
// IMC also controls some material animation, Example: The Howling Spirit and The Wailing Spirit, but unknown what it does.
|
||||
|
|
@ -316,134 +318,135 @@ public static class EquipmentSwap
|
|||
}
|
||||
|
||||
// Example: Crimson Standard Bracelet
|
||||
public static FileSwap? CreateDecal( Func< Utf8GamePath, FullPath > redirections, byte decalId )
|
||||
public static FileSwap? CreateDecal(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, byte decalId)
|
||||
{
|
||||
if( decalId == 0 )
|
||||
{
|
||||
if (decalId == 0)
|
||||
return null;
|
||||
}
|
||||
|
||||
var decalPath = GamePaths.Equipment.Decal.Path( decalId );
|
||||
return FileSwap.CreateSwap( ResourceType.Tex, redirections, decalPath, decalPath );
|
||||
var decalPath = GamePaths.Equipment.Decal.Path(decalId);
|
||||
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, decalPath, decalPath);
|
||||
}
|
||||
|
||||
|
||||
// Example: Abyssos Helm / Body
|
||||
public static FileSwap? CreateAvfx( Func< Utf8GamePath, FullPath > redirections, SetId idFrom, SetId idTo, byte vfxId )
|
||||
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, SetId idFrom, SetId idTo, byte vfxId)
|
||||
{
|
||||
if( vfxId == 0 )
|
||||
{
|
||||
if (vfxId == 0)
|
||||
return null;
|
||||
}
|
||||
|
||||
var vfxPathFrom = GamePaths.Equipment.Avfx.Path( idFrom.Value, vfxId );
|
||||
var vfxPathTo = GamePaths.Equipment.Avfx.Path( idTo.Value, vfxId );
|
||||
var avfx = FileSwap.CreateSwap( ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo );
|
||||
var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom.Value, vfxId);
|
||||
var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo.Value, vfxId);
|
||||
var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo);
|
||||
|
||||
foreach( ref var filePath in avfx.AsAvfx()!.Textures.AsSpan() )
|
||||
foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan())
|
||||
{
|
||||
var atex = CreateAtex( redirections, ref filePath, ref avfx.DataWasChanged );
|
||||
avfx.ChildSwaps.Add( atex );
|
||||
var atex = CreateAtex(manager, redirections, ref filePath, ref avfx.DataWasChanged);
|
||||
avfx.ChildSwaps.Add(atex);
|
||||
}
|
||||
|
||||
return avfx;
|
||||
}
|
||||
|
||||
public static MetaSwap? CreateEqp( Func< MetaManipulation, MetaManipulation > manips, EquipSlot slot, SetId idFrom, SetId idTo )
|
||||
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
|
||||
SetId idTo)
|
||||
{
|
||||
if( slot.IsAccessory() )
|
||||
{
|
||||
if (slot.IsAccessory())
|
||||
return null;
|
||||
}
|
||||
|
||||
var eqpValueFrom = ExpandedEqpFile.GetDefault( idFrom.Value );
|
||||
var eqpValueTo = ExpandedEqpFile.GetDefault( idTo.Value );
|
||||
var eqpFrom = new EqpManipulation( eqpValueFrom, slot, idFrom.Value );
|
||||
var eqpTo = new EqpManipulation( eqpValueTo, slot, idFrom.Value );
|
||||
return new MetaSwap( manips, eqpFrom, eqpTo );
|
||||
var eqpValueFrom = ExpandedEqpFile.GetDefault(manager, idFrom.Value);
|
||||
var eqpValueTo = ExpandedEqpFile.GetDefault(manager, idTo.Value);
|
||||
var eqpFrom = new EqpManipulation(eqpValueFrom, slot, idFrom.Value);
|
||||
var eqpTo = new EqpManipulation(eqpValueTo, slot, idFrom.Value);
|
||||
return new MetaSwap(manips, eqpFrom, eqpTo);
|
||||
}
|
||||
|
||||
public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slot, SetId idFrom, SetId idTo, byte variantTo, ref string fileName,
|
||||
ref bool dataWasChanged )
|
||||
=> CreateMtrl( redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged );
|
||||
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, SetId idFrom,
|
||||
SetId idTo, byte variantTo, ref string fileName,
|
||||
ref bool dataWasChanged)
|
||||
=> CreateMtrl(manager, redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged);
|
||||
|
||||
public static FileSwap? CreateMtrl( Func< Utf8GamePath, FullPath > redirections, EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo, byte variantTo, ref string fileName,
|
||||
ref bool dataWasChanged )
|
||||
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<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}" ) )
|
||||
{
|
||||
if (!fileName.Contains($"{prefix}{idTo.Value:D4}"))
|
||||
return null;
|
||||
}
|
||||
|
||||
var folderTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idTo, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idTo, variantTo );
|
||||
var pathTo = $"{folderTo}{fileName}";
|
||||
var folderTo = slotTo.IsAccessory()
|
||||
? GamePaths.Accessory.Mtrl.FolderPath(idTo, variantTo)
|
||||
: GamePaths.Equipment.Mtrl.FolderPath(idTo, variantTo);
|
||||
var pathTo = $"{folderTo}{fileName}";
|
||||
|
||||
var folderFrom = slotFrom.IsAccessory() ? GamePaths.Accessory.Mtrl.FolderPath( idFrom, variantTo ) : GamePaths.Equipment.Mtrl.FolderPath( idFrom, variantTo );
|
||||
var newFileName = ItemSwap.ReplaceId( fileName, prefix, idTo, idFrom );
|
||||
newFileName = ItemSwap.ReplaceSlot( newFileName, slotTo, slotFrom, slotTo != slotFrom );
|
||||
var pathFrom = $"{folderFrom}{newFileName}";
|
||||
var folderFrom = slotFrom.IsAccessory()
|
||||
? GamePaths.Accessory.Mtrl.FolderPath(idFrom, variantTo)
|
||||
: GamePaths.Equipment.Mtrl.FolderPath(idFrom, variantTo);
|
||||
var newFileName = ItemSwap.ReplaceId(fileName, prefix, idTo, idFrom);
|
||||
newFileName = ItemSwap.ReplaceSlot(newFileName, slotTo, slotFrom, slotTo != slotFrom);
|
||||
var pathFrom = $"{folderFrom}{newFileName}";
|
||||
|
||||
if( newFileName != fileName )
|
||||
if (newFileName != fileName)
|
||||
{
|
||||
fileName = newFileName;
|
||||
dataWasChanged = true;
|
||||
}
|
||||
|
||||
var mtrl = FileSwap.CreateSwap( ResourceType.Mtrl, redirections, pathFrom, pathTo );
|
||||
var shpk = CreateShader( redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged );
|
||||
mtrl.ChildSwaps.Add( shpk );
|
||||
var mtrl = FileSwap.CreateSwap(manager, ResourceType.Mtrl, redirections, pathFrom, pathTo);
|
||||
var shpk = CreateShader(manager, redirections, ref mtrl.AsMtrl()!.ShaderPackage.Name, ref mtrl.DataWasChanged);
|
||||
mtrl.ChildSwaps.Add(shpk);
|
||||
|
||||
foreach( ref var texture in mtrl.AsMtrl()!.Textures.AsSpan() )
|
||||
foreach (ref var texture in mtrl.AsMtrl()!.Textures.AsSpan())
|
||||
{
|
||||
var tex = CreateTex( redirections, prefix, slotFrom, slotTo, idFrom, idTo, ref texture, ref mtrl.DataWasChanged );
|
||||
mtrl.ChildSwaps.Add( tex );
|
||||
var tex = CreateTex(manager, redirections, prefix, slotFrom, slotTo, idFrom, idTo, ref texture, ref mtrl.DataWasChanged);
|
||||
mtrl.ChildSwaps.Add(tex);
|
||||
}
|
||||
|
||||
return mtrl;
|
||||
}
|
||||
|
||||
public static FileSwap CreateTex( Func< Utf8GamePath, FullPath > redirections, char prefix, SetId idFrom, SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged )
|
||||
=> CreateTex( redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged );
|
||||
public static FileSwap CreateTex(MetaFileManager manager, Func<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;
|
||||
if( texture.DX11 )
|
||||
if (texture.DX11)
|
||||
{
|
||||
var fileName = Path.GetFileName( path );
|
||||
if( !fileName.StartsWith( "--" ) )
|
||||
var fileName = Path.GetFileName(path);
|
||||
if (!fileName.StartsWith("--"))
|
||||
{
|
||||
path = path.Replace( fileName, $"--{fileName}" );
|
||||
path = path.Replace(fileName, $"--{fileName}");
|
||||
addedDashes = true;
|
||||
}
|
||||
}
|
||||
|
||||
var newPath = ItemSwap.ReplaceAnyId( path, prefix, idFrom );
|
||||
newPath = ItemSwap.ReplaceSlot( newPath, slotTo, slotFrom, slotTo != slotFrom );
|
||||
newPath = ItemSwap.AddSuffix( newPath, ".tex", $"_{Path.GetFileName( texture.Path ).GetStableHashCode():x8}" );
|
||||
if( newPath != path )
|
||||
var newPath = ItemSwap.ReplaceAnyId(path, prefix, idFrom);
|
||||
newPath = ItemSwap.ReplaceSlot(newPath, slotTo, slotFrom, slotTo != slotFrom);
|
||||
newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}");
|
||||
if (newPath != path)
|
||||
{
|
||||
texture.Path = addedDashes ? newPath.Replace( "--", string.Empty ) : newPath;
|
||||
texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath;
|
||||
dataWasChanged = true;
|
||||
}
|
||||
|
||||
return FileSwap.CreateSwap( ResourceType.Tex, redirections, newPath, path, path );
|
||||
return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, newPath, path, path);
|
||||
}
|
||||
|
||||
public static FileSwap CreateShader( Func< Utf8GamePath, FullPath > redirections, ref string shaderName, ref bool dataWasChanged )
|
||||
public static FileSwap CreateShader(MetaFileManager manager, Func<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}" );
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -7,18 +7,19 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta;
|
||||
using static Penumbra.Mods.ItemSwap.ItemSwap;
|
||||
using Penumbra.Services;
|
||||
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Mods.ItemSwap;
|
||||
|
||||
public class Swap
|
||||
{
|
||||
/// <summary> Any further swaps belonging specifically to this tree of changes. </summary>
|
||||
public readonly List< Swap > ChildSwaps = new();
|
||||
public readonly List<Swap> ChildSwaps = new();
|
||||
|
||||
public IEnumerable< Swap > WithChildren()
|
||||
=> ChildSwaps.SelectMany( c => c.WithChildren() ).Prepend( this );
|
||||
public IEnumerable<Swap> WithChildren()
|
||||
=> ChildSwaps.SelectMany(c => c.WithChildren()).Prepend(this);
|
||||
}
|
||||
|
||||
public sealed class MetaSwap : Swap
|
||||
|
|
@ -47,15 +48,15 @@ public sealed class MetaSwap : Swap
|
|||
/// <param name="manipulations">A function that converts the given manipulation to the modded one.</param>
|
||||
/// <param name="manipFrom">The original meta identifier with its default value.</param>
|
||||
/// <param name="manipTo">The target meta identifier with its default value.</param>
|
||||
public MetaSwap( Func< MetaManipulation, MetaManipulation > manipulations, MetaManipulation manipFrom, MetaManipulation manipTo )
|
||||
public MetaSwap(Func<MetaManipulation, MetaManipulation> manipulations, MetaManipulation manipFrom, MetaManipulation manipTo)
|
||||
{
|
||||
SwapFrom = manipFrom;
|
||||
SwapToDefault = manipTo;
|
||||
|
||||
SwapToModded = manipulations( manipTo );
|
||||
SwapToIsDefault = manipTo.EntryEquals( SwapToModded );
|
||||
SwapApplied = SwapFrom.WithEntryOf( SwapToModded );
|
||||
SwapAppliedIsDefault = SwapApplied.EntryEquals( SwapFrom );
|
||||
SwapToModded = manipulations(manipTo);
|
||||
SwapToIsDefault = manipTo.EntryEquals(SwapToModded);
|
||||
SwapApplied = SwapFrom.WithEntryOf(SwapToModded);
|
||||
SwapAppliedIsDefault = SwapApplied.EntryEquals(SwapFrom);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,8 +96,8 @@ public sealed class FileSwap : Swap
|
|||
/// <summary> Whether SwapFromPreChangePath equals SwapFromRequest. </summary>
|
||||
public bool SwapFromChanged;
|
||||
|
||||
public string GetNewPath( string newMod )
|
||||
=> Path.Combine( newMod, new Utf8RelPath( SwapFromRequestPath ).ToString() );
|
||||
public string GetNewPath(string newMod)
|
||||
=> Path.Combine(newMod, new Utf8RelPath(SwapFromRequestPath).ToString());
|
||||
|
||||
public MdlFile? AsMdl()
|
||||
=> FileData as MdlFile;
|
||||
|
|
@ -116,8 +117,9 @@ public sealed class FileSwap : Swap
|
|||
/// <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,
|
||||
string? swapFromPreChange = null )
|
||||
public static FileSwap CreateSwap(MetaFileManager manager, ResourceType type, Func<Utf8GamePath, FullPath> redirections,
|
||||
string swapFromRequest, string swapToRequest,
|
||||
string? swapFromPreChange = null)
|
||||
{
|
||||
var swap = new FileSwap
|
||||
{
|
||||
|
|
@ -131,49 +133,25 @@ public sealed class FileSwap : Swap
|
|||
SwapToModded = FullPath.Empty,
|
||||
};
|
||||
|
||||
if( swapFromRequest.Length == 0
|
||||
|| swapToRequest.Length == 0
|
||||
|| !Utf8GamePath.FromString( swapToRequest, out swap.SwapToRequestPath )
|
||||
|| !Utf8GamePath.FromString( swapFromRequest, out swap.SwapFromRequestPath ) )
|
||||
{
|
||||
throw new Exception( $"Could not create UTF8 String for \"{swapFromRequest}\" or \"{swapToRequest}\"." );
|
||||
}
|
||||
if (swapFromRequest.Length == 0
|
||||
|| swapToRequest.Length == 0
|
||||
|| !Utf8GamePath.FromString(swapToRequest, out swap.SwapToRequestPath)
|
||||
|| !Utf8GamePath.FromString(swapFromRequest, out swap.SwapFromRequestPath))
|
||||
throw new Exception($"Could not create UTF8 String for \"{swapFromRequest}\" or \"{swapToRequest}\".");
|
||||
|
||||
swap.SwapToModded = redirections( swap.SwapToRequestPath );
|
||||
swap.SwapToModdedExistsInGame = !swap.SwapToModded.IsRooted && DalamudServices.SGameData.FileExists( swap.SwapToModded.InternalName.ToString() );
|
||||
swap.SwapToModdedEqualsOriginal = !swap.SwapToModded.IsRooted && swap.SwapToModded.InternalName.Equals( swap.SwapFromRequestPath.Path );
|
||||
swap.SwapToModded = redirections(swap.SwapToRequestPath);
|
||||
swap.SwapToModdedExistsInGame =
|
||||
!swap.SwapToModded.IsRooted && DalamudServices.SGameData.FileExists(swap.SwapToModded.InternalName.ToString());
|
||||
swap.SwapToModdedEqualsOriginal = !swap.SwapToModded.IsRooted && swap.SwapToModded.InternalName.Equals(swap.SwapFromRequestPath.Path);
|
||||
|
||||
swap.FileData = type switch
|
||||
{
|
||||
ResourceType.Mdl => LoadMdl( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ),
|
||||
ResourceType.Mtrl => LoadMtrl( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ),
|
||||
ResourceType.Avfx => LoadAvfx( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ),
|
||||
_ => LoadFile( swap.SwapToModded, out var f ) ? f : throw new MissingFileException( type, swap.SwapToModded ),
|
||||
ResourceType.Mdl => LoadMdl(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded),
|
||||
ResourceType.Mtrl => LoadMtrl(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded),
|
||||
ResourceType.Avfx => LoadAvfx(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded),
|
||||
_ => LoadFile(manager, swap.SwapToModded, out var f) ? f : throw new MissingFileException(type, swap.SwapToModded),
|
||||
};
|
||||
|
||||
return swap;
|
||||
}
|
||||
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.Import;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// A sub mod is a collection of
|
||||
/// - file replacements
|
||||
/// - file swaps
|
||||
|
|
@ -18,14 +19,14 @@ namespace Penumbra.Mods;
|
|||
/// that can be used either as an option or as the default data for a mod.
|
||||
/// It can be loaded and reloaded from Json.
|
||||
/// Nothing is checked for existence or validity when loading.
|
||||
/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
|
||||
/// Objects are also not checked for uniqueness, the first appearance of a game path or meta path decides.
|
||||
/// </summary>
|
||||
public sealed class SubMod : ISubMod
|
||||
public sealed class SubMod : ISubMod
|
||||
{
|
||||
public string Name { get; set; } = "Default";
|
||||
|
||||
public string FullName
|
||||
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[ GroupIdx ].Name}: {Name}";
|
||||
=> GroupIdx < 0 ? "Default Option" : $"{ParentMod.Groups[GroupIdx].Name}: {Name}";
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
|
|
@ -36,180 +37,165 @@ namespace Penumbra.Mods;
|
|||
public bool IsDefault
|
||||
=> GroupIdx < 0;
|
||||
|
||||
public Dictionary< Utf8GamePath, FullPath > FileData = new();
|
||||
public Dictionary< Utf8GamePath, FullPath > FileSwapData = new();
|
||||
public HashSet< MetaManipulation > ManipulationData = new();
|
||||
public Dictionary<Utf8GamePath, FullPath> FileData = new();
|
||||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = new();
|
||||
public HashSet<MetaManipulation> ManipulationData = new();
|
||||
|
||||
public SubMod( IMod parentMod )
|
||||
public SubMod(IMod parentMod)
|
||||
=> ParentMod = parentMod;
|
||||
|
||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > Files
|
||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> Files
|
||||
=> FileData;
|
||||
|
||||
public IReadOnlyDictionary< Utf8GamePath, FullPath > FileSwaps
|
||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> FileSwaps
|
||||
=> FileSwapData;
|
||||
|
||||
public IReadOnlySet< MetaManipulation > Manipulations
|
||||
public IReadOnlySet<MetaManipulation> Manipulations
|
||||
=> ManipulationData;
|
||||
|
||||
public void SetPosition( int groupIdx, int optionIdx )
|
||||
public void SetPosition(int groupIdx, int optionIdx)
|
||||
{
|
||||
GroupIdx = groupIdx;
|
||||
OptionIdx = optionIdx;
|
||||
}
|
||||
|
||||
public void Load( DirectoryInfo basePath, JToken json, out int priority )
|
||||
public void Load(DirectoryInfo basePath, JToken json, out int priority)
|
||||
{
|
||||
FileData.Clear();
|
||||
FileSwapData.Clear();
|
||||
ManipulationData.Clear();
|
||||
|
||||
// Every option has a name, but priorities are only relevant for multi group options.
|
||||
Name = json[ nameof( ISubMod.Name ) ]?.ToObject< string >() ?? string.Empty;
|
||||
Description = json[ nameof( ISubMod.Description ) ]?.ToObject< string >() ?? string.Empty;
|
||||
priority = json[ nameof( IModGroup.Priority ) ]?.ToObject< int >() ?? 0;
|
||||
Name = json[nameof(ISubMod.Name)]?.ToObject<string>() ?? string.Empty;
|
||||
Description = json[nameof(ISubMod.Description)]?.ToObject<string>() ?? string.Empty;
|
||||
priority = json[nameof(IModGroup.Priority)]?.ToObject<int>() ?? 0;
|
||||
|
||||
var files = ( JObject? )json[ nameof( Files ) ];
|
||||
if( files != null )
|
||||
{
|
||||
foreach( var property in files.Properties() )
|
||||
var files = (JObject?)json[nameof(Files)];
|
||||
if (files != null)
|
||||
foreach (var property in files.Properties())
|
||||
{
|
||||
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
|
||||
{
|
||||
FileData.TryAdd( p, new FullPath( basePath, property.Value.ToObject< Utf8RelPath >() ) );
|
||||
}
|
||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
FileData.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>()));
|
||||
}
|
||||
}
|
||||
|
||||
var swaps = ( JObject? )json[ nameof( FileSwaps ) ];
|
||||
if( swaps != null )
|
||||
{
|
||||
foreach( var property in swaps.Properties() )
|
||||
var swaps = (JObject?)json[nameof(FileSwaps)];
|
||||
if (swaps != null)
|
||||
foreach (var property in swaps.Properties())
|
||||
{
|
||||
if( Utf8GamePath.FromString( property.Name, out var p, true ) )
|
||||
{
|
||||
FileSwapData.TryAdd( p, new FullPath( property.Value.ToObject< string >()! ) );
|
||||
}
|
||||
if (Utf8GamePath.FromString(property.Name, out var p, true))
|
||||
FileSwapData.TryAdd(p, new FullPath(property.Value.ToObject<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 ) )
|
||||
{
|
||||
ManipulationData.Add( s );
|
||||
}
|
||||
}
|
||||
var manips = json[nameof(Manipulations)];
|
||||
if (manips != null)
|
||||
foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
|
||||
.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown))
|
||||
ManipulationData.Add(s);
|
||||
}
|
||||
|
||||
// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
|
||||
// If delete is true, the files are deleted afterwards.
|
||||
public (bool Changes, List< string > DeleteList) IncorporateMetaChanges( DirectoryInfo basePath, bool delete )
|
||||
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(DirectoryInfo basePath, bool delete)
|
||||
{
|
||||
var deleteList = new List< string >();
|
||||
var deleteList = new List<string>();
|
||||
var oldSize = ManipulationData.Count;
|
||||
var deleteString = delete ? "with deletion." : "without deletion.";
|
||||
foreach( var (key, file) in Files.ToList() )
|
||||
foreach (var (key, file) in Files.ToList())
|
||||
{
|
||||
var ext1 = key.Extension().AsciiToLower().ToString();
|
||||
var ext2 = file.Extension.ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
if( ext1 == ".meta" || ext2 == ".meta" )
|
||||
if (ext1 == ".meta" || ext2 == ".meta")
|
||||
{
|
||||
FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
FileData.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
}
|
||||
|
||||
var meta = new TexToolsMeta( Penumbra.GamePathParser, File.ReadAllBytes( file.FullName ), Penumbra.Config.KeepDefaultMetaChanges );
|
||||
Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}" );
|
||||
deleteList.Add( file.FullName );
|
||||
ManipulationData.UnionWith( meta.MetaManipulations );
|
||||
var meta = new TexToolsMeta(Penumbra.MetaFileManager, Penumbra.GamePathParser, File.ReadAllBytes(file.FullName),
|
||||
Penumbra.Config.KeepDefaultMetaChanges);
|
||||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
ManipulationData.UnionWith(meta.MetaManipulations);
|
||||
}
|
||||
else if( ext1 == ".rgsp" || ext2 == ".rgsp" )
|
||||
else if (ext1 == ".rgsp" || ext2 == ".rgsp")
|
||||
{
|
||||
FileData.Remove( key );
|
||||
if( !file.Exists )
|
||||
{
|
||||
FileData.Remove(key);
|
||||
if (!file.Exists)
|
||||
continue;
|
||||
}
|
||||
|
||||
var rgsp = TexToolsMeta.FromRgspFile( file.FullName, File.ReadAllBytes( file.FullName ), Penumbra.Config.KeepDefaultMetaChanges );
|
||||
Penumbra.Log.Verbose( $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}" );
|
||||
deleteList.Add( file.FullName );
|
||||
var rgsp = TexToolsMeta.FromRgspFile(Penumbra.MetaFileManager, file.FullName, File.ReadAllBytes(file.FullName),
|
||||
Penumbra.Config.KeepDefaultMetaChanges);
|
||||
Penumbra.Log.Verbose(
|
||||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||
deleteList.Add(file.FullName);
|
||||
|
||||
ManipulationData.UnionWith( rgsp.MetaManipulations );
|
||||
ManipulationData.UnionWith(rgsp.MetaManipulations);
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not incorporate meta changes in mod {basePath} from file {file.FullName}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
DeleteDeleteList( deleteList, delete );
|
||||
return ( oldSize < ManipulationData.Count, deleteList );
|
||||
DeleteDeleteList(deleteList, delete);
|
||||
return (oldSize < ManipulationData.Count, deleteList);
|
||||
}
|
||||
|
||||
internal static void DeleteDeleteList( IEnumerable< string > deleteList, bool delete )
|
||||
internal static void DeleteDeleteList(IEnumerable<string> deleteList, bool delete)
|
||||
{
|
||||
if( !delete )
|
||||
{
|
||||
if (!delete)
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var file in deleteList )
|
||||
foreach (var file in deleteList)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete( file );
|
||||
File.Delete(file);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not delete incorporated meta file {file}:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not delete incorporated meta file {file}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false )
|
||||
public void WriteTexToolsMeta(MetaFileManager manager, DirectoryInfo basePath, bool test = false)
|
||||
{
|
||||
var files = TexToolsMeta.ConvertToTexTools( Manipulations );
|
||||
var files = TexToolsMeta.ConvertToTexTools(manager, Manipulations);
|
||||
|
||||
foreach( var (file, data) in files )
|
||||
foreach (var (file, data) in files)
|
||||
{
|
||||
var path = Path.Combine( basePath.FullName, file );
|
||||
var path = Path.Combine(basePath.FullName, file);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
|
||||
File.WriteAllBytes( path, data );
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
File.WriteAllBytes(path, data);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Could not write meta file {path}:\n{e}" );
|
||||
Penumbra.Log.Error($"Could not write meta file {path}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
if( test )
|
||||
{
|
||||
TestMetaWriting( files );
|
||||
}
|
||||
if (test)
|
||||
TestMetaWriting(manager, files);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG" )]
|
||||
private void TestMetaWriting( Dictionary< string, byte[] > files )
|
||||
[Conditional("DEBUG")]
|
||||
private void TestMetaWriting(MetaFileManager manager, Dictionary<string, byte[]> files)
|
||||
{
|
||||
var meta = new HashSet< MetaManipulation >( Manipulations.Count );
|
||||
foreach( var (file, data) in files )
|
||||
var meta = new HashSet<MetaManipulation>(Manipulations.Count);
|
||||
foreach (var (file, data) in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var x = file.EndsWith( "rgsp" )
|
||||
? TexToolsMeta.FromRgspFile( file, data, Penumbra.Config.KeepDefaultMetaChanges )
|
||||
: new TexToolsMeta( Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges );
|
||||
meta.UnionWith( x.MetaManipulations );
|
||||
var x = file.EndsWith("rgsp")
|
||||
? TexToolsMeta.FromRgspFile(manager, file, data, Penumbra.Config.KeepDefaultMetaChanges)
|
||||
: new TexToolsMeta(manager, Penumbra.GamePathParser, data, Penumbra.Config.KeepDefaultMetaChanges);
|
||||
meta.UnionWith(x.MetaManipulations);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -217,27 +203,21 @@ namespace Penumbra.Mods;
|
|||
}
|
||||
}
|
||||
|
||||
if( !Manipulations.SetEquals( meta ) )
|
||||
if (!Manipulations.SetEquals(meta))
|
||||
{
|
||||
Penumbra.Log.Information( "Meta Sets do not equal." );
|
||||
foreach( var (m1, m2) in Manipulations.Zip( meta ) )
|
||||
{
|
||||
Penumbra.Log.Information( $"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}" );
|
||||
}
|
||||
Penumbra.Log.Information("Meta Sets do not equal.");
|
||||
foreach (var (m1, m2) in Manipulations.Zip(meta))
|
||||
Penumbra.Log.Information($"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}");
|
||||
|
||||
foreach( var m in Manipulations.Skip( meta.Count ) )
|
||||
{
|
||||
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
|
||||
}
|
||||
foreach (var m in Manipulations.Skip(meta.Count))
|
||||
Penumbra.Log.Information($"{m} {m.EntryToString()} ");
|
||||
|
||||
foreach( var m in meta.Skip( Manipulations.Count ) )
|
||||
{
|
||||
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
|
||||
}
|
||||
foreach (var m in meta.Skip(Manipulations.Count))
|
||||
Penumbra.Log.Information($"{m} {m.EntryToString()} ");
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Log.Information( "Meta Sets are equal." );
|
||||
Penumbra.Log.Information("Meta Sets are equal.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ using Penumbra.Interop.Services;
|
|||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods;
|
||||
|
||||
using Penumbra.Meta;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
public class Penumbra : IDalamudPlugin
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ using OtterGui.Raii;
|
|||
using OtterGui.Widgets;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.ItemSwap;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
|
@ -25,21 +26,23 @@ namespace Penumbra.UI.AdvancedWindow;
|
|||
|
||||
public class ItemSwapTab : IDisposable, ITab
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly ItemService _itemService;
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly Configuration _config;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly ItemService _itemService;
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly ModManager _modManager;
|
||||
private readonly Configuration _config;
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
|
||||
public ItemSwapTab(CommunicatorService communicator, ItemService itemService, CollectionManager collectionManager,
|
||||
ModManager modManager, Configuration config, IdentifierService identifier)
|
||||
ModManager modManager, Configuration config, IdentifierService identifier, MetaFileManager metaFileManager)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_itemService = itemService;
|
||||
_collectionManager = collectionManager;
|
||||
_modManager = modManager;
|
||||
_config = config;
|
||||
_swapData = new ItemSwapContainer(identifier.AwaitedService);
|
||||
_metaFileManager = metaFileManager;
|
||||
_swapData = new ItemSwapContainer(metaFileManager, identifier.AwaitedService);
|
||||
|
||||
_selectors = new Dictionary<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;
|
||||
|
||||
|
|
@ -762,7 +765,7 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
private void OnInheritanceChange(ModCollection collection, bool _)
|
||||
{
|
||||
if (collection != _collectionManager.Active.Current || _mod == null)
|
||||
return;
|
||||
return;
|
||||
|
||||
UpdateMod(_mod, collection[_mod.Index].Settings);
|
||||
_swapData.LoadMod(_mod, _modSettings);
|
||||
|
|
@ -772,8 +775,9 @@ public class ItemSwapTab : IDisposable, ITab
|
|||
private void OnModOptionChange(ModOptionChangeType type, Mod mod, int a, int b, int c)
|
||||
{
|
||||
if (type is ModOptionChangeType.PrepareChange or ModOptionChangeType.GroupAdded or ModOptionChangeType.OptionAdded || mod != _mod)
|
||||
return;
|
||||
_swapData.LoadMod(_mod, _modSettings);
|
||||
return;
|
||||
|
||||
_swapData.LoadMod(_mod, _modSettings);
|
||||
UpdateOption();
|
||||
_dirty = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Import.Textures;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
@ -31,6 +32,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
private readonly Configuration _config;
|
||||
private readonly ItemSwapTab _itemSwapTab;
|
||||
private readonly DataManager _gameData;
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
|
||||
private Mod? _mod;
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
|
|
@ -493,16 +495,17 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, DataManager gameData,
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches)
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, ModCacheManager modCaches, MetaFileManager metaFileManager)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
_itemSwapTab = itemSwapTab;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_modCaches = modCaches;
|
||||
_gameData = gameData;
|
||||
_fileDialog = fileDialog;
|
||||
_performance = performance;
|
||||
_itemSwapTab = itemSwapTab;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_modCaches = modCaches;
|
||||
_metaFileManager = metaFileManager;
|
||||
_gameData = gameData;
|
||||
_fileDialog = fileDialog;
|
||||
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _fileDialog, "Materials", ".mtrl",
|
||||
() => _editor.Files.Mtrl, DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
||||
bytes => new MtrlTab(this, new MtrlFile(bytes)));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue