add LinkedModCollection to be able to retrospectively verify which gamepath was resolved for which game object

This commit is contained in:
Stanley Dimant 2022-09-03 16:09:31 +02:00 committed by Ottermandias
parent 07af64feed
commit dcdc6d1be1
16 changed files with 151 additions and 79 deletions

View file

@ -27,6 +27,7 @@ public delegate void CreatingCharacterBaseDelegate( IntPtr gameObject, ModCollec
IntPtr equipData );
public delegate void CreatedCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr drawObject );
public delegate void GameObjectResourceResolvedDelegate( IntPtr gameObject, string gamePath, string localPath );
public enum PenumbraApiEc
{
@ -79,6 +80,8 @@ public interface IPenumbraApi : IPenumbraApiBase
// so you can apply flag changes after finishing.
public event CreatedCharacterBaseDelegate? CreatedCharacterBase;
public event GameObjectResourceResolvedDelegate GameObjectResourceResolved;
// Queue redrawing of all actors of the given name with the given RedrawType.
public void RedrawObject( string name, RedrawType setting );

View file

@ -13,6 +13,7 @@ using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Resolver;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
@ -21,7 +22,7 @@ namespace Penumbra.Api;
public class PenumbraApi : IDisposable, IPenumbraApi
{
public (int, int) ApiVersion
=> ( 4, 12 );
=> ( 4, 13 );
private Penumbra? _penumbra;
private Lumina.GameData? _lumina;
@ -54,7 +55,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public bool Valid
=> _penumbra != null;
public PenumbraApi( Penumbra penumbra )
public unsafe PenumbraApi( Penumbra penumbra )
{
_penumbra = penumbra;
_lumina = ( Lumina.GameData? )Dalamud.GameData.GetType()
@ -66,10 +67,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
}
Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections;
Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded;
}
public void Dispose()
public unsafe void Dispose()
{
Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded;
Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections;
_penumbra = null;
_lumina = null;
@ -90,6 +93,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return Penumbra.Config.ModDirectory;
}
private unsafe void OnResourceLoaded( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, LinkedModCollection? resolveData )
{
if( resolveData == null ) return;
GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), manipulatedPath?.ToString() ?? originalPath.ToString() );
}
public event Action< string, bool >? ModDirectoryChanged
{
add => Penumbra.ModManager.ModDirectoryChanged += value;
@ -103,6 +112,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
}
public event ChangedItemHover? ChangedItemTooltip;
public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved;
public void RedrawObject( int tableIndex, RedrawType setting )
{
@ -232,7 +242,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
{
CheckInitialized();
var (obj, collection) = PathResolver.IdentifyDrawObject( drawObject );
return ( obj, collection.Name );
return ( obj, collection.ModCollection.Name );
}
public int GetCutsceneParentIndex( int actor )

View file

@ -283,6 +283,7 @@ public partial class PenumbraIpc
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase";
public const string LabelProviderGameObjectResourcePathResolved = "Penumbra.GameObjectResourcePathResolved";
internal ICallGateProvider< string, string >? ProviderResolveDefault;
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
@ -293,6 +294,7 @@ public partial class PenumbraIpc
internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer;
internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase;
internal ICallGateProvider< IntPtr, string, IntPtr, object? >? ProviderCreatedCharacterBase;
internal ICallGateProvider<IntPtr, string, string, object?>? ProviderGameObjectResourcePathResolved;
private void InitializeResolveProviders( DalamudPluginInterface pi )
{
@ -387,6 +389,21 @@ public partial class PenumbraIpc
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatedCharacterBase}:\n{e}" );
}
try
{
ProviderGameObjectResourcePathResolved = pi.GetIpcProvider<IntPtr, string, string, object?>( LabelProviderGameObjectResourcePathResolved );
Api.GameObjectResourceResolved += GameObjectResourceResolvdedEvent;
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderGameObjectResourcePathResolved}:\n{e}" );
}
}
private void GameObjectResourceResolvdedEvent( IntPtr gameObject, string gamePath, string localPath )
{
ProviderGameObjectResourcePathResolved?.SendMessage( gameObject, gamePath, localPath );
}
private void DisposeResolveProviders()

