diff --git a/Penumbra.GameData/Enums/EquipSlot.cs b/Penumbra.GameData/Enums/EquipSlot.cs index a0bab5ea..a68cacb9 100644 --- a/Penumbra.GameData/Enums/EquipSlot.cs +++ b/Penumbra.GameData/Enums/EquipSlot.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reflection.Metadata.Ecma335; -using Newtonsoft.Json; namespace Penumbra.GameData.Enums; @@ -31,7 +29,9 @@ public enum EquipSlot : byte FullBody = 19, BodyHands = 20, BodyLegsFeet = 21, - All = 22, // Not officially existing + ChestHands = 22, + Nothing = 23, + All = 24, // Not officially existing } public static class EquipSlotExtensions @@ -111,7 +111,8 @@ public static class EquipSlotExtensions EquipSlot.FullBody => EquipSlot.Body, EquipSlot.BodyHands => EquipSlot.Body, EquipSlot.BodyLegsFeet => EquipSlot.Body, - _ => throw new InvalidEnumArgumentException(), + EquipSlot.ChestHands => EquipSlot.Body, + _ => throw new InvalidEnumArgumentException($"{value} ({(int) value}) is not valid."), }; } diff --git a/Penumbra/Import/Dds/TextureImporter.cs b/Penumbra/Import/Dds/TextureImporter.cs index 1c71f526..81162465 100644 --- a/Penumbra/Import/Dds/TextureImporter.cs +++ b/Penumbra/Import/Dds/TextureImporter.cs @@ -1,14 +1,12 @@ using System; using System.IO; using Lumina.Data.Files; -using OtterGui; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using Functions = Penumbra.GameData.Util.Functions; namespace Penumbra.Import.Dds; -public class TextureImporter +public static class TextureImporter { private static void WriteHeader( byte[] target, int width, int height ) { @@ -91,7 +89,4 @@ public class TextureImporter texData = buffer; return true; } - - public void Import( string inputFile ) - { } } \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index 0f6db067..1175eb52 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -7,6 +7,8 @@ namespace Penumbra.Interop; public unsafe class CharacterUtility : IDisposable { + public record struct InternalIndex( int Value ); + // A static pointer to the CharacterUtility address. [Signature( "48 8B 05 ?? ?? ?? ?? 83 B9", ScanType = ScanType.StaticAddress )] private readonly Structs.CharacterUtility** _characterUtilityAddress = null; @@ -28,25 +30,22 @@ public unsafe class CharacterUtility : IDisposable // 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 >() - .Append( Structs.CharacterUtility.EqpIdx ) - .Append( Structs.CharacterUtility.GmpIdx ) - .Concat( Enumerable.Range( Structs.CharacterUtility.EqdpStartIdx, Structs.CharacterUtility.NumEqdpFiles ) - .Where( i => i is not Structs.CharacterUtility.EqdpStartIdx + 15 or Structs.CharacterUtility.EqdpStartIdx + 15 + Structs.CharacterUtility.NumEqdpFiles / 2 ) ) // TODO: Female Hrothgar - .Append( Structs.CharacterUtility.HumanCmpIdx ) - .Concat( Enumerable.Range( Structs.CharacterUtility.FaceEstIdx, 4 ) ) + public static readonly Structs.CharacterUtility.Index[] + RelevantIndices = Enum.GetValues< Structs.CharacterUtility.Index >(); + + public static readonly InternalIndex[] ReverseIndices + = Enumerable.Range( 0, Structs.CharacterUtility.TotalNumResources ) + .Select( i => new InternalIndex( Array.IndexOf( RelevantIndices, (Structs.CharacterUtility.Index) i ) ) ) .ToArray(); - public static readonly int[] ReverseIndices - = Enumerable.Range( 0, Structs.CharacterUtility.NumResources ) - .Select( i => Array.IndexOf( RelevantIndices, i ) ).ToArray(); + private readonly (IntPtr Address, int Size)[] _defaultResources = new (IntPtr, int)[RelevantIndices.Length]; - public readonly (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[RelevantIndices.Length]; + public (IntPtr Address, int Size) DefaultResource( Structs.CharacterUtility.Index idx ) + => _defaultResources[ ReverseIndices[ ( int )idx ].Value ]; - public (IntPtr Address, int Size) DefaultResource( int fullIdx ) - => DefaultResources[ ReverseIndices[ fullIdx ] ]; + public (IntPtr Address, int Size) DefaultResource( InternalIndex idx ) + => _defaultResources[ idx.Value ]; public CharacterUtility() { @@ -70,13 +69,13 @@ public unsafe class CharacterUtility : IDisposable for( var i = 0; i < RelevantIndices.Length; ++i ) { - if( DefaultResources[ i ].Size == 0 ) + if( _defaultResources[ i ].Size == 0 ) { - var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ]; + var resource = Address->Resource( RelevantIndices[i] ); var data = resource->GetData(); if( data.Data != IntPtr.Zero && data.Length != 0 ) { - DefaultResources[ i ] = data; + _defaultResources[ i ] = data; } else { @@ -94,7 +93,7 @@ public unsafe class CharacterUtility : IDisposable } // Set the data of one of the stored resources to a given pointer and length. - public bool SetResource( int resourceIdx, IntPtr data, int length ) + public bool SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length ) { if( !Ready ) { @@ -109,7 +108,7 @@ public unsafe class CharacterUtility : IDisposable } // Reset the data of one of the stored resources to its default values. - public void ResetResource( int resourceIdx ) + public void ResetResource( Structs.CharacterUtility.Index resourceIdx ) { if( !Ready ) { @@ -117,8 +116,7 @@ public unsafe class CharacterUtility : IDisposable return; } - var relevantIdx = ReverseIndices[ resourceIdx ]; - var (data, length) = DefaultResources[ relevantIdx ]; + var (data, length) = DefaultResource( resourceIdx); var resource = Address->Resource( resourceIdx ); PluginLog.Verbose( "Reset resource {Idx} to default at 0x{DefaultData:X} ({NewLength} bytes).", resourceIdx, ( ulong )data, length ); resource->SetData( data, length ); diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index f041fbd7..44fe74f2 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -160,11 +160,11 @@ public unsafe partial class PathResolver { if( _inChangeCustomize ) { - using var rsp = MetaChanger.ChangeCmp( _parent, drawObject ); _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); } else { + using var rsp = MetaChanger.ChangeCmp( _parent, drawObject ); _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); } } diff --git a/Penumbra/Interop/Structs/CharacterUtility.cs b/Penumbra/Interop/Structs/CharacterUtility.cs index 02426db7..40e346c1 100644 --- a/Penumbra/Interop/Structs/CharacterUtility.cs +++ b/Penumbra/Interop/Structs/CharacterUtility.cs @@ -8,87 +8,153 @@ namespace Penumbra.Interop.Structs; [StructLayout( LayoutKind.Explicit )] public unsafe struct CharacterUtility { - // TODO: female Hrothgar - public static readonly int[] EqdpIndices - = Enumerable.Range( EqdpStartIdx, NumEqdpFiles ).Where( i => i != EqdpStartIdx + 15 && i != EqdpStartIdx + 15 + NumEqdpFiles / 2 ).ToArray(); + public enum Index : int + { + Eqp = 0, + Gmp = 2, - public const int NumResources = 86; - public const int EqpIdx = 0; - public const int GmpIdx = 2; - public const int HumanCmpIdx = 64; - public const int FaceEstIdx = 65; - public const int HairEstIdx = 66; - public const int HeadEstIdx = 67; - public const int BodyEstIdx = 68; - public const int EqdpStartIdx = 3; - public const int NumEqdpFiles = 2 * 28; + Eqdp0101 = 3, + Eqdp0201, + Eqdp0301, + Eqdp0401, + Eqdp0501, + Eqdp0601, + Eqdp0701, + Eqdp0801, + Eqdp0901, + Eqdp1001, + Eqdp1101, + Eqdp1201, + Eqdp1301, + Eqdp1401, + Eqdp1501, - public static int EqdpIdx( GenderRace raceCode, bool accessory ) - => ( accessory ? NumEqdpFiles / 2 : 0 ) - + ( int )raceCode switch - { - 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, // TODO: female Hrothgar - 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, - _ => -1, - }; + //Eqdp1601, // TODO: female Hrothgar + Eqdp1701 = Eqdp1501 + 2, + Eqdp1801, + Eqdp0104, + Eqdp0204, + Eqdp0504, + Eqdp0604, + Eqdp0704, + Eqdp0804, + Eqdp1304, + Eqdp1404, + Eqdp9104, + Eqdp9204, + + Eqdp0101Acc, + Eqdp0201Acc, + Eqdp0301Acc, + Eqdp0401Acc, + Eqdp0501Acc, + Eqdp0601Acc, + Eqdp0701Acc, + Eqdp0801Acc, + Eqdp0901Acc, + Eqdp1001Acc, + Eqdp1101Acc, + Eqdp1201Acc, + Eqdp1301Acc, + Eqdp1401Acc, + Eqdp1501Acc, + + //Eqdp1601Acc, // TODO: female Hrothgar + Eqdp1701Acc = Eqdp1501Acc + 2, + Eqdp1801Acc, + Eqdp0104Acc, + Eqdp0204Acc, + Eqdp0504Acc, + Eqdp0604Acc, + Eqdp0704Acc, + Eqdp0804Acc, + Eqdp1304Acc, + Eqdp1404Acc, + Eqdp9104Acc, + Eqdp9204Acc, + + HumanCmp = 64, + FaceEst, + HairEst, + HeadEst, + BodyEst, + } + + public static readonly Index[] EqdpIndices = Enum.GetNames< Index >() + .Zip( Enum.GetValues< Index >() ) + .Where( n => n.First.StartsWith( "Eqdp" ) ) + .Select( n => n.Second ).ToArray(); + + public const int TotalNumResources = 87; + + public static Index EqdpIdx( GenderRace raceCode, bool accessory ) + => +( int )raceCode switch + { + 0101 => accessory ? Index.Eqdp0101Acc : Index.Eqdp0101, + 0201 => accessory ? Index.Eqdp0201Acc : Index.Eqdp0201, + 0301 => accessory ? Index.Eqdp0301Acc : Index.Eqdp0301, + 0401 => accessory ? Index.Eqdp0401Acc : Index.Eqdp0401, + 0501 => accessory ? Index.Eqdp0501Acc : Index.Eqdp0501, + 0601 => accessory ? Index.Eqdp0601Acc : Index.Eqdp0601, + 0701 => accessory ? Index.Eqdp0701Acc : Index.Eqdp0701, + 0801 => accessory ? Index.Eqdp0801Acc : Index.Eqdp0801, + 0901 => accessory ? Index.Eqdp0901Acc : Index.Eqdp0901, + 1001 => accessory ? Index.Eqdp1001Acc : Index.Eqdp1001, + 1101 => accessory ? Index.Eqdp1101Acc : Index.Eqdp1101, + 1201 => accessory ? Index.Eqdp1201Acc : Index.Eqdp1201, + 1301 => accessory ? Index.Eqdp1301Acc : Index.Eqdp1301, + 1401 => accessory ? Index.Eqdp1401Acc : Index.Eqdp1401, + 1501 => accessory ? Index.Eqdp1501Acc : Index.Eqdp1501, + //1601 => accessory ? RelevantIndex.Eqdp1601Acc : RelevantIndex.Eqdp1601, Female Hrothgar + 1701 => accessory ? Index.Eqdp1701Acc : Index.Eqdp1701, + 1801 => accessory ? Index.Eqdp1801Acc : Index.Eqdp1801, + 0104 => accessory ? Index.Eqdp0104Acc : Index.Eqdp0104, + 0204 => accessory ? Index.Eqdp0204Acc : Index.Eqdp0204, + 0504 => accessory ? Index.Eqdp0504Acc : Index.Eqdp0504, + 0604 => accessory ? Index.Eqdp0604Acc : Index.Eqdp0604, + 0704 => accessory ? Index.Eqdp0704Acc : Index.Eqdp0704, + 0804 => accessory ? Index.Eqdp0804Acc : Index.Eqdp0804, + 1304 => accessory ? Index.Eqdp1304Acc : Index.Eqdp1304, + 1404 => accessory ? Index.Eqdp1404Acc : Index.Eqdp1404, + 9104 => accessory ? Index.Eqdp9104Acc : Index.Eqdp9104, + 9204 => accessory ? Index.Eqdp9204Acc : Index.Eqdp9204, + _ => ( Index )( -1 ), + }; [FieldOffset( 0 )] public void* VTable; [FieldOffset( 8 )] - public fixed ulong Resources[NumResources]; + public fixed ulong Resources[TotalNumResources]; - [FieldOffset( 8 + EqpIdx * 8 )] + [FieldOffset( 8 + ( int )Index.Eqp * 8 )] public ResourceHandle* EqpResource; - [FieldOffset( 8 + GmpIdx * 8 )] + [FieldOffset( 8 + ( int )Index.Gmp * 8 )] public ResourceHandle* GmpResource; public ResourceHandle* Resource( int idx ) => ( ResourceHandle* )Resources[ idx ]; - public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory ) - => Resource( EqdpIdx( raceCode, accessory ) ); + public ResourceHandle* Resource( Index idx ) + => Resource( ( int )idx ); - [FieldOffset( 8 + HumanCmpIdx * 8 )] + public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory ) + => Resource( ( int )EqdpIdx( raceCode, accessory ) ); + + [FieldOffset( 8 + ( int )Index.HumanCmp * 8 )] public ResourceHandle* HumanCmpResource; - [FieldOffset( 8 + FaceEstIdx * 8 )] + [FieldOffset( 8 + ( int )Index.FaceEst * 8 )] public ResourceHandle* FaceEstResource; - [FieldOffset( 8 + HairEstIdx * 8 )] + [FieldOffset( 8 + ( int )Index.HairEst * 8 )] public ResourceHandle* HairEstResource; - [FieldOffset( 8 + BodyEstIdx * 8 )] + [FieldOffset( 8 + ( int )Index.BodyEst * 8 )] public ResourceHandle* BodyEstResource; - [FieldOffset( 8 + HeadEstIdx * 8 )] + [FieldOffset( 8 + ( int )Index.HeadEst * 8 )] public ResourceHandle* HeadEstResource; // not included resources have no known use case. diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index 356d826d..0f308bd6 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -11,6 +11,9 @@ namespace Penumbra.Meta.Files; // We only support manipulating the racial scaling parameters at the moment. public sealed unsafe class CmpFile : MetaBaseFile { + public static readonly Interop.CharacterUtility.InternalIndex InternalIndex = + Interop.CharacterUtility.ReverseIndices[ ( int )CharacterUtility.Index.HumanCmp ]; + private const int RacialScalingStart = 0x2A800; public float this[ SubRace subRace, RspAttribute attribute ] @@ -31,7 +34,7 @@ public sealed unsafe class CmpFile : MetaBaseFile } public CmpFile() - : base( CharacterUtility.HumanCmpIdx ) + : base( CharacterUtility.Index.HumanCmp ) { AllocateData( DefaultData.Length ); Reset(); @@ -39,7 +42,7 @@ public sealed unsafe class CmpFile : MetaBaseFile public static float GetDefault( SubRace subRace, RspAttribute attribute ) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( CharacterUtility.HumanCmpIdx ).Address; + var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( InternalIndex ).Address; return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); } diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs index b2504bbf..d9a22c41 100644 --- a/Penumbra/Meta/Files/EqdpFile.cs +++ b/Penumbra/Meta/Files/EqdpFile.cs @@ -114,8 +114,8 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile public EqdpEntry GetDefault( int setIdx ) => GetDefault( Index, setIdx ); - public static EqdpEntry GetDefault( int fileIdx, int setIdx ) - => GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( fileIdx ).Address, setIdx ); + public static EqdpEntry GetDefault( Interop.CharacterUtility.InternalIndex idx, int setIdx ) + => GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( idx ).Address, setIdx ); public static EqdpEntry GetDefault( byte* data, int setIdx ) { @@ -139,5 +139,5 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile } public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx ) - => GetDefault( CharacterUtility.EqdpIdx( raceCode, accessory ), setIdx ); + => GetDefault( Interop.CharacterUtility.ReverseIndices[ ( int )CharacterUtility.EqdpIdx( raceCode, accessory ) ], setIdx ); } \ No newline at end of file diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index 0a7fe90f..e6f48a3d 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -76,15 +76,15 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile } public ExpandedEqpGmpBase( bool gmp ) - : base( gmp ? CharacterUtility.GmpIdx : CharacterUtility.EqpIdx ) + : base( gmp ? CharacterUtility.Index.Gmp : CharacterUtility.Index.Eqp ) { AllocateData( MaxSize ); Reset(); } - protected static ulong GetDefaultInternal( int fileIdx, int setIdx, ulong def ) + protected static ulong GetDefaultInternal( Interop.CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def ) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address; + var data = ( byte* )Penumbra.CharacterUtility.DefaultResource(fileIndex).Address; if( setIdx == 0 ) { setIdx = 1; @@ -112,6 +112,9 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable { + public static readonly Interop.CharacterUtility.InternalIndex InternalIndex = + Interop.CharacterUtility.ReverseIndices[ (int) CharacterUtility.Index.Eqp ]; + public ExpandedEqpFile() : base( false ) { } @@ -124,7 +127,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable public static EqpEntry GetDefault( int setIdx ) - => ( EqpEntry )GetDefaultInternal( CharacterUtility.EqpIdx, setIdx, ( ulong )Eqp.DefaultEntry ); + => ( EqpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )Eqp.DefaultEntry ); protected override unsafe void SetEmptyBlock( int idx ) { @@ -156,6 +159,9 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable { + public static readonly Interop.CharacterUtility.InternalIndex InternalIndex = + Interop.CharacterUtility.ReverseIndices[( int )CharacterUtility.Index.Gmp]; + public ExpandedGmpFile() : base( true ) { } @@ -167,7 +173,7 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable } public static GmpEntry GetDefault( int setIdx ) - => ( GmpEntry )GetDefaultInternal( CharacterUtility.GmpIdx, setIdx, ( ulong )GmpEntry.Default ); + => ( GmpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )GmpEntry.Default ); public void Reset( IEnumerable< int > entries ) { diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs index 822286d0..7c6b3591 100644 --- a/Penumbra/Meta/Files/EstFile.cs +++ b/Penumbra/Meta/Files/EstFile.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.InteropServices; using Penumbra.GameData.Enums; using Penumbra.GameData.Util; +using Penumbra.Interop.Structs; using Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Files; @@ -53,7 +54,7 @@ public sealed unsafe class EstFile : MetaBaseFile ResizeResources( Length + IncreaseSize ); } - var control = ( Info* )( Data + 4 ); + var control = ( Info* )( Data + 4 ); var entries = ( ushort* )( control + Count ); for( var i = Count - 1; i >= idx; --i ) @@ -95,7 +96,7 @@ public sealed unsafe class EstFile : MetaBaseFile for( var i = idx; i < Count - 1; ++i ) { - entries[i - 2] = entries[i + 1]; + entries[ i - 2 ] = entries[ i + 1 ]; } entries[ Count - 3 ] = 0; @@ -174,7 +175,7 @@ public sealed unsafe class EstFile : MetaBaseFile } public EstFile( EstManipulation.EstType estType ) - : base( ( int )estType ) + : base( ( CharacterUtility.Index )estType ) { var length = DefaultData.Length; AllocateData( length + IncreaseSize ); @@ -182,11 +183,11 @@ public sealed unsafe class EstFile : MetaBaseFile } public ushort GetDefault( GenderRace genderRace, ushort setId ) - => GetDefault( ( EstManipulation.EstType )Index, genderRace, setId ); + => GetDefault( Index, genderRace, setId ); - public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId ) + public static ushort GetDefault( Interop.CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId ) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( ( int )estType ).Address; + var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( index ).Address; var count = *( int* )data; var span = new ReadOnlySpan< Info >( data + 4, count ); var (idx, found) = FindEntry( span, genderRace, setId ); @@ -197,4 +198,10 @@ public sealed unsafe class EstFile : MetaBaseFile return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize ); } + + public static ushort GetDefault( CharacterUtility.Index index, GenderRace genderRace, ushort setId ) + => GetDefault( Interop.CharacterUtility.ReverseIndices[ ( int )index ], genderRace, setId ); + + public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId ) + => GetDefault( ( CharacterUtility.Index )estType, genderRace, setId ); } \ No newline at end of file diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index 303499a5..8bc827f5 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -1,6 +1,7 @@ using System; using Dalamud.Memory; using Penumbra.GameData.Util; +using CharacterUtility = Penumbra.Interop.CharacterUtility; namespace Penumbra.Meta.Files; @@ -8,10 +9,10 @@ public unsafe class MetaBaseFile : IDisposable { public byte* Data { get; private set; } public int Length { get; private set; } - public int Index { get; } + public CharacterUtility.InternalIndex Index { get; } - public MetaBaseFile( int idx ) - => Index = idx; + public MetaBaseFile( Interop.Structs.CharacterUtility.Index idx ) + => Index = CharacterUtility.ReverseIndices[(int) idx]; protected (IntPtr Data, int Length) DefaultData => Penumbra.CharacterUtility.DefaultResource( Index ); diff --git a/Penumbra/Meta/Manager/MetaManager.Cmp.cs b/Penumbra/Meta/Manager/MetaManager.Cmp.cs index 2a916c5b..b624a360 100644 --- a/Penumbra/Meta/Manager/MetaManager.Cmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Cmp.cs @@ -13,10 +13,10 @@ public partial class MetaManager private readonly List< RspManipulation > _cmpManipulations = new(); public void SetCmpFiles() - => SetFile( _cmpFile, CharacterUtility.HumanCmpIdx ); + => SetFile( _cmpFile, CharacterUtility.Index.HumanCmp ); public static void ResetCmpFiles() - => SetFile( null, CharacterUtility.HumanCmpIdx ); + => SetFile( null, CharacterUtility.Index.HumanCmp ); public void ResetCmp() { diff --git a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs index e89bd67e..dc9a31d6 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs @@ -11,7 +11,7 @@ namespace Penumbra.Meta.Manager; public partial class MetaManager { - private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles - 2]; // TODO: female Hrothgar + private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtility.EqdpIndices.Length]; // TODO: female Hrothgar private readonly List< EqdpManipulation > _eqdpManipulations = new(); @@ -33,9 +33,10 @@ public partial class MetaManager public void ResetEqdp() { - foreach( var file in _eqdpFiles ) + foreach( var file in _eqdpFiles.OfType() ) { - file?.Reset( _eqdpManipulations.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) ); + var relevant = Interop.CharacterUtility.RelevantIndices[ file.Index.Value ]; + file.Reset( _eqdpManipulations.Where( m => m.FileIndex() == relevant ).Select( m => ( int )m.SetId ) ); } _eqdpManipulations.Clear(); diff --git a/Penumbra/Meta/Manager/MetaManager.Eqp.cs b/Penumbra/Meta/Manager/MetaManager.Eqp.cs index 4c75615f..adae4378 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqp.cs @@ -13,10 +13,10 @@ public partial class MetaManager private readonly List< EqpManipulation > _eqpManipulations = new(); public void SetEqpFiles() - => SetFile( _eqpFile, CharacterUtility.EqpIdx ); + => SetFile( _eqpFile, CharacterUtility.Index.Eqp ); public static void ResetEqpFiles() - => SetFile( null, CharacterUtility.EqpIdx ); + => SetFile( null, CharacterUtility.Index.Eqp ); public void ResetEqp() { diff --git a/Penumbra/Meta/Manager/MetaManager.Est.cs b/Penumbra/Meta/Manager/MetaManager.Est.cs index 702b53b6..728a024d 100644 --- a/Penumbra/Meta/Manager/MetaManager.Est.cs +++ b/Penumbra/Meta/Manager/MetaManager.Est.cs @@ -19,18 +19,18 @@ public partial class MetaManager public void SetEstFiles() { - SetFile( _estFaceFile, CharacterUtility.FaceEstIdx ); - SetFile( _estHairFile, CharacterUtility.HairEstIdx ); - SetFile( _estBodyFile, CharacterUtility.BodyEstIdx ); - SetFile( _estHeadFile, CharacterUtility.HeadEstIdx ); + SetFile( _estFaceFile, CharacterUtility.Index.FaceEst ); + SetFile( _estHairFile, CharacterUtility.Index.HairEst ); + SetFile( _estBodyFile, CharacterUtility.Index.BodyEst ); + SetFile( _estHeadFile, CharacterUtility.Index.HeadEst ); } public static void ResetEstFiles() { - SetFile( null, CharacterUtility.FaceEstIdx ); - SetFile( null, CharacterUtility.HairEstIdx ); - SetFile( null, CharacterUtility.BodyEstIdx ); - SetFile( null, CharacterUtility.HeadEstIdx ); + SetFile( null, CharacterUtility.Index.FaceEst ); + SetFile( null, CharacterUtility.Index.HairEst ); + SetFile( null, CharacterUtility.Index.BodyEst ); + SetFile( null, CharacterUtility.Index.HeadEst ); } public void ResetEst() diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Meta/Manager/MetaManager.Gmp.cs index 1aaba52f..df35cace 100644 --- a/Penumbra/Meta/Manager/MetaManager.Gmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Gmp.cs @@ -16,10 +16,10 @@ public partial class MetaManager private readonly List< GmpManipulation > _gmpManipulations = new(); public void SetGmpFiles() - => SetFile( _gmpFile, CharacterUtility.GmpIdx ); + => SetFile( _gmpFile, CharacterUtility.Index.Gmp ); public static void ResetGmpFiles() - => SetFile( null, CharacterUtility.GmpIdx ); + => SetFile( null, CharacterUtility.Index.Gmp ); public void ResetGmp() { diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index 7f83e98f..aa46e7f7 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Runtime.CompilerServices; using Dalamud.Logging; using Penumbra.Collections; +using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Mods; @@ -168,7 +169,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM } [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] - private static unsafe void SetFile( MetaBaseFile? file, int index ) + private static unsafe void SetFile( MetaBaseFile? file, CharacterUtility.Index index ) { if( file == null ) { diff --git a/Penumbra/Meta/Manipulations/EqdpManipulation.cs b/Penumbra/Meta/Manipulations/EqdpManipulation.cs index 287a5367..bd800a64 100644 --- a/Penumbra/Meta/Manipulations/EqdpManipulation.cs +++ b/Penumbra/Meta/Manipulations/EqdpManipulation.cs @@ -67,7 +67,7 @@ public readonly struct EqdpManipulation : IMetaManipulation< EqdpManipulation > return set != 0 ? set : Slot.CompareTo( other.Slot ); } - public int FileIndex() + public CharacterUtility.Index FileIndex() => CharacterUtility.EqdpIdx( Names.CombinedRace( Gender, Race ), Slot.IsAccessory() ); public bool Apply( ExpandedEqdpFile file ) diff --git a/Penumbra/Meta/Manipulations/EqpManipulation.cs b/Penumbra/Meta/Manipulations/EqpManipulation.cs index c80696ac..dd2c8875 100644 --- a/Penumbra/Meta/Manipulations/EqpManipulation.cs +++ b/Penumbra/Meta/Manipulations/EqpManipulation.cs @@ -47,8 +47,8 @@ public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation > return set != 0 ? set : Slot.CompareTo( other.Slot ); } - public int FileIndex() - => CharacterUtility.EqpIdx; + public CharacterUtility.Index FileIndex() + => CharacterUtility.Index.Eqp; public bool Apply( ExpandedEqpFile file ) { diff --git a/Penumbra/Meta/Manipulations/EstManipulation.cs b/Penumbra/Meta/Manipulations/EstManipulation.cs index 314972b3..1abe3220 100644 --- a/Penumbra/Meta/Manipulations/EstManipulation.cs +++ b/Penumbra/Meta/Manipulations/EstManipulation.cs @@ -13,10 +13,10 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation > { public enum EstType : byte { - Hair = CharacterUtility.HairEstIdx, - Face = CharacterUtility.FaceEstIdx, - Body = CharacterUtility.BodyEstIdx, - Head = CharacterUtility.HeadEstIdx, + Hair = CharacterUtility.Index.HairEst, + Face = CharacterUtility.Index.FaceEst, + Body = CharacterUtility.Index.BodyEst, + Head = CharacterUtility.Index.HeadEst, } public ushort Entry { get; init; } // SkeletonIdx. @@ -76,8 +76,8 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation > return s != 0 ? s : SetId.CompareTo( other.SetId ); } - public int FileIndex() - => ( int )Slot; + public CharacterUtility.Index FileIndex() + => ( CharacterUtility.Index )Slot; public bool Apply( EstFile file ) { diff --git a/Penumbra/Meta/Manipulations/GmpManipulation.cs b/Penumbra/Meta/Manipulations/GmpManipulation.cs index ad31d6b2..f686ec31 100644 --- a/Penumbra/Meta/Manipulations/GmpManipulation.cs +++ b/Penumbra/Meta/Manipulations/GmpManipulation.cs @@ -33,8 +33,8 @@ public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation > public int CompareTo( GmpManipulation other ) => SetId.CompareTo( other.SetId ); - public int FileIndex() - => CharacterUtility.GmpIdx; + public CharacterUtility.Index FileIndex() + => CharacterUtility.Index.Gmp; public bool Apply( ExpandedGmpFile file ) { diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs index b3acf680..e9c01c0b 100644 --- a/Penumbra/Meta/Manipulations/ImcManipulation.cs +++ b/Penumbra/Meta/Manipulations/ImcManipulation.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; +using Penumbra.Interop.Structs; using Penumbra.Meta.Files; namespace Penumbra.Meta.Manipulations; @@ -119,8 +120,8 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation > return b != 0 ? b : Variant.CompareTo( other.Variant ); } - public int FileIndex() - => -1; + public CharacterUtility.Index FileIndex() + => ( CharacterUtility.Index )( -1 ); public Utf8GamePath GamePath() { diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs index bd0c132c..4380e0a0 100644 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs @@ -3,12 +3,13 @@ using System.Runtime.InteropServices; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Penumbra.GameData.Util; +using Penumbra.Interop.Structs; namespace Penumbra.Meta.Manipulations; public interface IMetaManipulation { - public int FileIndex(); + public CharacterUtility.Index FileIndex(); } public interface IMetaManipulation< T > diff --git a/Penumbra/Meta/Manipulations/RspManipulation.cs b/Penumbra/Meta/Manipulations/RspManipulation.cs index 1f10cf7b..70908324 100644 --- a/Penumbra/Meta/Manipulations/RspManipulation.cs +++ b/Penumbra/Meta/Manipulations/RspManipulation.cs @@ -45,8 +45,8 @@ public readonly struct RspManipulation : IMetaManipulation< RspManipulation > return s != 0 ? s : Attribute.CompareTo( other.Attribute ); } - public int FileIndex() - => CharacterUtility.HumanCmpIdx; + public CharacterUtility.Index FileIndex() + => CharacterUtility.Index.HumanCmp; public bool Apply( CmpFile file ) { diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 77c60420..06c3799e 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -69,75 +69,84 @@ public class Penumbra : IDalamudPlugin public Penumbra( DalamudPluginInterface pluginInterface ) { - Dalamud.Initialize( pluginInterface ); - GameData.GameData.GetIdentifier( Dalamud.GameData ); - DevPenumbraExists = CheckDevPluginPenumbra(); - IsNotInstalledPenumbra = CheckIsNotInstalled(); - - Framework = new FrameworkManager(); - CharacterUtility = new CharacterUtility(); - Backup.CreateBackup( pluginInterface.ConfigDirectory, PenumbraBackupFiles() ); - Config = Configuration.Load(); - - TempMods = new TempModManager(); - MetaFileManager = new MetaFileManager(); - ResourceLoader = new ResourceLoader( this ); - ResourceLoader.EnableHooks(); - ResourceLogger = new ResourceLogger( ResourceLoader ); - ResidentResources = new ResidentResourceManager(); - ModManager = new Mod.Manager( Config.ModDirectory ); - ModManager.DiscoverMods(); - CollectionManager = new ModCollection.Manager( ModManager ); - ModFileSystem = ModFileSystem.Load(); - ObjectReloader = new ObjectReloader(); - PathResolver = new PathResolver( ResourceLoader ); - - Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand ) + try { - HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", - } ); + Dalamud.Initialize( pluginInterface ); + GameData.GameData.GetIdentifier( Dalamud.GameData ); + DevPenumbraExists = CheckDevPluginPenumbra(); + IsNotInstalledPenumbra = CheckIsNotInstalled(); - SetupInterface( out _configWindow, out _launchButton, out _windowSystem ); + Framework = new FrameworkManager(); + CharacterUtility = new CharacterUtility(); + Backup.CreateBackup( pluginInterface.ConfigDirectory, PenumbraBackupFiles() ); + Config = Configuration.Load(); - if( Config.EnableMods ) - { - ResourceLoader.EnableReplacements(); - PathResolver.Enable(); + TempMods = new TempModManager(); + MetaFileManager = new MetaFileManager(); + ResourceLoader = new ResourceLoader( this ); + ResourceLoader.EnableHooks(); + ResourceLogger = new ResourceLogger( ResourceLoader ); + ResidentResources = new ResidentResourceManager(); + ModManager = new Mod.Manager( Config.ModDirectory ); + ModManager.DiscoverMods(); + CollectionManager = new ModCollection.Manager( ModManager ); + ModFileSystem = ModFileSystem.Load(); + ObjectReloader = new ObjectReloader(); + PathResolver = new PathResolver( ResourceLoader ); + + Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand ) + { + HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", + } ); + + SetupInterface( out _configWindow, out _launchButton, out _windowSystem ); + + if( Config.EnableMods ) + { + ResourceLoader.EnableReplacements(); + PathResolver.Enable(); + } + + if( Config.EnableHttpApi ) + { + CreateWebServer(); + } + + if( Config.DebugMode ) + { + ResourceLoader.EnableDebug(); + _configWindow.IsOpen = true; + } + + if( Config.EnableFullResourceLogging ) + { + ResourceLoader.EnableFullLogging(); + } + + if( CharacterUtility.Ready ) + { + ResidentResources.Reload(); + } + + Api = new PenumbraApi( this ); + Ipc = new PenumbraIpc( Dalamud.PluginInterface, Api ); + SubscribeItemLinks(); + if( ImcExceptions > 0 ) + { + PluginLog.Error( $"{ImcExceptions} IMC Exceptions thrown. Please repair your game files." ); + } + else + { + PluginLog.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." ); + } + + Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; } - - if( Config.EnableHttpApi ) + catch { - CreateWebServer(); + Dispose(); + throw; } - - if( Config.DebugMode ) - { - ResourceLoader.EnableDebug(); - _configWindow.IsOpen = true; - } - - if( Config.EnableFullResourceLogging ) - { - ResourceLoader.EnableFullLogging(); - } - - if( CharacterUtility.Ready ) - { - ResidentResources.Reload(); - } - - Api = new PenumbraApi( this ); - Ipc = new PenumbraIpc( Dalamud.PluginInterface, Api ); - SubscribeItemLinks(); - if( ImcExceptions > 0 ) - { - PluginLog.Error( $"{ImcExceptions} IMC Exceptions thrown. Please repair your game files." ); - } - else - { - PluginLog.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." ); - } - Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; } private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system ) @@ -154,8 +163,8 @@ public class Penumbra : IDalamudPlugin { Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw; Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle; - _launchButton.Dispose(); - _configWindow.Dispose(); + _launchButton?.Dispose(); + _configWindow?.Dispose(); } public bool Enable() @@ -249,18 +258,18 @@ public class Penumbra : IDalamudPlugin public void Dispose() { DisposeInterface(); - Ipc.Dispose(); - Api.Dispose(); - ObjectReloader.Dispose(); - ModFileSystem.Dispose(); - CollectionManager.Dispose(); + Ipc?.Dispose(); + Api?.Dispose(); + ObjectReloader?.Dispose(); + ModFileSystem?.Dispose(); + CollectionManager?.Dispose(); Dalamud.Commands.RemoveHandler( CommandName ); - PathResolver.Dispose(); - ResourceLogger.Dispose(); - ResourceLoader.Dispose(); - CharacterUtility.Dispose(); + PathResolver?.Dispose(); + ResourceLogger?.Dispose(); + ResourceLoader?.Dispose(); + CharacterUtility?.Dispose(); ShutdownWebServer(); } diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 081b0e79..764dc452 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -237,7 +237,8 @@ public partial class ConfigWindow for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i ) { var idx = CharacterUtility.RelevantIndices[ i ]; - var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ idx ]; + var intern = new CharacterUtility.InternalIndex( i ); + var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resource(idx); ImGui.TableNextColumn(); ImGui.TextUnformatted( $"0x{( ulong )resource:X}" ); ImGui.TableNextColumn(); @@ -259,18 +260,18 @@ public partial class ConfigWindow ImGui.TableNextColumn(); ImGui.TextUnformatted( $"{resource->GetData().Length}" ); ImGui.TableNextColumn(); - ImGui.Selectable( $"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}" ); + ImGui.Selectable( $"0x{Penumbra.CharacterUtility.DefaultResource(intern).Address:X}" ); if( ImGui.IsItemClicked() ) { ImGui.SetClipboardText( string.Join( "\n", - new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address, - Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResource(intern).Address, + Penumbra.CharacterUtility.DefaultResource(intern).Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); } ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." ); ImGui.TableNextColumn(); - ImGui.TextUnformatted( $"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}" ); + ImGui.TextUnformatted( $"{Penumbra.CharacterUtility.DefaultResource(intern).Size}" ); } }