Rework ResolveData.

This commit is contained in:
Ottermandias 2022-09-05 13:30:07 +02:00
parent 75182d094b
commit d12a3dd152
14 changed files with 199 additions and 170 deletions

View file

@ -67,12 +67,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
} }
Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections; Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections;
Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded; Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded;
} }
public unsafe void Dispose() public unsafe void Dispose()
{ {
Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded; Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded;
Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections; Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections;
_penumbra = null; _penumbra = null;
_lumina = null; _lumina = null;
@ -93,10 +93,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return Penumbra.Config.ModDirectory; return Penumbra.Config.ModDirectory;
} }
private unsafe void OnResourceLoaded( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, LinkedModCollection? resolveData ) private unsafe void OnResourceLoaded( ResourceHandle* _, Utf8GamePath originalPath, FullPath? manipulatedPath,
ResolveData resolveData )
{ {
if( resolveData == null ) return; GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(),
GameObjectResourceResolved?.Invoke( resolveData.AssociatedGameObject, originalPath.ToString(), manipulatedPath?.ToString() ?? originalPath.ToString() ); manipulatedPath?.ToString() ?? originalPath.ToString() );
} }
public event Action< string, bool >? ModDirectoryChanged public event Action< string, bool >? ModDirectoryChanged

View file

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

View file

@ -0,0 +1,46 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
namespace Penumbra.Collections;
public readonly struct ResolveData
{
public static readonly ResolveData Invalid = new(ModCollection.Empty);
public readonly ModCollection ModCollection;
public readonly IntPtr AssociatedGameObject;
public bool Valid
=> ModCollection != ModCollection.Empty;
public ResolveData()
{
ModCollection = ModCollection.Empty;
AssociatedGameObject = IntPtr.Zero;
}
public ResolveData( ModCollection collection, IntPtr gameObject )
{
ModCollection = collection;
AssociatedGameObject = gameObject;
}
public ResolveData( ModCollection collection )
: this( collection, IntPtr.Zero )
{ }
public override string ToString()
=> ModCollection.Name;
}
public static class ResolveDataExtensions
{
public static ResolveData ToResolveData( this ModCollection collection )
=> new(collection);
public static ResolveData ToResolveData( this ModCollection collection, IntPtr ptr )
=> new(collection, ptr);
public static unsafe ResolveData ToResolveData( this ModCollection collection, void* ptr )
=> new(collection, ( IntPtr )ptr);
}

View file