View file

@ -0,0 +1,32 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
namespace Penumbra.Collections;
public class LinkedModCollection
{
private IntPtr? _associatedGameObject;
public IntPtr AssociatedGameObject
{
get => _associatedGameObject ?? IntPtr.Zero;
set => _associatedGameObject = value;
}
public ModCollection ModCollection;
public LinkedModCollection(ModCollection modCollection)
{
ModCollection = modCollection;
}
public LinkedModCollection(IntPtr? gameObject, ModCollection collection)
{
AssociatedGameObject = gameObject ?? IntPtr.Zero;
ModCollection = collection;
}
public unsafe LinkedModCollection(GameObject* gameObject, ModCollection collection)
{
AssociatedGameObject = ( IntPtr )gameObject;
ModCollection = collection;
}
}

View file

@ -7,6 +7,7 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.STD;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
@ -69,7 +70,7 @@ public unsafe partial class ResourceLoader
}
private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
object? resolverInfo )
LinkedModCollection? resolverInfo )
{
if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 )
{
@ -243,7 +244,7 @@ public unsafe partial class ResourceLoader
private static void LogPath( Utf8GamePath path, bool synchronous )
=> PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, LinkedModCollection? _ )
{
var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString();
PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );

View file

@ -9,6 +9,7 @@ using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
@ -113,14 +114,14 @@ public unsafe partial class ResourceLoader
// Use the default method of path replacement.
public static (FullPath?, object?) DefaultResolver( Utf8GamePath path )
public static (FullPath?, LinkedModCollection?) DefaultResolver( Utf8GamePath path )
{
var resolved = Penumbra.CollectionManager.Default.ResolvePath( path );
return ( resolved, null );
return ( resolved, new LinkedModCollection( Penumbra.CollectionManager.Default ) );
}
// Try all resolve path subscribers or use the default replacer.
private (FullPath?, object?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash )
private (FullPath?, LinkedModCollection?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash )
{
if( !DoReplacements || _incMode.Value )
{

View file

@ -2,6 +2,7 @@ using System;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
@ -118,7 +119,7 @@ public unsafe partial class ResourceLoader : IDisposable
// If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource.
// resolveData is additional data returned by the current ResolvePath function and is user-defined.
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
object? resolveData );
LinkedModCollection? resolveData );
public event ResourceLoadedDelegate? ResourceLoaded;
@ -133,7 +134,7 @@ public unsafe partial class ResourceLoader : IDisposable
// Resolving goes through all subscribed functions in arbitrary order until one returns true,
// or uses default resolving if none return true.
public delegate bool ResolvePathDelegate( Utf8GamePath path, ResourceCategory category, ResourceType type, int hash,
out (FullPath?, object?) ret );
out (FullPath?, LinkedModCollection?) ret );
public event ResolvePathDelegate? ResolvePathCustomization;

View file

