mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-08 00:44:41 +01:00
Merge branch 'pr/n248_master'
This commit is contained in:
commit
1ba38a7704
17 changed files with 336 additions and 191 deletions
|
|
@ -27,6 +27,7 @@ public delegate void CreatingCharacterBaseDelegate( IntPtr gameObject, ModCollec
|
|||
IntPtr equipData );
|
||||
|
||||
public delegate void CreatedCharacterBaseDelegate( IntPtr gameObject, ModCollection collection, IntPtr drawObject );
|
||||
public delegate void GameObjectResourceResolvedDelegate( IntPtr gameObject, string gamePath, string localPath );
|
||||
|
||||
public enum PenumbraApiEc
|
||||
{
|
||||
|
|
@ -79,6 +80,10 @@ public interface IPenumbraApi : IPenumbraApiBase
|
|||
// so you can apply flag changes after finishing.
|
||||
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.
|
||||
public void RedrawObject( string name, RedrawType setting );
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,12 @@ public class IpcTester : IDisposable
|
|||
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, object? > _characterBaseCreated;
|
||||
private readonly ICallGateSubscriber< IntPtr, string, string, object? > _gameObjectResourcePathResolved;
|
||||
|
||||
private readonly List< DateTimeOffset > _initializedList = new();
|
||||
private readonly List< DateTimeOffset > _disposedList = new();
|
||||
private bool _subscribed = false;
|
||||
|
||||
|
||||
public IpcTester( DalamudPluginInterface pi, PenumbraIpc ipc )
|
||||
{
|
||||
|
|
@ -51,31 +54,51 @@ public class IpcTester : IDisposable
|
|||
_characterBaseCreating =
|
||||
_pi.GetIpcSubscriber< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >( PenumbraIpc.LabelProviderCreatingCharacterBase );
|
||||
_characterBaseCreated = _pi.GetIpcSubscriber< IntPtr, string, IntPtr, object? >( PenumbraIpc.LabelProviderCreatedCharacterBase );
|
||||
_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 =
|
||||
_pi.GetIpcSubscriber< IntPtr, string, string, object? >( PenumbraIpc.LabelProviderGameObjectResourcePathResolved );
|
||||
}
|
||||
|
||||
private void SubscribeEvents()
|
||||
{
|
||||
if( !_subscribed )
|
||||
{
|
||||
|
||||
_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()
|
||||
{
|
||||
_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 );
|
||||
}
|
||||
=> UnsubscribeEvents();
|
||||
|
||||
private void AddInitialized()
|
||||
=> _initializedList.Add( DateTimeOffset.UtcNow );
|
||||
|
|
@ -87,6 +110,7 @@ public class IpcTester : IDisposable
|
|||
{
|
||||
try
|
||||
{
|
||||
SubscribeEvents();
|
||||
DrawAvailable();
|
||||
DrawGeneral();
|
||||
DrawResolve();
|
||||
|
|
@ -224,6 +248,10 @@ public class IpcTester : IDisposable
|
|||
private string _lastCreatedGameObjectName = string.Empty;
|
||||
private IntPtr _lastCreatedDrawObject = IntPtr.Zero;
|
||||
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 )
|
||||
{
|
||||
|
|
@ -241,6 +269,15 @@ public class IpcTester : IDisposable
|
|||
_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()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode( "Resolve IPC" );
|
||||
|
|
@ -336,6 +373,13 @@ public class IpcTester : IDisposable
|
|||
? $"0x{_lastCreatedDrawObject:X} 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;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Penumbra.Collections;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Resolver;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ namespace Penumbra.Api;
|
|||
public class PenumbraApi : IDisposable, IPenumbraApi
|
||||
{
|
||||
public (int, int) ApiVersion
|
||||
=> ( 4, 12 );
|
||||
=> ( 4, 13 );
|
||||
|
||||
private Penumbra? _penumbra;
|
||||
private Lumina.GameData? _lumina;
|
||||
|
|
@ -54,7 +55,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
public bool Valid
|
||||
=> _penumbra != null;
|
||||
|
||||
public PenumbraApi( Penumbra penumbra )
|
||||
public unsafe PenumbraApi( Penumbra penumbra )
|
||||
{
|
||||
_penumbra = penumbra;
|
||||
_lumina = ( Lumina.GameData? )Dalamud.GameData.GetType()
|
||||
|
|
@ -66,10 +67,12 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
}
|
||||
|
||||
Penumbra.CollectionManager.CollectionChanged += SubscribeToNewCollections;
|
||||
Penumbra.ResourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
Penumbra.ResourceLoader.ResourceLoaded -= OnResourceLoaded;
|
||||
Penumbra.CollectionManager.CollectionChanged -= SubscribeToNewCollections;
|
||||
_penumbra = null;
|
||||
_lumina = null;
|
||||
|
|
@ -90,6 +93,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
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
|
||||
{
|
||||
add => Penumbra.ModManager.ModDirectoryChanged += value;
|
||||
|
|
@ -103,6 +116,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
}
|
||||
|
||||
public event ChangedItemHover? ChangedItemTooltip;
|
||||
public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved;
|
||||
|
||||
public void RedrawObject( int tableIndex, RedrawType setting )
|
||||
{
|
||||
|
|
@ -232,7 +246,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
{
|
||||
CheckInitialized();
|
||||
var (obj, collection) = PathResolver.IdentifyDrawObject( drawObject );
|
||||
return ( obj, collection.Name );
|
||||
return ( obj, collection.ModCollection.Name );
|
||||
}
|
||||
|
||||
public int GetCutsceneParentIndex( int actor )
|
||||
|
|
|
|||
|
|
@ -274,15 +274,16 @@ public partial class PenumbraIpc
|
|||
|
||||
public partial class PenumbraIpc
|
||||
{
|
||||
public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath";
|
||||
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
|
||||
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
|
||||
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
|
||||
public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex";
|
||||
public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath";
|
||||
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
|
||||
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
|
||||
public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase";
|
||||
public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath";
|
||||
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
|
||||
public const string LabelProviderResolvePlayer = "Penumbra.ResolvePlayerPath";
|
||||
public const string LabelProviderGetDrawObjectInfo = "Penumbra.GetDrawObjectInfo";
|
||||
public const string LabelProviderGetCutsceneParentIndex = "Penumbra.GetCutsceneParentIndex";
|
||||
public const string LabelProviderReverseResolvePath = "Penumbra.ReverseResolvePath";
|
||||
public const string LabelProviderReverseResolvePlayerPath = "Penumbra.ReverseResolvePlayerPath";
|
||||
public const string LabelProviderCreatingCharacterBase = "Penumbra.CreatingCharacterBase";
|
||||
public const string LabelProviderCreatedCharacterBase = "Penumbra.CreatedCharacterBase";
|
||||
public const string LabelProviderGameObjectResourcePathResolved = "Penumbra.GameObjectResourcePathResolved";
|
||||
|
||||
internal ICallGateProvider< string, string >? ProviderResolveDefault;
|
||||
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
|
||||
|
|
@ -293,6 +294,7 @@ public partial class PenumbraIpc
|
|||
internal ICallGateProvider< string, string[] >? ProviderReverseResolvePathPlayer;
|
||||
internal ICallGateProvider< IntPtr, string, IntPtr, IntPtr, IntPtr, object? >? ProviderCreatingCharacterBase;
|
||||
internal ICallGateProvider< IntPtr, string, IntPtr, object? >? ProviderCreatedCharacterBase;
|
||||
internal ICallGateProvider< IntPtr, string, string, object? >? ProviderGameObjectResourcePathResolved;
|
||||
|
||||
private void InitializeResolveProviders( DalamudPluginInterface pi )
|
||||
{
|
||||
|
|
@ -387,6 +389,22 @@ public partial class PenumbraIpc
|
|||
{
|
||||
PluginLog.Error( $"Error registering IPC provider for {LabelProviderCreatedCharacterBase}:\n{e}" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ProviderGameObjectResourcePathResolved =
|
||||
pi.GetIpcProvider< IntPtr, string, string, object? >( LabelProviderGameObjectResourcePathResolved );
|
||||
Api.GameObjectResourceResolved += 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()
|
||||
|
|
@ -397,8 +415,9 @@ public partial class PenumbraIpc
|
|||
ProviderResolveCharacter?.UnregisterFunc();
|
||||
ProviderReverseResolvePath?.UnregisterFunc();
|
||||
ProviderReverseResolvePathPlayer?.UnregisterFunc();
|
||||
Api.CreatingCharacterBase -= CreatingCharacterBaseEvent;
|
||||
Api.CreatedCharacterBase -= CreatedCharacterBaseEvent;
|
||||
Api.CreatingCharacterBase -= CreatingCharacterBaseEvent;
|
||||
Api.CreatedCharacterBase -= CreatedCharacterBaseEvent;
|
||||
Api.GameObjectResourceResolved -= GameObjectResourceResolvedEvent;
|
||||
}
|
||||
|
||||
private void CreatingCharacterBaseEvent( IntPtr gameObject, ModCollection collection, IntPtr modelId, IntPtr customize, IntPtr equipData )
|
||||
|
|
|
|||
46
Penumbra/Collections/ResolveData.cs
Normal file
46
Penumbra/Collections/ResolveData.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using Dalamud.Utility.Signatures;
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using FFXIVClientStructs.STD;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ public unsafe partial class ResourceLoader
|
|||
public Utf8GamePath OriginalPath;
|
||||
public FullPath ManipulatedPath;
|
||||
public ResourceCategory Category;
|
||||
public object? ResolverInfo;
|
||||
public ResolveData ResolverInfo;
|
||||
public ResourceType Extension;
|
||||
}
|
||||
|
||||
|
|
@ -58,18 +59,18 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
public void EnableDebug()
|
||||
{
|
||||
_decRefHook?.Enable();
|
||||
_decRefHook.Enable();
|
||||
ResourceLoaded += AddModifiedDebugInfo;
|
||||
}
|
||||
|
||||
public void DisableDebug()
|
||||
{
|
||||
_decRefHook?.Disable();
|
||||
_decRefHook.Disable();
|
||||
ResourceLoaded -= AddModifiedDebugInfo;
|
||||
}
|
||||
|
||||
private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
|
||||
object? resolverInfo )
|
||||
ResolveData resolverInfo )
|
||||
{
|
||||
if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 )
|
||||
{
|
||||
|
|
@ -243,7 +244,7 @@ public unsafe partial class ResourceLoader
|
|||
private static void LogPath( Utf8GamePath path, bool synchronous )
|
||||
=> PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
|
||||
|
||||
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
|
||||
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData _ )
|
||||
{
|
||||
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})" );
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Dalamud.Hooking;
|
|||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
|
@ -113,18 +114,18 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
|
||||
// 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 );
|
||||
return ( resolved, null );
|
||||
return ( resolved, Penumbra.CollectionManager.Default.ToResolveData() );
|
||||
}
|
||||
|
||||
// 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 )
|
||||
{
|
||||
return ( null, null );
|
||||
return ( null, ResolveData.Invalid );
|
||||
}
|
||||
|
||||
path = path.ToLower();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
|
@ -116,9 +117,9 @@ public unsafe partial class ResourceLoader : IDisposable
|
|||
|
||||
// Event fired whenever a resource is returned.
|
||||
// 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,
|
||||
object? resolveData );
|
||||
ResolveData resolveData );
|
||||
|
||||
public event ResourceLoadedDelegate? ResourceLoaded;
|
||||
|
||||
|
|
@ -133,7 +134,7 @@ public unsafe partial class ResourceLoader : IDisposable
|
|||
// Resolving goes through all subscribed functions in arbitrary order until one returns true,
|
||||
// or uses default resolving if none return true.
|
||||
public delegate bool ResolvePathDelegate( Utf8GamePath path, ResourceCategory category, ResourceType type, int hash,
|
||||
out (FullPath?, object?) ret );
|
||||
out (FullPath?, ResolveData) ret );
|
||||
|
||||
public event ResolvePathDelegate? ResolvePathCustomization;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
private readonly DrawObjectState _drawObjectState;
|
||||
|
||||
private ModCollection? _animationLoadCollection;
|
||||
private ModCollection? _lastAvfxCollection;
|
||||
private ResolveData _animationLoadData = ResolveData.Invalid;
|
||||
private ResolveData _lastAvfxData = ResolveData.Invalid;
|
||||
|
||||
public AnimationState( DrawObjectState drawObjectState )
|
||||
{
|
||||
|
|
@ -24,46 +24,48 @@ public unsafe partial class PathResolver
|
|||
SignatureHelper.Initialise( this );
|
||||
}
|
||||
|
||||
public bool HandleFiles( ResourceType type, Utf8GamePath _, [NotNullWhen( true )] out ModCollection? collection )
|
||||
public bool HandleFiles( ResourceType type, Utf8GamePath _, out ResolveData resolveData )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case ResourceType.Tmb:
|
||||
case ResourceType.Pap:
|
||||
case ResourceType.Scd:
|
||||
if( _animationLoadCollection != null )
|
||||
if( _animationLoadData.Valid )
|
||||
{
|
||||
collection = _animationLoadCollection;
|
||||
resolveData = _animationLoadData;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case ResourceType.Avfx:
|
||||
_lastAvfxCollection = _animationLoadCollection ?? Penumbra.CollectionManager.Default;
|
||||
if( _animationLoadCollection != null )
|
||||
_lastAvfxData = _animationLoadData.Valid
|
||||
? _animationLoadData
|
||||
: Penumbra.CollectionManager.Default.ToResolveData();
|
||||
if( _animationLoadData.Valid )
|
||||
{
|
||||
collection = _animationLoadCollection;
|
||||
resolveData = _animationLoadData;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case ResourceType.Atex:
|
||||
if( _lastAvfxCollection != null )
|
||||
if( _lastAvfxData.Valid )
|
||||
{
|
||||
collection = _lastAvfxCollection;
|
||||
resolveData = _lastAvfxData;
|
||||
return true;
|
||||
}
|
||||
|
||||
if( _animationLoadCollection != null )
|
||||
if( _animationLoadData.Valid )
|
||||
{
|
||||
collection = _animationLoadCollection;
|
||||
resolveData = _animationLoadData;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
resolveData = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ public unsafe partial class PathResolver
|
|||
private ulong LoadTimelineResourcesDetour( IntPtr timeline )
|
||||
{
|
||||
ulong ret;
|
||||
var old = _animationLoadCollection;
|
||||
var old = _animationLoadData;
|
||||
try
|
||||
{
|
||||
if( timeline != IntPtr.Zero )
|
||||
|
|
@ -117,11 +119,11 @@ public unsafe partial class PathResolver
|
|||
if( idx >= 0 && idx < Dalamud.Objects.Length )
|
||||
{
|
||||
var obj = Dalamud.Objects[ idx ];
|
||||
_animationLoadCollection = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : null;
|
||||
_animationLoadData = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : ResolveData.Invalid;
|
||||
}
|
||||
else
|
||||
{
|
||||
_animationLoadCollection = null;
|
||||
_animationLoadData = ResolveData.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +132,7 @@ public unsafe partial class PathResolver
|
|||
ret = _loadTimelineResourcesHook.Original( timeline );
|
||||
}
|
||||
|
||||
_animationLoadCollection = old;
|
||||
_animationLoadData = old;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -145,11 +147,14 @@ public unsafe partial class PathResolver
|
|||
|
||||
private void CharacterBaseLoadAnimationDetour( IntPtr drawObject )
|
||||
{
|
||||
var last = _animationLoadCollection;
|
||||
_animationLoadCollection = _drawObjectState.LastCreatedCollection
|
||||
?? ( FindParent( drawObject, out var collection ) != null ? collection : Penumbra.CollectionManager.Default );
|
||||
var last = _animationLoadData;
|
||||
_animationLoadData = _drawObjectState.LastCreatedCollection.Valid
|
||||
? _drawObjectState.LastCreatedCollection
|
||||
: FindParent( drawObject, out var collection ) != null
|
||||
? collection
|
||||
: Penumbra.CollectionManager.Default.ToResolveData();
|
||||
_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 )
|
||||
{
|
||||
var last = _animationLoadCollection;
|
||||
_animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject );
|
||||
var last = _animationLoadData;
|
||||
_animationLoadData = IdentifyCollection( ( GameObject* )gameObject );
|
||||
var ret = _loadSomeAvfxHook.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 );
|
||||
_animationLoadCollection = last;
|
||||
_animationLoadData = last;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -177,18 +182,18 @@ public unsafe partial class PathResolver
|
|||
private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 )
|
||||
{
|
||||
var timelinePtr = a1 + 0x50;
|
||||
var last = _animationLoadCollection;
|
||||
var last = _animationLoadData;
|
||||
if( timelinePtr != IntPtr.Zero )
|
||||
{
|
||||
var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 );
|
||||
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 );
|
||||
_animationLoadCollection = last;
|
||||
_animationLoadData = last;
|
||||
}
|
||||
|
||||
// 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 )
|
||||
{
|
||||
var last = _animationLoadCollection;
|
||||
_animationLoadCollection = IdentifyCollection( ( GameObject* )gameObject );
|
||||
var last = _animationLoadData;
|
||||
_animationLoadData = IdentifyCollection( ( GameObject* )gameObject );
|
||||
_someActionLoadHook.Original( gameObject );
|
||||
_animationLoadCollection = last;
|
||||
_animationLoadData = last;
|
||||
}
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 44 84 A3", DetourName = nameof( SomeOtherAvfxDetour ) )]
|
||||
|
|
@ -208,11 +213,11 @@ public unsafe partial class PathResolver
|
|||
|
||||
private void SomeOtherAvfxDetour( IntPtr unk )
|
||||
{
|
||||
var last = _animationLoadCollection;
|
||||
var last = _animationLoadData;
|
||||
var gameObject = ( GameObject* )( unk - 0x8D0 );
|
||||
_animationLoadCollection = IdentifyCollection( gameObject );
|
||||
_animationLoadData = IdentifyCollection( gameObject );
|
||||
_someOtherAvfxHook.Original( unk );
|
||||
_animationLoadCollection = last;
|
||||
_animationLoadData = last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,13 +20,13 @@ public unsafe partial class PathResolver
|
|||
public static event CreatingCharacterBaseDelegate? CreatingCharacterBase;
|
||||
public static event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
||||
|
||||
public IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjects
|
||||
public IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjects
|
||||
=> _drawObjectToObject;
|
||||
|
||||
public int Count
|
||||
=> _drawObjectToObject.Count;
|
||||
|
||||
public bool TryGetValue( IntPtr drawObject, out (ModCollection, int) value, out GameObject* gameObject )
|
||||
public bool TryGetValue( IntPtr drawObject, out (ResolveData, int) value, out GameObject* gameObject )
|
||||
{
|
||||
gameObject = null;
|
||||
if( !_drawObjectToObject.TryGetValue( drawObject, out value ) )
|
||||
|
|
@ -40,7 +40,7 @@ public unsafe partial class PathResolver
|
|||
|
||||
|
||||
// Set and update a parent object if it exists and a last game object is set.
|
||||
public ModCollection? CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject )
|
||||
public ResolveData CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject )
|
||||
{
|
||||
if( parentObject == IntPtr.Zero && LastGameObject != null )
|
||||
{
|
||||
|
|
@ -49,26 +49,26 @@ public unsafe partial class PathResolver
|
|||
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
|
||||
&& LastCreatedCollection != null
|
||||
&& LastCreatedCollection.Valid
|
||||
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l', '_', 'f', 'a', 'c', 'e' ) )
|
||||
{
|
||||
collection = LastCreatedCollection!;
|
||||
resolveData = LastCreatedCollection;
|
||||
return true;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
resolveData = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public ModCollection? LastCreatedCollection
|
||||
public ResolveData LastCreatedCollection
|
||||
=> _lastCreatedCollection;
|
||||
|
||||
public GameObject* LastGameObject { get; private set; }
|
||||
|
|
@ -124,8 +124,8 @@ public unsafe partial class PathResolver
|
|||
|
||||
// This map links DrawObjects directly to Actors (by ObjectTable index) and their collections.
|
||||
// It contains any DrawObjects that correspond to a human actor, even those without specific collections.
|
||||
private readonly Dictionary< IntPtr, (ModCollection, int) > _drawObjectToObject = new();
|
||||
private ModCollection? _lastCreatedCollection;
|
||||
private readonly Dictionary< IntPtr, (ResolveData, int) > _drawObjectToObject = new();
|
||||
private ResolveData _lastCreatedCollection = ResolveData.Invalid;
|
||||
|
||||
// Keep track of created DrawObjects that are CharacterBase,
|
||||
// and use the last game object that called EnableDraw to link them.
|
||||
|
|
@ -141,14 +141,14 @@ public unsafe partial class PathResolver
|
|||
if( LastGameObject != null )
|
||||
{
|
||||
var modelPtr = &a;
|
||||
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ( IntPtr )modelPtr, b, c );
|
||||
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c );
|
||||
}
|
||||
|
||||
var ret = _characterBaseCreateHook.Original( a, b, c, d );
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
_drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex );
|
||||
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!, ret );
|
||||
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ret );
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public unsafe partial class PathResolver
|
|||
return null;
|
||||
}
|
||||
|
||||
var ui = ( AtkUnitBase* )addon;
|
||||
var ui = ( AtkUnitBase* )addon;
|
||||
var nodeId = Dalamud.GameData.GetExcelSheet< Title >()?.GetRow( *_inspectTitleId )?.IsPrefix == true ? 2u : 6u;
|
||||
|
||||
var text = ( AtkTextNode* )ui->UldManager.SearchNodeById( nodeId );
|
||||
|
|
@ -61,7 +61,8 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
return null;
|
||||
}
|
||||
var data = *( byte** )( (byte*) agent + 0x28 );
|
||||
|
||||
var data = *( byte** )( ( byte* )agent + 0x28 );
|
||||
if( data == null )
|
||||
{
|
||||
return null;
|
||||
|
|
@ -139,11 +140,11 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
|
||||
// Identify the correct collection for a GameObject by index and name.
|
||||
private static ModCollection IdentifyCollection( GameObject* gameObject )
|
||||
private static ResolveData IdentifyCollection( GameObject* gameObject )
|
||||
{
|
||||
if( gameObject == null )
|
||||
{
|
||||
return Penumbra.CollectionManager.Default;
|
||||
return new ResolveData( Penumbra.CollectionManager.Default );
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -153,8 +154,9 @@ public unsafe partial class PathResolver
|
|||
// Actors are also not named. So use Yourself > Players > Racial > Default.
|
||||
if( !Dalamud.ClientState.IsLoggedIn )
|
||||
{
|
||||
return Penumbra.CollectionManager.ByType( CollectionType.Yourself )
|
||||
var collection = Penumbra.CollectionManager.ByType( CollectionType.Yourself )
|
||||
?? ( CollectionByActor( string.Empty, gameObject, out var c ) ? c : Penumbra.CollectionManager.Default );
|
||||
return collection.ToResolveData( gameObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -163,7 +165,7 @@ public unsafe partial class PathResolver
|
|||
&& gameObject->ObjectKind == ( byte )ObjectKind.EventNpc
|
||||
&& gameObject->DataID is 1011832 or 1011021 ) // cf. "E8 ?? ?? ?? ?? 0F B6 F8 88 45", male or female retainer
|
||||
{
|
||||
return Penumbra.CollectionManager.Default;
|
||||
return Penumbra.CollectionManager.Default.ToResolveData( gameObject );
|
||||
}
|
||||
|
||||
string? actorName = null;
|
||||
|
|
@ -174,7 +176,7 @@ public unsafe partial class PathResolver
|
|||
if( actorName.Length > 0
|
||||
&& 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();
|
||||
|
||||
// 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
|
||||
: CollectionByActor( actualName, gameObject, out c )
|
||||
? c
|
||||
: Penumbra.CollectionManager.Default;
|
||||
return collection.ToResolveData( gameObject );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Error identifying collection:\n{e}" );
|
||||
return Penumbra.CollectionManager.Default;
|
||||
return Penumbra.CollectionManager.Default.ToResolveData( gameObject );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
|
|
@ -20,7 +21,7 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
private readonly PathState _paths;
|
||||
|
||||
private ModCollection? _mtrlCollection;
|
||||
private ResolveData _mtrlData = ResolveData.Invalid;
|
||||
|
||||
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.
|
||||
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;
|
||||
}
|
||||
|
||||
collection = null;
|
||||
collection = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Materials need to be set per collection so they can load their textures independently from each other.
|
||||
public static void HandleCollection( ModCollection collection, string path, bool nonDefault, ResourceType type, FullPath? resolved,
|
||||
out (FullPath?, object?) data )
|
||||
public static void HandleCollection( ResolveData resolveData, string path, bool nonDefault, ResourceType type, FullPath? resolved,
|
||||
out (FullPath?, ResolveData) data )
|
||||
{
|
||||
if( nonDefault && type == ResourceType.Mtrl )
|
||||
{
|
||||
var fullPath = new FullPath( $"|{collection.Name}_{collection.ChangeCounter}|{path}" );
|
||||
data = ( fullPath, collection );
|
||||
var fullPath = new FullPath( $"|{resolveData.ModCollection.Name}_{resolveData.ModCollection.ChangeCounter}|{path}" );
|
||||
data = ( fullPath, resolveData );
|
||||
}
|
||||
else
|
||||
{
|
||||
data = ( resolved, collection );
|
||||
data = ( resolved, resolveData );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,8 +74,8 @@ public unsafe partial class PathResolver
|
|||
public void Dispose()
|
||||
{
|
||||
Disable();
|
||||
_loadMtrlShpkHook?.Dispose();
|
||||
_loadMtrlTexHook?.Dispose();
|
||||
_loadMtrlShpkHook.Dispose();
|
||||
_loadMtrlTexHook.Dispose();
|
||||
}
|
||||
|
||||
// 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
|
||||
PluginLog.Verbose( "Using MtrlLoadHandler with collection {$Split:l} for path {$Path:l}.", name, path );
|
||||
#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
|
||||
{
|
||||
|
|
@ -123,7 +127,7 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
LoadMtrlHelper( mtrlResourceHandle );
|
||||
var ret = _loadMtrlTexHook.Original( mtrlResourceHandle );
|
||||
_mtrlCollection = null;
|
||||
_mtrlData = ResolveData.Invalid;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +139,7 @@ public unsafe partial class PathResolver
|
|||
{
|
||||
LoadMtrlHelper( mtrlResourceHandle );
|
||||
var ret = _loadMtrlShpkHook.Original( mtrlResourceHandle );
|
||||
_mtrlCollection = null;
|
||||
_mtrlData = ResolveData.Invalid;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +152,7 @@ public unsafe partial class PathResolver
|
|||
|
||||
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,11 +79,11 @@ public unsafe partial class PathResolver
|
|||
|
||||
private void OnModelLoadCompleteDetour( IntPtr drawObject )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
var collection = GetResolveData( drawObject );
|
||||
if( collection.Valid )
|
||||
{
|
||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
||||
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
|
||||
_onModelLoadCompleteHook.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
|
|
@ -106,11 +106,11 @@ public unsafe partial class PathResolver
|
|||
return;
|
||||
}
|
||||
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
var collection = GetResolveData( drawObject );
|
||||
if( collection.Valid )
|
||||
{
|
||||
using var eqp = MetaChanger.ChangeEqp( collection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection );
|
||||
using var eqp = MetaChanger.ChangeEqp( collection.ModCollection );
|
||||
using var eqdp = MetaChanger.ChangeEqdp( collection.ModCollection );
|
||||
_updateModelsHook.Original.Invoke( drawObject );
|
||||
}
|
||||
else
|
||||
|
|
@ -212,26 +212,26 @@ public unsafe partial class PathResolver
|
|||
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 );
|
||||
if( collection != null )
|
||||
var resolveData = GetResolveData( drawObject );
|
||||
if( resolveData.Valid )
|
||||
{
|
||||
return ChangeEqp( collection );
|
||||
return ChangeEqp( resolveData.ModCollection );
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
// We only need to change anything if it is actually equipment here.
|
||||
public static MetaChanger ChangeEqdp( PathResolver resolver, IntPtr drawObject, uint modelType )
|
||||
public static MetaChanger ChangeEqdp( PathResolver _, IntPtr drawObject, uint modelType )
|
||||
{
|
||||
if( modelType < 10 )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
var collection = GetResolveData( drawObject );
|
||||
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 )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
var resolveData = GetResolveData( drawObject );
|
||||
if( resolveData.Valid )
|
||||
{
|
||||
collection.SetGmpFiles();
|
||||
resolveData.ModCollection.SetGmpFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Gmp );
|
||||
}
|
||||
|
||||
|
|
@ -258,30 +258,30 @@ public unsafe partial class PathResolver
|
|||
|
||||
public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
var resolveData = GetResolveData( drawObject );
|
||||
if( resolveData.Valid )
|
||||
{
|
||||
collection.SetEstFiles();
|
||||
resolveData.ModCollection.SetEstFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Est );
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
}
|
||||
|
||||
public static MetaChanger ChangeCmp( GameObject* gameObject, out ModCollection? collection )
|
||||
public static MetaChanger ChangeCmp( GameObject* gameObject, out ResolveData resolveData )
|
||||
{
|
||||
if( gameObject != null )
|
||||
{
|
||||
collection = IdentifyCollection( gameObject );
|
||||
if( collection != Penumbra.CollectionManager.Default && collection.HasCache )
|
||||
resolveData = IdentifyCollection( gameObject );
|
||||
if( resolveData.ModCollection != Penumbra.CollectionManager.Default && resolveData.ModCollection.HasCache )
|
||||
{
|
||||
collection.SetCmpFiles();
|
||||
resolveData.ModCollection.SetCmpFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Rsp );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
collection = null;
|
||||
resolveData = ResolveData.Invalid;
|
||||
}
|
||||
|
||||
return new MetaChanger( MetaManipulation.Type.Unknown );
|
||||
|
|
@ -289,10 +289,10 @@ public unsafe partial class PathResolver
|
|||
|
||||
public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject )
|
||||
{
|
||||
var collection = GetCollection( drawObject );
|
||||
if( collection != null )
|
||||
var resolveData = GetResolveData( drawObject );
|
||||
if( resolveData.Valid )
|
||||
{
|
||||
collection.SetCmpFiles();
|
||||
resolveData.ModCollection.SetCmpFiles();
|
||||
return new MetaChanger( MetaManipulation.Type.Rsp );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public unsafe partial class PathResolver
|
|||
private readonly ResolverHooks _monster;
|
||||
|
||||
// This map links files to their corresponding collection, if it is non-default.
|
||||
private readonly ConcurrentDictionary< Utf8String, ModCollection > _pathCollections = new();
|
||||
private readonly ConcurrentDictionary< Utf8String, ResolveData > _pathCollections = new();
|
||||
|
||||
public PathState( PathResolver parent )
|
||||
{
|
||||
|
|
@ -70,18 +70,18 @@ public unsafe partial class PathResolver
|
|||
public int Count
|
||||
=> _pathCollections.Count;
|
||||
|
||||
public IEnumerable< KeyValuePair< Utf8String, ModCollection > > Paths
|
||||
public IEnumerable< KeyValuePair< Utf8String, ResolveData > > Paths
|
||||
=> _pathCollections;
|
||||
|
||||
public bool TryGetValue( Utf8String path, [NotNullWhen( true )] out ModCollection? collection )
|
||||
public bool TryGetValue( Utf8String path, out ResolveData 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 );
|
||||
|
||||
// Just add or remove the resolved path.
|
||||
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
|
||||
public IntPtr ResolvePath( ModCollection collection, IntPtr path )
|
||||
public IntPtr ResolvePath( IntPtr gameObject, ModCollection collection, IntPtr path )
|
||||
{
|
||||
if( path == IntPtr.Zero )
|
||||
{
|
||||
|
|
@ -89,20 +89,20 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
|
||||
var gamePath = new Utf8String( ( byte* )path );
|
||||
SetCollection( gamePath, collection );
|
||||
SetCollection( gameObject, gamePath, collection );
|
||||
return path;
|
||||
}
|
||||
|
||||
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary.
|
||||
public void SetCollection( Utf8String path, ModCollection collection )
|
||||
public void SetCollection( IntPtr gameObject, Utf8String path, ModCollection collection )
|
||||
{
|
||||
if( _pathCollections.ContainsKey( path ) || path.IsOwned )
|
||||
{
|
||||
_pathCollections[ path ] = collection;
|
||||
_pathCollections[ path ] = collection.ToResolveData( gameObject );
|
||||
}
|
||||
else
|
||||
{
|
||||
_pathCollections[ path.Clone() ] = collection;
|
||||
_pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Collections;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
|
|
@ -226,9 +227,9 @@ public partial class PathResolver
|
|||
// Implementation
|
||||
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
|
||||
private IntPtr ResolvePath( IntPtr drawObject, IntPtr path )
|
||||
=> _parent._paths.ResolvePath( FindParent( drawObject, out var collection ) == null
|
||||
=> _parent._paths.ResolvePath( (IntPtr?)FindParent( drawObject, out _) ?? IntPtr.Zero, FindParent( drawObject, out var collection ) == null
|
||||
? Penumbra.CollectionManager.Default
|
||||
: collection, path );
|
||||
: collection.ModCollection, path );
|
||||
|
||||
// Weapons have the characters DrawObject as a parent,
|
||||
// but that may not be set yet when creating a new object, so we have to do the same detour
|
||||
|
|
@ -239,20 +240,20 @@ public partial class PathResolver
|
|||
var parent = FindParent( drawObject, out var collection );
|
||||
if( parent != null )
|
||||
{
|
||||
return _parent._paths.ResolvePath( collection, path );
|
||||
return _parent._paths.ResolvePath( (IntPtr)parent, collection.ModCollection, path );
|
||||
}
|
||||
|
||||
var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject;
|
||||
var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject );
|
||||
if( parentCollection != null )
|
||||
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 );
|
||||
return _parent._paths.ResolvePath( parent == null
|
||||
return _parent._paths.ResolvePath( (IntPtr?)parent ?? IntPtr.Zero, parent == null
|
||||
? Penumbra.CollectionManager.Default
|
||||
: collection, path );
|
||||
: collection.ModCollection, path );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ public partial class PathResolver : IDisposable
|
|||
}
|
||||
|
||||
// The modified resolver that handles game path resolving.
|
||||
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, object?) data )
|
||||
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data )
|
||||
{
|
||||
// Check if the path was marked for a specific collection,
|
||||
// or if it is a file loaded by a material, and if we are currently in a material load,
|
||||
|
|
@ -48,23 +48,23 @@ public partial class PathResolver : IDisposable
|
|||
// If not use the default collection.
|
||||
// We can remove paths after they have actually been loaded.
|
||||
// A potential next request will add the path anew.
|
||||
var nonDefault = _materials.HandleSubFiles( type, out var collection )
|
||||
|| _paths.Consume( gamePath.Path, out collection )
|
||||
|| _animations.HandleFiles( type, gamePath, out collection )
|
||||
|| DrawObjects.HandleDecalFile( type, gamePath, out collection );
|
||||
if( !nonDefault || collection == null )
|
||||
var nonDefault = _materials.HandleSubFiles( type, out var resolveData )
|
||||
|| _paths.Consume( gamePath.Path, out resolveData )
|
||||
|| _animations.HandleFiles( type, gamePath, out resolveData )
|
||||
|| DrawObjects.HandleDecalFile( type, gamePath, out resolveData );
|
||||
if( !nonDefault || !resolveData.Valid )
|
||||
{
|
||||
collection = Penumbra.CollectionManager.Default;
|
||||
resolveData = Penumbra.CollectionManager.Default.ToResolveData();
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -117,50 +117,50 @@ public partial class PathResolver : IDisposable
|
|||
_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 );
|
||||
return ( ( IntPtr )parent, collection );
|
||||
var parent = FindParent( drawObject, out var resolveData );
|
||||
return ( ( IntPtr )parent, resolveData );
|
||||
}
|
||||
|
||||
public int CutsceneActor( int idx )
|
||||
=> Cutscenes.GetParentIndex( idx );
|
||||
|
||||
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
|
||||
public static unsafe GameObject* FindParent( IntPtr drawObject, out ModCollection collection )
|
||||
public static unsafe GameObject* FindParent( IntPtr drawObject, out ResolveData resolveData )
|
||||
{
|
||||
if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) )
|
||||
{
|
||||
collection = data.Item1;
|
||||
resolveData = data.Item1;
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
if( DrawObjects.LastGameObject != null
|
||||
&& ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
|
||||
{
|
||||
collection = IdentifyCollection( DrawObjects.LastGameObject );
|
||||
resolveData = IdentifyCollection( DrawObjects.LastGameObject );
|
||||
return DrawObjects.LastGameObject;
|
||||
}
|
||||
|
||||
collection = IdentifyCollection( null );
|
||||
resolveData = IdentifyCollection( null );
|
||||
return null;
|
||||
}
|
||||
|
||||
private static unsafe ModCollection? GetCollection( IntPtr drawObject )
|
||||
private static unsafe ResolveData GetResolveData( IntPtr drawObject )
|
||||
{
|
||||
var parent = FindParent( drawObject, out var collection );
|
||||
if( parent == null || collection == Penumbra.CollectionManager.Default )
|
||||
var parent = FindParent( drawObject, out var resolveData );
|
||||
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;
|
||||
|
||||
internal IEnumerable< KeyValuePair< IntPtr, (ModCollection, int) > > DrawObjectMap
|
||||
internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap
|
||||
=> DrawObjects.DrawObjects;
|
||||
|
||||
internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ public partial class ConfigWindow
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( name );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( c.Name );
|
||||
ImGui.TextUnformatted( c.ModCollection.Name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,7 +195,7 @@ public partial class ConfigWindow
|
|||
ImGui.TableNextColumn();
|
||||
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted( collection.Name );
|
||||
ImGui.TextUnformatted( collection.ModCollection.Name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -422,6 +422,7 @@ public partial class ConfigWindow
|
|||
{
|
||||
if( !ImGui.CollapsingHeader( "IPC" ) )
|
||||
{
|
||||
_window._penumbra.Ipc.Tester.UnsubscribeEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue