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 OtterGui.Classes;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Meta.Manager; using Penumbra.Meta.Manager;
using Penumbra.Mods; using Penumbra.Mods;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using Penumbra.Interop;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections; namespace Penumbra.Collections;
@ -203,4 +206,21 @@ public partial class ModCollection
Penumbra.Log.Debug( $"Set CharacterUtility resources for collection {Name}." ); 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 idx = ReverseIndices[( int )resourceIdx];
var list = _lists[ idx.Value ]; var list = _lists[idx.Value];
return list.SetResource( data, length ); 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 idx = ReverseIndices[ ( int )resourceIdx ];
var list = _lists[ idx.Value ]; 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. // 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 ) public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData )
{ {
if( type == ResourceType.Tex if( type == ResourceType.Tex
&& LastCreatedCollection.Valid && LastCreatedCollection.Valid
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) ) && 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. // 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. // 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; private ResolveData _lastCreatedCollection = ResolveData.Invalid;
// Keep track of created DrawObjects that are CharacterBase, // 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 ) 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 ) if( LastGameObject != null )
{ {
_lastCreatedCollection = IdentifyCollection( LastGameObject );
var modelPtr = &a; 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 ); var ret = _characterBaseCreateHook.Original( a, b, c, d );
if( LastGameObject != null ) using( cmp )
{ {
_drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); if( LastGameObject != null )
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret ); {
} _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 System;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.Collections; using Penumbra.GameData.Enums;
using Penumbra.Meta.Manipulations; using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType;
namespace Penumbra.Interop.Resolver; namespace Penumbra.Interop.Resolver;
@ -29,17 +29,13 @@ namespace Penumbra.Interop.Resolver;
// ChangeCustomize and RspSetupCharacter, which is hooked here. // 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. // 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 partial class PathResolver
{ {
public unsafe class MetaState : IDisposable public class MetaState : IDisposable
{ {
private readonly PathResolver _parent; public MetaState( IntPtr* humanVTable )
public MetaState( PathResolver parent, IntPtr* humanVTable )
{ {
SignatureHelper.Initialise( this ); SignatureHelper.Initialise( this );
_parent = parent;
_onModelLoadCompleteHook = Hook< OnModelLoadCompleteDelegate >.FromAddress( humanVTable[ 58 ], OnModelLoadCompleteDetour ); _onModelLoadCompleteHook = Hook< OnModelLoadCompleteDelegate >.FromAddress( humanVTable[ 58 ], OnModelLoadCompleteDetour );
} }
@ -81,8 +77,10 @@ public unsafe partial class PathResolver
var collection = GetResolveData( drawObject ); var collection = GetResolveData( drawObject );
if( collection.Valid ) if( collection.Valid )
{ {
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); var race = GetDrawObjectGenderRace( drawObject );
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); 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 ); _onModelLoadCompleteHook.Original.Invoke( drawObject );
} }
else else
@ -108,8 +106,10 @@ public unsafe partial class PathResolver
var collection = GetResolveData( drawObject ); var collection = GetResolveData( drawObject );
if( collection.Valid ) if( collection.Valid )
{ {
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); var race = GetDrawObjectGenderRace( drawObject );
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); 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 ); _updateModelsHook.Original.Invoke( drawObject );
} }
else 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", [Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C",
DetourName = nameof( GetEqpIndirectDetour ) )] DetourName = nameof( GetEqpIndirectDetour ) )]
private readonly Hook< OnModelLoadCompleteDelegate > _getEqpIndirectHook = null!; private readonly Hook< OnModelLoadCompleteDelegate > _getEqpIndirectHook = null!;
@ -131,7 +149,8 @@ public unsafe partial class PathResolver
return; return;
} }
using var eqp = MetaChanger.ChangeEqp( _parent, drawObject ); var resolveData = GetResolveData( drawObject );
using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetEqpFile() : null;
_getEqpIndirectHook.Original( drawObject ); _getEqpIndirectHook.Original( drawObject );
} }
@ -145,7 +164,8 @@ public unsafe partial class PathResolver
private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState ) 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 ); return _setupVisorHook.Original( drawObject, modelId, visorState );
} }
@ -163,13 +183,14 @@ public unsafe partial class PathResolver
} }
else 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 ); _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 );
} }
} }
// ChangeCustomize calls RspSetupCharacter, so skip the additional cmp change. // 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 ); private delegate bool ChangeCustomizeDelegate( IntPtr human, IntPtr data, byte skipEquipment );
[Signature( "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86", DetourName = nameof( ChangeCustomizeDetour ) )] [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 ) private bool ChangeCustomizeDetour( IntPtr human, IntPtr data, byte skipEquipment )
{ {
_inChangeCustomize = true; _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 ); 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 System.Runtime.CompilerServices;
using Dalamud.Hooking; using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Interop.Resolver; namespace Penumbra.Interop.Resolver;
@ -140,34 +143,64 @@ public partial class PathResolver
private IntPtr ResolveMdlHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType ) 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 ) ); return ResolvePath( drawObject, _resolveMdlPathHook.Original( drawObject, path, unk3, modelType ) );
} }
private IntPtr ResolvePapHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) 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 ) ); return ResolvePath( drawObject, _resolvePapPathHook.Original( drawObject, path, unk3, unk4, unk5 ) );
} }
private IntPtr ResolvePhybHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) 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 ) ); return ResolvePath( drawObject, _resolvePhybPathHook.Original( drawObject, path, unk3, unk4 ) );
} }
private IntPtr ResolveSklbHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint 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 ) ); return ResolvePath( drawObject, _resolveSklbPathHook.Original( drawObject, path, unk3, unk4 ) );
} }
private IntPtr ResolveSkpHuman( IntPtr drawObject, IntPtr path, IntPtr unk3, uint 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 ) ); 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 ) private IntPtr ResolveDecalWeapon( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 )
=> ResolveWeaponPath( drawObject, _resolveDecalPathHook.Original( drawObject, path, unk3, unk4 ) ); => ResolveWeaponPath( drawObject, _resolveDecalPathHook.Original( drawObject, path, unk3, unk4 ) );
@ -226,9 +259,10 @@ public partial class PathResolver
// Implementation // Implementation
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
private IntPtr ResolvePath( IntPtr drawObject, IntPtr path ) private IntPtr ResolvePath( IntPtr drawObject, IntPtr path )
=> _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _) ?? IntPtr.Zero, FindParent( drawObject, out var collection ) == null => _parent._paths.ResolvePath( ( IntPtr? )FindParent( drawObject, out _ ) ?? IntPtr.Zero,
? Penumbra.CollectionManager.Default FindParent( drawObject, out var collection ) == null
: collection.ModCollection, path ); ? Penumbra.CollectionManager.Default
: collection.ModCollection, path );
// Weapons have the characters DrawObject as a parent, // 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 // 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 ); var parent = FindParent( drawObject, out var collection );
if( parent != null ) 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 parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject;
var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject ); var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject );
if( parentCollection.Valid ) 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 ); 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 ? Penumbra.CollectionManager.Default
: collection.ModCollection, path ); : collection.ModCollection, path );
} }

