diff --git a/Penumbra/Collections/ResolveData.cs b/Penumbra/Collections/ResolveData.cs
index f816069e..efabaaf2 100644
--- a/Penumbra/Collections/ResolveData.cs
+++ b/Penumbra/Collections/ResolveData.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
+using Penumbra.GameData.Actors;
namespace Penumbra.Collections;
@@ -36,30 +37,6 @@ public readonly struct ResolveData
public override string ToString()
=> ModCollection.Name;
-
- public unsafe string AssociatedName()
- {
- if (AssociatedGameObject == IntPtr.Zero)
- return "no associated object.";
-
- try
- {
- var id = Penumbra.Actors.FromObject((GameObject*)AssociatedGameObject, out _, false, true, true);
- if (id.IsValid)
- {
- var name = id.ToString();
- var parts = name.Split(' ', 3);
- return string.Join(" ",
- parts.Length != 3 ? parts.Select(n => $"{n[0]}.") : parts[..2].Select(n => $"{n[0]}.").Append(parts[2]));
- }
- }
- catch
- {
- // ignored
- }
-
- return $"0x{AssociatedGameObject:X}";
- }
}
public static class ResolveDataExtensions
diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs
index ff2a5e64..efe21ffe 100644
--- a/Penumbra/Configuration.cs
+++ b/Penumbra/Configuration.cs
@@ -62,9 +62,9 @@ public class Configuration : IPluginConfiguration, ISavable
public bool OnlyAddMatchingResources { get; set; } = true;
public int MaxResourceWatcherRecords { get; set; } = ResourceWatcher.DefaultMaxEntries;
- public ResourceTypeFlag ResourceWatcherResourceTypes { get; set; } = ResourceExtensions.AllResourceTypes;
- public ResourceCategoryFlag ResourceWatcherResourceCategories { get; set; } = ResourceExtensions.AllResourceCategories;
- public ResourceWatcher.RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords;
+ public ResourceTypeFlag ResourceWatcherResourceTypes { get; set; } = ResourceExtensions.AllResourceTypes;
+ public ResourceCategoryFlag ResourceWatcherResourceCategories { get; set; } = ResourceExtensions.AllResourceCategories;
+ public RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords;
[JsonConverter(typeof(SortModeConverter))]
diff --git a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs
index 3ec38c87..5d7ba16a 100644
--- a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs
+++ b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs
@@ -66,7 +66,7 @@ public unsafe class ResourceLoader : IDisposable
_fileReadService.ReadSqPack -= ReadSqPackDetour;
}
- private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
+ private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, Utf8GamePath original,
GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue)
{
if (returnValue != null)
@@ -86,9 +86,10 @@ public unsafe class ResourceLoader : IDisposable
_texMdlService.AddCrc(type, resolvedPath);
// Replace the hash and path with the correct one for the replacement.
hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
+ var oldPath = path;
path = p;
returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters);
- ResourceLoaded?.Invoke(returnValue, p, resolvedPath.Value, data);
+ ResourceLoaded?.Invoke(returnValue, oldPath, resolvedPath.Value, data);
}
private void ReadSqPackDetour(SeFileDescriptor* fileDescriptor, ref int priority, ref bool isSync, ref byte? returnValue)
diff --git a/Penumbra/Interop/ResourceLoading/ResourceService.cs b/Penumbra/Interop/ResourceLoading/ResourceService.cs
index 1027ed5f..bced539c 100644
--- a/Penumbra/Interop/ResourceLoading/ResourceService.cs
+++ b/Penumbra/Interop/ResourceLoading/ResourceService.cs
@@ -60,7 +60,7 @@ public unsafe class ResourceService : IDisposable
/// Mainly used for SCD streaming, can be null.
/// Whether to request the resource synchronously or asynchronously.
/// The returned resource handle. If this is not null, calling original will be skipped.
- public delegate void GetResourcePreDelegate(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
+ public delegate void GetResourcePreDelegate(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, Utf8GamePath original,
GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue);
///
@@ -104,7 +104,7 @@ public unsafe class ResourceService : IDisposable
}
ResourceHandle* returnValue = null;
- ResourceRequested?.Invoke(ref *categoryId, ref *resourceType, ref *resourceHash, ref gamePath, pGetResParams, ref isSync,
+ ResourceRequested?.Invoke(ref *categoryId, ref *resourceType, ref *resourceHash, ref gamePath, gamePath, pGetResParams, ref isSync,
ref returnValue);
if (returnValue != null)
return returnValue;
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs
index 3cc82173..9b54cb85 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs
@@ -7,112 +7,123 @@ using Penumbra.String;
namespace Penumbra.UI;
-public partial class ResourceWatcher
+[Flags]
+public enum RecordType : byte
{
- private unsafe struct Record
+ Request = 0x01,
+ ResourceLoad = 0x02,
+ FileLoad = 0x04,
+ Destruction = 0x08,
+}
+
+internal unsafe struct Record
+{
+ public DateTime Time;
+ public ByteString Path;
+ public ByteString OriginalPath;
+ public string AssociatedGameObject;
+ public ModCollection? Collection;
+ public ResourceHandle* Handle;
+ public ResourceTypeFlag ResourceType;
+ public ResourceCategoryFlag Category;
+ public uint RefCount;
+ public RecordType RecordType;
+ public OptionalBool Synchronously;
+ public OptionalBool ReturnValue;
+ public OptionalBool CustomLoad;
+
+ public static Record CreateRequest(ByteString path, bool sync)
+ => new()
+ {
+ Time = DateTime.UtcNow,
+ Path = path.IsOwned ? path : path.Clone(),
+ OriginalPath = ByteString.Empty,
+ Collection = null,
+ Handle = null,
+ ResourceType = ResourceExtensions.Type(path).ToFlag(),
+ Category = ResourceExtensions.Category(path).ToFlag(),
+ RefCount = 0,
+ RecordType = RecordType.Request,
+ Synchronously = sync,
+ ReturnValue = OptionalBool.Null,
+ CustomLoad = OptionalBool.Null,
+ AssociatedGameObject = string.Empty,
+ };
+
+ public static Record CreateDefaultLoad(ByteString path, ResourceHandle* handle, ModCollection collection, string associatedGameObject)
{
- public DateTime Time;
- public ByteString Path;
- public ByteString OriginalPath;
- public ModCollection? Collection;
- public ResourceHandle* Handle;
- public ResourceTypeFlag ResourceType;
- public ResourceCategoryFlag Category;
- public uint RefCount;
- public RecordType RecordType;
- public OptionalBool Synchronously;
- public OptionalBool ReturnValue;
- public OptionalBool CustomLoad;
-
- public static Record CreateRequest( ByteString path, bool sync )
- => new()
- {
- Time = DateTime.UtcNow,
- Path = path.IsOwned ? path : path.Clone(),
- OriginalPath = ByteString.Empty,
- Collection = null,
- Handle = null,
- ResourceType = ResourceExtensions.Type( path ).ToFlag(),
- Category = ResourceExtensions.Category( path ).ToFlag(),
- RefCount = 0,
- RecordType = RecordType.Request,
- Synchronously = sync,
- ReturnValue = OptionalBool.Null,
- CustomLoad = OptionalBool.Null,
- };
-
- public static Record CreateDefaultLoad( ByteString path, ResourceHandle* handle, ModCollection collection )
+ path = path.IsOwned ? path : path.Clone();
+ return new Record
{
- path = path.IsOwned ? path : path.Clone();
- return new Record
- {
- Time = DateTime.UtcNow,
- Path = path,
- OriginalPath = path,
- Collection = collection,
- Handle = handle,
- ResourceType = handle->FileType.ToFlag(),
- Category = handle->Category.ToFlag(),
- RefCount = handle->RefCount,
- RecordType = RecordType.ResourceLoad,
- Synchronously = OptionalBool.Null,
- ReturnValue = OptionalBool.Null,
- CustomLoad = false,
- };
- }
-
- public static Record CreateLoad( ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection )
- => new()
- {
- Time = DateTime.UtcNow,
- Path = path.IsOwned ? path : path.Clone(),
- OriginalPath = originalPath.IsOwned ? originalPath : originalPath.Clone(),
- Collection = collection,
- Handle = handle,
- ResourceType = handle->FileType.ToFlag(),
- Category = handle->Category.ToFlag(),
- RefCount = handle->RefCount,
- RecordType = RecordType.ResourceLoad,
- Synchronously = OptionalBool.Null,
- ReturnValue = OptionalBool.Null,
- CustomLoad = true,
- };
-
- public static Record CreateDestruction( ResourceHandle* handle )
- {
- var path = handle->FileName().Clone();
- return new Record
- {
- Time = DateTime.UtcNow,
- Path = path,
- OriginalPath = ByteString.Empty,
- Collection = null,
- Handle = handle,
- ResourceType = handle->FileType.ToFlag(),
- Category = handle->Category.ToFlag(),
- RefCount = handle->RefCount,
- RecordType = RecordType.Destruction,
- Synchronously = OptionalBool.Null,
- ReturnValue = OptionalBool.Null,
- CustomLoad = OptionalBool.Null,
- };
- }
-
- public static Record CreateFileLoad( ByteString path, ResourceHandle* handle, bool ret, bool custom )
- => new()
- {
- Time = DateTime.UtcNow,
- Path = path.IsOwned ? path : path.Clone(),
- OriginalPath = ByteString.Empty,
- Collection = null,
- Handle = handle,
- ResourceType = handle->FileType.ToFlag(),
- Category = handle->Category.ToFlag(),
- RefCount = handle->RefCount,
- RecordType = RecordType.FileLoad,
- Synchronously = OptionalBool.Null,
- ReturnValue = ret,
- CustomLoad = custom,
- };
+ Time = DateTime.UtcNow,
+ Path = path,
+ OriginalPath = path,
+ Collection = collection,
+ Handle = handle,
+ ResourceType = handle->FileType.ToFlag(),
+ Category = handle->Category.ToFlag(),
+ RefCount = handle->RefCount,
+ RecordType = RecordType.ResourceLoad,
+ Synchronously = OptionalBool.Null,
+ ReturnValue = OptionalBool.Null,
+ CustomLoad = false,
+ AssociatedGameObject = associatedGameObject,
+ };
}
-}
\ No newline at end of file
+
+ public static Record CreateLoad(ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection,
+ string associatedGameObject)
+ => new()
+ {
+ Time = DateTime.UtcNow,
+ Path = path.IsOwned ? path : path.Clone(),
+ OriginalPath = originalPath.IsOwned ? originalPath : originalPath.Clone(),
+ Collection = collection,
+ Handle = handle,
+ ResourceType = handle->FileType.ToFlag(),
+ Category = handle->Category.ToFlag(),
+ RefCount = handle->RefCount,
+ RecordType = RecordType.ResourceLoad,
+ Synchronously = OptionalBool.Null,
+ ReturnValue = OptionalBool.Null,
+ CustomLoad = true,
+ AssociatedGameObject = associatedGameObject,
+ };
+
+ public static Record CreateDestruction(ResourceHandle* handle)
+ {
+ var path = handle->FileName().Clone();
+ return new Record
+ {
+ Time = DateTime.UtcNow,
+ Path = path,
+ OriginalPath = ByteString.Empty,
+ Collection = null,
+ Handle = handle,
+ ResourceType = handle->FileType.ToFlag(),
+ Category = handle->Category.ToFlag(),
+ RefCount = handle->RefCount,
+ RecordType = RecordType.Destruction,
+ Synchronously = OptionalBool.Null,
+ ReturnValue = OptionalBool.Null,
+ CustomLoad = OptionalBool.Null,
+ };
+ }
+
+ public static Record CreateFileLoad(ByteString path, ResourceHandle* handle, bool ret, bool custom)
+ => new()
+ {
+ Time = DateTime.UtcNow,
+ Path = path.IsOwned ? path : path.Clone(),
+ OriginalPath = ByteString.Empty,
+ Collection = null,
+ Handle = handle,
+ ResourceType = handle->FileType.ToFlag(),
+ Category = handle->Category.ToFlag(),
+ RefCount = handle->RefCount,
+ RecordType = RecordType.FileLoad,
+ Synchronously = OptionalBool.Null,
+ ReturnValue = ret,
+ CustomLoad = custom,
+ };
+}
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs
deleted file mode 100644
index 3b3fed73..00000000
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace Penumbra.UI;
-
-public partial class ResourceWatcher
-{
- [Flags]
- public enum RecordType : byte
- {
- Request = 0x01,
- ResourceLoad = 0x02,
- FileLoad = 0x04,
- Destruction = 0x08,
- }
-
- public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
-}
\ No newline at end of file
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs
index 1cb787ef..d5ff7d7f 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs
@@ -13,340 +13,328 @@ using Penumbra.String;
namespace Penumbra.UI;
-public partial class ResourceWatcher
+internal sealed class ResourceWatcherTable : Table
{
- private sealed class Table : Table< Record >
+ public ResourceWatcherTable(Configuration config, ICollection records)
+ : base("##records",
+ records,
+ new PathColumn { Label = "Path" },
+ new RecordTypeColumn(config) { Label = "Record" },
+ new CollectionColumn { Label = "Collection" },
+ new ObjectColumn { Label = "Game Object" },
+ new CustomLoadColumn { Label = "Custom" },
+ new SynchronousLoadColumn { Label = "Sync" },
+ new OriginalPathColumn { Label = "Original Path" },
+ new ResourceCategoryColumn { Label = "Category" },
+ new ResourceTypeColumn { Label = "Type" },
+ new HandleColumn { Label = "Resource" },
+ new RefCountColumn { Label = "#Ref" },
+ new DateColumn { Label = "Time" }
+ )
+ { }
+
+ public void Reset()
+ => FilterDirty = true;
+
+ private sealed class PathColumn : ColumnString
{
- private static readonly PathColumn Path = new() { Label = "Path" };
- private static readonly RecordTypeColumn RecordType = new() { Label = "Record" };
- private static readonly DateColumn Date = new() { Label = "Time" };
+ public override float Width
+ => 300 * UiHelpers.Scale;
- private static readonly CollectionColumn Coll = new() { Label = "Collection" };
- private static readonly CustomLoadColumn Custom = new() { Label = "Custom" };
- private static readonly SynchronousLoadColumn Sync = new() { Label = "Sync" };
+ public override string ToName(Record item)
+ => item.Path.ToString();
- private static readonly OriginalPathColumn Orig = new() { Label = "Original Path" };
- private static readonly ResourceCategoryColumn Cat = new() { Label = "Category" };
- private static readonly ResourceTypeColumn Type = new() { Label = "Type" };
+ public override int Compare(Record lhs, Record rhs)
+ => lhs.Path.CompareTo(rhs.Path);
- private static readonly HandleColumn Handle = new() { Label = "Resource" };
- private static readonly RefCountColumn Ref = new() { Label = "#Ref" };
+ public override void DrawColumn(Record item, int _)
+ => DrawByteString(item.Path, 280 * UiHelpers.Scale);
+ }
- public Table( ICollection< Record > records )
- : base( "##records", records, Path, RecordType, Coll, Custom, Sync, Orig, Cat, Type, Handle, Ref, Date )
- { }
-
- public void Reset()
- => FilterDirty = true;
-
- private sealed class PathColumn : ColumnString< Record >
+ private static unsafe void DrawByteString(ByteString path, float length)
+ {
+ Vector2 vec;
+ ImGuiNative.igCalcTextSize(&vec, path.Path, path.Path + path.Length, 0, 0);
+ if (vec.X <= length)
{
- public override float Width
- => 300 * UiHelpers.Scale;
-
- public override string ToName( Record item )
- => item.Path.ToString();
-
- public override int Compare( Record lhs, Record rhs )
- => lhs.Path.CompareTo( rhs.Path );
-
- public override void DrawColumn( Record item, int _ )
- => DrawByteString( item.Path, 290 * UiHelpers.Scale );
+ ImGuiNative.igTextUnformatted(path.Path, path.Path + path.Length);
}
-
- private static unsafe void DrawByteString( ByteString path, float length )
+ else
{
- Vector2 vec;
- ImGuiNative.igCalcTextSize( &vec, path.Path, path.Path + path.Length, 0, 0 );
- if( vec.X <= length )
+ var fileName = path.LastIndexOf((byte)'/');
+ ByteString shortPath;
+ if (fileName != -1)
{
- ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
+ using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * UiHelpers.Scale));
+ using var font = ImRaii.PushFont(UiBuilder.IconFont);
+ ImGui.TextUnformatted(FontAwesomeIcon.EllipsisH.ToIconString());
+ ImGui.SameLine();
+ shortPath = path.Substring(fileName, path.Length - fileName);
}
else
{
- var fileName = path.LastIndexOf( ( byte )'/' );
- ByteString shortPath;
- if( fileName != -1 )
- {
- using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 2 * UiHelpers.Scale ) );
- using var font = ImRaii.PushFont( UiBuilder.IconFont );
- ImGui.TextUnformatted( FontAwesomeIcon.EllipsisH.ToIconString() );
- ImGui.SameLine();
- shortPath = path.Substring( fileName, path.Length - fileName );
- }
- else
- {
- shortPath = path;
- }
-
- ImGuiNative.igTextUnformatted( shortPath.Path, shortPath.Path + shortPath.Length );
- if( ImGui.IsItemClicked() )
- {
- ImGuiNative.igSetClipboardText( path.Path );
- }
-
- if( ImGui.IsItemHovered() )
- {
- ImGuiNative.igSetTooltip( path.Path );
- }
- }
- }
-
- private sealed class RecordTypeColumn : ColumnFlags< RecordType, Record >
- {
- public RecordTypeColumn()
- => AllFlags = AllRecords;
-
- public override float Width
- => 80 * UiHelpers.Scale;
-
- public override bool FilterFunc( Record item )
- => FilterValue.HasFlag( item.RecordType );
-
- public override RecordType FilterValue
- => Penumbra.Config.ResourceWatcherRecordTypes;
-
- protected override void SetValue( RecordType value, bool enable )
- {
- if( enable )
- {
- Penumbra.Config.ResourceWatcherRecordTypes |= value;
- }
- else
- {
- Penumbra.Config.ResourceWatcherRecordTypes &= ~value;
- }
-
- Penumbra.Config.Save();
+ shortPath = path;
}
- public override void DrawColumn( Record item, int idx )
- {
- ImGui.TextUnformatted( item.RecordType switch
- {
- ResourceWatcher.RecordType.Request => "REQ",
- ResourceWatcher.RecordType.ResourceLoad => "LOAD",
- ResourceWatcher.RecordType.FileLoad => "FILE",
- ResourceWatcher.RecordType.Destruction => "DEST",
- _ => string.Empty,
- } );
- }
- }
+ ImGuiNative.igTextUnformatted(shortPath.Path, shortPath.Path + shortPath.Length);
+ if (ImGui.IsItemClicked())
+ ImGuiNative.igSetClipboardText(path.Path);
- private sealed class DateColumn : Column< Record >
- {
- public override float Width
- => 80 * UiHelpers.Scale;
-
- public override int Compare( Record lhs, Record rhs )
- => lhs.Time.CompareTo( rhs.Time );
-
- public override void DrawColumn( Record item, int _ )
- => ImGui.TextUnformatted( $"{item.Time.ToLongTimeString()}.{item.Time.Millisecond:D4}" );
- }
-
-
- private sealed class CollectionColumn : ColumnString< Record >
- {
- public override float Width
- => 80 * UiHelpers.Scale;
-
- public override string ToName( Record item )
- => item.Collection?.Name ?? string.Empty;
- }
-
- private sealed class OriginalPathColumn : ColumnString< Record >
- {
- public override float Width
- => 200 * UiHelpers.Scale;
-
- public override string ToName( Record item )
- => item.OriginalPath.ToString();
-
- public override int Compare( Record lhs, Record rhs )
- => lhs.OriginalPath.CompareTo( rhs.OriginalPath );
-
- public override void DrawColumn( Record item, int _ )
- => DrawByteString( item.OriginalPath, 190 * UiHelpers.Scale );
- }
-
- private sealed class ResourceCategoryColumn : ColumnFlags< ResourceCategoryFlag, Record >
- {
- public ResourceCategoryColumn()
- => AllFlags = ResourceExtensions.AllResourceCategories;
-
- public override float Width
- => 80 * UiHelpers.Scale;
-
- public override bool FilterFunc( Record item )
- => FilterValue.HasFlag( item.Category );
-
- public override ResourceCategoryFlag FilterValue
- => Penumbra.Config.ResourceWatcherResourceCategories;
-
- protected override void SetValue( ResourceCategoryFlag value, bool enable )
- {
- if( enable )
- {
- Penumbra.Config.ResourceWatcherResourceCategories |= value;
- }
- else
- {
- Penumbra.Config.ResourceWatcherResourceCategories &= ~value;
- }
-
- Penumbra.Config.Save();
- }
-
- public override void DrawColumn( Record item, int idx )
- {
- ImGui.TextUnformatted( item.Category.ToString() );
- }
- }
-
- private sealed class ResourceTypeColumn : ColumnFlags< ResourceTypeFlag, Record >
- {
- public ResourceTypeColumn()
- {
- AllFlags = Enum.GetValues< ResourceTypeFlag >().Aggregate( ( v, f ) => v | f );
- for( var i = 0; i < Names.Length; ++i )
- {
- Names[ i ] = Names[ i ].ToLowerInvariant();
- }
- }
-
- public override float Width
- => 50 * UiHelpers.Scale;
-
- public override bool FilterFunc( Record item )
- => FilterValue.HasFlag( item.ResourceType );
-
- public override ResourceTypeFlag FilterValue
- => Penumbra.Config.ResourceWatcherResourceTypes;
-
- protected override void SetValue( ResourceTypeFlag value, bool enable )
- {
- if( enable )
- {
- Penumbra.Config.ResourceWatcherResourceTypes |= value;
- }
- else
- {
- Penumbra.Config.ResourceWatcherResourceTypes &= ~value;
- }
-
- Penumbra.Config.Save();
- }
-
- public override void DrawColumn( Record item, int idx )
- {
- ImGui.TextUnformatted( item.ResourceType.ToString().ToLowerInvariant() );
- }
- }
-
- private sealed class HandleColumn : ColumnString< Record >
- {
- public override float Width
- => 120 * UiHelpers.Scale;
-
- public override unsafe string ToName( Record item )
- => item.Handle == null ? string.Empty : $"0x{( ulong )item.Handle:X}";
-
- public override unsafe void DrawColumn( Record item, int _ )
- {
- using var font = ImRaii.PushFont( UiBuilder.MonoFont, item.Handle != null );
- ImGuiUtil.RightAlign( ToName( item ) );
- }
- }
-
- [Flags]
- private enum BoolEnum : byte
- {
- True = 0x01,
- False = 0x02,
- Unknown = 0x04,
- }
-
- private class OptBoolColumn : ColumnFlags< BoolEnum, Record >
- {
- private BoolEnum _filter;
-
- public OptBoolColumn()
- {
- AllFlags = BoolEnum.True | BoolEnum.False | BoolEnum.Unknown;
- _filter = AllFlags;
- Flags &= ~ImGuiTableColumnFlags.NoSort;
- }
-
- protected bool FilterFunc( OptionalBool b )
- => b.Value switch
- {
- null => _filter.HasFlag( BoolEnum.Unknown ),
- true => _filter.HasFlag( BoolEnum.True ),
- false => _filter.HasFlag( BoolEnum.False ),
- };
-
- public override BoolEnum FilterValue
- => _filter;
-
- protected override void SetValue( BoolEnum value, bool enable )
- {
- if( enable )
- {
- _filter |= value;
- }
- else
- {
- _filter &= ~value;
- }
- }
-
- protected static void DrawColumn( OptionalBool b )
- {
- using var font = ImRaii.PushFont( UiBuilder.IconFont );
- ImGui.TextUnformatted( b.Value switch
- {
- null => string.Empty,
- true => FontAwesomeIcon.Check.ToIconString(),
- false => FontAwesomeIcon.Times.ToIconString(),
- } );
- }
- }
-
- private sealed class CustomLoadColumn : OptBoolColumn
- {
- public override float Width
- => 60 * UiHelpers.Scale;
-
- public override bool FilterFunc( Record item )
- => FilterFunc( item.CustomLoad );
-
- public override void DrawColumn( Record item, int idx )
- => DrawColumn( item.CustomLoad );
- }
-
- private sealed class SynchronousLoadColumn : OptBoolColumn
- {
- public override float Width
- => 45 * UiHelpers.Scale;
-
- public override bool FilterFunc( Record item )
- => FilterFunc( item.Synchronously );
-
- public override void DrawColumn( Record item, int idx )
- => DrawColumn( item.Synchronously );
- }
-
- private sealed class RefCountColumn : Column< Record >
- {
- public override float Width
- => 30 * UiHelpers.Scale;
-
- public override void DrawColumn( Record item, int _ )
- => ImGuiUtil.RightAlign( item.RefCount.ToString() );
-
- public override int Compare( Record lhs, Record rhs )
- => lhs.RefCount.CompareTo( rhs.RefCount );
+ if (ImGui.IsItemHovered())
+ ImGuiNative.igSetTooltip(path.Path);
}
}
-}
\ No newline at end of file
+
+ private sealed class RecordTypeColumn : ColumnFlags
+ {
+ private readonly Configuration _config;
+
+ public RecordTypeColumn(Configuration config)
+ {
+ AllFlags = ResourceWatcher.AllRecords;
+ _config = config;
+ }
+
+ public override float Width
+ => 80 * UiHelpers.Scale;
+
+ public override bool FilterFunc(Record item)
+ => FilterValue.HasFlag(item.RecordType);
+
+ public override RecordType FilterValue
+ => _config.ResourceWatcherRecordTypes;
+
+ protected override void SetValue(RecordType value, bool enable)
+ {
+ if (enable)
+ _config.ResourceWatcherRecordTypes |= value;
+ else
+ _config.ResourceWatcherRecordTypes &= ~value;
+
+ Penumbra.Config.Save();
+ }
+
+ public override void DrawColumn(Record item, int idx)
+ {
+ ImGui.TextUnformatted(item.RecordType switch
+ {
+ RecordType.Request => "REQ",
+ RecordType.ResourceLoad => "LOAD",
+ RecordType.FileLoad => "FILE",
+ RecordType.Destruction => "DEST",
+ _ => string.Empty,
+ });
+ }
+ }
+
+ private sealed class DateColumn : Column
+ {
+ public override float Width
+ => 80 * UiHelpers.Scale;
+
+ public override int Compare(Record lhs, Record rhs)
+ => lhs.Time.CompareTo(rhs.Time);
+
+ public override void DrawColumn(Record item, int _)
+ => ImGui.TextUnformatted($"{item.Time.ToLongTimeString()}.{item.Time.Millisecond:D4}");
+ }
+
+
+ private sealed class CollectionColumn : ColumnString
+ {
+ public override float Width
+ => 80 * UiHelpers.Scale;
+
+ public override string ToName(Record item)
+ => item.Collection?.Name ?? string.Empty;
+ }
+
+ private sealed class ObjectColumn : ColumnString
+ {
+ public override float Width
+ => 200 * UiHelpers.Scale;
+
+ public override string ToName(Record item)
+ => item.AssociatedGameObject;
+ }
+
+ private sealed class OriginalPathColumn : ColumnString
+ {
+ public override float Width
+ => 200 * UiHelpers.Scale;
+
+ public override string ToName(Record item)
+ => item.OriginalPath.ToString();
+
+ public override int Compare(Record lhs, Record rhs)
+ => lhs.OriginalPath.CompareTo(rhs.OriginalPath);
+
+ public override void DrawColumn(Record item, int _)
+ => DrawByteString(item.OriginalPath, 190 * UiHelpers.Scale);
+ }
+
+ private sealed class ResourceCategoryColumn : ColumnFlags
+ {
+ public ResourceCategoryColumn()
+ => AllFlags = ResourceExtensions.AllResourceCategories;
+
+ public override float Width
+ => 80 * UiHelpers.Scale;
+
+ public override bool FilterFunc(Record item)
+ => FilterValue.HasFlag(item.Category);
+
+ public override ResourceCategoryFlag FilterValue
+ => Penumbra.Config.ResourceWatcherResourceCategories;
+
+ protected override void SetValue(ResourceCategoryFlag value, bool enable)
+ {
+ if (enable)
+ Penumbra.Config.ResourceWatcherResourceCategories |= value;
+ else
+ Penumbra.Config.ResourceWatcherResourceCategories &= ~value;
+
+ Penumbra.Config.Save();
+ }
+
+ public override void DrawColumn(Record item, int idx)
+ {
+ ImGui.TextUnformatted(item.Category.ToString());
+ }
+ }
+
+ private sealed class ResourceTypeColumn : ColumnFlags
+ {
+ public ResourceTypeColumn()
+ {
+ AllFlags = Enum.GetValues().Aggregate((v, f) => v | f);
+ for (var i = 0; i < Names.Length; ++i)
+ Names[i] = Names[i].ToLowerInvariant();
+ }
+
+ public override float Width
+ => 50 * UiHelpers.Scale;
+
+ public override bool FilterFunc(Record item)
+ => FilterValue.HasFlag(item.ResourceType);
+
+ public override ResourceTypeFlag FilterValue
+ => Penumbra.Config.ResourceWatcherResourceTypes;
+
+ protected override void SetValue(ResourceTypeFlag value, bool enable)
+ {
+ if (enable)
+ Penumbra.Config.ResourceWatcherResourceTypes |= value;
+ else
+ Penumbra.Config.ResourceWatcherResourceTypes &= ~value;
+
+ Penumbra.Config.Save();
+ }
+
+ public override void DrawColumn(Record item, int idx)
+ {
+ ImGui.TextUnformatted(item.ResourceType.ToString().ToLowerInvariant());
+ }
+ }
+
+ private sealed class HandleColumn : ColumnString
+ {
+ public override float Width
+ => 120 * UiHelpers.Scale;
+
+ public override unsafe string ToName(Record item)
+ => item.Handle == null ? string.Empty : $"0x{(ulong)item.Handle:X}";
+
+ public override unsafe void DrawColumn(Record item, int _)
+ {
+ using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null);
+ ImGuiUtil.RightAlign(ToName(item));
+ }
+ }
+
+ [Flags]
+ private enum BoolEnum : byte
+ {
+ True = 0x01,
+ False = 0x02,
+ Unknown = 0x04,
+ }
+
+ private class OptBoolColumn : ColumnFlags
+ {
+ private BoolEnum _filter;
+
+ public OptBoolColumn()
+ {
+ AllFlags = BoolEnum.True | BoolEnum.False | BoolEnum.Unknown;
+ _filter = AllFlags;
+ Flags &= ~ImGuiTableColumnFlags.NoSort;
+ }
+
+ protected bool FilterFunc(OptionalBool b)
+ => b.Value switch
+ {
+ null => _filter.HasFlag(BoolEnum.Unknown),
+ true => _filter.HasFlag(BoolEnum.True),
+ false => _filter.HasFlag(BoolEnum.False),
+ };
+
+ public override BoolEnum FilterValue
+ => _filter;
+
+ protected override void SetValue(BoolEnum value, bool enable)
+ {
+ if (enable)
+ _filter |= value;
+ else
+ _filter &= ~value;
+ }
+
+ protected static void DrawColumn(OptionalBool b)
+ {
+ using var font = ImRaii.PushFont(UiBuilder.IconFont);
+ ImGui.TextUnformatted(b.Value switch
+ {
+ null => string.Empty,
+ true => FontAwesomeIcon.Check.ToIconString(),
+ false => FontAwesomeIcon.Times.ToIconString(),
+ });
+ }
+ }
+
+ private sealed class CustomLoadColumn : OptBoolColumn
+ {
+ public override float Width
+ => 60 * UiHelpers.Scale;
+
+ public override bool FilterFunc(Record item)
+ => FilterFunc(item.CustomLoad);
+
+ public override void DrawColumn(Record item, int idx)
+ => DrawColumn(item.CustomLoad);
+ }
+
+ private sealed class SynchronousLoadColumn : OptBoolColumn
+ {
+ public override float Width
+ => 45 * UiHelpers.Scale;
+
+ public override bool FilterFunc(Record item)
+ => FilterFunc(item.Synchronously);
+
+ public override void DrawColumn(Record item, int idx)
+ => DrawColumn(item.Synchronously);
+ }
+
+ private sealed class RefCountColumn : Column
+ {
+ public override float Width
+ => 30 * UiHelpers.Scale;
+
+ public override void DrawColumn(Record item, int _)
+ => ImGuiUtil.RightAlign(item.RefCount.ToString());
+
+ public override int Compare(Record lhs, Record rhs)
+ => lhs.RefCount.CompareTo(rhs.RefCount);
+ }
+}
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
index 66ece068..8b054033 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
@@ -1,18 +1,19 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
+using System.Linq;
using System.Text.RegularExpressions;
-using Dalamud.Interface;
+using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImGuiNET;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.Collections;
+using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Structs;
+using Penumbra.Services;
using Penumbra.String;
using Penumbra.String.Classes;
using Penumbra.UI.Classes;
@@ -21,24 +22,27 @@ namespace Penumbra.UI;
public partial class ResourceWatcher : IDisposable, ITab
{
- public const int DefaultMaxEntries = 1024;
+ public const int DefaultMaxEntries = 1024;
+ public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
private readonly Configuration _config;
private readonly ResourceService _resources;
private readonly ResourceLoader _loader;
+ private readonly ActorService _actors;
private readonly List _records = new();
private readonly ConcurrentQueue _newRecords = new();
- private readonly Table _table;
+ private readonly ResourceWatcherTable _table;
private string _logFilter = string.Empty;
private Regex? _logRegex;
private int _newMaxEntries;
- public unsafe ResourceWatcher(Configuration config, ResourceService resources, ResourceLoader loader)
+ public unsafe ResourceWatcher(ActorService actors, Configuration config, ResourceService resources, ResourceLoader loader)
{
+ _actors = actors;
_config = config;
_resources = resources;
_loader = loader;
- _table = new Table(_records);
+ _table = new ResourceWatcherTable(config, _records);
_resources.ResourceRequested += OnResourceRequested;
_resources.ResourceHandleDestructor += OnResourceDestroyed;
_loader.ResourceLoaded += OnResourceLoaded;
@@ -75,8 +79,8 @@ public partial class ResourceWatcher : IDisposable, ITab
var isEnabled = _config.EnableResourceWatcher;
if (ImGui.Checkbox("Enable", ref isEnabled))
{
- Penumbra.Config.EnableResourceWatcher = isEnabled;
- Penumbra.Config.Save();
+ _config.EnableResourceWatcher = isEnabled;
+ _config.Save();
}
ImGui.SameLine();
@@ -89,16 +93,16 @@ public partial class ResourceWatcher : IDisposable, ITab
var onlyMatching = _config.OnlyAddMatchingResources;
if (ImGui.Checkbox("Store Only Matching", ref onlyMatching))
{
- Penumbra.Config.OnlyAddMatchingResources = onlyMatching;
- Penumbra.Config.Save();
+ _config.OnlyAddMatchingResources = onlyMatching;
+ _config.Save();
}
ImGui.SameLine();
var writeToLog = _config.EnableResourceLogging;
if (ImGui.Checkbox("Write to Log", ref writeToLog))
{
- Penumbra.Config.EnableResourceLogging = writeToLog;
- Penumbra.Config.Save();
+ _config.EnableResourceLogging = writeToLog;
+ _config.Save();
}
ImGui.SameLine();
@@ -137,8 +141,8 @@ public partial class ResourceWatcher : IDisposable, ITab
if (config)
{
- Penumbra.Config.ResourceLoggingFilter = newString;
- Penumbra.Config.Save();
+ _config.ResourceLoggingFilter = newString;
+ _config.Save();
}
}
@@ -168,43 +172,44 @@ public partial class ResourceWatcher : IDisposable, ITab
return;
_newMaxEntries = Math.Max(16, _newMaxEntries);
- if (_newMaxEntries != maxEntries)
- {
- _config.MaxResourceWatcherRecords = _newMaxEntries;
- Penumbra.Config.Save();
- if (_newMaxEntries > _records.Count)
- _records.RemoveRange(0, _records.Count - _newMaxEntries);
- }
+ if (_newMaxEntries == maxEntries)
+ return;
+
+ _config.MaxResourceWatcherRecords = _newMaxEntries;
+ _config.Save();
+ if (_newMaxEntries > _records.Count)
+ _records.RemoveRange(0, _records.Count - _newMaxEntries);
}
private void UpdateRecords()
{
var count = _newRecords.Count;
- if (count > 0)
- {
- while (_newRecords.TryDequeue(out var rec) && count-- > 0)
- _records.Add(rec);
+ if (count <= 0)
+ return;
- if (_records.Count > _config.MaxResourceWatcherRecords)
- _records.RemoveRange(0, _records.Count - _config.MaxResourceWatcherRecords);
+ while (_newRecords.TryDequeue(out var rec) && count-- > 0)
+ _records.Add(rec);
- _table.Reset();
- }
+ if (_records.Count > _config.MaxResourceWatcherRecords)
+ _records.RemoveRange(0, _records.Count - _config.MaxResourceWatcherRecords);
+
+ _table.Reset();
}
private unsafe void OnResourceRequested(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
+ Utf8GamePath original,
GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue)
{
- if (_config.EnableResourceLogging && FilterMatch(path.Path, out var match))
+ if (_config.EnableResourceLogging && FilterMatch(original.Path, out var match))
Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested {(sync ? "synchronously." : "asynchronously.")}");
- if (_config.EnableResourceWatcher)
- {
- var record = Record.CreateRequest(path.Path, sync);
- if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
- _newRecords.Enqueue(record);
- }
+ if (!_config.EnableResourceWatcher)
+ return;
+
+ var record = Record.CreateRequest(original.Path, sync);
+ if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
+ _newRecords.Enqueue(record);
}
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data)
@@ -220,19 +225,18 @@ public partial class ResourceWatcher : IDisposable, ITab
{
var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name;
Penumbra.Log.Information(
- $"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.AnonymizedName} for {data.AssociatedName()} (Refcount {handle->RefCount}) ");
+ $"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) ");
}
}
- if (_config.EnableResourceWatcher)
- {
- var record = manipulatedPath == null
- ? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection)
- : Record.CreateLoad(path.Path, manipulatedPath.Value.InternalName, handle,
- data.ModCollection);
- if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
- _newRecords.Enqueue(record);
- }
+ if (!_config.EnableResourceWatcher)
+ return;
+
+ var record = manipulatedPath == null
+ ? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection, Name(data))
+ : Record.CreateLoad(manipulatedPath.Value.InternalName, path.Path, handle, data.ModCollection, Name(data));
+ if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
+ _newRecords.Enqueue(record);
}
private unsafe void OnFileLoaded(ResourceHandle* resource, ByteString path, bool success, bool custom, ByteString _)
@@ -241,12 +245,12 @@ public partial class ResourceWatcher : IDisposable, ITab
Penumbra.Log.Information(
$"[ResourceLoader] [FILE] [{resource->FileType}] Loading {match} from {(custom ? "local files" : "SqPack")} into 0x{(ulong)resource:X} returned {success}.");
- if (_config.EnableResourceWatcher)
- {
- var record = Record.CreateFileLoad(path, resource, success, custom);
- if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
- _newRecords.Enqueue(record);
- }
+ if (!_config.EnableResourceWatcher)
+ return;
+
+ var record = Record.CreateFileLoad(path, resource, success, custom);
+ if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
+ _newRecords.Enqueue(record);
}
private unsafe void OnResourceDestroyed(ResourceHandle* resource)
@@ -255,11 +259,37 @@ public partial class ResourceWatcher : IDisposable, ITab
Penumbra.Log.Information(
$"[ResourceLoader] [DEST] [{resource->FileType}] Destroyed {match} at 0x{(ulong)resource:X}.");
- if (_config.EnableResourceWatcher)
+ if (!_config.EnableResourceWatcher)
+ return;
+
+ var record = Record.CreateDestruction(resource);
+ if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
+ _newRecords.Enqueue(record);
+ }
+
+ public unsafe string Name(ResolveData resolve, string none = "")
+ {
+ if (resolve.AssociatedGameObject == IntPtr.Zero || !_actors.Valid)
+ return none;
+
+ try
{
- var record = Record.CreateDestruction(resource);
- if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
- _newRecords.Enqueue(record);
+ var id = _actors.AwaitedService.FromObject((GameObject*)resolve.AssociatedGameObject, out _, false, true, true);
+ if (id.IsValid)
+ {
+ if (id.Type is not (IdentifierType.Player or IdentifierType.Owned))
+ return id.ToString();
+
+ var parts = id.ToString().Split(' ', 3);
+ return string.Join(" ",
+ parts.Length != 3 ? parts.Select(n => $"{n[0]}.") : parts[..2].Select(n => $"{n[0]}.").Append(parts[2]));
+ }
}
+ catch
+ {
+ // ignored
+ }
+
+ return $"0x{resolve.AssociatedGameObject:X}";
}
}