@ -15,8 +15,8 @@ public unsafe partial class PathResolver
{
private readonly DrawObjectState _drawObjectState;
private ModCollection? _animationLoadCollection;
private ModCollection? _lastAvfxCollection;
private LinkedModCollection? _animationLoadCollection;
private LinkedModCollection? _lastAvfxCollection;
public AnimationState( DrawObjectState drawObjectState )
{
@ -24,7 +24,7 @@ public unsafe partial class PathResolver
SignatureHelper.Initialise( this );
}
public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out ModCollection? collection )
public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out LinkedModCollection? collection )
{
switch( type )
{
@ -39,7 +39,7 @@ public unsafe partial class PathResolver
break;
case ResourceType.Avfx:
_lastAvfxCollection = _animationLoadCollection ?? Penumbra.CollectionManager.Default;
_lastAvfxCollection = _animationLoadCollection ?? new LinkedModCollection(Penumbra.CollectionManager.Default);
if( _animationLoadCollection != null )
{
collection = _animationLoadCollection;
@ -147,7 +147,7 @@ public unsafe partial class PathResolver
{
var last = _animationLoadCollection;
_animationLoadCollection = _drawObjectState.LastCreatedCollection
?? ( FindParent( drawObject, out var collection ) != null ? collection : Penumbra.CollectionManager.Default );
?? ( FindParent( drawObject, out var collection ) != null ? collection : new LinkedModCollection(Penumbra.CollectionManager.Default) );
_characterBaseLoadAnimationHook.Original( drawObject );
_animationLoadCollection = last;
}

View file

@ -20,13 +20,13 @@ public unsafe partial class PathResolver
public static event CreatingCharacterBaseDelegate? CreatingCharacterBase;
public static event CreatedCharacterBaseDelegate? CreatedCharacterBase;
public IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjects
public IEnumerable< KeyValuePair< IntPtr, (LinkedModCollection, int) > > DrawObjects
=> _drawObjectToObject;
public int Count
=> _drawObjectToObject.Count;
public bool TryGetValue( IntPtr drawObject, out (ModCollection, int) value, out GameObject* gameObject )
public bool TryGetValue( IntPtr drawObject, out (LinkedModCollection, int) value, out GameObject* gameObject )
{
gameObject = null;
if( !_drawObjectToObject.TryGetValue( drawObject, out value ) )
@ -40,7 +40,7 @@ public unsafe partial class PathResolver
// Set and update a parent object if it exists and a last game object is set.
public ModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject )
public LinkedModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject )
{
if( parentObject == IntPtr.Zero && LastGameObject != null )
{
@ -53,7 +53,7 @@ public unsafe partial class PathResolver
}
public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out ModCollection? collection )
public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, [NotNullWhen( true )] out LinkedModCollection? collection )
{
if( type == ResourceType.Tex
&& LastCreatedCollection != null
@ -68,7 +68,7 @@ public unsafe partial class PathResolver
}
public ModCollection? LastCreatedCollection
public LinkedModCollection? LastCreatedCollection
=> _lastCreatedCollection;
public GameObject* LastGameObject { get; private set; }
@ -124,8 +124,8 @@ 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, (ModCollection, int) > _drawObjectToObject = new();
private ModCollection? _lastCreatedCollection;
private readonly Dictionary< IntPtr, (LinkedModCollection, int) > _drawObjectToObject = new();
private LinkedModCollection? _lastCreatedCollection;
// Keep track of created DrawObjects that are CharacterBase,
// and use the last game object that called EnableDraw to link them.
@ -141,14 +141,14 @@ public unsafe partial class PathResolver
if( LastGameObject != null )
{
var modelPtr = &a;
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ( IntPtr )modelPtr, b, c );
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c );
}
var ret = _characterBaseCreateHook.Original( a, b, c, d );
if( LastGameObject != null )
{
_drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex );
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ret );
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret );
}
return ret;

View file