View file

@ -34,7 +34,7 @@ public partial class PathResolver : IDisposable
_loader = loader; _loader = loader;
_animations = new AnimationState( DrawObjects ); _animations = new AnimationState( DrawObjects );
_paths = new PathState( this ); _paths = new PathState( this );
_meta = new MetaState( this, _paths.HumanVTable ); _meta = new MetaState( _paths.HumanVTable );
_materials = new MaterialState( _paths ); _materials = new MaterialState( _paths );
} }

View file

@ -18,6 +18,9 @@ public partial class MetaManager
public static void ResetCmpFiles() public static void ResetCmpFiles()
=> SetFile( null, CharacterUtility.Index.HumanCmp ); => SetFile( null, CharacterUtility.Index.HumanCmp );
public Interop.CharacterUtility.List.MetaReverter TemporarilySetCmpFile()
=> TemporarilySetFile( _cmpFile, CharacterUtility.Index.HumanCmp );
public void ResetCmp() public void ResetCmp()
{ {
if( _cmpFile == null ) if( _cmpFile == null )

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OtterGui;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs; 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() public static void ResetEqdpFiles()
{ {
foreach( var idx in CharacterUtility.EqdpIndices ) foreach( var idx in CharacterUtility.EqdpIndices )
@ -33,7 +49,7 @@ public partial class MetaManager
public void ResetEqdp() 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 ]; var relevant = Interop.CharacterUtility.RelevantIndices[ file.Index.Value ];
file.Reset( _eqdpManipulations.Where( m => m.FileIndex() == relevant ).Select( m => ( int )m.SetId ) ); 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() public static void ResetEqpFiles()
=> SetFile( null, CharacterUtility.Index.Eqp ); => SetFile( null, CharacterUtility.Index.Eqp );
public Interop.CharacterUtility.List.MetaReverter TemporarilySetEqpFile()
=> TemporarilySetFile( _eqpFile, CharacterUtility.Index.Eqp );
public void ResetEqp() public void ResetEqp()
{ {
if( _eqpFile == null ) if( _eqpFile == null )

View file

@ -33,6 +33,20 @@ public partial class MetaManager
SetFile( null, CharacterUtility.Index.HeadEst ); 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() public void ResetEst()
{ {
_estFaceFile?.Reset(); _estFaceFile?.Reset();

View file

@ -18,6 +18,9 @@ public partial class MetaManager
public static void ResetGmpFiles() public static void ResetGmpFiles()
=> SetFile( null, CharacterUtility.Index.Gmp ); => SetFile( null, CharacterUtility.Index.Gmp );
public Interop.CharacterUtility.List.MetaReverter TemporarilySetGmpFile()
=> TemporarilySetFile( _gmpFile, CharacterUtility.Index.Gmp );
public void ResetGmp() public void ResetGmp()
{ {
if( _gmpFile == null ) 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 ); 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 );
} }