Merge branch 'pr/n248_master'

This commit is contained in:
Ottermandias 2022-09-05 14:10:05 +02:00
commit 1ba38a7704
17 changed files with 336 additions and 191 deletions

View file

@ -27,6 +27,7 @@ public delegate void CreatingCharacterBaseDelegate( IntPtr gameObject, ModCollec
IntPtr equipData ); IntPtr equipData );
public delegate void CreatedCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr drawObject ); public delegate void CreatedCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr drawObject );
public delegate void GameObjectResourceResolvedDelegate( IntPtr gameObject, string gamePath, string localPath );
public enum PenumbraApiEc public enum PenumbraApiEc
{ {
@ -79,6 +80,10 @@ public interface IPenumbraApi : IPenumbraApiBase
// so you can apply flag changes after finishing. // so you can apply flag changes after finishing.
public event CreatedCharacterBaseDelegate? CreatedCharacterBase; public event CreatedCharacterBaseDelegate? CreatedCharacterBase;
// Triggered whenever a resource is redirected by Penumbra for a specific, identified game object.
// Does not trigger if the resource is not requested for a known game object.
public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved;
// Queue redrawing of all actors of the given name with the given RedrawType. // Queue redrawing of all actors of the given name with the given RedrawType.
public void RedrawObject( string name, RedrawType setting ); public void RedrawObject( string name, RedrawType setting );

View file

@ -33,9 +33,12 @@ public class IpcTester : IDisposable
private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged; private readonly ICallGateSubscriber< ModSettingChange, string, string, bool, object? > _settingChanged;
private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? > _characterBaseCreating; private readonly ICallGateSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? > _characterBaseCreating;
private readonly ICallGateSubscriber< IntPtr, string, IntPtr, object? > _characterBaseCreated; private readonly ICallGateSubscriber< IntPtr, string, IntPtr, object? > _characterBaseCreated;
private readonly ICallGateSubscriber< IntPtr, string, string, object? > _gameObjectResourcePathResolved;
private readonly List< DateTimeOffset > _initializedList = new(); private readonly List< DateTimeOffset > _initializedList = new();
private readonly List< DateTimeOffset > _disposedList = new(); private readonly List< DateTimeOffset > _disposedList = new();
private bool _subscribed = false;
public IpcTester( DalamudPluginInterface pi, PenumbraIpc ipc ) public IpcTester( DalamudPluginInterface pi, PenumbraIpc ipc )
{ {
@ -51,31 +54,51 @@ public class IpcTester : IDisposable
_characterBaseCreating = _characterBaseCreating =
_pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase ); _pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase );
_characterBaseCreated = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, object? >( PenumbraIpc.LabelProviderCreatedCharacterBase ); _characterBaseCreated = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, object? >( PenumbraIpc.LabelProviderCreatedCharacterBase );
_initialized.Subscribe( AddInitialized ); _gameObjectResourcePathResolved =
_disposed.Subscribe( AddDisposed ); _pi.GetIpcSubscriber< IntPtr, string, string, object? >( PenumbraIpc.LabelProviderGameObjectResourcePathResolved );
_redrawn.Subscribe( SetLastRedrawn ); }
_preSettingsDraw.Subscribe( UpdateLastDrawnMod );
_postSettingsDraw.Subscribe( UpdateLastDrawnMod ); private void SubscribeEvents()
_settingChanged.Subscribe( UpdateLastModSetting ); {
_characterBaseCreating.Subscribe( UpdateLastCreated ); if( !_subscribed )
_characterBaseCreated.Subscribe( UpdateLastCreated2 ); {
_modDirectoryChanged.Subscribe( UpdateModDirectoryChanged );
_initialized.Subscribe( AddInitialized );
_disposed.Subscribe( AddDisposed );
_redrawn.Subscribe( SetLastRedrawn );
_preSettingsDraw.Subscribe( UpdateLastDrawnMod );
_postSettingsDraw.Subscribe( UpdateLastDrawnMod );
_settingChanged.Subscribe( UpdateLastModSetting );
_characterBaseCreating.Subscribe( UpdateLastCreated );
_characterBaseCreated.Subscribe( UpdateLastCreated2 );
_modDirectoryChanged.Subscribe( UpdateModDirectoryChanged );
_gameObjectResourcePathResolved.Subscribe( UpdateGameObjectResourcePath );
_subscribed = true;
}
}
public void UnsubscribeEvents()
{
if( _subscribed )
{
_initialized.Unsubscribe( AddInitialized );
_disposed.Unsubscribe( AddDisposed );
_redrawn.Subscribe( SetLastRedrawn );
_tooltip?.Unsubscribe( AddedTooltip );
_click?.Unsubscribe( AddedClick );
_preSettingsDraw.Unsubscribe( UpdateLastDrawnMod );
_postSettingsDraw.Unsubscribe( UpdateLastDrawnMod );
_settingChanged.Unsubscribe( UpdateLastModSetting );
_characterBaseCreating.Unsubscribe( UpdateLastCreated );
_characterBaseCreated.Unsubscribe( UpdateLastCreated2 );
_modDirectoryChanged.Unsubscribe( UpdateModDirectoryChanged );
_gameObjectResourcePathResolved.Unsubscribe( UpdateGameObjectResourcePath );
_subscribed = false;
}
} }
public void Dispose() public void Dispose()
{ => UnsubscribeEvents();
_initialized.Unsubscribe( AddInitialized );
_disposed.Unsubscribe( AddDisposed );
_redrawn.Subscribe( SetLastRedrawn );
_tooltip?.Unsubscribe( AddedTooltip );
_click?.Unsubscribe( AddedClick );
_preSettingsDraw.Unsubscribe( UpdateLastDrawnMod );
_postSettingsDraw.Unsubscribe( UpdateLastDrawnMod );
_settingChanged.Unsubscribe( UpdateLastModSetting );
_characterBaseCreating.Unsubscribe( UpdateLastCreated );
_characterBaseCreated.Unsubscribe( UpdateLastCreated2 );
_modDirectoryChanged.Unsubscribe( UpdateModDirectoryChanged );
}
private void AddInitialized() private void AddInitialized()
=> _initializedList.Add( DateTimeOffset.UtcNow ); => _initializedList.Add( DateTimeOffset.UtcNow );
@ -87,6 +110,7 @@ public class IpcTester : IDisposable
{ {
try try
{ {
SubscribeEvents();
DrawAvailable(); DrawAvailable();
DrawGeneral(); DrawGeneral();
DrawResolve(); DrawResolve();
@ -224,6 +248,10 @@ public class IpcTester : IDisposable
private string _lastCreatedGameObjectName = string.Empty; private string _lastCreatedGameObjectName = string.Empty;
private IntPtr _lastCreatedDrawObject = IntPtr.Zero; private IntPtr _lastCreatedDrawObject = IntPtr.Zero;
private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue; private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
private string _lastResolvedGamePath = string.Empty;
private string _lastResolvedFullPath = string.Empty;
private string _lastResolvedObject = string.Empty;
private DateTimeOffset _lastResolvedGamePathTime = DateTimeOffset.MaxValue;
private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 ) private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 )
{ {
@ -241,6 +269,15 @@ public class IpcTester : IDisposable
_lastCreatedDrawObject = drawObject; _lastCreatedDrawObject = drawObject;
} }
private unsafe void UpdateGameObjectResourcePath( IntPtr gameObject, string gamePath, string fullPath )
{
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
_lastResolvedObject = obj != null ? new Utf8String( obj->GetName() ).ToString() : "Unknown";
_lastResolvedGamePath = gamePath;
_lastResolvedFullPath = fullPath;
_lastResolvedGamePathTime = DateTimeOffset.Now;
}
private void DrawResolve() private void DrawResolve()
{ {
using var _ = ImRaii.TreeNode( "Resolve IPC" ); using var _ = ImRaii.TreeNode( "Resolve IPC" );
@ -336,6 +373,13 @@ public class IpcTester : IDisposable
? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" ? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}"
: $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" ); : $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}" );
} }
DrawIntro( PenumbraIpc.LabelProviderGameObjectResourcePathResolved, "Last GamePath resolved" );
if( _lastResolvedGamePathTime < DateTimeOffset.Now )
{
ImGui.TextUnformatted(
$"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}" );
}
} }
private string _redrawName = string.Empty; private string _redrawName = string.Empty;