@ -139,11 +139,11 @@ public unsafe partial class PathResolver
}
// Identify the correct collection for a GameObject by index and name.
private static ModCollection IdentifyCollection( GameObject* gameObject )
private static LinkedModCollection IdentifyCollection( GameObject* gameObject )
{
if( gameObject == null )
{
return Penumbra.CollectionManager.Default;
return new LinkedModCollection(Penumbra.CollectionManager.Default);
}
try
@ -153,8 +153,8 @@ public unsafe partial class PathResolver
// Actors are also not named. So use Yourself > Players > Racial > Default.
if( !Dalamud.ClientState.IsLoggedIn )
{
return Penumbra.CollectionManager.ByType( CollectionType.Yourself )
?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default );
return new LinkedModCollection(gameObject, Penumbra.CollectionManager.ByType( CollectionType.Yourself )
?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default ));
}
else
{
@ -163,7 +163,7 @@ public unsafe partial class PathResolver
&& gameObject->ObjectKind == ( byte )ObjectKind.EventNpc
&& gameObject->DataID is 1011832 or 1011021 ) // cf. "E8 ?? ?? ?? ?? 0F B6 F8 88 45", male or female retainer
{
return Penumbra.CollectionManager.Default;
return new LinkedModCollection((IntPtr)gameObject, Penumbra.CollectionManager.Default);
}
string? actorName = null;
@ -174,7 +174,7 @@ public unsafe partial class PathResolver
if( actorName.Length > 0
&& CollectionByActorName( actorName, out var actorCollection ) )
{
return actorCollection;
return new LinkedModCollection(gameObject, actorCollection);
}
}
@ -193,17 +193,17 @@ public unsafe partial class PathResolver
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
// First check temporary character collections, then the own configuration, then special collections.
return CollectionByActorName( actualName, out var c )
return new LinkedModCollection(gameObject, CollectionByActorName( actualName, out var c )
? c
: CollectionByActor( actualName, gameObject, out c )
? c
: Penumbra.CollectionManager.Default;
: Penumbra.CollectionManager.Default);
}
}
catch( Exception e )
{
PluginLog.Error( $"Error identifying collection:\n{e}" );
return Penumbra.CollectionManager.Default;
return new LinkedModCollection(gameObject, Penumbra.CollectionManager.Default);
}
}

View file