@ -48,7 +48,7 @@ public unsafe partial class ResourceLoader
public Utf8GamePath OriginalPath; public Utf8GamePath OriginalPath;
public FullPath ManipulatedPath; public FullPath ManipulatedPath;
public ResourceCategory Category; public ResourceCategory Category;
public object? ResolverInfo; public ResolveData ResolverInfo;
public ResourceType Extension; public ResourceType Extension;
} }
@ -59,18 +59,18 @@ public unsafe partial class ResourceLoader
public void EnableDebug() public void EnableDebug()
{ {
_decRefHook?.Enable(); _decRefHook.Enable();
ResourceLoaded += AddModifiedDebugInfo; ResourceLoaded += AddModifiedDebugInfo;
} }
public void DisableDebug() public void DisableDebug()
{ {
_decRefHook?.Disable(); _decRefHook.Disable();
ResourceLoaded -= AddModifiedDebugInfo; ResourceLoaded -= AddModifiedDebugInfo;
} }
private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
LinkedModCollection? resolverInfo ) ResolveData resolverInfo )
{ {
if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 ) if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 )
{ {
@ -244,7 +244,7 @@ public unsafe partial class ResourceLoader
private static void LogPath( Utf8GamePath path, bool synchronous ) private static void LogPath( Utf8GamePath path, bool synchronous )
=> PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" ); => PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, LinkedModCollection? _ ) private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData _ )
{ {
var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString(); 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})" ); PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );

View file

@ -114,18 +114,18 @@ public unsafe partial class ResourceLoader
// Use the default method of path replacement. // Use the default method of path replacement.
public static (FullPath?, LinkedModCollection?) DefaultResolver( Utf8GamePath path ) public static (FullPath?, ResolveData) DefaultResolver( Utf8GamePath path )
{ {
var resolved = Penumbra.CollectionManager.Default.ResolvePath( path ); var resolved = Penumbra.CollectionManager.Default.ResolvePath( path );
return ( resolved, new LinkedModCollection( Penumbra.CollectionManager.Default ) ); return ( resolved, Penumbra.CollectionManager.Default.ToResolveData() );
} }
// Try all resolve path subscribers or use the default replacer. // Try all resolve path subscribers or use the default replacer.
private (FullPath?, LinkedModCollection?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) private (FullPath?, ResolveData) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash )
{ {
if( !DoReplacements || _incMode.Value ) if( !DoReplacements || _incMode.Value )
{ {
return ( null, null ); return ( null, ResolveData.Invalid );
} }
path = path.ToLower(); path = path.ToLower();

View file

@ -117,9 +117,9 @@ public unsafe partial class ResourceLoader : IDisposable
// Event fired whenever a resource is returned. // Event fired whenever a resource is returned.
// If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource. // 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. // resolveData is additional data returned by the current ResolvePath function which can contain the collection and associated game object.
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
LinkedModCollection? resolveData ); ResolveData resolveData );
public event ResourceLoadedDelegate? ResourceLoaded; public event ResourceLoadedDelegate? ResourceLoaded;
@ -134,7 +134,7 @@ public unsafe partial class ResourceLoader : IDisposable
// Resolving goes through all subscribed functions in arbitrary order until one returns true, // Resolving goes through all subscribed functions in arbitrary order until one returns true,
// or uses default resolving if none return true. // or uses default resolving if none return true.
public delegate bool ResolvePathDelegate( Utf8GamePath path, ResourceCategory category, ResourceType type, int hash, public delegate bool ResolvePathDelegate( Utf8GamePath path, ResourceCategory category, ResourceType type, int hash,
out (FullPath?, LinkedModCollection?) ret ); out (FullPath?, ResolveData) ret );
public event ResolvePathDelegate? ResolvePathCustomization; public event ResolvePathDelegate? ResolvePathCustomization;

View file

@ -15,8 +15,8 @@ public unsafe partial class PathResolver
{ {
private readonly DrawObjectState _drawObjectState; private readonly DrawObjectState _drawObjectState;
private LinkedModCollection? _animationLoadCollection; private ResolveData _animationLoadData = ResolveData.Invalid;
private LinkedModCollection? _lastAvfxCollection; private ResolveData _lastAvfxData = ResolveData.Invalid;
public AnimationState( DrawObjectState drawObjectState ) public AnimationState( DrawObjectState drawObjectState )
{ {
@ -24,46 +24,48 @@ public unsafe partial class PathResolver
SignatureHelper.Initialise( this ); SignatureHelper.Initialise( this );
} }
public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out LinkedModCollection? collection ) public bool HandleFiles( ResourceType type, Utf8GamePath _, out ResolveData resolveData )
{ {
switch( type ) switch( type )
{ {
case ResourceType.Tmb: case ResourceType.Tmb:
case ResourceType.Pap: case ResourceType.Pap:
case ResourceType.Scd: case ResourceType.Scd:
if( _animationLoadCollection != null ) if( _animationLoadData.Valid )
{ {
collection = _animationLoadCollection; resolveData = _animationLoadData;
return true; return true;
} }
break; break;
case ResourceType.Avfx: case ResourceType.Avfx:
_lastAvfxCollection = _animationLoadCollection ?? new LinkedModCollection(Penumbra.CollectionManager.Default); _lastAvfxData = _animationLoadData.Valid
if( _animationLoadCollection != null ) ? _animationLoadData
: Penumbra.CollectionManager.Default.ToResolveData();
if( _animationLoadData.Valid )
{ {
collection = _animationLoadCollection; resolveData = _animationLoadData;
return true; return true;
} }
break; break;
case ResourceType.Atex: case ResourceType.Atex:
if( _lastAvfxCollection != null ) if( _lastAvfxData.Valid )
{ {
collection = _lastAvfxCollection; resolveData = _lastAvfxData;
return true; return true;
} }
if( _animationLoadCollection != null ) if( _animationLoadData.Valid )
{ {
collection = _animationLoadCollection; resolveData = _animationLoadData;
return true; return true;
} }
break; break;
} }
collection = null; resolveData = ResolveData.Invalid;
return false; return false;
} }
@ -107,7 +109,7 @@ public unsafe partial class PathResolver
private ulong LoadTimelineResourcesDetour( IntPtr timeline ) private ulong LoadTimelineResourcesDetour( IntPtr timeline )
{ {
ulong ret; ulong ret;
var old = _animationLoadCollection; var old = _animationLoadData;
try try
{ {
if( timeline != IntPtr.Zero ) if( timeline != IntPtr.Zero )
@ -117,11 +119,11 @@ public unsafe partial class PathResolver
if( idx >= 0 && idx < Dalamud.Objects.Length ) if( idx >= 0 && idx < Dalamud.Objects.Length )
{ {
var obj = Dalamud.Objects[ idx ]; var obj = Dalamud.Objects[ idx ];
_animationLoadCollection = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : null; _animationLoadData = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : ResolveData.Invalid;
} }
else else
{ {
_animationLoadCollection = null; _animationLoadData = ResolveData.Invalid;
} }
} }
} }
@ -130,7 +132,7 @@ public unsafe partial class PathResolver
ret = _loadTimelineResourcesHook.Original( timeline ); ret = _loadTimelineResourcesHook.Original( timeline );
} }
_animationLoadCollection = old; _animationLoadData = old;
return ret; return ret;
} }
@ -145,11 +147,14 @@ public unsafe partial class PathResolver
private void CharacterBaseLoadAnimationDetour( IntPtr drawObject ) private void CharacterBaseLoadAnimationDetour( IntPtr drawObject )
{ {
var last = _animationLoadCollection; var last = _animationLoadData;
_animationLoadCollection = _drawObjectState.LastCreatedCollection _animationLoadData = _drawObjectState.LastCreatedCollection.Valid
?? ( FindParent( drawObject, out var collection ) != null ? collection : new LinkedModCollection(Penumbra.CollectionManager.Default) ); ? _drawObjectState.LastCreatedCollection
: FindParent( drawObject, out var collection ) != null
? collection
: Penumbra.CollectionManager.Default.ToResolveData();
_characterBaseLoadAnimationHook.Original( drawObject ); _characterBaseLoadAnimationHook.Original( drawObject );
_animationLoadCollection = last; _animationLoadData = last;
} }
@ -160,10 +165,10 @@ public unsafe partial class PathResolver
private ulong LoadSomeAvfxDetour( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 ) private ulong LoadSomeAvfxDetour( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 )
{ {
var last = _animationLoadCollection; var last = _animationLoadData;
_animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); _animationLoadData = IdentifyCollection( ( GameObject* )gameObject );
var ret = _loadSomeAvfxHook.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 ); var ret = _loadSomeAvfxHook.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 );
_animationLoadCollection = last; _animationLoadData = last;
return ret; return ret;
} }
@ -177,18 +182,18 @@ public unsafe partial class PathResolver
private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 ) private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 )
{ {
var timelinePtr = a1 + 0x50; var timelinePtr = a1 + 0x50;
var last = _animationLoadCollection; var last = _animationLoadData;
if( timelinePtr != IntPtr.Zero ) if( timelinePtr != IntPtr.Zero )
{ {
var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 ); var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 );
if( actorIdx >= 0 && actorIdx < Dalamud.Objects.Length ) if( actorIdx >= 0 && actorIdx < Dalamud.Objects.Length )
{ {
_animationLoadCollection = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) ); _animationLoadData = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) );
} }
} }
_loadSomePapHook.Original( a1, a2, a3, a4 ); _loadSomePapHook.Original( a1, a2, a3, a4 );
_animationLoadCollection = last; _animationLoadData = last;
} }
// Seems to load character actions when zoning or changing class, maybe. // Seems to load character actions when zoning or changing class, maybe.
@ -197,10 +202,10 @@ public unsafe partial class PathResolver
private void SomeActionLoadDetour( IntPtr gameObject ) private void SomeActionLoadDetour( IntPtr gameObject )
{ {
var last = _animationLoadCollection; var last = _animationLoadData;
_animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject ); _animationLoadData = IdentifyCollection( ( GameObject* )gameObject );
_someActionLoadHook.Original( gameObject ); _someActionLoadHook.Original( gameObject );
_animationLoadCollection = last; _animationLoadData = last;
} }
[Signature( "E8 ?? ?? ?? ?? 44 84 A3", DetourName = nameof( SomeOtherAvfxDetour ) )] [Signature( "E8 ?? ?? ?? ?? 44 84 A3", DetourName = nameof( SomeOtherAvfxDetour ) )]
@ -208,11 +213,11 @@ public unsafe partial class PathResolver
private void SomeOtherAvfxDetour( IntPtr unk ) private void SomeOtherAvfxDetour( IntPtr unk )
{ {
var last = _animationLoadCollection; var last = _animationLoadData;
var gameObject = ( GameObject* )( unk - 0x8D0 ); var gameObject = ( GameObject* )( unk - 0x8D0 );
_animationLoadCollection = IdentifyCollection( gameObject ); _animationLoadData = IdentifyCollection( gameObject );
_someOtherAvfxHook.Original( unk ); _someOtherAvfxHook.Original( unk );
_animationLoadCollection = last; _animationLoadData = last;
} }
} }
} }

View file

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

View file

@ -40,7 +40,7 @@ public unsafe partial class PathResolver
return null; return null;
} }
var ui = ( AtkUnitBase* )addon; var ui = ( AtkUnitBase* )addon;
var nodeId = Dalamud.GameData.GetExcelSheet< Title >()?.GetRow( *_inspectTitleId )?.IsPrefix == true ? 2u : 6u; var nodeId = Dalamud.GameData.GetExcelSheet< Title >()?.GetRow( *_inspectTitleId )?.IsPrefix == true ? 2u : 6u;
var text = ( AtkTextNode* )ui->UldManager.SearchNodeById( nodeId ); var text = ( AtkTextNode* )ui->UldManager.SearchNodeById( nodeId );
@ -61,7 +61,8 @@ public unsafe partial class PathResolver
{ {
return null; return null;
} }
var data = *( byte** )( (byte*) agent + 0x28 );
var data = *( byte** )( ( byte* )agent + 0x28 );
if( data == null ) if( data == null )
{ {
return null; return null;
@ -139,11 +140,11 @@ public unsafe partial class PathResolver
} }
// Identify the correct collection for a GameObject by index and name. // Identify the correct collection for a GameObject by index and name.
private static LinkedModCollection IdentifyCollection( GameObject* gameObject ) private static ResolveData IdentifyCollection( GameObject* gameObject )
{ {
if( gameObject == null ) if( gameObject == null )
{ {
return new LinkedModCollection(Penumbra.CollectionManager.Default); return new ResolveData( Penumbra.CollectionManager.Default );
} }
try try
@ -153,8 +154,9 @@ public unsafe partial class PathResolver
// Actors are also not named. So use Yourself > Players > Racial > Default. // Actors are also not named. So use Yourself > Players > Racial > Default.
if( !Dalamud.ClientState.IsLoggedIn ) if( !Dalamud.ClientState.IsLoggedIn )
{ {
return new LinkedModCollection(gameObject, Penumbra.CollectionManager.ByType( CollectionType.Yourself ) var collection = Penumbra.CollectionManager.ByType( CollectionType.Yourself )
?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default )); ?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default );
return collection.ToResolveData( gameObject );
} }
else else
{ {
@ -163,7 +165,7 @@ public unsafe partial class PathResolver
&& gameObject->ObjectKind == ( byte )ObjectKind.EventNpc && gameObject->ObjectKind == ( byte )ObjectKind.EventNpc
&& gameObject->DataID is 1011832 or 1011021 ) // cf. "E8 ?? ?? ?? ?? 0F B6 F8 88 45", male or female retainer && gameObject->DataID is 1011832 or 1011021 ) // cf. "E8 ?? ?? ?? ?? 0F B6 F8 88 45", male or female retainer
{ {
return new LinkedModCollection((IntPtr)gameObject, Penumbra.CollectionManager.Default); return Penumbra.CollectionManager.Default.ToResolveData( gameObject );
} }
string? actorName = null; string? actorName = null;
@ -174,7 +176,7 @@ public unsafe partial class PathResolver
if( actorName.Length > 0 if( actorName.Length > 0
&& CollectionByActorName( actorName, out var actorCollection ) ) && CollectionByActorName( actorName, out var actorCollection ) )
{ {
return new LinkedModCollection(gameObject, actorCollection); return actorCollection.ToResolveData( gameObject );
} }
} }
@ -193,17 +195,18 @@ public unsafe partial class PathResolver
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString(); ?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
// First check temporary character collections, then the own configuration, then special collections. // First check temporary character collections, then the own configuration, then special collections.
return new LinkedModCollection(gameObject, CollectionByActorName( actualName, out var c ) var collection = CollectionByActorName( actualName, out var c )
? c ? c
: CollectionByActor( actualName, gameObject, out c ) : CollectionByActor( actualName, gameObject, out c )
? c ? c
: Penumbra.CollectionManager.Default); : Penumbra.CollectionManager.Default;
return collection.ToResolveData( gameObject );
} }
} }
catch( Exception e ) catch( Exception e )
{ {
PluginLog.Error( $"Error identifying collection:\n{e}" ); PluginLog.Error( $"Error identifying collection:\n{e}" );
return new LinkedModCollection(gameObject, Penumbra.CollectionManager.Default); return Penumbra.CollectionManager.Default.ToResolveData( gameObject );
} }
} }

View file

@ -21,7 +21,7 @@ public unsafe partial class PathResolver
{ {
private readonly PathState _paths; private readonly PathState _paths;
private LinkedModCollection? _mtrlCollection; private ResolveData _mtrlData = ResolveData.Invalid;
public MaterialState( PathState paths ) public MaterialState( PathState paths )
{ {
@ -30,30 +30,30 @@ public unsafe partial class PathResolver
} }
// Check specifically for shpk and tex files whether we are currently in a material load. // Check specifically for shpk and tex files whether we are currently in a material load.
public bool HandleSubFiles( ResourceType type, [NotNullWhen( true )] out LinkedModCollection? collection ) public bool HandleSubFiles( ResourceType type, out ResolveData collection )
{ {
if( _mtrlCollection != null && type is ResourceType.Tex or ResourceType.Shpk ) if( _mtrlData.Valid && type is ResourceType.Tex or ResourceType.Shpk )
{ {
collection = _mtrlCollection; collection = _mtrlData;
return true; return true;
} }
collection = null; collection = ResolveData.Invalid;
return false; return false;
} }
// Materials need to be set per collection so they can load their textures independently from each other. // Materials need to be set per collection so they can load their textures independently from each other.
public static void HandleCollection( LinkedModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved, public static void HandleCollection( ResolveData resolveData, string path, bool nonDefault, ResourceType type, FullPath? resolved,
out (FullPath?, LinkedModCollection?) data ) out (FullPath?, ResolveData) data )
{ {
if( nonDefault && type == ResourceType.Mtrl ) if( nonDefault && type == ResourceType.Mtrl )
{ {
var fullPath = new FullPath( $"|{collection.ModCollection.Name}_{collection.ModCollection.ChangeCounter}|{path}" ); var fullPath = new FullPath( $"|{resolveData.ModCollection.Name}_{resolveData.ModCollection.ChangeCounter}|{path}" );
data = ( fullPath, collection ); data = ( fullPath, resolveData );
} }
else else
{ {
data = ( resolved, collection ); data = ( resolved, resolveData );
} }
} }
@ -74,8 +74,8 @@ public unsafe partial class PathResolver
public void Dispose() public void Dispose()
{ {
Disable(); Disable();
_loadMtrlShpkHook?.Dispose(); _loadMtrlShpkHook.Dispose();
_loadMtrlTexHook?.Dispose(); _loadMtrlTexHook.Dispose();
} }
// We need to set the correct collection for the actual material path that is loaded // We need to set the correct collection for the actual material path that is loaded
@ -97,9 +97,9 @@ public unsafe partial class PathResolver
#if DEBUG #if DEBUG
PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path ); PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path );
#endif #endif
var objFromObjTable = Dalamud.Objects.FirstOrDefault( f => f.Name.TextValue == name ); var objFromObjTable = Dalamud.Objects.FirstOrDefault( f => f.Name.TextValue == name );
IntPtr gameObjAddr = objFromObjTable?.Address ?? IntPtr.Zero; var gameObjAddr = objFromObjTable?.Address ?? IntPtr.Zero;
_paths.SetCollection( gameObjAddr, path, collection ); _paths.SetCollection( gameObjAddr, path, collection );
} }
else else
@ -127,7 +127,7 @@ public unsafe partial class PathResolver
{ {
LoadMtrlHelper( mtrlResourceHandle ); LoadMtrlHelper( mtrlResourceHandle );
var ret = _loadMtrlTexHook.Original( mtrlResourceHandle ); var ret = _loadMtrlTexHook.Original( mtrlResourceHandle );
_mtrlCollection = null; _mtrlData = ResolveData.Invalid;
return ret; return ret;
} }
@ -139,7 +139,7 @@ public unsafe partial class PathResolver
{ {
LoadMtrlHelper( mtrlResourceHandle ); LoadMtrlHelper( mtrlResourceHandle );
var ret = _loadMtrlShpkHook.Original( mtrlResourceHandle ); var ret = _loadMtrlShpkHook.Original( mtrlResourceHandle );
_mtrlCollection = null; _mtrlData = ResolveData.Invalid;
return ret; return ret;
} }
@ -152,7 +152,7 @@ public unsafe partial class PathResolver
var mtrl = ( MtrlResource* )mtrlResourceHandle; var mtrl = ( MtrlResource* )mtrlResourceHandle;
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true ); var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
_mtrlCollection = _paths.TryGetValue( mtrlPath, out var c ) ? c : null; _mtrlData = _paths.TryGetValue( mtrlPath, out var c ) ? c : ResolveData.Invalid;
} }
} }
} }

