diff --git a/OtterGui b/OtterGui index 2b4b78b0..b92dbe60 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 2b4b78b03d679144440d35b30830608b6d2bcb79 +Subproject commit b92dbe60887503a77a89aeae80729236fb2bfa10 diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 918d361c..351ae2fc 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -1,11 +1,14 @@ using OtterGui.Classes; using Penumbra.GameData.ByteString; +using Penumbra.GameData.Enums; using Penumbra.Meta.Manager; using Penumbra.Mods; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using Penumbra.Interop; +using Penumbra.Meta.Manipulations; namespace Penumbra.Collections; @@ -203,4 +206,21 @@ public partial class ModCollection Penumbra.Log.Debug( $"Set CharacterUtility resources for collection {Name}." ); } } -} \ No newline at end of file + + // Used for short periods of changed files. + public CharacterUtility.List.MetaReverter? TemporarilySetEqdpFile( GenderRace genderRace, bool accessory ) + => _cache?.MetaManipulations.TemporarilySetEqdpFile( genderRace, accessory ); + + public CharacterUtility.List.MetaReverter? TemporarilySetEqpFile() + => _cache?.MetaManipulations.TemporarilySetEqpFile(); + + public CharacterUtility.List.MetaReverter? TemporarilySetGmpFile() + => _cache?.MetaManipulations.TemporarilySetGmpFile(); + + public CharacterUtility.List.MetaReverter? TemporarilySetCmpFile() + => _cache?.MetaManipulations.TemporarilySetCmpFile(); + + public CharacterUtility.List.MetaReverter? TemporarilySetEstFile( EstManipulation.EstType type ) + => _cache?.MetaManipulations.TemporarilySetEstFile( type ); + +} diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index c168f8c4..a5b155c1 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -84,18 +84,32 @@ public unsafe partial class CharacterUtility : IDisposable } } - public List.MetaReverter SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length ) + public void SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length ) { - var idx = ReverseIndices[ ( int )resourceIdx ]; - var list = _lists[ idx.Value ]; - return list.SetResource( data, length ); + var idx = ReverseIndices[( int )resourceIdx]; + var list = _lists[idx.Value]; + list.SetResource( data, length ); } - public List.MetaReverter ResetResource( Structs.CharacterUtility.Index resourceIdx ) + public void ResetResource( Structs.CharacterUtility.Index resourceIdx ) + { + var idx = ReverseIndices[( int )resourceIdx]; + var list = _lists[idx.Value]; + list.ResetResource(); + } + + public List.MetaReverter TemporarilySetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length ) { var idx = ReverseIndices[ ( int )resourceIdx ]; var list = _lists[ idx.Value ]; - return list.ResetResource(); + return list.TemporarilySetResource( data, length ); + } + + public List.MetaReverter TemporarilyResetResource( Structs.CharacterUtility.Index resourceIdx ) + { + var idx = ReverseIndices[ ( int )resourceIdx ]; + var list = _lists[ idx.Value ]; + return list.TemporarilyResetResource(); } // Return all relevant resources to the default resource. diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index e681a364..d6e6ec0e 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -54,7 +54,7 @@ public unsafe partial class PathResolver public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData ) { - if( type == ResourceType.Tex + if( type == ResourceType.Tex && LastCreatedCollection.Valid && gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) ) { @@ -123,7 +123,7 @@ public unsafe partial class PathResolver // This map links DrawObjects directly to Actors (by ObjectTable index) and their collections. // It contains any DrawObjects that correspond to a human actor, even those without specific collections. - private readonly Dictionary< IntPtr, (ResolveData, int) > _drawObjectToObject = new(); + private readonly Dictionary< IntPtr, (ResolveData, int) > _drawObjectToObject = new(); private ResolveData _lastCreatedCollection = ResolveData.Invalid; // Keep track of created DrawObjects that are CharacterBase, @@ -135,22 +135,37 @@ public unsafe partial class PathResolver private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) { - using var cmp = MetaChanger.ChangeCmp( LastGameObject, out _lastCreatedCollection ); - + CharacterUtility.List.MetaReverter? cmp = null; if( LastGameObject != null ) { + _lastCreatedCollection = IdentifyCollection( LastGameObject ); var modelPtr = &a; - CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c ); + if( _lastCreatedCollection.ModCollection != Penumbra.CollectionManager.Default ) + { + cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile(); + } + + try + { + CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c ); + } + catch( Exception e ) + { + Penumbra.Log.Error( $"Unknown Error during CreatingCharacterBase:\n{e}" ); + } } var ret = _characterBaseCreateHook.Original( a, b, c, d ); - if( LastGameObject != null ) + using( cmp ) { - _drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); - CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret ); - } + if( LastGameObject != null ) + { + _drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); + CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret ); + } - return ret; + return ret; + } } diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index 94d760e0..ba52182a 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -1,9 +1,9 @@ using System; using Dalamud.Hooking; using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Penumbra.Collections; -using Penumbra.Meta.Manipulations; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.GameData.Enums; +using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; namespace Penumbra.Interop.Resolver; @@ -29,17 +29,13 @@ namespace Penumbra.Interop.Resolver; // ChangeCustomize and RspSetupCharacter, which is hooked here. // GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which has a DrawObject as its first parameter. - public unsafe partial class PathResolver { - public unsafe class MetaState : IDisposable + public class MetaState : IDisposable { - private readonly PathResolver _parent; - - public MetaState( PathResolver parent, IntPtr* humanVTable ) + public MetaState( IntPtr* humanVTable ) { SignatureHelper.Initialise( this ); - _parent = parent; _onModelLoadCompleteHook = Hook< OnModelLoadCompleteDelegate >.FromAddress( humanVTable[ 58 ], OnModelLoadCompleteDetour ); } @@ -81,8 +77,10 @@ public unsafe partial class PathResolver var collection = GetResolveData( drawObject ); if( collection.Valid ) { - using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); - using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); + var race = GetDrawObjectGenderRace( drawObject ); + using var eqp = collection.ModCollection.TemporarilySetEqpFile(); + using var eqdp1 = collection.ModCollection.TemporarilySetEqdpFile( race, false ); + using var eqdp2 = collection.ModCollection.TemporarilySetEqdpFile( race, true ); _onModelLoadCompleteHook.Original.Invoke( drawObject ); } else @@ -108,8 +106,10 @@ public unsafe partial class PathResolver var collection = GetResolveData( drawObject ); if( collection.Valid ) { - using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); - using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); + var race = GetDrawObjectGenderRace( drawObject ); + using var eqp = collection.ModCollection.TemporarilySetEqpFile(); + using var eqdp1 = collection.ModCollection.TemporarilySetEqdpFile( race, false ); + using var eqdp2 = collection.ModCollection.TemporarilySetEqdpFile( race, true ); _updateModelsHook.Original.Invoke( drawObject ); } else @@ -118,6 +118,24 @@ public unsafe partial class PathResolver } } + private static GenderRace GetDrawObjectGenderRace( IntPtr drawObject ) + { + var draw = ( DrawObject* )drawObject; + if( draw->Object.GetObjectType() == ObjectType.CharacterBase ) + { + var c = ( CharacterBase* )drawObject; + if( c->GetModelType() == CharacterBase.ModelType.Human ) + { + return GetHumanGenderRace( drawObject ); + } + } + + return GenderRace.Unknown; + } + + public static GenderRace GetHumanGenderRace( IntPtr human ) + => ( GenderRace )( ( Human* )human )->RaceSexId; + [Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C", DetourName = nameof( GetEqpIndirectDetour ) )] private readonly Hook< OnModelLoadCompleteDelegate > _getEqpIndirectHook = null!; @@ -131,7 +149,8 @@ public unsafe partial class PathResolver return; } - using var eqp = MetaChanger.ChangeEqp( _parent, drawObject ); + var resolveData = GetResolveData( drawObject ); + using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetEqpFile() : null; _getEqpIndirectHook.Original( drawObject ); } @@ -145,7 +164,8 @@ public unsafe partial class PathResolver private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState ) { - using var gmp = MetaChanger.ChangeGmp( _parent, drawObject ); + var resolveData = GetResolveData( drawObject ); + using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetGmpFile() : null; return _setupVisorHook.Original( drawObject, modelId, visorState ); } @@ -163,13 +183,14 @@ public unsafe partial class PathResolver } else { - using var rsp = MetaChanger.ChangeCmp( _parent, drawObject ); + var resolveData = GetResolveData( drawObject ); + using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetCmpFile() : null; _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); } } // ChangeCustomize calls RspSetupCharacter, so skip the additional cmp change. - private bool _inChangeCustomize = false; + private bool _inChangeCustomize; private delegate bool ChangeCustomizeDelegate( IntPtr human, IntPtr data, byte skipEquipment ); [Signature( "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86", DetourName = nameof( ChangeCustomizeDetour ) )] @@ -178,154 +199,9 @@ public unsafe partial class PathResolver private bool ChangeCustomizeDetour( IntPtr human, IntPtr data, byte skipEquipment ) { _inChangeCustomize = true; - using var rsp = MetaChanger.ChangeCmp( _parent, human ); + var resolveData = GetResolveData( human ); + using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetEqpFile() : null; return _changeCustomize.Original( human, data, skipEquipment ); } } - - // Small helper to handle setting metadata and reverting it at the end of the function. - // Since eqp and eqdp may be called multiple times in a row, we need to count them, - // so that we do not reset the files too early. - private readonly struct MetaChanger : IDisposable - { - private static int _eqpCounter; - private static int _eqdpCounter; - private readonly MetaManipulation.Type _type; - - private MetaChanger( MetaManipulation.Type type ) - { - _type = type; - if( type == MetaManipulation.Type.Eqp ) - { - ++_eqpCounter; - } - else if( type == MetaManipulation.Type.Eqdp ) - { - ++_eqdpCounter; - } - } - - public static MetaChanger ChangeEqp( ModCollection collection ) - { - collection.SetEqpFiles(); - return new MetaChanger( MetaManipulation.Type.Eqp ); - } - - public static MetaChanger ChangeEqp( PathResolver _, IntPtr drawObject ) - { - var resolveData = GetResolveData( drawObject ); - if( resolveData.Valid ) - { - return ChangeEqp( resolveData.ModCollection ); - } - - return new MetaChanger( MetaManipulation.Type.Unknown ); - } - - // We only need to change anything if it is actually equipment here. - public static MetaChanger ChangeEqdp( PathResolver _, IntPtr drawObject, uint modelType ) - { - if( modelType < 10 ) - { - var collection = GetResolveData( drawObject ); - if( collection.Valid ) - { - return ChangeEqdp( collection.ModCollection ); - } - } - - return new MetaChanger( MetaManipulation.Type.Unknown ); - } - - public static MetaChanger ChangeEqdp( ModCollection collection ) - { - collection.SetEqdpFiles(); - return new MetaChanger( MetaManipulation.Type.Eqdp ); - } - - public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject ) - { - var resolveData = GetResolveData( drawObject ); - if( resolveData.Valid ) - { - resolveData.ModCollection.SetGmpFiles(); - return new MetaChanger( MetaManipulation.Type.Gmp ); - } - - return new MetaChanger( MetaManipulation.Type.Unknown ); - } - - public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject ) - { - var resolveData = GetResolveData( drawObject ); - if( resolveData.Valid ) - { - resolveData.ModCollection.SetEstFiles(); - return new MetaChanger( MetaManipulation.Type.Est ); - } - - return new MetaChanger( MetaManipulation.Type.Unknown ); - } - - public static MetaChanger ChangeCmp( GameObject* gameObject, out ResolveData resolveData ) - { - if( gameObject != null ) - { - resolveData = IdentifyCollection( gameObject ); - if( resolveData.ModCollection != Penumbra.CollectionManager.Default && resolveData.ModCollection.HasCache ) - { - resolveData.ModCollection.SetCmpFiles(); - return new MetaChanger( MetaManipulation.Type.Rsp ); - } - } - else - { - resolveData = ResolveData.Invalid; - } - - return new MetaChanger( MetaManipulation.Type.Unknown ); - } - - public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject ) - { - var resolveData = GetResolveData( drawObject ); - if( resolveData.Valid ) - { - resolveData.ModCollection.SetCmpFiles(); - return new MetaChanger( MetaManipulation.Type.Rsp ); - } - - return new MetaChanger( MetaManipulation.Type.Unknown ); - } - - public void Dispose() - { - switch( _type ) - { - case MetaManipulation.Type.Eqdp: - if( --_eqdpCounter == 0 ) - { - Penumbra.CollectionManager.Default.SetEqdpFiles(); - } - - break; - case MetaManipulation.Type.Eqp: - if( --_eqpCounter == 0 ) - { - Penumbra.CollectionManager.Default.SetEqpFiles(); - } - - break; - case MetaManipulation.Type.Est: - Penumbra.CollectionManager.Default.SetEstFiles(); - break; - case MetaManipulation.Type.Gmp: - Penumbra.CollectionManager.Default.SetGmpFiles(); - break; - case MetaManipulation.Type.Rsp: - Penumbra.CollectionManager.Default.SetCmpFiles(); - break; - } - } - } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs index d847480e..b5335dc8 100644 --- a/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs +++ b/Penumbra/Interop/Resolver/PathResolver.ResolverHooks.cs @@ -2,6 +2,9 @@ using System; using System.Runtime.CompilerServices; using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Resolver; @@ -140,34 +143,64 @@ public partial class PathResolver private IntPtr ResolveMdlHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) { - using var eqdp = MetaChanger.ChangeEqdp( _parent, drawObject, modelType ); + CharacterUtility.List.MetaReverter? Get() + { + if( modelType > 9 ) + { + return null; + } + + var race = MetaState.GetHumanGenderRace( drawObject ); + if( race == GenderRace.Unknown ) + { + return null; + } + + var data = GetResolveData( drawObject ); + return !data.Valid ? null : data.ModCollection.TemporarilySetEqdpFile( race, modelType > 4 ); + } + + using var eqdp = Get(); return ResolvePath( drawObject, _resolveMdlPathHook.Original( drawObject, path, unk3, modelType ) ); } private IntPtr ResolvePapHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) { - using var est = MetaChanger.ChangeEst( _parent, drawObject ); + using var est = GetEstChanges( drawObject ); return ResolvePath( drawObject, _resolvePapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) ); } private IntPtr ResolvePhybHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) { - using var est = MetaChanger.ChangeEst( _parent, drawObject ); + using var est = GetEstChanges( drawObject ); return ResolvePath( drawObject, _resolvePhybPathHook.Original( drawObject, path, unk3, unk4 ) ); } private IntPtr ResolveSklbHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) { - using var est = MetaChanger.ChangeEst( _parent, drawObject ); + using var est = GetEstChanges( drawObject ); return ResolvePath( drawObject, _resolveSklbPathHook.Original( drawObject, path, unk3, unk4 ) ); } private IntPtr ResolveSkpHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) { - using var est = MetaChanger.ChangeEst( _parent, drawObject ); + using var est = GetEstChanges( drawObject ); return ResolvePath( drawObject, _resolveSkpPathHook.Original( drawObject, path, unk3, unk4 ) ); } + private DisposableContainer GetEstChanges( IntPtr drawObject ) + { + var data = GetResolveData( drawObject ); + if( !data.Valid ) + { + return DisposableContainer.Empty; + } + + return new DisposableContainer( data.ModCollection.TemporarilySetEstFile( EstManipulation.EstType.Face ), + data.ModCollection.TemporarilySetEstFile( EstManipulation.EstType.Body ), + data.ModCollection.TemporarilySetEstFile( EstManipulation.EstType.Hair ), + data.ModCollection.TemporarilySetEstFile( EstManipulation.EstType.Head ) ); + } private IntPtr ResolveDecalWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) => ResolveWeaponPath( drawObject, _resolveDecalPathHook.Original( drawObject, path, unk3, unk4 ) ); @@ -226,9 +259,10 @@ public partial class PathResolver // Implementation [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private IntPtr ResolvePath( IntPtr drawObject, IntPtr path ) - => _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _) ?? IntPtr.Zero, FindParent( drawObject, out var collection ) == null - ? Penumbra.CollectionManager.Default - : collection.ModCollection, path ); + => _parent._paths.ResolvePath( ( IntPtr? )FindParent( drawObject, out _ ) ?? IntPtr.Zero, + FindParent( drawObject, out var collection ) == null + ? Penumbra.CollectionManager.Default + : collection.ModCollection, path ); // Weapons have the characters DrawObject as a parent, // but that may not be set yet when creating a new object, so we have to do the same detour @@ -239,18 +273,18 @@ public partial class PathResolver var parent = FindParent( drawObject, out var collection ); if( parent != null ) { - return _parent._paths.ResolvePath( (IntPtr)parent, collection.ModCollection, path ); + return _parent._paths.ResolvePath( ( IntPtr )parent, collection.ModCollection, path ); } var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject; var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject ); if( parentCollection.Valid ) { - return _parent._paths.ResolvePath( (IntPtr)FindParent(parentObject, out _), parentCollection.ModCollection, path ); + return _parent._paths.ResolvePath( ( IntPtr )FindParent( parentObject, out _ ), parentCollection.ModCollection, path ); } parent = FindParent( parentObject, out collection ); - return _parent._paths.ResolvePath( (IntPtr?)parent ?? IntPtr.Zero, parent == null + return _parent._paths.ResolvePath( ( IntPtr? )parent ?? IntPtr.Zero, parent == null ? Penumbra.CollectionManager.Default : collection.ModCollection, path ); } diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index 0fda162c..6b1d76a4 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -34,7 +34,7 @@ public partial class PathResolver : IDisposable _loader = loader; _animations = new AnimationState( DrawObjects ); _paths = new PathState( this ); - _meta = new MetaState( this, _paths.HumanVTable ); + _meta = new MetaState( _paths.HumanVTable ); _materials = new MaterialState( _paths ); } diff --git a/Penumbra/Meta/Manager/MetaManager.Cmp.cs b/Penumbra/Meta/Manager/MetaManager.Cmp.cs index b624a360..8cd17a1e 100644 --- a/Penumbra/Meta/Manager/MetaManager.Cmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Cmp.cs @@ -18,6 +18,9 @@ public partial class MetaManager public static void ResetCmpFiles() => SetFile( null, CharacterUtility.Index.HumanCmp ); + public Interop.CharacterUtility.List.MetaReverter TemporarilySetCmpFile() + => TemporarilySetFile( _cmpFile, CharacterUtility.Index.HumanCmp ); + public void ResetCmp() { if( _cmpFile == null ) diff --git a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs index dc9a31d6..35857be0 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using OtterGui; using OtterGui.Filesystem; using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; @@ -23,6 +24,21 @@ public partial class MetaManager } } + public Interop.CharacterUtility.List.MetaReverter? TemporarilySetEqdpFile( GenderRace genderRace, bool accessory ) + { + var idx = CharacterUtility.EqdpIdx( genderRace, accessory ); + if( ( int )idx != -1 ) + { + var i = CharacterUtility.EqdpIndices.IndexOf( idx ); + if( i != -1 ) + { + return TemporarilySetFile( _eqdpFiles[ i ], idx ); + } + } + + return null; + } + public static void ResetEqdpFiles() { foreach( var idx in CharacterUtility.EqdpIndices ) @@ -33,7 +49,7 @@ public partial class MetaManager public void ResetEqdp() { - foreach( var file in _eqdpFiles.OfType() ) + foreach( var file in _eqdpFiles.OfType< ExpandedEqdpFile >() ) { var relevant = Interop.CharacterUtility.RelevantIndices[ file.Index.Value ]; file.Reset( _eqdpManipulations.Where( m => m.FileIndex() == relevant ).Select( m => ( int )m.SetId ) ); diff --git a/Penumbra/Meta/Manager/MetaManager.Eqp.cs b/Penumbra/Meta/Manager/MetaManager.Eqp.cs index adae4378..7a5da091 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqp.cs @@ -18,6 +18,9 @@ public partial class MetaManager public static void ResetEqpFiles() => SetFile( null, CharacterUtility.Index.Eqp ); + public Interop.CharacterUtility.List.MetaReverter TemporarilySetEqpFile() + => TemporarilySetFile( _eqpFile, CharacterUtility.Index.Eqp ); + public void ResetEqp() { if( _eqpFile == null ) diff --git a/Penumbra/Meta/Manager/MetaManager.Est.cs b/Penumbra/Meta/Manager/MetaManager.Est.cs index 728a024d..193e749d 100644 --- a/Penumbra/Meta/Manager/MetaManager.Est.cs +++ b/Penumbra/Meta/Manager/MetaManager.Est.cs @@ -33,6 +33,20 @@ public partial class MetaManager SetFile( null, CharacterUtility.Index.HeadEst ); } + public Interop.CharacterUtility.List.MetaReverter? TemporarilySetEstFile(EstManipulation.EstType type) + { + var (file, idx) = type switch + { + EstManipulation.EstType.Face => ( _estFaceFile, CharacterUtility.Index.FaceEst ), + EstManipulation.EstType.Hair => ( _estHairFile, CharacterUtility.Index.HairEst ), + EstManipulation.EstType.Body => ( _estBodyFile, CharacterUtility.Index.BodyEst ), + EstManipulation.EstType.Head => ( _estHeadFile, CharacterUtility.Index.HeadEst ), + _ => ( null, 0 ), + }; + + return idx != 0 ? TemporarilySetFile( file, idx ) : null; + } + public void ResetEst() { _estFaceFile?.Reset(); diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Meta/Manager/MetaManager.Gmp.cs index 91554e6a..02e1dc78 100644 --- a/Penumbra/Meta/Manager/MetaManager.Gmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Gmp.cs @@ -18,6 +18,9 @@ public partial class MetaManager public static void ResetGmpFiles() => SetFile( null, CharacterUtility.Index.Gmp ); + public Interop.CharacterUtility.List.MetaReverter TemporarilySetGmpFile() + => TemporarilySetFile( _gmpFile, CharacterUtility.Index.Gmp ); + public void ResetGmp() { if( _gmpFile == null ) diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index fde0614c..701b77c0 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -179,4 +179,10 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM Penumbra.CharacterUtility.SetResource( index, ( IntPtr )file.Data, file.Length ); } } + + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private static unsafe Interop.CharacterUtility.List.MetaReverter TemporarilySetFile( MetaBaseFile? file, CharacterUtility.Index index ) + => file == null + ? Penumbra.CharacterUtility.TemporarilyResetResource( index ) + : Penumbra.CharacterUtility.TemporarilySetResource( index, ( IntPtr )file.Data, file.Length ); } \ No newline at end of file