@ -4,6 +4,7 @@ using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using OtterGui;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
@ -20,7 +21,7 @@ public unsafe partial class PathResolver
{
private readonly PathState _paths;
private ModCollection? _mtrlCollection;
private LinkedModCollection? _mtrlCollection;
public MaterialState( PathState paths )
{
@ -29,7 +30,7 @@ public unsafe partial class PathResolver
}
// Check specifically for shpk and tex files whether we are currently in a material load.
public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out ModCollection? collection )
public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out LinkedModCollection? collection )
{
if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk )
{
@ -42,12 +43,12 @@ public unsafe partial class PathResolver
}
// Materials need to be set per collection so they can load their textures independently from each other.
public static void HandleCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
out (FullPath?, object?) data )
public static void HandleCollection( LinkedModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
out (FullPath?, LinkedModCollection?) data )
{
if( nonDefault && type == ResourceType.Mtrl )
{
var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" );
var fullPath = new FullPath( $"|{collection.ModCollection.Name}_{collection.ModCollection.ChangeCounter}|{path}" );
data = ( fullPath, collection );
}
else
@ -96,7 +97,12 @@ public unsafe partial class PathResolver
#if DEBUG
PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path );
#endif
_paths.SetCollection( path, collection );
IntPtr gameObjAddr = IntPtr.Zero;
if ( Dalamud.Objects.FindFirst(f => f.Name.TextValue == name, out var gameObj ) )
{
gameObjAddr = gameObj.Address;
}
_paths.SetCollection( gameObjAddr, path, collection );
}
else
{

View file

@ -82,8 +82,8 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
using var eqp = MetaChanger.ChangeEqp( collection );
using var eqdp = MetaChanger.ChangeEqdp( collection );
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
_onModelLoadCompleteHook.Original.Invoke( drawObject );
}
else
@ -109,8 +109,8 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
using var eqp = MetaChanger.ChangeEqp( collection );
using var eqdp = MetaChanger.ChangeEqdp( collection );
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
_updateModelsHook.Original.Invoke( drawObject );
}
else
@ -217,7 +217,7 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
return ChangeEqp( collection );
return ChangeEqp( collection.ModCollection );
}
return new MetaChanger( MetaManipulation.Type.Unknown );
@ -231,7 +231,7 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
return ChangeEqdp( collection );
return ChangeEqdp( collection.ModCollection );
}
}
@ -249,7 +249,7 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
collection.SetGmpFiles();
collection.ModCollection.SetGmpFiles();
return new MetaChanger( MetaManipulation.Type.Gmp );
}
@ -261,21 +261,21 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
collection.SetEstFiles();
collection.ModCollection.SetEstFiles();
return new MetaChanger( MetaManipulation.Type.Est );
}
return new MetaChanger( MetaManipulation.Type.Unknown );
}
public static MetaChanger ChangeCmp( GameObject* gameObject, out ModCollection? collection )
public static MetaChanger ChangeCmp( GameObject* gameObject, out LinkedModCollection? collection )
{
if( gameObject != null )
{
collection = IdentifyCollection( gameObject );
if( collection != Penumbra.CollectionManager.Default && collection.HasCache )
if( collection.ModCollection != Penumbra.CollectionManager.Default && collection.ModCollection.HasCache )
{
collection.SetCmpFiles();
collection.ModCollection.SetCmpFiles();
return new MetaChanger( MetaManipulation.Type.Rsp );
}
}
@ -292,7 +292,7 @@ public unsafe partial class PathResolver
var collection = GetCollection( drawObject );
if( collection != null )
{
collection.SetCmpFiles();
collection.ModCollection.SetCmpFiles();
return new MetaChanger( MetaManipulation.Type.Rsp );
}

View file

@ -32,7 +32,7 @@ public unsafe partial class PathResolver
private readonly ResolverHooks _monster;
// This map links files to their corresponding collection, if it is non-default.
private readonly ConcurrentDictionary< Utf8String, ModCollection > _pathCollections = new();
private readonly ConcurrentDictionary< Utf8String, LinkedModCollection > _pathCollections = new();
public PathState( PathResolver parent )
{
@ -70,18 +70,18 @@ public unsafe partial class PathResolver
public int Count
=> _pathCollections.Count;
public IEnumerable< KeyValuePair< Utf8String, ModCollection > > Paths
public IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > Paths
=> _pathCollections;
public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out ModCollection? collection )
public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection )
=> _pathCollections.TryGetValue( path, out collection );
public bool Consume( Utf8String path, [NotNullWhen( true )] out ModCollection? collection )
public bool Consume( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection )
=> _pathCollections.TryRemove( path, out collection );
// Just add or remove the resolved path.
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
public IntPtr ResolvePath( ModCollection collection, IntPtr path )
public IntPtr ResolvePath( IntPtr? gameObject, ModCollection collection, IntPtr path )
{
if( path == IntPtr.Zero )
{
@ -89,20 +89,20 @@ public unsafe partial class PathResolver
}
var gamePath = new Utf8String( ( byte* )path );
SetCollection( gamePath, collection );
SetCollection( gameObject, gamePath, collection );
return path;
}
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary.
public void SetCollection( Utf8String path, ModCollection collection )
public void SetCollection( IntPtr? gameObject, Utf8String path, ModCollection collection )
{
if( _pathCollections.ContainsKey( path ) || path.IsOwned )
{
_pathCollections[ path ] = collection;
_pathCollections[ path ] = new LinkedModCollection(gameObject, collection);
}
else
{
_pathCollections[ path.Clone() ] = collection;
_pathCollections[ path.Clone() ] = new LinkedModCollection(gameObject, collection);
}
}
}

View file

