From 6f527a1dbc28fdf04b73c43fd4e3f33a37586e85 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 14 Mar 2022 15:23:00 +0100 Subject: [PATCH] Metamanipulations seemingly working. --- .../ByteString/Utf8String.Manipulation.cs | 2 +- Penumbra/Importer/TexToolsMeta.cs | 86 +- Penumbra/Interop/CharacterUtility.cs | 55 +- .../Interop/ResourceLoader.Replacement.cs | 15 +- Penumbra/Interop/Structs/CharacterUtility.cs | 82 +- Penumbra/Meta/Files/EqdpFile.cs | 28 +- Penumbra/Meta/Files/EqpGmpFile.cs | 40 +- Penumbra/Meta/Files/ImcFile.cs | 81 +- Penumbra/Meta/Manager/MetaManager.Cmp.cs | 59 + Penumbra/Meta/Manager/MetaManager.Eqdp.cs | 72 + Penumbra/Meta/Manager/MetaManager.Eqp.cs | 59 + Penumbra/Meta/Manager/MetaManager.Est.cs | 78 + Penumbra/Meta/Manager/MetaManager.Gmp.cs | 57 + Penumbra/Meta/Manager/MetaManager.Imc.cs | 148 ++ Penumbra/Meta/Manager/MetaManager.cs | 99 ++ .../Meta/Manipulations/ImcManipulation.cs | 4 + Penumbra/Meta/MetaCollection.cs | 6 + Penumbra/Meta/MetaManager.cs | 367 ----- Penumbra/Mods/CollectionManager.cs | 17 +- Penumbra/Mods/ModCollectionCache.cs | 9 +- Penumbra/Mods/ModManager.Directory.cs | 12 +- Penumbra/Penumbra.cs | 2 +- Penumbra/Penumbra.csproj | 3 +- Penumbra/UI/MenuTabs/TabDebug.cs | 41 +- .../TabInstalledDetailsManipulations.cs | 1441 +++++++++-------- Penumbra/UI/MenuTabs/TabResourceManager.cs | 11 + 26 files changed, 1637 insertions(+), 1237 deletions(-) create mode 100644 Penumbra/Meta/Manager/MetaManager.Cmp.cs create mode 100644 Penumbra/Meta/Manager/MetaManager.Eqdp.cs create mode 100644 Penumbra/Meta/Manager/MetaManager.Eqp.cs create mode 100644 Penumbra/Meta/Manager/MetaManager.Est.cs create mode 100644 Penumbra/Meta/Manager/MetaManager.Gmp.cs create mode 100644 Penumbra/Meta/Manager/MetaManager.Imc.cs create mode 100644 Penumbra/Meta/Manager/MetaManager.cs delete mode 100644 Penumbra/Meta/MetaManager.cs diff --git a/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs b/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs index 08a38e91..0d895fa6 100644 --- a/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs +++ b/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs @@ -108,7 +108,7 @@ public sealed unsafe partial class Utf8String var start = 0; for( var idx = IndexOf( b, start ); idx >= 0; idx = IndexOf( b, start ) ) { - if( start + 1 != idx || !removeEmpty ) + if( start != idx || !removeEmpty ) { ret.Add( Substring( start, idx - start ) ); } diff --git a/Penumbra/Importer/TexToolsMeta.cs b/Penumbra/Importer/TexToolsMeta.cs index 8c9e418f..4b18fd07 100644 --- a/Penumbra/Importer/TexToolsMeta.cs +++ b/Penumbra/Importer/TexToolsMeta.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using Dalamud.Logging; using Lumina.Data.Files; @@ -10,6 +11,7 @@ using Penumbra.GameData.Util; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Util; +using ImcFile = Penumbra.Meta.Files.ImcFile; namespace Penumbra.Importer; @@ -254,22 +256,38 @@ public class TexToolsMeta using var reader = new BinaryReader( new MemoryStream( data ) ); var values = reader.ReadStructures< ImcEntry >( num ); ushort i = 0; - if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory ) + try { - // TODO check against default. - foreach( var value in values ) + if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory ) { - ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) ); - ++i; + var def = new ImcFile( new ImcManipulation( info.EquipSlot, i, info.PrimaryId, new ImcEntry() ).GamePath() ); + var partIdx = ImcFile.PartIndex( info.EquipSlot ); + foreach( var value in values ) + { + if( !value.Equals( def.GetEntry( partIdx, i ) ) ) + { + ImcManipulations.Add( new ImcManipulation( info.EquipSlot, i, info.PrimaryId, value ) ); + } + + ++i; + } + } + else + { + var def = new ImcFile( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, + new ImcEntry() ).GamePath() ); + foreach( var value in values.Where( v => true || !v.Equals( def.GetEntry( 0, i ) ) ) ) + { + ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, + value ) ); + ++i; + } } } - else + catch( Exception e ) { - foreach( var value in values ) - { - ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) ); - ++i; - } + PluginLog.Error( "Could not compute IMC manipulation. 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}" ); } } @@ -296,7 +314,7 @@ public class TexToolsMeta var headerSize = reader.ReadUInt32(); var headerStart = reader.ReadUInt32(); reader.BaseStream.Seek( headerStart, SeekOrigin.Begin ); - + List< (MetaManipulation.Type type, uint offset, int size) > entries = new(); for( var i = 0; i < numHeaders; ++i ) { @@ -307,7 +325,7 @@ public class TexToolsMeta entries.Add( ( type, offset, size ) ); reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin ); } - + byte[]? ReadEntry( MetaManipulation.Type type ) { var idx = entries.FindIndex( t => t.type == type ); @@ -315,11 +333,11 @@ public class TexToolsMeta { return null; } - + reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin ); return reader.ReadBytes( entries[ idx ].size ); } - + DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) ); DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) ); DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) ); @@ -373,32 +391,34 @@ public class TexToolsMeta void Add( RspAttribute attribute, float value ) { var def = CmpFile.GetDefault( subRace, attribute ); - if (value != def) - ret!.RspManipulations.Add(new RspManipulation(subRace, attribute, value)); + if( value != def ) + { + ret!.RspManipulations.Add( new RspManipulation( subRace, attribute, value ) ); + } } if( gender == 1 ) { - Add(RspAttribute.FemaleMinSize, br.ReadSingle() ); - Add(RspAttribute.FemaleMaxSize, br.ReadSingle() ); - Add(RspAttribute.FemaleMinTail, br.ReadSingle() ); - Add(RspAttribute.FemaleMaxTail, br.ReadSingle() ); - - Add(RspAttribute.BustMinX, br.ReadSingle() ); - Add(RspAttribute.BustMinY, br.ReadSingle() ); - Add(RspAttribute.BustMinZ, br.ReadSingle() ); - Add(RspAttribute.BustMaxX, br.ReadSingle() ); - Add(RspAttribute.BustMaxY, br.ReadSingle() ); - Add(RspAttribute.BustMaxZ, br.ReadSingle() ); + Add( RspAttribute.FemaleMinSize, br.ReadSingle() ); + Add( RspAttribute.FemaleMaxSize, br.ReadSingle() ); + Add( RspAttribute.FemaleMinTail, br.ReadSingle() ); + Add( RspAttribute.FemaleMaxTail, br.ReadSingle() ); + + Add( RspAttribute.BustMinX, br.ReadSingle() ); + Add( RspAttribute.BustMinY, br.ReadSingle() ); + Add( RspAttribute.BustMinZ, br.ReadSingle() ); + Add( RspAttribute.BustMaxX, br.ReadSingle() ); + Add( RspAttribute.BustMaxY, br.ReadSingle() ); + Add( RspAttribute.BustMaxZ, br.ReadSingle() ); } else { - Add(RspAttribute.MaleMinSize, br.ReadSingle() ); - Add(RspAttribute.MaleMaxSize, br.ReadSingle() ); - Add(RspAttribute.MaleMinTail, br.ReadSingle() ); - Add(RspAttribute.MaleMaxTail, br.ReadSingle() ); + Add( RspAttribute.MaleMinSize, br.ReadSingle() ); + Add( RspAttribute.MaleMaxSize, br.ReadSingle() ); + Add( RspAttribute.MaleMinTail, br.ReadSingle() ); + Add( RspAttribute.MaleMaxTail, br.ReadSingle() ); } - + return ret; } } \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index 9d938f34..03f4bba0 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; @@ -20,7 +21,33 @@ public unsafe class CharacterUtility : IDisposable public Structs.CharacterUtility* Address => *_characterUtilityAddress; - public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[Structs.CharacterUtility.NumRelevantResources]; + // The relevant indices depend on which meta manipulations we allow for. + // The defines are set in the project configuration. + public static readonly int[] RelevantIndices + = Array.Empty< int >() +#if USE_EQP + .Append( Structs.CharacterUtility.EqpIdx ) +#endif +#if USE_GMP + .Append( Structs.CharacterUtility.GmpIdx ) +#endif +#if USE_EQDP + .Concat( Enumerable.Range( Structs.CharacterUtility.EqdpStartIdx, Structs.CharacterUtility.NumEqdpFiles ) ) +#endif +#if USE_CMP + .Append( Structs.CharacterUtility.HumanCmpIdx ) +#endif +#if USE_EST + .Concat( Enumerable.Range( Structs.CharacterUtility.FaceEstIdx, 4 ) ) +#endif + .ToArray(); + + private static readonly int[] ReverseIndices + = Enumerable.Range( 0, Structs.CharacterUtility.NumResources ) + .Select( i => Array.IndexOf( RelevantIndices, i ) ).ToArray(); + + + public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[RelevantIndices.Length]; public CharacterUtility() { @@ -48,9 +75,9 @@ public unsafe class CharacterUtility : IDisposable // We store the default data of the resources so we can always restore them. private void LoadDefaultResources() { - for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i ) + for( var i = 0; i < RelevantIndices.Length; ++i ) { - var resource = ( Structs.ResourceHandle* )Address->Resources[ i ]; + var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ]; DefaultResources[ i ] = resource->GetData(); } } @@ -65,19 +92,25 @@ public unsafe class CharacterUtility : IDisposable } // Reset the data of one of the stored resources to its default values. - public void ResetResource( int idx ) + public void ResetResource( int fileIdx ) { - var resource = ( Structs.ResourceHandle* )Address->Resources[ idx ]; - resource->SetData( DefaultResources[ idx ].Address, DefaultResources[ idx ].Size ); + var (data, size) = DefaultResources[ ReverseIndices[ fileIdx ] ]; + var resource = ( Structs.ResourceHandle* )Address->Resources[ fileIdx ]; + resource->SetData( data, size ); + } + + // Return all relevant resources to the default resource. + public void ResetAll() + { + foreach( var idx in RelevantIndices ) + { + ResetResource( idx ); + } } public void Dispose() { - for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i ) - { - ResetResource( i ); - } - + ResetAll(); LoadDataFilesHook.Dispose(); } } \ No newline at end of file diff --git a/Penumbra/Interop/ResourceLoader.Replacement.cs b/Penumbra/Interop/ResourceLoader.Replacement.cs index 9ecb85f7..837d2c19 100644 --- a/Penumbra/Interop/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/ResourceLoader.Replacement.cs @@ -117,12 +117,15 @@ public unsafe partial class ResourceLoader // We use the IsRooted check to signify paths replaced by us pointing to the local filesystem instead of an SqPack. if( !valid || !gamePath.IsRooted() ) { - ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); - FileLoaded?.Invoke( gamePath.Path, ret != 0, false ); - } - else if( ResourceLoadCustomization != null && gamePath.Path[0] == (byte) '|' ) - { - ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync ); + if( valid && ResourceLoadCustomization != null && gamePath.Path[ 0 ] == ( byte )'|' ) + { + ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync ); + } + else + { + ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); + FileLoaded?.Invoke( gamePath.Path, ret != 0, false ); + } } else { diff --git a/Penumbra/Interop/Structs/CharacterUtility.cs b/Penumbra/Interop/Structs/CharacterUtility.cs index 62762243..cdd0c6cb 100644 --- a/Penumbra/Interop/Structs/CharacterUtility.cs +++ b/Penumbra/Interop/Structs/CharacterUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Runtime.InteropServices; using Penumbra.GameData.Enums; @@ -7,49 +8,52 @@ namespace Penumbra.Interop.Structs; [StructLayout( LayoutKind.Explicit )] public unsafe struct CharacterUtility { - public const int NumResources = 85; - public const int NumRelevantResources = 68; - public const int EqpIdx = 0; - public const int GmpIdx = 1; - public const int HumanCmpIdx = 63; - public const int FaceEstIdx = 64; - public const int HairEstIdx = 65; - public const int BodyEstIdx = 66; - public const int HeadEstIdx = 67; - public const int NumEqdpFiles = 2 * 28; + public static readonly int[] EqdpIndices + = Enumerable.Range( EqdpStartIdx, NumEqdpFiles ).ToArray(); + + public const int NumResources = 85; + public const int EqpIdx = 0; + public const int GmpIdx = 1; + public const int HumanCmpIdx = 63; + public const int FaceEstIdx = 64; + public const int HairEstIdx = 65; + public const int BodyEstIdx = 66; + public const int HeadEstIdx = 67; + public const int EqdpStartIdx = 2; + public const int NumEqdpFiles = 2 * 28; public static int EqdpIdx( GenderRace raceCode, bool accessory ) - => ( accessory ? 28 : 0 ) + => ( accessory ? NumEqdpFiles / 2 : 0 ) + ( int )raceCode switch { - 0101 => 2, - 0201 => 3, - 0301 => 4, - 0401 => 5, - 0501 => 6, - 0601 => 7, - 0701 => 8, - 0801 => 9, - 0901 => 10, - 1001 => 11, - 1101 => 12, - 1201 => 13, - 1301 => 14, - 1401 => 15, - 1501 => 16, - 1601 => 17, // Does not exist yet - 1701 => 18, - 1801 => 19, - 0104 => 20, - 0204 => 21, - 0504 => 22, - 0604 => 23, - 0704 => 24, - 0804 => 25, - 1304 => 26, - 1404 => 27, - 9104 => 28, - 9204 => 29, + 0101 => EqdpStartIdx, + 0201 => EqdpStartIdx + 1, + 0301 => EqdpStartIdx + 2, + 0401 => EqdpStartIdx + 3, + 0501 => EqdpStartIdx + 4, + 0601 => EqdpStartIdx + 5, + 0701 => EqdpStartIdx + 6, + 0801 => EqdpStartIdx + 7, + 0901 => EqdpStartIdx + 8, + 1001 => EqdpStartIdx + 9, + 1101 => EqdpStartIdx + 10, + 1201 => EqdpStartIdx + 11, + 1301 => EqdpStartIdx + 12, + 1401 => EqdpStartIdx + 13, + 1501 => EqdpStartIdx + 14, + 1601 => EqdpStartIdx + 15, // Does not exist yet + 1701 => EqdpStartIdx + 16, + 1801 => EqdpStartIdx + 17, + 0104 => EqdpStartIdx + 18, + 0204 => EqdpStartIdx + 19, + 0504 => EqdpStartIdx + 20, + 0604 => EqdpStartIdx + 21, + 0704 => EqdpStartIdx + 22, + 0804 => EqdpStartIdx + 23, + 1304 => EqdpStartIdx + 24, + 1404 => EqdpStartIdx + 25, + 9104 => EqdpStartIdx + 26, + 9204 => EqdpStartIdx + 27, _ => throw new ArgumentException(), }; diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs index ae64ad1c..053d83f0 100644 --- a/Penumbra/Meta/Files/EqdpFile.cs +++ b/Penumbra/Meta/Files/EqdpFile.cs @@ -47,7 +47,8 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile throw new IndexOutOfRangeException(); } - return *( EqdpEntry* )( Data + DataOffset + EqdpEntrySize * idx ); + var x = new ReadOnlySpan< ushort >( ( ushort* )Data, Length / 2 ); + return ( EqdpEntry )( *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) ); } set { @@ -56,7 +57,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile throw new IndexOutOfRangeException(); } - *( EqdpEntry* )( Data + DataOffset + EqdpEntrySize * idx ) = value; + *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) = ( ushort )value; } } @@ -65,9 +66,12 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile var def = ( byte* )DefaultData.Data; Functions.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize ); - var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize ); - var dataBasePtr = ( byte* )( controlPtr + BlockCount ); - var myDataPtr = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount ); + var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize ); + var dataBasePtr = controlPtr + BlockCount; + var myDataPtrStart = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount ); + var myDataPtr = myDataPtrStart; + var myControlPtr = ( ushort* )( Data + IdentifierSize + PreambleSize ); + var x = new ReadOnlySpan< ushort >( ( ushort* )Data, Length / 2 ); for( var i = 0; i < BlockCount; ++i ) { if( controlPtr[ i ] == CollapsedBlock ) @@ -76,11 +80,16 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile } else { + var y = new ReadOnlySpan< ushort >( dataBasePtr + controlPtr[ i ], BlockSize ); + var z = new ReadOnlySpan< ushort >( myDataPtr, BlockSize ); Functions.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize ); } - myDataPtr += BlockSize; + myControlPtr[ i ] = ( ushort )( i * BlockSize ); + myDataPtr += BlockSize; } + + Functions.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) ); } public void Reset( IEnumerable< int > entries ) @@ -102,7 +111,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile DataOffset = IdentifierSize + PreambleSize + totalBlockCount * BlockHeaderSize; var fullLength = DataOffset + totalBlockCount * totalBlockSize; - fullLength += ( FileAlignment - ( Length & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 ); + fullLength += ( FileAlignment - ( fullLength & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 ); AllocateData( fullLength ); Reset(); } @@ -128,8 +137,9 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile return 0; } - var blockData = ( EqdpEntry* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block ); - return *( blockData + blockIdx % blockSize ); + var blockData = ( ushort* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2 ); + var x = new ReadOnlySpan< ushort >( blockData, blockSize ); + return (EqdpEntry) (*( blockData + setIdx % blockSize )); } public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx ) diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index 5b6d6479..c85799a6 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.CompilerServices; using Penumbra.GameData.Structs; using Penumbra.GameData.Util; using Penumbra.Interop.Structs; @@ -24,17 +25,17 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile public ulong ControlBlock => *( ulong* )Data; - protected T Get< T >( int idx ) where T : unmanaged + protected ulong GetInternal( int idx ) { return idx switch { >= Count => throw new IndexOutOfRangeException(), - <= 1 => *( ( T* )Data + 1 ), - _ => *( ( T* )Data + idx ), + <= 1 => *( ( ulong* )Data + 1 ), + _ => *( ( ulong* )Data + idx ), }; } - protected void Set< T >( int idx, T value ) where T : unmanaged + protected void SetInternal( int idx, ulong value ) { idx = idx switch { @@ -43,7 +44,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile _ => idx, }; - *( ( T* )Data + idx ) = value; + *( ( ulong* )Data + idx ) = value; } protected virtual void SetEmptyBlock( int idx ) @@ -53,21 +54,24 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile public sealed override void Reset() { - var ptr = ( byte* )DefaultData.Data; - var controlBlock = *( ulong* )ptr; - *( ulong* )Data = ulong.MaxValue; - for( var i = 0; i < 64; ++i ) + var ptr = ( byte* )DefaultData.Data; + var controlBlock = *( ulong* )ptr; + var expandedBlocks = 0; + for( var i = 0; i < NumBlocks; ++i ) { var collapsed = ( ( controlBlock >> i ) & 1 ) == 0; if( !collapsed ) { - Functions.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + i * BlockSize * EntrySize, BlockSize * EntrySize ); + Functions.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize ); + expandedBlocks++; } else { SetEmptyBlock( i ); } } + + *( ulong* )Data = ulong.MaxValue; } public ExpandedEqpGmpBase( bool gmp ) @@ -77,7 +81,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile Reset(); } - protected static T GetDefault< T >( int fileIdx, int setIdx, T def ) where T : unmanaged + protected static ulong GetDefaultInternal( int fileIdx, int setIdx, ulong def ) { var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address; if( setIdx == 0 ) @@ -100,7 +104,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile var count = BitOperations.PopCount( control & ( blockBit - 1 ) ); var idx = setIdx % BlockSize; - var ptr = ( T* )data + BlockSize * count + idx; + var ptr = ( ulong* )data + BlockSize * count + idx; return *ptr; } } @@ -113,12 +117,12 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase public EqpEntry this[ int idx ] { - get => Get< EqpEntry >( idx ); - set => Set( idx, value ); + get => ( EqpEntry )GetInternal( idx ); + set => SetInternal( idx, ( ulong )value ); } public static EqpEntry GetDefault( int setIdx ) - => GetDefault( CharacterUtility.EqpIdx, setIdx, Eqp.DefaultEntry ); + => ( EqpEntry )GetDefaultInternal( CharacterUtility.EqpIdx, setIdx, ( ulong )Eqp.DefaultEntry ); protected override unsafe void SetEmptyBlock( int idx ) { @@ -147,12 +151,12 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase public GmpEntry this[ int idx ] { - get => Get< GmpEntry >( idx ); - set => Set( idx, value ); + get => ( GmpEntry )GetInternal( idx ); + set => SetInternal( idx, ( ulong )value ); } public static GmpEntry GetDefault( int setIdx ) - => GetDefault( CharacterUtility.GmpIdx, setIdx, GmpEntry.Default ); + => ( GmpEntry )GetDefaultInternal( CharacterUtility.GmpIdx, setIdx, ( ulong )GmpEntry.Default ); public void Reset( IEnumerable< int > entries ) { diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index d7eaaa80..35e53d66 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Numerics; using Dalamud.Logging; using Dalamud.Memory; @@ -7,6 +8,7 @@ using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.GameData.Util; using Penumbra.Interop.Structs; +using Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Files; @@ -64,41 +66,57 @@ public unsafe class ImcFile : MetaBaseFile => NumParts * sizeof( ImcEntry ) * ( Count + 1 ) + PreambleSize; public int Count - => *( ushort* )Data; - - public ushort PartMask - => *( ushort* )( Data + 2 ); + => CountInternal( Data ); public readonly int NumParts; public readonly Utf8GamePath Path; - public ImcEntry* DefaultPartPtr( int partIdx ) + private static int CountInternal( byte* data ) + => *( ushort* )data; + + private static ushort PartMask( byte* data ) + => *( ushort* )( data + 2 ); + + private static ImcEntry* DefaultPartPtr( byte* data, int partIdx ) { var flag = 1 << partIdx; - if( ( PartMask & flag ) == 0 ) + if( ( PartMask( data ) & flag ) == 0 ) { return null; } - return ( ImcEntry* )( Data + PreambleSize ) + partIdx; + return ( ImcEntry* )( data + PreambleSize ) + partIdx; } - public ImcEntry* VariantPtr( int partIdx, int variantIdx ) + private static ImcEntry* VariantPtr( byte* data, int partIdx, int variantIdx ) { + if( variantIdx == 0 ) + { + return DefaultPartPtr( data, partIdx ); + } + + --variantIdx; var flag = 1 << partIdx; - if( ( PartMask & flag ) == 0 || variantIdx >= Count ) + + if( ( PartMask( data ) & flag ) == 0 || variantIdx >= CountInternal( data ) ) { return null; } - var numParts = NumParts; - var ptr = ( ImcEntry* )( Data + PreambleSize ); + var numParts = BitOperations.PopCount( PartMask( data ) ); + var ptr = ( ImcEntry* )( data + PreambleSize ); ptr += numParts; ptr += variantIdx * numParts; ptr += partIdx; return ptr; } + public ImcEntry GetEntry( int partIdx, int variantIdx ) + { + var ptr = VariantPtr( Data, partIdx, variantIdx ); + return ptr == null ? new ImcEntry() : *ptr; + } + public static int PartIndex( EquipSlot slot ) => slot switch { @@ -122,7 +140,6 @@ public unsafe class ImcFile : MetaBaseFile return true; } - var numParts = NumParts; if( ActualLength > Length ) { PluginLog.Warning( "Adding too many variants to IMC, size exceeded." ); @@ -130,10 +147,10 @@ public unsafe class ImcFile : MetaBaseFile } var defaultPtr = ( ImcEntry* )( Data + PreambleSize ); - var endPtr = defaultPtr + ( numVariants + 1 ) * numParts; - for( var ptr = defaultPtr + numParts; ptr < endPtr; ptr += numParts ) + var endPtr = defaultPtr + ( numVariants + 1 ) * NumParts; + for( var ptr = defaultPtr + NumParts; ptr < endPtr; ptr += NumParts ) { - Functions.MemCpyUnchecked( ptr, defaultPtr, numParts * sizeof( ImcEntry ) ); + Functions.MemCpyUnchecked( ptr, defaultPtr, NumParts * sizeof( ImcEntry ) ); } PluginLog.Verbose( "Expanded imc from {Count} to {NewCount} variants.", Count, numVariants ); @@ -143,15 +160,14 @@ public unsafe class ImcFile : MetaBaseFile public bool SetEntry( int partIdx, int variantIdx, ImcEntry entry ) { - var numParts = NumParts; - if( partIdx >= numParts ) + if( partIdx >= NumParts ) { return false; } - EnsureVariantCount( variantIdx + 1 ); + EnsureVariantCount( variantIdx ); - var variantPtr = VariantPtr( partIdx, variantIdx ); + var variantPtr = VariantPtr( Data, partIdx, variantIdx ); if( variantPtr == null ) { PluginLog.Error( "Error during expansion of imc file." ); @@ -168,9 +184,20 @@ public unsafe class ImcFile : MetaBaseFile } + public override void Reset() + { + var file = Dalamud.GameData.GetFile( Path.ToString() ); + fixed( byte* ptr = file!.Data ) + { + Functions.MemCpyUnchecked( Data, ptr, file.Data.Length ); + Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length ); + } + } + public ImcFile( Utf8GamePath path ) : base( 0 ) { + Path = path; var file = Dalamud.GameData.GetFile( path.ToString() ); if( file == null ) { @@ -182,6 +209,22 @@ public unsafe class ImcFile : MetaBaseFile NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) ); AllocateData( file.Data.Length + sizeof( ImcEntry ) * 100 * NumParts ); Functions.MemCpyUnchecked( Data, ptr, file.Data.Length ); + Functions.MemSet( Data + file.Data.Length, 0, sizeof( ImcEntry ) * 100 * NumParts ); + } + } + + public static ImcEntry GetDefault( Utf8GamePath path, EquipSlot slot, int variantIdx ) + { + var file = Dalamud.GameData.GetFile( path.ToString() ); + if( file == null ) + { + throw new Exception(); + } + + fixed( byte* ptr = file.Data ) + { + var entry = VariantPtr( ptr, PartIndex( slot ), variantIdx ); + return entry == null ? new ImcEntry() : *entry; } } diff --git a/Penumbra/Meta/Manager/MetaManager.Cmp.cs b/Penumbra/Meta/Manager/MetaManager.Cmp.cs new file mode 100644 index 00000000..d073631a --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.Cmp.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager +{ + public struct MetaManagerCmp : IDisposable + { + public CmpFile? File = null; + public readonly Dictionary< RspManipulation, Mod.Mod > Manipulations = new(); + + public MetaManagerCmp() + { } + + [Conditional( "USE_CMP" )] + public void Reset() + { + if( File == null ) + { + return; + } + + File.Reset( Manipulations.Keys.Select( m => ( m.SubRace, m.Attribute ) ) ); + Manipulations.Clear(); + } + + [Conditional( "USE_CMP" )] + public void SetFiles() + => SetFile( File, CharacterUtility.HumanCmpIdx ); + + public bool ApplyMod( RspManipulation m, Mod.Mod mod ) + { +#if USE_CMP + if( !Manipulations.TryAdd( m, mod ) ) + { + return false; + } + + File ??= new CmpFile(); + return m.Apply( File ); +#else + return false; +#endif + } + + public void Dispose() + { + File?.Dispose(); + File = null; + Manipulations.Clear(); + } + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs new file mode 100644 index 00000000..608c77d4 --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Penumbra.GameData.Enums; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager +{ + public struct MetaManagerEqdp : IDisposable + { + public ExpandedEqdpFile?[] Files = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles]; + + public readonly Dictionary< EqdpManipulation, Mod.Mod > Manipulations = new(); + + public MetaManagerEqdp() + { } + + [Conditional( "USE_EQDP" )] + public void SetFiles() + { + foreach( var idx in CharacterUtility.EqdpIndices ) + { + SetFile( Files[ idx - CharacterUtility.EqdpStartIdx ], idx ); + } + } + + [Conditional( "USE_EQDP" )] + public void Reset() + { + foreach( var file in Files ) + { + file?.Reset( Manipulations.Keys.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) ); + } + + Manipulations.Clear(); + } + + public bool ApplyMod( EqdpManipulation m, Mod.Mod mod ) + { +#if USE_EQDP + if( !Manipulations.TryAdd( m, mod ) ) + { + return false; + } + + var file = Files[ m.FileIndex() - 2 ] ??= new ExpandedEqdpFile( Names.CombinedRace( m.Gender, m.Race ), m.Slot.IsAccessory() ); + return m.Apply( file ); +#else + return false; +#endif + } + + public ExpandedEqdpFile? File( GenderRace race, bool accessory ) + => Files[ CharacterUtility.EqdpIdx( race, accessory ) - CharacterUtility.EqdpStartIdx ]; + + public void Dispose() + { + for( var i = 0; i < Files.Length; ++i ) + { + Files[ i ]?.Dispose(); + Files[ i ] = null; + } + + Manipulations.Clear(); + } + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.Eqp.cs b/Penumbra/Meta/Manager/MetaManager.Eqp.cs new file mode 100644 index 00000000..65c38ef5 --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.Eqp.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager +{ + public struct MetaManagerEqp : IDisposable + { + public ExpandedEqpFile? File = null; + public readonly Dictionary< EqpManipulation, Mod.Mod > Manipulations = new(); + + public MetaManagerEqp() + { } + + [Conditional( "USE_EQP" )] + public void SetFiles() + => SetFile( File, CharacterUtility.EqpIdx ); + + [Conditional( "USE_EQP" )] + public void Reset() + { + if( File == null ) + { + return; + } + + File.Reset( Manipulations.Keys.Select( m => ( int )m.SetId ) ); + Manipulations.Clear(); + } + + public bool ApplyMod( EqpManipulation m, Mod.Mod mod ) + { +#if USE_EQP + if( !Manipulations.TryAdd( m, mod ) ) + { + return false; + } + + File ??= new ExpandedEqpFile(); + return m.Apply( File ); +#else + return false; +#endif + } + + public void Dispose() + { + File?.Dispose(); + File = null; + Manipulations.Clear(); + } + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.Est.cs b/Penumbra/Meta/Manager/MetaManager.Est.cs new file mode 100644 index 00000000..558873cc --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.Est.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager +{ + public struct MetaManagerEst : IDisposable + { + public EstFile? FaceFile = null; + public EstFile? HairFile = null; + public EstFile? BodyFile = null; + public EstFile? HeadFile = null; + + public readonly Dictionary< EstManipulation, Mod.Mod > Manipulations = new(); + + public MetaManagerEst() + { } + + [Conditional( "USE_EST" )] + public void SetFiles() + { + SetFile( FaceFile, CharacterUtility.FaceEstIdx ); + SetFile( HairFile, CharacterUtility.HairEstIdx ); + SetFile( BodyFile, CharacterUtility.BodyEstIdx ); + SetFile( HeadFile, CharacterUtility.HeadEstIdx ); + } + + [Conditional( "USE_EST" )] + public void Reset() + { + FaceFile?.Reset(); + HairFile?.Reset(); + BodyFile?.Reset(); + HeadFile?.Reset(); + Manipulations.Clear(); + } + + public bool ApplyMod( EstManipulation m, Mod.Mod mod ) + { +#if USE_EST + if( !Manipulations.TryAdd( m, mod ) ) + { + return false; + } + + var file = m.Slot switch + { + EstManipulation.EstType.Hair => HairFile ??= new EstFile( EstManipulation.EstType.Hair ), + EstManipulation.EstType.Face => FaceFile ??= new EstFile( EstManipulation.EstType.Face ), + EstManipulation.EstType.Body => BodyFile ??= new EstFile( EstManipulation.EstType.Body ), + EstManipulation.EstType.Head => HeadFile ??= new EstFile( EstManipulation.EstType.Head ), + _ => throw new ArgumentOutOfRangeException(), + }; + return m.Apply( file ); +#else + return false; +#endif + } + + public void Dispose() + { + FaceFile?.Dispose(); + HairFile?.Dispose(); + BodyFile?.Dispose(); + HeadFile?.Dispose(); + FaceFile = null; + HairFile = null; + BodyFile = null; + HeadFile = null; + Manipulations.Clear(); + } + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Meta/Manager/MetaManager.Gmp.cs new file mode 100644 index 00000000..8daf8dd8 --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.Gmp.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager +{ + public struct MetaManagerGmp : IDisposable + { + public ExpandedGmpFile? File = null; + public readonly Dictionary< GmpManipulation, Mod.Mod > Manipulations = new(); + + public MetaManagerGmp() + { } + + [Conditional( "USE_GMP" )] + public void Reset() + { + if( File != null ) + { + File.Reset( Manipulations.Keys.Select( m => ( int )m.SetId ) ); + Manipulations.Clear(); + } + } + + [Conditional( "USE_GMP" )] + public void SetFiles() + => SetFile( File, CharacterUtility.GmpIdx ); + + public bool ApplyMod( GmpManipulation m, Mod.Mod mod ) + { +#if USE_GMP + if( Manipulations.TryAdd( m, mod ) ) + { + return false; + } + + File ??= new ExpandedGmpFile(); + return m.Apply( File ); +#else + return false; +#endif + } + + public void Dispose() + { + File?.Dispose(); + File = null; + Manipulations.Clear(); + } + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.Imc.cs b/Penumbra/Meta/Manager/MetaManager.Imc.cs new file mode 100644 index 00000000..e2457668 --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.Imc.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using FFXIVClientStructs.FFXIV.Client.System.Resource; +using Penumbra.GameData.ByteString; +using Penumbra.Interop; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager +{ + public readonly struct MetaManagerImc : IDisposable + { + public readonly Dictionary< Utf8GamePath, ImcFile > Files = new(); + public readonly Dictionary< ImcManipulation, Mod.Mod > Manipulations = new(); + + private readonly ModCollection _collection; + private readonly ResourceLoader.ResourceLoadCustomizationDelegate? _previousDelegate; + + + public MetaManagerImc( ModCollection collection ) + { + _collection = collection; + _previousDelegate = Penumbra.ResourceLoader.ResourceLoadCustomization; + } + + [Conditional( "USE_IMC" )] + public void SetFiles() + { + if( _collection.Cache == null ) + { + return; + } + + foreach( var path in Files.Keys ) + { + _collection.Cache.ResolvedFiles[ path ] = CreateImcPath( path ); + } + } + + [Conditional( "USE_IMC" )] + public void Reset() + { + foreach( var (path, file) in Files ) + { + _collection.Cache?.ResolvedFiles.Remove( path ); + file.Reset(); + } + + Manipulations.Clear(); + } + + public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod ) + { + const uint imcExt = 0x00696D63; +#if USE_IMC + if( !Manipulations.TryAdd( m, mod ) ) + { + return false; + } + + var path = m.GamePath(); + if( !Files.TryGetValue( path, out var file ) ) + { + file = new ImcFile( path ); + } + + if( !m.Apply( file ) ) + { + return false; + } + + Files[ path ] = file; + var fullPath = CreateImcPath( path ); + if( _collection.Cache != null ) + { + _collection.Cache.ResolvedFiles[ path ] = fullPath; + } + + var resource = ResourceLoader.FindResource( ResourceCategory.Chara, imcExt, ( uint )path.Path.Crc32 ); + if( resource != null ) + { + file.Replace( ( ResourceHandle* )resource ); + } + + return true; +#else + return false; +#endif + } + + + public void Dispose() + { + foreach( var file in Files.Values ) + { + file.Dispose(); + } + + Files.Clear(); + Manipulations.Clear(); + RestoreDelegate(); + } + + [Conditional( "USE_IMC" )] + private unsafe void SetupDelegate() + { + Penumbra.ResourceLoader.ResourceLoadCustomization = ImcHandler; + } + + [Conditional( "USE_IMC" )] + private unsafe void RestoreDelegate() + { + if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcHandler ) + { + Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate; + } + } + + private FullPath CreateImcPath( Utf8GamePath path ) + => new($"|{_collection.Name}|{path}"); + + private static unsafe byte ImcHandler( Utf8GamePath gamePath, ResourceManager* resourceManager, + SeFileDescriptor* fileDescriptor, int priority, bool isSync ) + { + var split = gamePath.Path.Split( ( byte )'|', 2, true ); + fileDescriptor->ResourceHandle->FileNameData = split[ 1 ].Path; + fileDescriptor->ResourceHandle->FileNameLength = split[ 1 ].Length; + + var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); + if( Penumbra.ModManager.Collections.Collections.TryGetValue( split[ 0 ].ToString(), out var collection ) + && collection.Cache != null + && collection.Cache.MetaManipulations.Imc.Files.TryGetValue( + Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) ) + { + file.Replace( fileDescriptor->ResourceHandle ); + } + + fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; + fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; + return ret; + } + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs new file mode 100644 index 00000000..3eef46fd --- /dev/null +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -0,0 +1,99 @@ +using System; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods; + +namespace Penumbra.Meta.Manager; + +public partial class MetaManager : IDisposable +{ + public MetaManagerEqp Eqp = new(); + public MetaManagerEqdp Eqdp = new(); + public MetaManagerGmp Gmp = new(); + public MetaManagerEst Est = new(); + public MetaManagerCmp Cmp = new(); + public MetaManagerImc Imc; + + private static unsafe void SetFile( MetaBaseFile? file, int index ) + { + if( file == null ) + { + Penumbra.CharacterUtility.ResetResource( index ); + } + else + { + Penumbra.CharacterUtility.SetResource( index, ( IntPtr )file.Data, file.Length ); + } + } + + public bool TryGetValue( MetaManipulation manip, out Mod.Mod? mod ) + { + mod = manip.ManipulationType switch + { + MetaManipulation.Type.Eqp => Eqp.Manipulations.TryGetValue( manip.Eqp, out var m ) ? m : null, + MetaManipulation.Type.Gmp => Gmp.Manipulations.TryGetValue( manip.Gmp, out var m ) ? m : null, + MetaManipulation.Type.Eqdp => Eqdp.Manipulations.TryGetValue( manip.Eqdp, out var m ) ? m : null, + MetaManipulation.Type.Est => Est.Manipulations.TryGetValue( manip.Est, out var m ) ? m : null, + MetaManipulation.Type.Rsp => Cmp.Manipulations.TryGetValue( manip.Rsp, out var m ) ? m : null, + MetaManipulation.Type.Imc => Imc.Manipulations.TryGetValue( manip.Imc, out var m ) ? m : null, + _ => throw new ArgumentOutOfRangeException(), + }; + return mod != null; + } + + public int Count + => Imc.Manipulations.Count + + Eqdp.Manipulations.Count + + Cmp.Manipulations.Count + + Gmp.Manipulations.Count + + Est.Manipulations.Count + + Eqp.Manipulations.Count; + + public MetaManager( ModCollection collection ) + => Imc = new MetaManagerImc( collection ); + + public void SetFiles() + { + Eqp.SetFiles(); + Eqdp.SetFiles(); + Gmp.SetFiles(); + Est.SetFiles(); + Cmp.SetFiles(); + Imc.SetFiles(); + } + + public void Reset() + { + Eqp.Reset(); + Eqdp.Reset(); + Gmp.Reset(); + Est.Reset(); + Cmp.Reset(); + Imc.Reset(); + } + + public void Dispose() + { + Eqp.Dispose(); + Eqdp.Dispose(); + Gmp.Dispose(); + Est.Dispose(); + Cmp.Dispose(); + Imc.Dispose(); + } + + public bool ApplyMod( MetaManipulation m, Mod.Mod mod ) + { + return m.ManipulationType switch + { + MetaManipulation.Type.Eqp => Eqp.ApplyMod( m.Eqp, mod ), + MetaManipulation.Type.Gmp => Gmp.ApplyMod( m.Gmp, mod ), + MetaManipulation.Type.Eqdp => Eqdp.ApplyMod( m.Eqdp, mod ), + MetaManipulation.Type.Est => Est.ApplyMod( m.Est, mod ), + MetaManipulation.Type.Rsp => Cmp.ApplyMod( m.Rsp, mod ), + MetaManipulation.Type.Imc => Imc.ApplyMod( m.Imc, mod ), + MetaManipulation.Type.Unknown => false, + _ => false, + }; + } +} \ No newline at end of file diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs index f00aaffc..5ac63e99 100644 --- a/Penumbra/Meta/Manipulations/ImcManipulation.cs +++ b/Penumbra/Meta/Manipulations/ImcManipulation.cs @@ -61,6 +61,10 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation > EquipSlot = equipSlot; } + public ImcManipulation( ImcManipulation copy, ImcEntry entry ) + : this( copy.ObjectType, copy.BodySlot, copy.PrimaryId, copy.SecondaryId, copy.Variant, copy.EquipSlot, entry ) + {} + public override string ToString() => ObjectType is ObjectType.Equipment or ObjectType.Accessory ? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}" diff --git a/Penumbra/Meta/MetaCollection.cs b/Penumbra/Meta/MetaCollection.cs index 2c09aba3..8e0337e6 100644 --- a/Penumbra/Meta/MetaCollection.cs +++ b/Penumbra/Meta/MetaCollection.cs @@ -219,6 +219,12 @@ public class MetaCollection if( collection != null ) { + if( collection.DefaultData.Concat( collection.GroupData.Values.SelectMany( kvp => kvp.Values.SelectMany( l => l ) ) ) + .Any( m => m.ManipulationType == MetaManipulation.Type.Unknown || !Enum.IsDefined( m.ManipulationType ) ) ) + { + throw new Exception( "Invalid collection" ); + } + collection.Count = collection.DefaultData.Count + collection.GroupData.Values.SelectMany( kvp => kvp.Values ).Sum( l => l.Count ); } diff --git a/Penumbra/Meta/MetaManager.cs b/Penumbra/Meta/MetaManager.cs deleted file mode 100644 index f733b6ee..00000000 --- a/Penumbra/Meta/MetaManager.cs +++ /dev/null @@ -1,367 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using FFXIVClientStructs.FFXIV.Client.System.Resource; -using Penumbra.GameData.ByteString; -using Penumbra.GameData.Enums; -using Penumbra.Interop; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods; -using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility; -using ImcFile = Penumbra.Meta.Files.ImcFile; - -namespace Penumbra.Meta; - -public class MetaManager2 : IDisposable -{ - public readonly List< MetaBaseFile > ChangedData = new(7 + CharacterUtility.NumEqdpFiles); - - public ExpandedEqpFile? EqpFile; - public ExpandedGmpFile? GmpFile; - public ExpandedEqdpFile?[] EqdpFile = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles]; - public EstFile? FaceEstFile; - public EstFile? HairEstFile; - public EstFile? BodyEstFile; - public EstFile? HeadEstFile; - public CmpFile? CmpFile; - - public readonly Dictionary< EqpManipulation, Mod.Mod > EqpManipulations = new(); - public readonly Dictionary< EstManipulation, Mod.Mod > EstManipulations = new(); - public readonly Dictionary< GmpManipulation, Mod.Mod > GmpManipulations = new(); - public readonly Dictionary< RspManipulation, Mod.Mod > RspManipulations = new(); - public readonly Dictionary< EqdpManipulation, Mod.Mod > EqdpManipulations = new(); - - public readonly Dictionary< ImcManipulation, Mod.Mod > ImcManipulations = new(); - public readonly Dictionary< Utf8GamePath, ImcFile > ImcFiles = new(); - - private readonly ModCollection _collection; - - public unsafe void SetFiles() - { - foreach( var file in ChangedData ) - { - Penumbra.CharacterUtility.SetResource( file.Index, ( IntPtr )file.Data, file.Length ); - } - } - - public bool TryGetValue( MetaManipulation manip, out Mod.Mod? mod ) - { - mod = manip.ManipulationType switch - { - MetaManipulation.Type.Eqp => EqpManipulations.TryGetValue( manip.Eqp, out var m ) ? m : null, - MetaManipulation.Type.Gmp => GmpManipulations.TryGetValue( manip.Gmp, out var m ) ? m : null, - MetaManipulation.Type.Eqdp => EqdpManipulations.TryGetValue( manip.Eqdp, out var m ) ? m : null, - MetaManipulation.Type.Est => EstManipulations.TryGetValue( manip.Est, out var m ) ? m : null, - MetaManipulation.Type.Rsp => RspManipulations.TryGetValue( manip.Rsp, out var m ) ? m : null, - MetaManipulation.Type.Imc => ImcManipulations.TryGetValue( manip.Imc, out var m ) ? m : null, - _ => throw new ArgumentOutOfRangeException(), - }; - return mod != null; - } - - public int Count - => ImcManipulations.Count - + EqdpManipulations.Count - + RspManipulations.Count - + GmpManipulations.Count - + EstManipulations.Count - + EqpManipulations.Count; - - public MetaManager2( ModCollection collection ) - => _collection = collection; - - public void ApplyImcFiles( Dictionary< Utf8GamePath, FullPath > resolvedFiles ) - { - foreach( var path in ImcFiles.Keys ) - { - resolvedFiles[ path ] = CreateImcPath( path ); - } - } - - public void ResetEqp() - { - if( EqpFile != null ) - { - EqpFile.Reset( EqpManipulations.Keys.Select( m => ( int )m.SetId ) ); - EqpManipulations.Clear(); - ChangedData.Remove( EqpFile ); - } - } - - public void ResetGmp() - { - if( GmpFile != null ) - { - GmpFile.Reset( GmpManipulations.Keys.Select( m => ( int )m.SetId ) ); - GmpManipulations.Clear(); - ChangedData.Remove( GmpFile ); - } - } - - public void ResetCmp() - { - if( CmpFile != null ) - { - CmpFile.Reset( RspManipulations.Keys.Select( m => ( m.SubRace, m.Attribute ) ) ); - RspManipulations.Clear(); - ChangedData.Remove( CmpFile ); - } - } - - public void ResetEst() - { - FaceEstFile?.Reset(); - HairEstFile?.Reset(); - BodyEstFile?.Reset(); - HeadEstFile?.Reset(); - RspManipulations.Clear(); - ChangedData.RemoveAll( f => f is EstFile ); - } - - public void ResetEqdp() - { - foreach( var file in EqdpFile ) - { - file?.Reset( EqdpManipulations.Keys.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) ); - } - - ChangedData.RemoveAll( f => f is ExpandedEqdpFile ); - EqdpManipulations.Clear(); - } - - private FullPath CreateImcPath( Utf8GamePath path ) - { - var d = new DirectoryInfo( $":{_collection.Name}/" ); - return new FullPath( d, new Utf8RelPath( path ) ); - } - - public void ResetImc() - { - foreach( var (path, file) in ImcFiles ) - { - _collection.Cache?.ResolvedFiles.Remove( path ); - path.Dispose(); - file.Dispose(); - } - - ImcFiles.Clear(); - ImcManipulations.Clear(); - } - - public void Reset() - { - ChangedData.Clear(); - ResetEqp(); - ResetGmp(); - ResetCmp(); - ResetEst(); - ResetEqdp(); - ResetImc(); - } - - private static void Dispose< T >( ref T? file ) where T : class, IDisposable - { - if( file != null ) - { - file.Dispose(); - file = null; - } - } - - public void Dispose() - { - ChangedData.Clear(); - EqpManipulations.Clear(); - EstManipulations.Clear(); - GmpManipulations.Clear(); - RspManipulations.Clear(); - EqdpManipulations.Clear(); - Dispose( ref EqpFile ); - Dispose( ref GmpFile ); - Dispose( ref FaceEstFile ); - Dispose( ref HairEstFile ); - Dispose( ref BodyEstFile ); - Dispose( ref HeadEstFile ); - Dispose( ref CmpFile ); - for( var i = 0; i < CharacterUtility.NumEqdpFiles; ++i ) - { - Dispose( ref EqdpFile[ i ] ); - } - - ResetImc(); - } - - private void AddFile( MetaBaseFile file ) - { - if( !ChangedData.Contains( file ) ) - { - ChangedData.Add( file ); - } - } - - public bool ApplyMod( EqpManipulation m, Mod.Mod mod ) - { - if( !EqpManipulations.TryAdd( m, mod ) ) - { - return false; - } - - EqpFile ??= new ExpandedEqpFile(); - if( !m.Apply( EqpFile ) ) - { - return false; - } - - AddFile( EqpFile ); - return true; - } - - public bool ApplyMod( GmpManipulation m, Mod.Mod mod ) - { - if( !GmpManipulations.TryAdd( m, mod ) ) - { - return false; - } - - GmpFile ??= new ExpandedGmpFile(); - if( !m.Apply( GmpFile ) ) - { - return false; - } - - AddFile( GmpFile ); - return true; - } - - public bool ApplyMod( EstManipulation m, Mod.Mod mod ) - { - if( !EstManipulations.TryAdd( m, mod ) ) - { - return false; - } - - var file = m.Slot switch - { - EstManipulation.EstType.Hair => HairEstFile ??= new EstFile( EstManipulation.EstType.Hair ), - EstManipulation.EstType.Face => FaceEstFile ??= new EstFile( EstManipulation.EstType.Face ), - EstManipulation.EstType.Body => BodyEstFile ??= new EstFile( EstManipulation.EstType.Body ), - EstManipulation.EstType.Head => HeadEstFile ??= new EstFile( EstManipulation.EstType.Head ), - _ => throw new ArgumentOutOfRangeException(), - }; - if( !m.Apply( file ) ) - { - return false; - } - - AddFile( file ); - return true; - } - - public bool ApplyMod( RspManipulation m, Mod.Mod mod ) - { - if( !RspManipulations.TryAdd( m, mod ) ) - { - return false; - } - - CmpFile ??= new CmpFile(); - if( !m.Apply( CmpFile ) ) - { - return false; - } - - AddFile( CmpFile ); - return true; - } - - public bool ApplyMod( EqdpManipulation m, Mod.Mod mod ) - { - if( !EqdpManipulations.TryAdd( m, mod ) ) - { - return false; - } - - var file = EqdpFile[ m.FileIndex() - 2 ] ??= new ExpandedEqdpFile( Names.CombinedRace( m.Gender, m.Race ), m.Slot.IsAccessory() ); - if( !m.Apply( file ) ) - { - return false; - } - - AddFile( file ); - return true; - } - - public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod ) - { - const uint imcExt = 0x00696D63; - - if( !ImcManipulations.TryAdd( m, mod ) ) - { - return false; - } - - var path = m.GamePath(); - if( !ImcFiles.TryGetValue( path, out var file ) ) - { - file = new ImcFile( path ); - } - - if( !m.Apply( file ) ) - { - return false; - } - - ImcFiles[ path ] = file; - var fullPath = CreateImcPath( path ); - if( _collection.Cache != null ) - { - _collection.Cache.ResolvedFiles[ path ] = fullPath; - } - - var resource = ResourceLoader.FindResource( ResourceCategory.Chara, imcExt, ( uint )path.Path.Crc32 ); - if( resource != null ) - { - file.Replace( ( ResourceHandle* )resource ); - } - - return true; - } - - public static unsafe byte ImcHandler( Utf8GamePath gamePath, ResourceManager* resourceManager, - SeFileDescriptor* fileDescriptor, int priority, bool isSync ) - { - var split = gamePath.Path.Split( ( byte )'|', 2, true ); - fileDescriptor->ResourceHandle->FileNameData = split[ 1 ].Path; - fileDescriptor->ResourceHandle->FileNameLength = split[ 1 ].Length; - - var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); - if( Penumbra.ModManager.Collections.Collections.TryGetValue( split[ 0 ].ToString(), out var collection ) - && collection.Cache != null - && collection.Cache.MetaManipulations.ImcFiles.TryGetValue( - Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) ) - { - file.Replace( fileDescriptor->ResourceHandle ); - } - - fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; - fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; - return ret; - } - - public bool ApplyMod( MetaManipulation m, Mod.Mod mod ) - { - return m.ManipulationType switch - { - MetaManipulation.Type.Eqp => ApplyMod( m.Eqp, mod ), - MetaManipulation.Type.Gmp => ApplyMod( m.Gmp, mod ), - MetaManipulation.Type.Eqdp => ApplyMod( m.Eqdp, mod ), - MetaManipulation.Type.Est => ApplyMod( m.Est, mod ), - MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ), - MetaManipulation.Type.Imc => ApplyMod( m.Imc, mod ), - _ => throw new ArgumentOutOfRangeException(), - }; - } -} \ No newline at end of file diff --git a/Penumbra/Mods/CollectionManager.cs b/Penumbra/Mods/CollectionManager.cs index 82874d87..bf52a347 100644 --- a/Penumbra/Mods/CollectionManager.cs +++ b/Penumbra/Mods/CollectionManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Logging; +using Penumbra.Interop.Structs; using Penumbra.Mod; using Penumbra.Util; @@ -43,6 +44,14 @@ public class CollectionManager { ActiveCollection = newActive; Penumbra.ResidentResources.Reload(); + if( ActiveCollection.Cache == null ) + { + Penumbra.CharacterUtility.ResetAll(); + } + else + { + ActiveCollection.Cache.MetaManipulations.SetFiles(); + } } else { @@ -206,10 +215,9 @@ public class CollectionManager public void SetDefaultCollection( ModCollection newCollection ) => SetCollection( newCollection, DefaultCollection, c => { - if( !CollectionChangedTo.Any() ) + if( CollectionChangedTo.Length == 0 ) { - ActiveCollection = c; - Penumbra.ResidentResources.Reload(); + SetActiveCollection( c, string.Empty ); } DefaultCollection = c; @@ -228,8 +236,7 @@ public class CollectionManager { if( CollectionChangedTo == characterName && CharacterCollection.TryGetValue( characterName, out var collection ) ) { - ActiveCollection = c; - Penumbra.ResidentResources.Reload(); + SetActiveCollection( c, CollectionChangedTo ); } CharacterCollection[ characterName ] = c; diff --git a/Penumbra/Mods/ModCollectionCache.cs b/Penumbra/Mods/ModCollectionCache.cs index 20a7b583..6af67ad3 100644 --- a/Penumbra/Mods/ModCollectionCache.cs +++ b/Penumbra/Mods/ModCollectionCache.cs @@ -7,8 +7,7 @@ using System.IO; using System.Linq; using Dalamud.Logging; using Penumbra.GameData.ByteString; -using Penumbra.GameData.Util; -using Penumbra.Meta; +using Penumbra.Meta.Manager; using Penumbra.Mod; using Penumbra.Util; @@ -27,7 +26,7 @@ public class ModCollectionCache private readonly SortedList< string, object? > _changedItems = new(); public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new(); public readonly HashSet< FullPath > MissingFiles = new(); - public readonly MetaManager2 MetaManipulations; + public readonly MetaManager MetaManipulations; public IReadOnlyDictionary< string, object? > ChangedItems { @@ -39,7 +38,7 @@ public class ModCollectionCache } public ModCollectionCache( ModCollection collection ) - => MetaManipulations = new MetaManager2( collection ); + => MetaManipulations = new MetaManager( collection ); private static void ResetFileSeen( int size ) { @@ -258,7 +257,7 @@ public class ModCollectionCache } private void AddMetaFiles() - => MetaManipulations.ApplyImcFiles( ResolvedFiles ); + => MetaManipulations.Imc.SetFiles(); private void AddSwaps( Mod.Mod mod ) { diff --git a/Penumbra/Mods/ModManager.Directory.cs b/Penumbra/Mods/ModManager.Directory.cs index b14f4929..59f52ace 100644 --- a/Penumbra/Mods/ModManager.Directory.cs +++ b/Penumbra/Mods/ModManager.Directory.cs @@ -2,14 +2,16 @@ using System; using System.Collections.Generic; using System.IO; using Dalamud.Logging; +using Penumbra.Meta.Manipulations; using Penumbra.Mod; namespace Penumbra.Mods; public partial class ModManagerNew { - private readonly List _mods = new(); - public IReadOnlyList Mods + private readonly List< ModData > _mods = new(); + + public IReadOnlyList< ModData > Mods => _mods; public void DiscoverMods() @@ -35,7 +37,6 @@ public partial class ModManagerNew //Collections.RecreateCaches(); } } - public partial class ModManagerNew { public DirectoryInfo BasePath { get; private set; } = null!; @@ -52,7 +53,7 @@ public partial class ModManagerNew { if( Valid ) { - Valid = Directory.Exists(BasePath.FullName); + Valid = Directory.Exists( BasePath.FullName ); } return Valid; @@ -87,7 +88,6 @@ public partial class ModManagerNew return; } - ( BasePath, Valid ) = CreateDirectory( path ); if( Penumbra.Config.ModDirectory != BasePath.FullName ) @@ -105,6 +105,6 @@ public partial class ModManagerNew } InitBaseDirectory( path ); - BasePathChanged?.Invoke( BasePath ); + BasePathChanged?.Invoke( BasePath ); } } \ No newline at end of file diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 666a349f..930c72c7 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -238,7 +238,7 @@ public class Penumbra : IDalamudPlugin //PathResolver.Dispose(); ResourceLogger.Dispose(); ResourceLoader.Dispose(); - + CharacterUtility.Dispose(); ShutdownWebServer(); } diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 347b9ddc..13a25cc0 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -18,11 +18,12 @@ full - DEBUG;TRACE + DEBUG;TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC pdbonly + $(DefineConstants)TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC diff --git a/Penumbra/UI/MenuTabs/TabDebug.cs b/Penumbra/UI/MenuTabs/TabDebug.cs index 7d2e8ca9..b353192f 100644 --- a/Penumbra/UI/MenuTabs/TabDebug.cs +++ b/Penumbra/UI/MenuTabs/TabDebug.cs @@ -1,28 +1,19 @@ using System; using System.Collections.Generic; -using System.Drawing.Text; using System.IO; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Objects.Types; -using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using FFXIVClientStructs.FFXIV.Client.System.String; using ImGuiNET; using Penumbra.Api; -using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.GameData.Util; using Penumbra.Interop; -using Penumbra.Meta; -using Penumbra.Mods; +using Penumbra.Meta.Files; using Penumbra.UI.Custom; -using Penumbra.Util; -using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility; using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle; -using Utf8String = Penumbra.GameData.ByteString.Utf8String; namespace Penumbra.UI; @@ -391,6 +382,31 @@ public partial class SettingsInterface return; } + var eqp = 0; + ImGui.InputInt( "##EqpInput", ref eqp ); + try + { + var def = ExpandedEqpFile.GetDefault( eqp ); + var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Eqp.File?[ eqp ] ?? def; + ImGui.Text( Convert.ToString( ( long )def, 2 ).PadLeft( 64, '0' ) ); + ImGui.Text( Convert.ToString( ( long )val, 2 ).PadLeft( 64, '0' ) ); + } + catch + { } + + var eqdp = 0; + ImGui.InputInt( "##EqdpInput", ref eqdp ); + try + { + var def = ExpandedEqdpFile.GetDefault(GenderRace.MidlanderMale, false, eqdp ); + var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Eqdp.File(GenderRace.MidlanderMale, false)?[eqdp] ?? def; + ImGui.Text( Convert.ToString( ( ushort )def, 2 ).PadLeft( 16, '0' ) ); + ImGui.Text( Convert.ToString( ( ushort )val, 2 ).PadLeft( 16, '0' ) ); + } + catch + { } + + if( !ImGui.BeginTable( "##CharacterUtilityDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) { return; @@ -398,9 +414,10 @@ public partial class SettingsInterface using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - for( var i = 0; i < CharacterUtility.NumRelevantResources; ++i ) + for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i ) { - var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ i ]; + var idx = CharacterUtility.RelevantIndices[ i ]; + var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ idx ]; ImGui.TableNextColumn(); ImGui.Text( $"0x{( ulong )resource:X}" ); ImGui.TableNextColumn(); diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs index cca21491..3e545e34 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs @@ -3,772 +3,805 @@ using System.Collections.Generic; using System.Drawing.Text; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using Dalamud.Interface; using ImGuiNET; +using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.UI.Custom; using ObjectType = Penumbra.GameData.Enums.ObjectType; -namespace Penumbra.UI +namespace Penumbra.UI; + +public partial class SettingsInterface { - public partial class SettingsInterface + private partial class PluginDetails { - private partial class PluginDetails + private int _newManipTypeIdx = 0; + private ushort _newManipSetId = 0; + private ushort _newManipSecondaryId = 0; + private int _newManipSubrace = 0; + private int _newManipRace = 0; + private int _newManipAttribute = 0; + private int _newManipEquipSlot = 0; + private int _newManipObjectType = 0; + private int _newManipGender = 0; + private int _newManipBodySlot = 0; + private ushort _newManipVariant = 0; + + + private static readonly (string, EquipSlot)[] EqpEquipSlots = { - private int _newManipTypeIdx = 0; - private ushort _newManipSetId = 0; - private ushort _newManipSecondaryId = 0; - private int _newManipSubrace = 0; - private int _newManipRace = 0; - private int _newManipAttribute = 0; - private int _newManipEquipSlot = 0; - private int _newManipObjectType = 0; - private int _newManipGender = 0; - private int _newManipBodySlot = 0; - private ushort _newManipVariant = 0; + ( "Head", EquipSlot.Head ), + ( "Body", EquipSlot.Body ), + ( "Hands", EquipSlot.Hands ), + ( "Legs", EquipSlot.Legs ), + ( "Feet", EquipSlot.Feet ), + }; + private static readonly (string, EquipSlot)[] EqdpEquipSlots = + { + EqpEquipSlots[ 0 ], + EqpEquipSlots[ 1 ], + EqpEquipSlots[ 2 ], + EqpEquipSlots[ 3 ], + EqpEquipSlots[ 4 ], + ( "Ears", EquipSlot.Ears ), + ( "Neck", EquipSlot.Neck ), + ( "Wrist", EquipSlot.Wrists ), + ( "Left Finger", EquipSlot.LFinger ), + ( "Right Finger", EquipSlot.RFinger ), + }; - private static readonly (string, EquipSlot)[] EqpEquipSlots = + private static readonly (string, ModelRace)[] Races = + { + ( ModelRace.Midlander.ToName(), ModelRace.Midlander ), + ( ModelRace.Highlander.ToName(), ModelRace.Highlander ), + ( ModelRace.Elezen.ToName(), ModelRace.Elezen ), + ( ModelRace.Miqote.ToName(), ModelRace.Miqote ), + ( ModelRace.Roegadyn.ToName(), ModelRace.Roegadyn ), + ( ModelRace.Lalafell.ToName(), ModelRace.Lalafell ), + ( ModelRace.AuRa.ToName(), ModelRace.AuRa ), + ( ModelRace.Viera.ToName(), ModelRace.Viera ), + ( ModelRace.Hrothgar.ToName(), ModelRace.Hrothgar ), + }; + + private static readonly (string, Gender)[] Genders = + { + ( Gender.Male.ToName(), Gender.Male ), + ( Gender.Female.ToName(), Gender.Female ), + ( Gender.MaleNpc.ToName(), Gender.MaleNpc ), + ( Gender.FemaleNpc.ToName(), Gender.FemaleNpc ), + }; + + private static readonly (string, ObjectType)[] ObjectTypes = + { + ( "Equipment", ObjectType.Equipment ), + ( "Customization", ObjectType.Character ), + }; + + private static readonly (string, EquipSlot)[] EstEquipSlots = + { + EqpEquipSlots[ 0 ], + EqpEquipSlots[ 1 ], + }; + + private static readonly (string, BodySlot)[] EstBodySlots = + { + ( "Hair", BodySlot.Hair ), + ( "Face", BodySlot.Face ), + }; + + private static readonly (string, SubRace)[] Subraces = + { + ( SubRace.Midlander.ToName(), SubRace.Midlander ), + ( SubRace.Highlander.ToName(), SubRace.Highlander ), + ( SubRace.Wildwood.ToName(), SubRace.Wildwood ), + ( SubRace.Duskwight.ToName(), SubRace.Duskwight ), + ( SubRace.SeekerOfTheSun.ToName(), SubRace.SeekerOfTheSun ), + ( SubRace.KeeperOfTheMoon.ToName(), SubRace.KeeperOfTheMoon ), + ( SubRace.Seawolf.ToName(), SubRace.Seawolf ), + ( SubRace.Hellsguard.ToName(), SubRace.Hellsguard ), + ( SubRace.Plainsfolk.ToName(), SubRace.Plainsfolk ), + ( SubRace.Dunesfolk.ToName(), SubRace.Dunesfolk ), + ( SubRace.Raen.ToName(), SubRace.Raen ), + ( SubRace.Xaela.ToName(), SubRace.Xaela ), + ( SubRace.Rava.ToName(), SubRace.Rava ), + ( SubRace.Veena.ToName(), SubRace.Veena ), + ( SubRace.Helion.ToName(), SubRace.Helion ), + ( SubRace.Lost.ToName(), SubRace.Lost ), + }; + + private static readonly (string, RspAttribute)[] RspAttributes = + { + ( RspAttribute.MaleMinSize.ToFullString(), RspAttribute.MaleMinSize ), + ( RspAttribute.MaleMaxSize.ToFullString(), RspAttribute.MaleMaxSize ), + ( RspAttribute.FemaleMinSize.ToFullString(), RspAttribute.FemaleMinSize ), + ( RspAttribute.FemaleMaxSize.ToFullString(), RspAttribute.FemaleMaxSize ), + ( RspAttribute.BustMinX.ToFullString(), RspAttribute.BustMinX ), + ( RspAttribute.BustMaxX.ToFullString(), RspAttribute.BustMaxX ), + ( RspAttribute.BustMinY.ToFullString(), RspAttribute.BustMinY ), + ( RspAttribute.BustMaxY.ToFullString(), RspAttribute.BustMaxY ), + ( RspAttribute.BustMinZ.ToFullString(), RspAttribute.BustMinZ ), + ( RspAttribute.BustMaxZ.ToFullString(), RspAttribute.BustMaxZ ), + ( RspAttribute.MaleMinTail.ToFullString(), RspAttribute.MaleMinTail ), + ( RspAttribute.MaleMaxTail.ToFullString(), RspAttribute.MaleMaxTail ), + ( RspAttribute.FemaleMinTail.ToFullString(), RspAttribute.FemaleMinTail ), + ( RspAttribute.FemaleMaxTail.ToFullString(), RspAttribute.FemaleMaxTail ), + }; + + private static readonly (string, ObjectType)[] ImcObjectType = + { + ObjectTypes[ 0 ], + ( "Weapon", ObjectType.Weapon ), + ( "Demihuman", ObjectType.DemiHuman ), + ( "Monster", ObjectType.Monster ), + }; + + private static readonly (string, BodySlot)[] ImcBodySlots = + { + EstBodySlots[ 0 ], + EstBodySlots[ 1 ], + ( "Body", BodySlot.Body ), + ( "Tail", BodySlot.Tail ), + ( "Ears", BodySlot.Zear ), + }; + + private static bool PrintCheckBox( string name, ref bool value, bool def ) + { + var color = value == def ? 0 : value ? ColorDarkGreen : ColorDarkRed; + if( color == 0 ) { - ( "Head", EquipSlot.Head ), - ( "Body", EquipSlot.Body ), - ( "Hands", EquipSlot.Hands ), - ( "Legs", EquipSlot.Legs ), - ( "Feet", EquipSlot.Feet ), - }; - - private static readonly (string, EquipSlot)[] EqdpEquipSlots = - { - EqpEquipSlots[ 0 ], - EqpEquipSlots[ 1 ], - EqpEquipSlots[ 2 ], - EqpEquipSlots[ 3 ], - EqpEquipSlots[ 4 ], - ( "Ears", EquipSlot.Ears ), - ( "Neck", EquipSlot.Neck ), - ( "Wrist", EquipSlot.Wrists ), - ( "Left Finger", EquipSlot.LFinger ), - ( "Right Finger", EquipSlot.RFinger ), - }; - - private static readonly (string, ModelRace)[] Races = - { - ( ModelRace.Midlander.ToName(), ModelRace.Midlander ), - ( ModelRace.Highlander.ToName(), ModelRace.Highlander ), - ( ModelRace.Elezen.ToName(), ModelRace.Elezen ), - ( ModelRace.Miqote.ToName(), ModelRace.Miqote ), - ( ModelRace.Roegadyn.ToName(), ModelRace.Roegadyn ), - ( ModelRace.Lalafell.ToName(), ModelRace.Lalafell ), - ( ModelRace.AuRa.ToName(), ModelRace.AuRa ), - ( ModelRace.Viera.ToName(), ModelRace.Viera ), - ( ModelRace.Hrothgar.ToName(), ModelRace.Hrothgar ), - }; - - private static readonly (string, Gender)[] Genders = - { - ( Gender.Male.ToName(), Gender.Male ), - ( Gender.Female.ToName(), Gender.Female ), - ( Gender.MaleNpc.ToName(), Gender.MaleNpc ), - ( Gender.FemaleNpc.ToName(), Gender.FemaleNpc ), - }; - - private static readonly (string, ObjectType)[] ObjectTypes = - { - ( "Equipment", ObjectType.Equipment ), - ( "Customization", ObjectType.Character ), - }; - - private static readonly (string, EquipSlot)[] EstEquipSlots = - { - EqpEquipSlots[ 0 ], - EqpEquipSlots[ 1 ], - }; - - private static readonly (string, BodySlot)[] EstBodySlots = - { - ( "Hair", BodySlot.Hair ), - ( "Face", BodySlot.Face ), - }; - - private static readonly (string, SubRace)[] Subraces = - { - ( SubRace.Midlander.ToName(), SubRace.Midlander ), - ( SubRace.Highlander.ToName(), SubRace.Highlander ), - ( SubRace.Wildwood.ToName(), SubRace.Wildwood ), - ( SubRace.Duskwight.ToName(), SubRace.Duskwight ), - ( SubRace.SeekerOfTheSun.ToName(), SubRace.SeekerOfTheSun ), - ( SubRace.KeeperOfTheMoon.ToName(), SubRace.KeeperOfTheMoon ), - ( SubRace.Seawolf.ToName(), SubRace.Seawolf ), - ( SubRace.Hellsguard.ToName(), SubRace.Hellsguard ), - ( SubRace.Plainsfolk.ToName(), SubRace.Plainsfolk ), - ( SubRace.Dunesfolk.ToName(), SubRace.Dunesfolk ), - ( SubRace.Raen.ToName(), SubRace.Raen ), - ( SubRace.Xaela.ToName(), SubRace.Xaela ), - ( SubRace.Rava.ToName(), SubRace.Rava ), - ( SubRace.Veena.ToName(), SubRace.Veena ), - ( SubRace.Helion.ToName(), SubRace.Helion ), - ( SubRace.Lost.ToName(), SubRace.Lost ), - }; - - private static readonly (string, RspAttribute)[] RspAttributes = - { - ( RspAttribute.MaleMinSize.ToFullString(), RspAttribute.MaleMinSize ), - ( RspAttribute.MaleMaxSize.ToFullString(), RspAttribute.MaleMaxSize ), - ( RspAttribute.FemaleMinSize.ToFullString(), RspAttribute.FemaleMinSize ), - ( RspAttribute.FemaleMaxSize.ToFullString(), RspAttribute.FemaleMaxSize ), - ( RspAttribute.BustMinX.ToFullString(), RspAttribute.BustMinX ), - ( RspAttribute.BustMaxX.ToFullString(), RspAttribute.BustMaxX ), - ( RspAttribute.BustMinY.ToFullString(), RspAttribute.BustMinY ), - ( RspAttribute.BustMaxY.ToFullString(), RspAttribute.BustMaxY ), - ( RspAttribute.BustMinZ.ToFullString(), RspAttribute.BustMinZ ), - ( RspAttribute.BustMaxZ.ToFullString(), RspAttribute.BustMaxZ ), - ( RspAttribute.MaleMinTail.ToFullString(), RspAttribute.MaleMinTail ), - ( RspAttribute.MaleMaxTail.ToFullString(), RspAttribute.MaleMaxTail ), - ( RspAttribute.FemaleMinTail.ToFullString(), RspAttribute.FemaleMinTail ), - ( RspAttribute.FemaleMaxTail.ToFullString(), RspAttribute.FemaleMaxTail ), - }; - - private static readonly (string, ObjectType)[] ImcObjectType = - { - ObjectTypes[ 0 ], - ( "Weapon", ObjectType.Weapon ), - ( "Demihuman", ObjectType.DemiHuman ), - ( "Monster", ObjectType.Monster ), - }; - - private static readonly (string, BodySlot)[] ImcBodySlots = - { - EstBodySlots[ 0 ], - EstBodySlots[ 1 ], - ( "Body", BodySlot.Body ), - ( "Tail", BodySlot.Tail ), - ( "Ears", BodySlot.Zear ), - }; - - private static bool PrintCheckBox( string name, ref bool value, bool def ) - { - var color = value == def ? 0 : value ? ColorDarkGreen : ColorDarkRed; - if( color == 0 ) - { - return ImGui.Checkbox( name, ref value ); - } - - using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color ); - var ret = ImGui.Checkbox( name, ref value ); - return ret; + return ImGui.Checkbox( name, ref value ); } - private bool RestrictedInputInt( string name, ref ushort value, ushort min, ushort max ) - { - int tmp = value; - if( ImGui.InputInt( name, ref tmp, 0, 0, _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) - && tmp != value - && tmp >= min - && tmp <= max ) - { - value = ( ushort )tmp; - return true; - } + using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color ); + var ret = ImGui.Checkbox( name, ref value ); + return ret; + } + private bool RestrictedInputInt( string name, ref ushort value, ushort min, ushort max ) + { + int tmp = value; + if( ImGui.InputInt( name, ref tmp, 0, 0, _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) + && tmp != value + && tmp >= min + && tmp <= max ) + { + value = ( ushort )tmp; + return true; + } + + return false; + } + + private static bool DefaultButton< T >( string name, ref T value, T defaultValue ) where T : IComparable< T > + { + var compare = defaultValue.CompareTo( value ); + var color = compare < 0 ? ColorDarkGreen : + compare > 0 ? ColorDarkRed : ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Button ] ); + + using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Button, color ); + var ret = ImGui.Button( name, Vector2.UnitX * 120 ) && compare != 0; + ImGui.SameLine(); + return ret; + } + + private bool DrawInputWithDefault( string name, ref ushort value, ushort defaultValue, ushort max ) + => DefaultButton( $"{( _editMode ? "Set to " : "" )}Default: {defaultValue}##imc{name}", ref value, defaultValue ) + || RestrictedInputInt( name, ref value, 0, max ); + + private static bool CustomCombo< T >( string label, IList< (string, T) > namesAndValues, out T value, ref int idx ) + { + value = idx < namesAndValues.Count ? namesAndValues[ idx ].Item2 : default!; + + if( !ImGui.BeginCombo( label, idx < namesAndValues.Count ? namesAndValues[ idx ].Item1 : string.Empty ) ) + { return false; } - private static bool DefaultButton< T >( string name, ref T value, T defaultValue ) where T : IComparable< T > + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); + + for( var i = 0; i < namesAndValues.Count; ++i ) { - var compare = defaultValue.CompareTo( value ); - var color = compare < 0 ? ColorDarkGreen : - compare > 0 ? ColorDarkRed : ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Button ] ); - - using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Button, color ); - var ret = ImGui.Button( name, Vector2.UnitX * 120 ) && compare != 0; - ImGui.SameLine(); - return ret; - } - - private bool DrawInputWithDefault( string name, ref ushort value, ushort defaultValue, ushort max ) - => DefaultButton( $"{( _editMode ? "Set to " : "" )}Default: {defaultValue}##imc{name}", ref value, defaultValue ) - || RestrictedInputInt( name, ref value, 0, max ); - - private static bool CustomCombo< T >( string label, IList< (string, T) > namesAndValues, out T value, ref int idx ) - { - value = idx < namesAndValues.Count ? namesAndValues[ idx ].Item2 : default!; - - if( !ImGui.BeginCombo( label, idx < namesAndValues.Count ? namesAndValues[ idx ].Item1 : string.Empty ) ) + if( !ImGui.Selectable( $"{namesAndValues[ i ].Item1}##{label}{i}", idx == i ) || idx == i ) { - return false; + continue; } - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); + idx = i; + value = namesAndValues[ i ].Item2; + return true; + } - for( var i = 0; i < namesAndValues.Count; ++i ) + return false; + } + + private bool DrawEqpRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].Eqp; + var val = id.Entry; + + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + var defaults = ExpandedEqpFile.GetDefault( id.SetId ); + var attributes = Eqp.EqpAttributes[ id.Slot ]; + + foreach( var flag in attributes ) { - if( !ImGui.Selectable( $"{namesAndValues[ i ].Item1}##{label}{i}", idx == i ) || idx == i ) + var name = flag.ToLocalName(); + var tmp = val.HasFlag( flag ); + if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) ) { - continue; + list[ manipIdx ] = new MetaManipulation( new EqpManipulation( tmp ? val | flag : val & ~flag, id.Slot, id.SetId ) ); + ret = true; } + } + } - idx = i; - value = namesAndValues[ i ].Item2; + ImGui.Text( ObjectType.Equipment.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Slot.ToString() ); + return ret; + } + + private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].Gmp; + var val = id.Entry; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + var defaults = ExpandedGmpFile.GetDefault( id.SetId ); + var enabled = val.Enabled; + var animated = val.Animated; + var rotationA = val.RotationA; + var rotationB = val.RotationB; + var rotationC = val.RotationC; + ushort unk = val.UnknownTotal; + + ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled; + ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated ); + ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF ); + ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF ); + ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF ); + ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF ); + + if( ret && _editMode ) + { + list[ manipIdx ] = new MetaManipulation( new GmpManipulation( new GmpEntry + { + Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk, + RotationA = rotationA, RotationB = rotationB, RotationC = rotationC, + }, id.SetId ) ); + } + } + + ImGui.Text( ObjectType.Equipment.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( EquipSlot.Head.ToString() ); + return ret; + } + + private static (bool, bool) GetEqdpBits( EquipSlot slot, EqdpEntry entry ) + { + return slot switch + { + EquipSlot.Head => ( entry.HasFlag( EqdpEntry.Head1 ), entry.HasFlag( EqdpEntry.Head2 ) ), + EquipSlot.Body => ( entry.HasFlag( EqdpEntry.Body1 ), entry.HasFlag( EqdpEntry.Body2 ) ), + EquipSlot.Hands => ( entry.HasFlag( EqdpEntry.Hands1 ), entry.HasFlag( EqdpEntry.Hands2 ) ), + EquipSlot.Legs => ( entry.HasFlag( EqdpEntry.Legs1 ), entry.HasFlag( EqdpEntry.Legs2 ) ), + EquipSlot.Feet => ( entry.HasFlag( EqdpEntry.Feet1 ), entry.HasFlag( EqdpEntry.Feet2 ) ), + EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ), + EquipSlot.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ), + EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ), + EquipSlot.RFinger => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ), + EquipSlot.LFinger => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ), + _ => ( false, false ), + }; + } + + private static EqdpEntry SetEqdpBits( EquipSlot slot, EqdpEntry value, bool bit1, bool bit2 ) + { + switch( slot ) + { + case EquipSlot.Head: + value = bit1 ? value | EqdpEntry.Head1 : value & ~EqdpEntry.Head1; + value = bit2 ? value | EqdpEntry.Head2 : value & ~EqdpEntry.Head2; + return value; + case EquipSlot.Body: + value = bit1 ? value | EqdpEntry.Body1 : value & ~EqdpEntry.Body1; + value = bit2 ? value | EqdpEntry.Body2 : value & ~EqdpEntry.Body2; + return value; + case EquipSlot.Hands: + value = bit1 ? value | EqdpEntry.Hands1 : value & ~EqdpEntry.Hands1; + value = bit2 ? value | EqdpEntry.Hands2 : value & ~EqdpEntry.Hands2; + return value; + case EquipSlot.Legs: + value = bit1 ? value | EqdpEntry.Legs1 : value & ~EqdpEntry.Legs1; + value = bit2 ? value | EqdpEntry.Legs2 : value & ~EqdpEntry.Legs2; + return value; + case EquipSlot.Feet: + value = bit1 ? value | EqdpEntry.Feet1 : value & ~EqdpEntry.Feet1; + value = bit2 ? value | EqdpEntry.Feet2 : value & ~EqdpEntry.Feet2; + return value; + case EquipSlot.Neck: + value = bit1 ? value | EqdpEntry.Neck1 : value & ~EqdpEntry.Neck1; + value = bit2 ? value | EqdpEntry.Neck2 : value & ~EqdpEntry.Neck2; + return value; + case EquipSlot.Ears: + value = bit1 ? value | EqdpEntry.Ears1 : value & ~EqdpEntry.Ears1; + value = bit2 ? value | EqdpEntry.Ears2 : value & ~EqdpEntry.Ears2; + return value; + case EquipSlot.Wrists: + value = bit1 ? value | EqdpEntry.Wrists1 : value & ~EqdpEntry.Wrists1; + value = bit2 ? value | EqdpEntry.Wrists2 : value & ~EqdpEntry.Wrists2; + return value; + case EquipSlot.RFinger: + value = bit1 ? value | EqdpEntry.RingR1 : value & ~EqdpEntry.RingR1; + value = bit2 ? value | EqdpEntry.RingR2 : value & ~EqdpEntry.RingR2; + return value; + case EquipSlot.LFinger: + value = bit1 ? value | EqdpEntry.RingL1 : value & ~EqdpEntry.RingL1; + value = bit2 ? value | EqdpEntry.RingL2 : value & ~EqdpEntry.RingL2; + return value; + } + + return value; + } + + private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].Eqdp; + var val = id.Entry; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + var defaults = ExpandedEqdpFile.GetDefault( id.FileIndex(), id.SetId ); + var (bit1, bit2) = GetEqdpBits( id.Slot, val ); + var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults ); + + ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 ); + ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 ); + + if( ret && _editMode ) + { + list[ manipIdx ] = new MetaManipulation( new EqdpManipulation( SetEqdpBits( id.Slot, val, bit1, bit2 ), id.Slot, id.Gender, + id.Race, id.SetId ) ); + } + } + + ImGui.Text( id.Slot.IsAccessory() + ? ObjectType.Accessory.ToString() + : ObjectType.Equipment.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Slot.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Race.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Gender.ToString() ); + return ret; + } + + private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].Est; + var val = id.SkeletonIdx; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + var defaults = EstFile.GetDefault( id.Slot, Names.CombinedRace( id.Gender, id.Race ), id.SetId ); + if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode ) + { + list[ manipIdx ] = new MetaManipulation( new EstManipulation( id.Gender, id.Race, id.Slot, id.SetId, val ) ); + ret = true; + } + } + + ImGui.Text( id.Slot.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.SetId.ToString() ); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.Text( id.Race.ToName() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Gender.ToName() ); + + return ret; + } + + private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].Imc; + var val = id.Entry; + + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + var defaults = new ImcFile( id.GamePath() ).GetEntry( ImcFile.PartIndex( id.EquipSlot ), id.Variant ); + ushort materialId = val.MaterialId; + ushort vfxId = val.VfxId; + ushort decalId = val.DecalId; + var soundId = ( ushort )val.SoundId; + var attributeMask = val.AttributeMask; + var materialAnimationId = ( ushort )val.MaterialAnimationId; + ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue ); + ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue ); + ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue ); + ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F ); + ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF ); + ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId, + byte.MaxValue ); + + if( ret && _editMode ) + { + var value = new ImcEntry( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId, ( byte )vfxId, + ( byte )materialAnimationId ); + list[ manipIdx ] = new MetaManipulation( new ImcManipulation( id, value ) ); + } + } + + ImGui.Text( id.ObjectType.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.PrimaryId.ToString() ); + ImGui.TableNextColumn(); + if( id.ObjectType is ObjectType.Accessory or ObjectType.Equipment ) + { + ImGui.Text( id.ObjectType is ObjectType.Equipment or ObjectType.Accessory + ? id.EquipSlot.ToString() + : id.BodySlot.ToString() ); + } + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + if( id.ObjectType != ObjectType.Equipment + && id.ObjectType != ObjectType.Accessory ) + { + ImGui.Text( id.SecondaryId.ToString() ); + } + + ImGui.TableNextColumn(); + ImGui.Text( id.Variant.ToString() ); + return ret; + } + + private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list ) + { + var ret = false; + var id = list[ manipIdx ].Rsp; + var defaults = CmpFile.GetDefault( id.SubRace, id.Attribute ); + var val = id.Entry; + if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + if( DefaultButton( + $"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults ) + && _editMode ) + { + list[ manipIdx ] = new MetaManipulation( new RspManipulation( id.SubRace, id.Attribute, val ) ); + ret = true; + } + + ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); + if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f", + _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) + && val >= 0 + && val <= 5 + && _editMode ) + { + list[ manipIdx ] = new MetaManipulation( new RspManipulation( id.SubRace, id.Attribute, val ) ); + ret = true; + } + } + + ImGui.Text( id.Attribute.ToUngenderedString() ); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.Text( id.SubRace.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( id.Attribute.ToGender().ToString() ); + return ret; + } + + private bool DrawManipulationRow( ref int manipIdx, IList< MetaManipulation > list, ref int count ) + { + var type = list[ manipIdx ].ManipulationType; + + if( _editMode ) + { + ImGui.TableNextColumn(); + using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); + if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##manipDelete{manipIdx}" ) ) + { + list.RemoveAt( manipIdx ); + ImGui.TableNextRow(); + --manipIdx; + --count; return true; } - - return false; } - private bool DrawEqpRow( int manipIdx, IList< MetaManipulation > list ) + ImGui.TableNextColumn(); + ImGui.Text( type.ToString() ); + ImGui.TableNextColumn(); + + var changes = false; + switch( type ) { - var ret = false; - //var id = list[ manipIdx ].EqpIdentifier; - //var val = list[ manipIdx ].EqpValue; - // - //if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - //{ - // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - // var defaults = ( EqpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!; - // var attributes = Eqp.EqpAttributes[ id.Slot ]; - // - // foreach( var flag in attributes ) - // { - // var name = flag.ToLocalName(); - // var tmp = val.HasFlag( flag ); - // if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) ) - // { - // list[ manipIdx ] = MetaManipulation.Eqp( id.Slot, id.SetId, tmp ? val | flag : val & ~flag ); - // ret = true; - // } - // } - //} - // - //ImGui.Text( ObjectType.Equipment.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.SetId.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.Slot.ToString() ); - return ret; - } - - private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - //var id = list[ manipIdx ].GmpIdentifier; - //var val = list[ manipIdx ].GmpValue; - // - //if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - //{ - // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - // var enabled = val.Enabled; - // var animated = val.Animated; - // var rotationA = val.RotationA; - // var rotationB = val.RotationB; - // var rotationC = val.RotationC; - // ushort unk = val.UnknownTotal; - // - // ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled; - // ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated ); - // ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF ); - // ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF ); - // ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF ); - // ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF ); - // - // if( ret && _editMode ) - // { - // list[ manipIdx ] = MetaManipulation.Gmp( id.SetId, - // new GmpEntry - // { - // Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk, - // RotationA = rotationA, RotationB = rotationB, RotationC = rotationC, - // } ); - // } - //} - // - //ImGui.Text( ObjectType.Equipment.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.SetId.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( EquipSlot.Head.ToString() ); - return ret; - } - - private static (bool, bool) GetEqdpBits( EquipSlot slot, EqdpEntry entry ) - { - return slot switch - { - EquipSlot.Head => ( entry.HasFlag( EqdpEntry.Head1 ), entry.HasFlag( EqdpEntry.Head2 ) ), - EquipSlot.Body => ( entry.HasFlag( EqdpEntry.Body1 ), entry.HasFlag( EqdpEntry.Body2 ) ), - EquipSlot.Hands => ( entry.HasFlag( EqdpEntry.Hands1 ), entry.HasFlag( EqdpEntry.Hands2 ) ), - EquipSlot.Legs => ( entry.HasFlag( EqdpEntry.Legs1 ), entry.HasFlag( EqdpEntry.Legs2 ) ), - EquipSlot.Feet => ( entry.HasFlag( EqdpEntry.Feet1 ), entry.HasFlag( EqdpEntry.Feet2 ) ), - EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ), - EquipSlot.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ), - EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ), - EquipSlot.RFinger => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ), - EquipSlot.LFinger => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ), - _ => ( false, false ), - }; - } - - private static EqdpEntry SetEqdpBits( EquipSlot slot, EqdpEntry value, bool bit1, bool bit2 ) - { - switch( slot ) - { - case EquipSlot.Head: - value = bit1 ? value | EqdpEntry.Head1 : value & ~EqdpEntry.Head1; - value = bit2 ? value | EqdpEntry.Head2 : value & ~EqdpEntry.Head2; - return value; - case EquipSlot.Body: - value = bit1 ? value | EqdpEntry.Body1 : value & ~EqdpEntry.Body1; - value = bit2 ? value | EqdpEntry.Body2 : value & ~EqdpEntry.Body2; - return value; - case EquipSlot.Hands: - value = bit1 ? value | EqdpEntry.Hands1 : value & ~EqdpEntry.Hands1; - value = bit2 ? value | EqdpEntry.Hands2 : value & ~EqdpEntry.Hands2; - return value; - case EquipSlot.Legs: - value = bit1 ? value | EqdpEntry.Legs1 : value & ~EqdpEntry.Legs1; - value = bit2 ? value | EqdpEntry.Legs2 : value & ~EqdpEntry.Legs2; - return value; - case EquipSlot.Feet: - value = bit1 ? value | EqdpEntry.Feet1 : value & ~EqdpEntry.Feet1; - value = bit2 ? value | EqdpEntry.Feet2 : value & ~EqdpEntry.Feet2; - return value; - case EquipSlot.Neck: - value = bit1 ? value | EqdpEntry.Neck1 : value & ~EqdpEntry.Neck1; - value = bit2 ? value | EqdpEntry.Neck2 : value & ~EqdpEntry.Neck2; - return value; - case EquipSlot.Ears: - value = bit1 ? value | EqdpEntry.Ears1 : value & ~EqdpEntry.Ears1; - value = bit2 ? value | EqdpEntry.Ears2 : value & ~EqdpEntry.Ears2; - return value; - case EquipSlot.Wrists: - value = bit1 ? value | EqdpEntry.Wrists1 : value & ~EqdpEntry.Wrists1; - value = bit2 ? value | EqdpEntry.Wrists2 : value & ~EqdpEntry.Wrists2; - return value; - case EquipSlot.RFinger: - value = bit1 ? value | EqdpEntry.RingR1 : value & ~EqdpEntry.RingR1; - value = bit2 ? value | EqdpEntry.RingR2 : value & ~EqdpEntry.RingR2; - return value; - case EquipSlot.LFinger: - value = bit1 ? value | EqdpEntry.RingL1 : value & ~EqdpEntry.RingL1; - value = bit2 ? value | EqdpEntry.RingL2 : value & ~EqdpEntry.RingL2; - return value; - } - - return value; - } - - private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - //var id = list[ manipIdx ].EqdpIdentifier; - //var val = list[ manipIdx ].EqdpValue; - // - //if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - //{ - // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - // var (bit1, bit2) = GetEqdpBits( id.Slot, val ); - // var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults ); - // - // ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 ); - // ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 ); - // - // if( ret && _editMode ) - // { - // list[ manipIdx ] = MetaManipulation.Eqdp( id.Slot, id.GenderRace, id.SetId, SetEqdpBits( id.Slot, val, bit1, bit2 ) ); - // } - //} - // - //ImGui.Text( id.Slot.IsAccessory() - // ? ObjectType.Accessory.ToString() - // : ObjectType.Equipment.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.SetId.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.Slot.ToString() ); - //ImGui.TableNextColumn(); - //var (gender, race) = id.GenderRace.Split(); - //ImGui.Text( race.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( gender.ToString() ); - return ret; - } - - private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - //var id = list[ manipIdx ].EstIdentifier; - //var val = list[ manipIdx ].EstValue; - //if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - //{ - // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - // if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode ) - // { - // list[ manipIdx ] = new MetaManipulation( id.Value, val ); - // ret = true; - // } - //} - // - //ImGui.Text( id.ObjectType.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.PrimaryId.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.ObjectType == ObjectType.Equipment - // ? id.EquipSlot.ToString() - // : id.BodySlot.ToString() ); - //ImGui.TableNextColumn(); - //var (gender, race) = id.GenderRace.Split(); - //ImGui.Text( race.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( gender.ToString() ); - - return ret; - } - - private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - //var id = list[ manipIdx ].ImcIdentifier; - //var val = list[ manipIdx ].ImcValue; - // - //if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - //{ - // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - // ushort materialId = val.MaterialId; - // ushort vfxId = val.VfxId; - // ushort decalId = val.DecalId; - // var soundId = ( ushort )( val.SoundId >> 10 ); - // var attributeMask = val.AttributeMask; - // var materialAnimationId = ( ushort )( val.MaterialAnimationId >> 12 ); - // ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue ); - // ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue ); - // ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue ); - // ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F ); - // ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF ); - // ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId, - // byte.MaxValue ); - // - // if( ret && _editMode ) - // { - // var value = ImcExtensions.FromValues( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId, - // ( byte )vfxId, ( byte )materialAnimationId ); - // list[ manipIdx ] = new MetaManipulation( id.Value, value.ToInteger() ); - // } - //} - // - //ImGui.Text( id.ObjectType.ToString() ); - //ImGui.TableNextColumn(); - //ImGui.Text( id.PrimaryId.ToString() ); - //ImGui.TableNextColumn(); - //if( id.ObjectType == ObjectType.Accessory - // || id.ObjectType == ObjectType.Equipment ) - //{ - // ImGui.Text( id.ObjectType == ObjectType.Equipment - // || id.ObjectType == ObjectType.Accessory - // ? id.EquipSlot.ToString() - // : id.BodySlot.ToString() ); - //} - // - //ImGui.TableNextColumn(); - //ImGui.TableNextColumn(); - //ImGui.TableNextColumn(); - //if( id.ObjectType != ObjectType.Equipment - // && id.ObjectType != ObjectType.Accessory ) - //{ - // ImGui.Text( id.SecondaryId.ToString() ); - //} - // - //ImGui.TableNextColumn(); - //ImGui.Text( id.Variant.ToString() ); - return ret; - } - - private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].RspIdentifier; - var val = list[ manipIdx ].RspValue; - - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - if( DefaultButton( - $"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults ) - && _editMode ) + case MetaManipulation.Type.Eqp: + changes = DrawEqpRow( manipIdx, list ); + ImGui.TableSetColumnIndex( 9 ); + if( ImGui.Selectable( $"{list[ manipIdx ].Eqp.Entry}##{manipIdx}" ) ) { - list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, defaults ); - ret = true; + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); } - - ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); - if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f", - _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) - && val >= 0 - && val <= 5 - && _editMode ) + + break; + case MetaManipulation.Type.Gmp: + changes = DrawGmpRow( manipIdx, list ); + ImGui.TableSetColumnIndex( 9 ); + if( ImGui.Selectable( $"{list[ manipIdx ].Gmp.Entry.Value}##{manipIdx}" ) ) { - list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, val ); - ret = true; + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); } - } - - ImGui.Text( id.Attribute.ToUngenderedString() ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.Text( id.SubRace.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Attribute.ToGender().ToString() ); - return ret; - } - private bool DrawManipulationRow( ref int manipIdx, IList< MetaManipulation > list, ref int count ) - { - var type = list[ manipIdx ].ManipulationType; - - if( _editMode ) - { - ImGui.TableNextColumn(); - using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##manipDelete{manipIdx}" ) ) + break; + case MetaManipulation.Type.Eqdp: + changes = DrawEqdpRow( manipIdx, list ); + ImGui.TableSetColumnIndex( 9 ); + var (bit1, bit2) = GetEqdpBits( list[ manipIdx ].Eqdp.Slot, list[ manipIdx ].Eqdp.Entry ); + if( ImGui.Selectable( $"{bit1} {bit2}##{manipIdx}" ) ) { - list.RemoveAt( manipIdx ); - ImGui.TableNextRow(); - --manipIdx; - --count; - return true; + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); } - } - ImGui.TableNextColumn(); - ImGui.Text( type.ToString() ); - ImGui.TableNextColumn(); + break; + case MetaManipulation.Type.Est: + changes = DrawEstRow( manipIdx, list ); + ImGui.TableSetColumnIndex( 9 ); + if( ImGui.Selectable( $"{list[ manipIdx ].Est.SkeletonIdx}##{manipIdx}" ) ) + { + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); + } - var changes = false; - switch( type ) - { - case MetaManipulation.Type.Eqp: - changes = DrawEqpRow( manipIdx, list ); - break; - case MetaManipulation.Type.Gmp: - changes = DrawGmpRow( manipIdx, list ); - break; - case MetaManipulation.Type.Eqdp: - changes = DrawEqdpRow( manipIdx, list ); - break; - case MetaManipulation.Type.Est: - changes = DrawEstRow( manipIdx, list ); - break; - case MetaManipulation.Type.Imc: - changes = DrawImcRow( manipIdx, list ); - break; - case MetaManipulation.Type.Rsp: - changes = DrawRspRow( manipIdx, list ); - break; - } - - ImGui.TableSetColumnIndex( 9 ); - if( ImGui.Selectable( $"{manipIdx}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - ImGui.TableNextRow(); - return changes; + break; + case MetaManipulation.Type.Imc: + changes = DrawImcRow( manipIdx, list ); + ImGui.TableSetColumnIndex( 9 ); + if( ImGui.Selectable( $"{list[ manipIdx ].Imc.Entry.MaterialId}##{manipIdx}" ) ) + { + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); + } + + break; + case MetaManipulation.Type.Rsp: + changes = DrawRspRow( manipIdx, list ); + ImGui.TableSetColumnIndex( 9 ); + if( ImGui.Selectable( $"{list[ manipIdx ].Rsp.Entry}##{manipIdx}" ) ) + { + ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); + } + + break; } - private MetaManipulation.Type DrawNewTypeSelection() + ImGui.TableNextRow(); + return changes; + } + + + private MetaManipulation.Type DrawNewTypeSelection() + { + ImGui.RadioButton( "IMC##newManipType", ref _newManipTypeIdx, 1 ); + ImGui.SameLine(); + ImGui.RadioButton( "EQDP##newManipType", ref _newManipTypeIdx, 2 ); + ImGui.SameLine(); + ImGui.RadioButton( "EQP##newManipType", ref _newManipTypeIdx, 3 ); + ImGui.SameLine(); + ImGui.RadioButton( "EST##newManipType", ref _newManipTypeIdx, 4 ); + ImGui.SameLine(); + ImGui.RadioButton( "GMP##newManipType", ref _newManipTypeIdx, 5 ); + ImGui.SameLine(); + ImGui.RadioButton( "RSP##newManipType", ref _newManipTypeIdx, 6 ); + return ( MetaManipulation.Type )_newManipTypeIdx; + } + + private bool DrawNewManipulationPopup( string popupName, IList< MetaManipulation > list, ref int count ) + { + var change = false; + if( !ImGui.BeginPopup( popupName ) ) { - ImGui.RadioButton( "IMC##newManipType", ref _newManipTypeIdx, 1 ); - ImGui.SameLine(); - ImGui.RadioButton( "EQDP##newManipType", ref _newManipTypeIdx, 2 ); - ImGui.SameLine(); - ImGui.RadioButton( "EQP##newManipType", ref _newManipTypeIdx, 3 ); - ImGui.SameLine(); - ImGui.RadioButton( "EST##newManipType", ref _newManipTypeIdx, 4 ); - ImGui.SameLine(); - ImGui.RadioButton( "GMP##newManipType", ref _newManipTypeIdx, 5 ); - ImGui.SameLine(); - ImGui.RadioButton( "RSP##newManipType", ref _newManipTypeIdx, 6 ); - return ( MetaManipulation.Type )_newManipTypeIdx; - } - - private bool DrawNewManipulationPopup( string popupName, IList< MetaManipulation > list, ref int count ) - { - var change = false; - if( !ImGui.BeginPopup( popupName ) ) - { - return change; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var manipType = DrawNewTypeSelection(); - MetaManipulation? newManip = null; - switch( manipType ) - { - case MetaManipulation.Type.Imc: - { - RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue ); - RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue ); - CustomCombo( "Object Type", ImcObjectType, out var objectType, ref _newManipObjectType ); - EquipSlot equipSlot = default; - switch( objectType ) - { - case ObjectType.Equipment: - CustomCombo( "Equipment Slot", EqdpEquipSlots, out equipSlot, ref _newManipEquipSlot ); - //newManip = MetaManipulation.Imc( equipSlot, _newManipSetId, _newManipVariant, - // new ImcFile.ImageChangeData() ); - break; - case ObjectType.DemiHuman: - case ObjectType.Weapon: - case ObjectType.Monster: - RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue ); - CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot ); - //newManip = MetaManipulation.Imc( objectType, bodySlot, _newManipSetId, _newManipSecondaryId, - // _newManipVariant, new ImcFile.ImageChangeData() ); - break; - } - - break; - } - case MetaManipulation.Type.Eqdp: - { - RestrictedInputInt( "Set Id##newManipEqdp", ref _newManipSetId, 0, ushort.MaxValue ); - CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); - CustomCombo( "Race", Races, out var race, ref _newManipRace ); - CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); - //newManip = MetaManipulation.Eqdp( equipSlot, Names.CombinedRace( gender, race ), ( ushort )_newManipSetId, - // new EqdpEntry() ); - break; - } - case MetaManipulation.Type.Eqp: - { - RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue ); - CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); - //newManip = MetaManipulation.Eqp( equipSlot, ( ushort )_newManipSetId, 0 ); - break; - } - case MetaManipulation.Type.Est: - { - RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue ); - CustomCombo( "Object Type", ObjectTypes, out var objectType, ref _newManipObjectType ); - EquipSlot equipSlot = default; - BodySlot bodySlot = default; - switch( ( ObjectType )_newManipObjectType ) - { - case ObjectType.Equipment: - CustomCombo( "Equipment Slot", EstEquipSlots, out equipSlot, ref _newManipEquipSlot ); - break; - case ObjectType.Character: - CustomCombo( "Body Slot", EstBodySlots, out bodySlot, ref _newManipBodySlot ); - break; - } - - CustomCombo( "Race", Races, out var race, ref _newManipRace ); - CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); - //newManip = MetaManipulation.Est( objectType, equipSlot, Names.CombinedRace( gender, race ), bodySlot, - // ( ushort )_newManipSetId, 0 ); - break; - } - case MetaManipulation.Type.Gmp: - RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue ); - //newManip = MetaManipulation.Gmp( ( ushort )_newManipSetId, new GmpEntry() ); - break; - case MetaManipulation.Type.Rsp: - CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace ); - CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute ); - //newManip = MetaManipulation.Rsp( subRace, rspAttribute, 1f ); - break; - } - - //if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 ) - // && newManip != null - // && list.All( m => m.Identifier != newManip.Value.Identifier ) ) - //{ - // var def = Penumbra.MetaDefaults.GetDefaultValue( newManip.Value ); - // if( def != null ) - // { - // var manip = newManip.Value.Type switch - // { - // MetaType.Est => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), - // MetaType.Eqp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), - // MetaType.Eqdp => new MetaManipulation( newManip.Value.Identifier, (ushort) def ), - // MetaType.Gmp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), - // MetaType.Imc => new MetaManipulation( newManip.Value.Identifier, - // ( ( ImcFile.ImageChangeData )def ).ToInteger() ), - // MetaType.Rsp => MetaManipulation.Rsp( newManip.Value.RspIdentifier.SubRace, - // newManip.Value.RspIdentifier.Attribute, ( float )def ), - // _ => throw new InvalidEnumArgumentException(), - // }; - // list.Add( manip ); - // change = true; - // ++count; - // } - // - // ImGui.CloseCurrentPopup(); - //} - return change; } - private bool DrawMetaManipulationsTable( string label, IList< MetaManipulation > list, ref int count ) + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + var manipType = DrawNewTypeSelection(); + MetaManipulation? newManip = null; + switch( manipType ) { - var numRows = _editMode ? 11 : 10; - var changes = false; - - - if( list.Count > 0 - && ImGui.BeginTable( label, numRows, - ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) + case MetaManipulation.Type.Imc: { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - if( _editMode ) + RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue ); + RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue ); + CustomCombo( "Object Type", ImcObjectType, out var objectType, ref _newManipObjectType ); + EquipSlot equipSlot = default; + switch( objectType ) { - ImGui.TableNextColumn(); + case ObjectType.Equipment: + CustomCombo( "Equipment Slot", EqdpEquipSlots, out equipSlot, ref _newManipEquipSlot ); + //newManip = MetaManipulation.Imc( equipSlot, _newManipSetId, _newManipVariant, + // new ImcFile.ImageChangeData() ); + break; + case ObjectType.DemiHuman: + case ObjectType.Weapon: + case ObjectType.Monster: + RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue ); + CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot ); + //newManip = MetaManipulation.Imc( objectType, bodySlot, _newManipSetId, _newManipSecondaryId, + // _newManipVariant, new ImcFile.ImageChangeData() ); + break; } - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Type##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Object Type##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Set##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Slot##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Race##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Gender##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Secondary ID##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Variant##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Value##{label}" ); - ImGui.TableNextRow(); - - for( var i = 0; i < list.Count; ++i ) - { - changes |= DrawManipulationRow( ref i, list, ref count ); - } + break; } + case MetaManipulation.Type.Eqdp: + { + RestrictedInputInt( "Set Id##newManipEqdp", ref _newManipSetId, 0, ushort.MaxValue ); + CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); + CustomCombo( "Race", Races, out var race, ref _newManipRace ); + CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); + //newManip = MetaManipulation.Eqdp( equipSlot, Names.CombinedRace( gender, race ), ( ushort )_newManipSetId, + // new EqdpEntry() ); + break; + } + case MetaManipulation.Type.Eqp: + { + RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue ); + CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); + //newManip = MetaManipulation.Eqp( equipSlot, ( ushort )_newManipSetId, 0 ); + break; + } + case MetaManipulation.Type.Est: + { + RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue ); + CustomCombo( "Object Type", ObjectTypes, out var objectType, ref _newManipObjectType ); + EquipSlot equipSlot = default; + BodySlot bodySlot = default; + switch( ( ObjectType )_newManipObjectType ) + { + case ObjectType.Equipment: + CustomCombo( "Equipment Slot", EstEquipSlots, out equipSlot, ref _newManipEquipSlot ); + break; + case ObjectType.Character: + CustomCombo( "Body Slot", EstBodySlots, out bodySlot, ref _newManipBodySlot ); + break; + } - var popupName = $"##newManip{label}"; + CustomCombo( "Race", Races, out var race, ref _newManipRace ); + CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); + //newManip = MetaManipulation.Est( objectType, equipSlot, Names.CombinedRace( gender, race ), bodySlot, + // ( ushort )_newManipSetId, 0 ); + break; + } + case MetaManipulation.Type.Gmp: + RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue ); + //newManip = MetaManipulation.Gmp( ( ushort )_newManipSetId, new GmpEntry() ); + break; + case MetaManipulation.Type.Rsp: + CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace ); + CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute ); + //newManip = MetaManipulation.Rsp( subRace, rspAttribute, 1f ); + break; + } + + //if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 ) + // && newManip != null + // && list.All( m => m.Identifier != newManip.Value.Identifier ) ) + //{ + // var def = Penumbra.MetaDefaults.GetDefaultValue( newManip.Value ); + // if( def != null ) + // { + // var manip = newManip.Value.Type switch + // { + // MetaType.Est => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + // MetaType.Eqp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + // MetaType.Eqdp => new MetaManipulation( newManip.Value.Identifier, (ushort) def ), + // MetaType.Gmp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ), + // MetaType.Imc => new MetaManipulation( newManip.Value.Identifier, + // ( ( ImcFile.ImageChangeData )def ).ToInteger() ), + // MetaType.Rsp => MetaManipulation.Rsp( newManip.Value.RspIdentifier.SubRace, + // newManip.Value.RspIdentifier.Attribute, ( float )def ), + // _ => throw new InvalidEnumArgumentException(), + // }; + // list.Add( manip ); + // change = true; + // ++count; + // } + // + // ImGui.CloseCurrentPopup(); + //} + + return change; + } + + private bool DrawMetaManipulationsTable( string label, IList< MetaManipulation > list, ref int count ) + { + var numRows = _editMode ? 11 : 10; + var changes = false; + + + if( list.Count > 0 + && ImGui.BeginTable( label, numRows, + ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) + { + using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); if( _editMode ) { - changes |= DrawNewManipulationPopup( $"##newManip{label}", list, ref count ); - if( ImGui.Button( $"Add New Manipulation##{label}", Vector2.UnitX * -1 ) ) - { - ImGui.OpenPopup( popupName ); - } - - return changes; + ImGui.TableNextColumn(); } - return false; + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Type##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Object Type##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Set##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Slot##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Race##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Gender##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Secondary ID##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Variant##{label}" ); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableHeader( $"Value##{label}" ); + ImGui.TableNextRow(); + + for( var i = 0; i < list.Count; ++i ) + { + changes |= DrawManipulationRow( ref i, list, ref count ); + } } + + var popupName = $"##newManip{label}"; + if( _editMode ) + { + changes |= DrawNewManipulationPopup( $"##newManip{label}", list, ref count ); + if( ImGui.Button( $"Add New Manipulation##{label}", Vector2.UnitX * -1 ) ) + { + ImGui.OpenPopup( popupName ); + } + + return changes; + } + + return false; } } } \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabResourceManager.cs b/Penumbra/UI/MenuTabs/TabResourceManager.cs index f46ad018..fa187a94 100644 --- a/Penumbra/UI/MenuTabs/TabResourceManager.cs +++ b/Penumbra/UI/MenuTabs/TabResourceManager.cs @@ -1,11 +1,13 @@ using System; using System.Linq; using System.Numerics; +using System.Reflection.Metadata.Ecma335; using Dalamud.Interface; using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.STD; using ImGuiNET; +using Penumbra.GameData.ByteString; using Penumbra.Interop; using Penumbra.UI.Custom; @@ -55,6 +57,11 @@ public partial class SettingsInterface ResourceLoader.IterateResourceMap( map, ( hash, r ) => { + if( _filter.Length != 0 && !r->FileName.ToString().Contains( _filter, StringComparison.InvariantCultureIgnoreCase ) ) + { + return; + } + ImGui.TableNextColumn(); ImGui.Text( $"0x{hash:X8}" ); ImGui.TableNextColumn(); @@ -148,6 +155,8 @@ public partial class SettingsInterface } ); } + private string _filter = string.Empty; + private unsafe void DrawResourceManagerTab() { if( !ImGui.BeginTabItem( "Resource Manager Tab" ) ) @@ -164,6 +173,8 @@ public partial class SettingsInterface return; } + ImGui.InputTextWithHint( "##resourceFilter", "Filter...", ref _filter, Utf8GamePath.MaxGamePathLength ); + raii.Push( ImGui.EndChild ); if( !ImGui.BeginChild( "##ResourceManagerChild", -Vector2.One, true ) ) {