View file

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

View file

@ -274,15 +274,16 @@ public partial class PenumbraIpc
public partial class PenumbraIpc public partial class PenumbraIpc
{ {
public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath"; public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath";
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath"; public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath"; public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo"; public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex"; public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex";
public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath"; public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath";
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath"; public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase"; public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase"; public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase";
public const string LabelProviderGameObjectResourcePathResolved = "Penumbra.GameObjectResourcePathResolved";
internal ICallGateProvider< string, string >? ProviderResolveDefault; internal ICallGateProvider< string, string >? ProviderResolveDefault;
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter; internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
@ -293,6 +294,7 @@ public partial class PenumbraIpc
internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer; internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer;
internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase; internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase;
internal ICallGateProvider< IntPtr, string, IntPtr, object? >? ProviderCreatedCharacterBase; internal ICallGateProvider< IntPtr, string, IntPtr, object? >? ProviderCreatedCharacterBase;
internal ICallGateProvider< IntPtr, string, string, object? >? ProviderGameObjectResourcePathResolved;
private void InitializeResolveProviders( DalamudPluginInterface pi ) private void InitializeResolveProviders( DalamudPluginInterface pi )
{ {
@ -387,6 +389,22 @@ public partial class PenumbraIpc
{ {
PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatedCharacterBase}:\n{e}" ); PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatedCharacterBase}:\n{e}" );
} }
try
{
ProviderGameObjectResourcePathResolved =
pi.GetIpcProvider< IntPtr, string, string, object? >( LabelProviderGameObjectResourcePathResolved );
Api.GameObjectResourceResolved += GameObjectResourceResolvedEvent;
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderGameObjectResourcePathResolved}:\n{e}" );
}
}
private void GameObjectResourceResolvedEvent( IntPtr gameObject, string gamePath, string localPath )
{
ProviderGameObjectResourcePathResolved?.SendMessage( gameObject, gamePath, localPath );
} }
private void DisposeResolveProviders() private void DisposeResolveProviders()
@ -397,8 +415,9 @@ public partial class PenumbraIpc
ProviderResolveCharacter?.UnregisterFunc(); ProviderResolveCharacter?.UnregisterFunc();
ProviderReverseResolvePath?.UnregisterFunc(); ProviderReverseResolvePath?.UnregisterFunc();
ProviderReverseResolvePathPlayer?.UnregisterFunc(); ProviderReverseResolvePathPlayer?.UnregisterFunc();
Api.CreatingCharacterBase -= CreatingCharacterBaseEvent; Api.CreatingCharacterBase -= CreatingCharacterBaseEvent;
Api.CreatedCharacterBase -= CreatedCharacterBaseEvent; Api.CreatedCharacterBase -= CreatedCharacterBaseEvent;
Api.GameObjectResourceResolved -= GameObjectResourceResolvedEvent;
} }
private void CreatingCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr modelId, IntPtr customize, IntPtr equipData ) private void CreatingCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr modelId, IntPtr customize, IntPtr equipData )

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

@ -7,6 +7,7 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.STD; using FFXIVClientStructs.STD;
using Penumbra.Collections;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -47,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;
} }
@ -58,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,
object? resolverInfo ) ResolveData resolverInfo )
{ {
if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 ) if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 )
{ {
@ -243,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, object? _ ) 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

@ -9,6 +9,7 @@ using Dalamud.Hooking;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
@ -113,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?, object?) 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, null ); 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?, object?) 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

@ -2,6 +2,7 @@ using System;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
@ -116,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,
object? resolveData ); ResolveData resolveData );
public event ResourceLoadedDelegate? ResourceLoaded; 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, // 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?, object?) 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 ModCollection? _animationLoadCollection; private ResolveData _animationLoadData = ResolveData.Invalid;
private ModCollection? _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 ModCollection? 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 ?? 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 : 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, (ModCollection, 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 (ModCollection, 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 ModCollection? 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 ModCollection? 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 ModCollection? 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, (ModCollection, int) > _drawObjectToObject = new(); private readonly Dictionary< IntPtr, (ResolveData, int) > _drawObjectToObject = new();
private ModCollection? _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.
@ -141,14 +141,14 @@ public unsafe partial class PathResolver
if( LastGameObject != null ) if( LastGameObject != null )
{ {
var modelPtr = &a; 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 ); var ret = _characterBaseCreateHook.Original( a, b, c, d );
if( LastGameObject != null ) if( LastGameObject != null )
{ {
_drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex ); _drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex );
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ret ); CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret );
} }
return ret; return ret;

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 ModCollection IdentifyCollection( GameObject* gameObject ) private static ResolveData IdentifyCollection( GameObject* gameObject )
{ {
if( gameObject == null ) if( gameObject == null )
{ {
return 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 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 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 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 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 Penumbra.CollectionManager.Default; return Penumbra.CollectionManager.Default.ToResolveData( gameObject );
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
@ -20,7 +21,7 @@ public unsafe partial class PathResolver
{ {
private readonly PathState _paths; private readonly PathState _paths;
private ModCollection? _mtrlCollection; private ResolveData _mtrlData = ResolveData.Invalid;
public MaterialState( PathState paths ) public MaterialState( PathState paths )
{ {
@ -29,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 ModCollection? 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( ModCollection 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?, object?) data ) out (FullPath?, ResolveData) data )
{ {
if( nonDefault && type == ResourceType.Mtrl ) if( nonDefault && type == ResourceType.Mtrl )
{ {
var fullPath = new FullPath( $"|{collection.Name}_{collection.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 );
} }
} }
@ -73,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
@ -96,7 +97,10 @@ 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
_paths.SetCollection( path, collection );
var objFromObjTable = Dalamud.Objects.FirstOrDefault( f => f.Name.TextValue == name );
var gameObjAddr = objFromObjTable?.Address ?? IntPtr.Zero;
_paths.SetCollection( gameObjAddr, path, collection );
} }
else else
{ {
@ -123,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;
} }
@ -135,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;
} }
@ -148,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,11 +79,11 @@ 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 ); using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
using var eqdp = MetaChanger.ChangeEqdp( collection ); using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
_onModelLoadCompleteHook.Original.Invoke( drawObject ); _onModelLoadCompleteHook.Original.Invoke( drawObject );
} }
else else
@ -106,11 +106,11 @@ 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 ); using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
using var eqdp = MetaChanger.ChangeEqdp( collection ); using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
_updateModelsHook.Original.Invoke( drawObject ); _updateModelsHook.Original.Invoke( drawObject );
} }
else else
@ -212,26 +212,26 @@ 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 ); 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 ); 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.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.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 ModCollection? collection ) public static MetaChanger ChangeCmp( GameObject* gameObject, out ResolveData resolveData )
{ {
if( gameObject != null ) if( gameObject != null )
{ {
collection = IdentifyCollection( gameObject ); resolveData = IdentifyCollection( gameObject );
if( collection != Penumbra.CollectionManager.Default && collection.HasCache ) if( resolveData.ModCollection != Penumbra.CollectionManager.Default && resolveData.ModCollection.HasCache )
{ {
collection.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.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, ModCollection > _pathCollections = new(); private readonly ConcurrentDictionary< Utf8String, ResolveData > _pathCollections = new();
public PathState( PathResolver parent ) public PathState( PathResolver parent )
{ {
@ -70,18 +70,18 @@ public unsafe partial class PathResolver
public int Count public int Count
=> _pathCollections.Count; => _pathCollections.Count;
public IEnumerable< KeyValuePair< Utf8String, ModCollection > > Paths public IEnumerable< KeyValuePair< Utf8String, ResolveData > > Paths
=> _pathCollections; => _pathCollections;
public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out ModCollection? 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 ModCollection? 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.
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] [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 ) if( path == IntPtr.Zero )
{ {
@ -89,20 +89,20 @@ public unsafe partial class PathResolver
} }
var gamePath = new Utf8String( ( byte* )path ); var gamePath = new Utf8String( ( byte* )path );
SetCollection( gamePath, collection ); SetCollection( gameObject, gamePath, collection );
return path; return path;
} }
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary. // 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 ) if( _pathCollections.ContainsKey( path ) || path.IsOwned )
{ {
_pathCollections[ path ] = collection; _pathCollections[ path ] = collection.ToResolveData( gameObject );
} }
else else
{ {
_pathCollections[ path.Clone() ] = collection; _pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject );
} }
} }
} }

View file

@ -2,6 +2,7 @@ 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 Penumbra.Collections;
namespace Penumbra.Interop.Resolver; namespace Penumbra.Interop.Resolver;
@ -226,9 +227,9 @@ 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( FindParent( drawObject, out var collection ) == null => _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _) ?? IntPtr.Zero, FindParent( drawObject, out var collection ) == null
? Penumbra.CollectionManager.Default ? Penumbra.CollectionManager.Default
: collection, path ); : 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,20 +240,20 @@ 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( collection, 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 != null ) if( parentCollection.Valid )
{ {
return _parent._paths.ResolvePath( parentCollection, 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( parent == null return _parent._paths.ResolvePath( (IntPtr?)parent ?? IntPtr.Zero, parent == null
? Penumbra.CollectionManager.Default ? 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. // 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?, 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 = 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.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, ModCollection) 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 ModCollection 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 ModCollection? 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 == Penumbra.CollectionManager.Default ) if( parent == null || resolveData.ModCollection == Penumbra.CollectionManager.Default )
{ {
return null; return ResolveData.Invalid;
} }
return collection.HasCache ? collection : null; return resolveData.ModCollection.HasCache ? resolveData : ResolveData.Invalid;
} }
internal IEnumerable< KeyValuePair< Utf8String, ModCollection > > PathCollections internal IEnumerable< KeyValuePair< Utf8String, ResolveData > > PathCollections
=> _paths.Paths; => _paths.Paths;
internal IEnumerable< KeyValuePair< IntPtr, (ModCollection, 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

View file

@ -177,7 +177,7 @@ public partial class ConfigWindow
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( name ); ImGui.TextUnformatted( name );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( c.Name ); ImGui.TextUnformatted( c.ModCollection.Name );
} }
} }
} }
@ -195,7 +195,7 @@ public partial class ConfigWindow
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( collection.Name ); ImGui.TextUnformatted( collection.ModCollection.Name );
} }
} }
} }
@ -422,6 +422,7 @@ public partial class ConfigWindow
{ {
if( !ImGui.CollapsingHeader( "IPC" ) ) if( !ImGui.CollapsingHeader( "IPC" ) )
{ {
_window._penumbra.Ipc.Tester.UnsubscribeEvents();
return; return;
} }