@ -2,6 +2,7 @@ using System;
using System.Runtime.CompilerServices;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.Collections;
namespace Penumbra.Interop.Resolver;
@ -226,9 +227,9 @@ public partial class PathResolver
// Implementation
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
private IntPtr ResolvePath( IntPtr drawObject, IntPtr path )
=> _parent._paths.ResolvePath( FindParent( drawObject, out var collection ) == null
=> _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _), FindParent( drawObject, out var collection ) == null
? Penumbra.CollectionManager.Default
: collection, path );
: 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,20 +240,20 @@ public partial class PathResolver
var parent = FindParent( drawObject, out var collection );
if( parent != null )
{
return _parent._paths.ResolvePath( collection, 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 != null )
{
return _parent._paths.ResolvePath( parentCollection, path );
return _parent._paths.ResolvePath( (IntPtr)FindParent(parentObject, out _), parentCollection.ModCollection, path );
}
parent = FindParent( parentObject, out collection );
return _parent._paths.ResolvePath( parent == null
return _parent._paths.ResolvePath( (IntPtr?)parent, parent == null
? Penumbra.CollectionManager.Default
: collection, path );
: collection.ModCollection, path );
}
}
}

View file

@ -40,7 +40,7 @@ public partial class PathResolver : IDisposable
}
// The modified resolver that handles game path resolving.
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, object?) data )
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, LinkedModCollection?) data )
{
// Check if the path was marked for a specific collection,
// or if it is a file loaded by a material, and if we are currently in a material load,
@ -54,11 +54,11 @@ public partial class PathResolver : IDisposable
|| DrawObjects.HandleDecalFile( type, gamePath, out collection );
if( !nonDefault || collection == null )
{
collection = Penumbra.CollectionManager.Default;
collection = new LinkedModCollection(Penumbra.CollectionManager.Default);
}
// Resolve using character/default collection first, otherwise forced, as usual.
var resolved = collection.ResolvePath( gamePath );
var resolved = collection.ModCollection.ResolvePath( gamePath );
// Since mtrl files load their files separately, we need to add the new, resolved path
// so that the functions loading tex and shpk can find that path and use its collection.
@ -117,7 +117,7 @@ public partial class PathResolver : IDisposable
_materials.Dispose();
}
public static unsafe (IntPtr, ModCollection) IdentifyDrawObject( IntPtr drawObject )
public static unsafe (IntPtr, LinkedModCollection) IdentifyDrawObject( IntPtr drawObject )
{
var parent = FindParent( drawObject, out var collection );
return ( ( IntPtr )parent, collection );
@ -127,7 +127,7 @@ public partial class PathResolver : IDisposable
=> Cutscenes.GetParentIndex( idx );
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
public static unsafe GameObject* FindParent( IntPtr drawObject, out ModCollection collection )
public static unsafe GameObject* FindParent( IntPtr drawObject, out LinkedModCollection collection )
{
if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) )
{
@ -146,21 +146,21 @@ public partial class PathResolver : IDisposable
return null;
}
private static unsafe ModCollection? GetCollection( IntPtr drawObject )
private static unsafe LinkedModCollection? GetCollection( IntPtr drawObject )
{
var parent = FindParent( drawObject, out var collection );
if( parent == null || collection == Penumbra.CollectionManager.Default )
if( parent == null || collection.ModCollection == Penumbra.CollectionManager.Default )
{
return null;
}
return collection.HasCache ? collection : null;
return collection.ModCollection.HasCache ? collection : null;
}
internal IEnumerable< KeyValuePair< Utf8String, ModCollection > > PathCollections
internal IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > PathCollections
=> _paths.Paths;
internal IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjectMap
internal IEnumerable< KeyValuePair< IntPtr, (LinkedModCollection, int) > > DrawObjectMap
=> DrawObjects.DrawObjects;
internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors

View file

@ -177,7 +177,7 @@ public partial class ConfigWindow
ImGui.TableNextColumn();
ImGui.TextUnformatted( name );
ImGui.TableNextColumn();
ImGui.TextUnformatted( c.Name );
ImGui.TextUnformatted( c.ModCollection.Name );
}
}
}
@ -195,7 +195,7 @@ public partial class ConfigWindow
ImGui.TableNextColumn();
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
ImGui.TableNextColumn();
ImGui.TextUnformatted( collection.Name );
ImGui.TextUnformatted( collection.ModCollection.Name );
}
}
}