Use MetaReverter for all cases, improve Eqdp handling through this.

This commit is contained in:
Ottermandias 2022-09-16 21:14:04 +02:00
parent af3a07c227
commit b34999a1a5
13 changed files with 199 additions and 195 deletions

@ -1 +1 @@
Subproject commit 2b4b78b03d679144440d35b30830608b6d2bcb79
Subproject commit b92dbe60887503a77a89aeae80729236fb2bfa10

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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