View file

@ -79,8 +79,8 @@ public unsafe partial class PathResolver
private void OnModelLoadCompleteDetour( IntPtr drawObject ) private void OnModelLoadCompleteDetour( IntPtr drawObject )
{ {
var collection = GetCollection( drawObject ); var collection = GetResolveData( drawObject );
if( collection != null ) if( collection.Valid )
{ {
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
@ -106,8 +106,8 @@ public unsafe partial class PathResolver
return; return;
} }
var collection = GetCollection( drawObject ); var collection = GetResolveData( drawObject );
if( collection != null ) if( collection.Valid )
{ {
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection ); using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection ); using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
@ -212,24 +212,24 @@ public unsafe partial class PathResolver
return new MetaChanger( MetaManipulation.Type.Eqp ); return new MetaChanger( MetaManipulation.Type.Eqp );
} }
public static MetaChanger ChangeEqp( PathResolver resolver, IntPtr drawObject ) public static MetaChanger ChangeEqp( PathResolver _, IntPtr drawObject )
{ {
var collection = GetCollection( drawObject ); var resolveData = GetResolveData( drawObject );
if( collection != null ) if( resolveData.Valid )
{ {
return ChangeEqp( collection.ModCollection ); return ChangeEqp( resolveData.ModCollection );
} }
return new MetaChanger( MetaManipulation.Type.Unknown ); return new MetaChanger( MetaManipulation.Type.Unknown );
} }
// We only need to change anything if it is actually equipment here. // We only need to change anything if it is actually equipment here.
public static MetaChanger ChangeEqdp( PathResolver resolver, IntPtr drawObject, uint modelType ) public static MetaChanger ChangeEqdp( PathResolver _, IntPtr drawObject, uint modelType )
{ {
if( modelType < 10 ) if( modelType < 10 )
{ {
var collection = GetCollection( drawObject ); var collection = GetResolveData( drawObject );
if( collection != null ) if( collection.Valid )
{ {
return ChangeEqdp( collection.ModCollection ); return ChangeEqdp( collection.ModCollection );
} }
@ -246,10 +246,10 @@ public unsafe partial class PathResolver
public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject ) public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject )
{ {
var collection = GetCollection( drawObject ); var resolveData = GetResolveData( drawObject );
if( collection != null ) if( resolveData.Valid )
{ {
collection.ModCollection.SetGmpFiles(); resolveData.ModCollection.SetGmpFiles();
return new MetaChanger( MetaManipulation.Type.Gmp ); return new MetaChanger( MetaManipulation.Type.Gmp );
} }
@ -258,30 +258,30 @@ public unsafe partial class PathResolver
public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject ) public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject )
{ {
var collection = GetCollection( drawObject ); var resolveData = GetResolveData( drawObject );
if( collection != null ) if( resolveData.Valid )
{ {
collection.ModCollection.SetEstFiles(); resolveData.ModCollection.SetEstFiles();
return new MetaChanger( MetaManipulation.Type.Est ); return new MetaChanger( MetaManipulation.Type.Est );
} }
return new MetaChanger( MetaManipulation.Type.Unknown ); return new MetaChanger( MetaManipulation.Type.Unknown );
} }
public static MetaChanger ChangeCmp( GameObject* gameObject, out LinkedModCollection? collection ) public static MetaChanger ChangeCmp( GameObject* gameObject, out ResolveData resolveData )
{ {
if( gameObject != null ) if( gameObject != null )
{ {
collection = IdentifyCollection( gameObject ); resolveData = IdentifyCollection( gameObject );
if( collection.ModCollection != Penumbra.CollectionManager.Default && collection.ModCollection.HasCache ) if( resolveData.ModCollection != Penumbra.CollectionManager.Default && resolveData.ModCollection.HasCache )
{ {
collection.ModCollection.SetCmpFiles(); resolveData.ModCollection.SetCmpFiles();
return new MetaChanger( MetaManipulation.Type.Rsp ); return new MetaChanger( MetaManipulation.Type.Rsp );
} }
} }
else else
{ {
collection = null; resolveData = ResolveData.Invalid;
} }
return new MetaChanger( MetaManipulation.Type.Unknown ); return new MetaChanger( MetaManipulation.Type.Unknown );
@ -289,10 +289,10 @@ public unsafe partial class PathResolver
public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject ) public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject )
{ {
var collection = GetCollection( drawObject ); var resolveData = GetResolveData( drawObject );
if( collection != null ) if( resolveData.Valid )
{ {
collection.ModCollection.SetCmpFiles(); resolveData.ModCollection.SetCmpFiles();
return new MetaChanger( MetaManipulation.Type.Rsp ); return new MetaChanger( MetaManipulation.Type.Rsp );
} }

View file

@ -32,7 +32,7 @@ public unsafe partial class PathResolver
private readonly ResolverHooks _monster; private readonly ResolverHooks _monster;
// This map links files to their corresponding collection, if it is non-default. // This map links files to their corresponding collection, if it is non-default.
private readonly ConcurrentDictionary< Utf8String, LinkedModCollection > _pathCollections = new(); private readonly ConcurrentDictionary< Utf8String, ResolveData > _pathCollections = new();
public PathState( PathResolver parent ) public PathState( PathResolver parent )
{ {
@ -70,13 +70,13 @@ public unsafe partial class PathResolver
public int Count public int Count
=> _pathCollections.Count; => _pathCollections.Count;
public IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > Paths public IEnumerable< KeyValuePair< Utf8String, ResolveData > > Paths
=> _pathCollections; => _pathCollections;
public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection ) public bool TryGetValue( Utf8String path, out ResolveData collection )
=> _pathCollections.TryGetValue( path, out collection ); => _pathCollections.TryGetValue( path, out collection );
public bool Consume( Utf8String path, [NotNullWhen( true )] out LinkedModCollection? collection ) public bool Consume( Utf8String path, out ResolveData collection )
=> _pathCollections.TryRemove( path, out collection ); => _pathCollections.TryRemove( path, out collection );
// Just add or remove the resolved path. // Just add or remove the resolved path.
@ -98,11 +98,11 @@ public unsafe partial class PathResolver
{ {
if( _pathCollections.ContainsKey( path ) || path.IsOwned ) if( _pathCollections.ContainsKey( path ) || path.IsOwned )
{ {
_pathCollections[ path ] = new LinkedModCollection(gameObject, collection); _pathCollections[ path ] = collection.ToResolveData( gameObject );
} }
else else
{ {
_pathCollections[ path.Clone() ] = new LinkedModCollection(gameObject, collection); _pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject );
} }
} }
} }

View file

@ -245,7 +245,7 @@ public partial class PathResolver
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 != null ) 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 );
} }

View file

@ -40,7 +40,7 @@ public partial class PathResolver : IDisposable
} }
// The modified resolver that handles game path resolving. // The modified resolver that handles game path resolving.
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, LinkedModCollection?) data ) private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data )
{ {
// Check if the path was marked for a specific collection, // 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, // or if it is a file loaded by a material, and if we are currently in a material load,
@ -48,23 +48,23 @@ public partial class PathResolver : IDisposable
// If not use the default collection. // If not use the default collection.
// We can remove paths after they have actually been loaded. // We can remove paths after they have actually been loaded.
// A potential next request will add the path anew. // A potential next request will add the path anew.
var nonDefault = _materials.HandleSubFiles( type, out var collection ) var nonDefault = _materials.HandleSubFiles( type, out var resolveData )
|| _paths.Consume( gamePath.Path, out collection ) || _paths.Consume( gamePath.Path, out resolveData )
|| _animations.HandleFiles( type, gamePath, out collection ) || _animations.HandleFiles( type, gamePath, out resolveData )
|| DrawObjects.HandleDecalFile( type, gamePath, out collection ); || DrawObjects.HandleDecalFile( type, gamePath, out resolveData );
if( !nonDefault || collection == null ) if( !nonDefault || !resolveData.Valid )
{ {
collection = new LinkedModCollection(Penumbra.CollectionManager.Default); resolveData = Penumbra.CollectionManager.Default.ToResolveData();
} }
// Resolve using character/default collection first, otherwise forced, as usual. // Resolve using character/default collection first, otherwise forced, as usual.
var resolved = collection.ModCollection.ResolvePath( gamePath ); var resolved = resolveData.ModCollection.ResolvePath( gamePath );
// Since mtrl files load their files separately, we need to add the new, resolved path // 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. // so that the functions loading tex and shpk can find that path and use its collection.
// We also need to handle defaulted materials against a non-default collection. // We also need to handle defaulted materials against a non-default collection.
var path = resolved == null ? gamePath.Path.ToString() : resolved.Value.FullName; var path = resolved == null ? gamePath.Path.ToString() : resolved.Value.FullName;
MaterialState.HandleCollection( collection, path, nonDefault, type, resolved, out data ); MaterialState.HandleCollection( resolveData, path, nonDefault, type, resolved, out data );
return true; return true;
} }
@ -117,50 +117,50 @@ public partial class PathResolver : IDisposable
_materials.Dispose(); _materials.Dispose();
} }
public static unsafe (IntPtr, LinkedModCollection) IdentifyDrawObject( IntPtr drawObject ) public static unsafe (IntPtr, ResolveData) IdentifyDrawObject( IntPtr drawObject )
{ {
var parent = FindParent( drawObject, out var collection ); var parent = FindParent( drawObject, out var resolveData );
return ( ( IntPtr )parent, collection ); return ( ( IntPtr )parent, resolveData );
} }
public int CutsceneActor( int idx ) public int CutsceneActor( int idx )
=> Cutscenes.GetParentIndex( idx ); => Cutscenes.GetParentIndex( idx );
// Use the stored information to find the GameObject and Collection linked to a DrawObject. // Use the stored information to find the GameObject and Collection linked to a DrawObject.
public static unsafe GameObject* FindParent( IntPtr drawObject, out LinkedModCollection collection ) public static unsafe GameObject* FindParent( IntPtr drawObject, out ResolveData resolveData )
{ {
if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) ) if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) )
{ {
collection = data.Item1; resolveData = data.Item1;
return gameObject; return gameObject;
} }
if( DrawObjects.LastGameObject != null if( DrawObjects.LastGameObject != null
&& ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) ) && ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
{ {
collection = IdentifyCollection( DrawObjects.LastGameObject ); resolveData = IdentifyCollection( DrawObjects.LastGameObject );
return DrawObjects.LastGameObject; return DrawObjects.LastGameObject;
} }
collection = IdentifyCollection( null ); resolveData = IdentifyCollection( null );
return null; return null;
} }
private static unsafe LinkedModCollection? GetCollection( IntPtr drawObject ) private static unsafe ResolveData GetResolveData( IntPtr drawObject )
{ {
var parent = FindParent( drawObject, out var collection ); var parent = FindParent( drawObject, out var resolveData );
if( parent == null || collection.ModCollection == Penumbra.CollectionManager.Default ) if( parent == null || resolveData.ModCollection == Penumbra.CollectionManager.Default )
{ {
return null; return ResolveData.Invalid;
} }
return collection.ModCollection.HasCache ? collection : null; return resolveData.ModCollection.HasCache ? resolveData : ResolveData.Invalid;
} }
internal IEnumerable< KeyValuePair< Utf8String, LinkedModCollection > > PathCollections internal IEnumerable< KeyValuePair< Utf8String, ResolveData > > PathCollections
=> _paths.Paths; => _paths.Paths;
internal IEnumerable< KeyValuePair< IntPtr, (LinkedModCollection, int) > > DrawObjectMap internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap
=> DrawObjects.DrawObjects; => DrawObjects.DrawObjects;
internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors