From 1d7829593e7cc59f319f133407bf4edac10cfc7e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Jul 2022 16:20:09 +0200 Subject: [PATCH] Add IncRef mode and debug DecRef check. --- .../Interop/Loader/ResourceLoader.Debug.cs | 22 +++++++++++-- .../Loader/ResourceLoader.Replacement.cs | 33 +++++++++++-------- Penumbra/Interop/Loader/ResourceLoader.cs | 7 ++++ 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs index ce95d523..6258dc13 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs @@ -14,6 +14,10 @@ namespace Penumbra.Interop.Loader; public unsafe partial class ResourceLoader { + // If in debug mode, this logs any resource at refcount 0 that gets decremented again, and skips the decrement instead. + private delegate byte ResourceHandleDecRef( ResourceHandle* handle ); + private readonly Hook _decRefHook; + public delegate IntPtr ResourceHandleDestructor( ResourceHandle* handle ); [Signature( "48 89 5C 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 B8", @@ -24,8 +28,8 @@ public unsafe partial class ResourceLoader { if( handle != null ) { - PluginLog.Information( "[ResourceLoader] Destructing Resource Handle {Path:l} at 0x{Address:X} (Refcount {Refcount}) .", handle->FileName, - ( ulong )handle, handle->RefCount ); + PluginLog.Information( "[ResourceLoader] Destructing Resource Handle {Path:l} at 0x{Address:X} (Refcount {Refcount}) .", + handle->FileName, ( ulong )handle, handle->RefCount ); } return ResourceHandleDestructorHook!.Original( handle ); @@ -54,11 +58,13 @@ public unsafe partial class ResourceLoader public void EnableDebug() { + _decRefHook?.Enable(); ResourceLoaded += AddModifiedDebugInfo; } public void DisableDebug() { + _decRefHook?.Disable(); ResourceLoaded -= AddModifiedDebugInfo; } @@ -207,6 +213,18 @@ public unsafe partial class ResourceLoader } } + // Prevent resource management weirdness. + private byte ResourceHandleDecRefDetour( ResourceHandle* handle ) + { + if( handle->RefCount != 0 ) + { + return _decRefHook!.Original( handle ); + } + + PluginLog.Error( $"Caught decrease of Reference Counter for {handle->FileName} at 0x{( ulong )handle:X} below 0." ); + return 1; + } + // Logging functions for EnableFullLogging. private static void LogPath( Utf8GamePath path, bool synchronous ) => PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" ); diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index eac70a20..0c9f6c7b 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -84,18 +84,6 @@ public unsafe partial class ResourceLoader ResourceRequested?.Invoke( gamePath, isSync ); - // Force metadata tables to load synchronously and not be able to be replaced. - switch( *resourceType ) - { - case ResourceType.Eqp: - case ResourceType.Gmp: - case ResourceType.Eqdp: - case ResourceType.Cmp: - case ResourceType.Est: - PluginLog.Verbose( "Forced resource {gamePath} to be loaded synchronously.", gamePath ); - return CallOriginalHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); - } - // If no replacements are being made, we still want to be able to trigger the event. var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash ); PathResolved?.Invoke( gamePath, *resourceType, resolvedPath, data ); @@ -126,7 +114,7 @@ public unsafe partial class ResourceLoader // Try all resolve path subscribers or use the default replacer. private (FullPath?, object?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash ) { - if( !DoReplacements ) + if( !DoReplacements || IsInIncRef ) { return ( null, null ); } @@ -259,7 +247,7 @@ public unsafe partial class ResourceLoader ResourceHandleDestructorHook?.Dispose(); } - private int ComputeHash( Utf8String path, GetResourceParameters* pGetResParams ) + private static int ComputeHash( Utf8String path, GetResourceParameters* pGetResParams ) { if( pGetResParams == null || !pGetResParams->IsPartialRead ) { @@ -276,4 +264,21 @@ public unsafe partial class ResourceLoader Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true ) ).Crc32; } + + + // A resource with ref count 0 that gets incremented goes through GetResourceAsync again. + // This means, that if the path determined from that is different than the resources path, + // a different resource gets loaded or incremented, while the IncRef'd resource stays at 0. + // This causes some problems and is hopefully prevented with this. + public bool IsInIncRef { get; private set; } = false; + private readonly Hook< ResourceHandleDestructor > _incRefHook; + + private IntPtr ResourceHandleIncRefDetour( ResourceHandle* handle ) + { + var tmp = IsInIncRef; + IsInIncRef = true; + var ret = _incRefHook.Original( handle ); + IsInIncRef = tmp; + return ret; + } } \ No newline at end of file diff --git a/Penumbra/Interop/Loader/ResourceLoader.cs b/Penumbra/Interop/Loader/ResourceLoader.cs index bc575d2e..f36b90c5 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.cs @@ -1,4 +1,5 @@ using System; +using Dalamud.Hooking; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; @@ -82,6 +83,7 @@ public unsafe partial class ResourceLoader : IDisposable ReadSqPackHook.Enable(); GetResourceSyncHook.Enable(); GetResourceAsyncHook.Enable(); + _incRefHook.Enable(); } public void DisableHooks() @@ -95,11 +97,16 @@ public unsafe partial class ResourceLoader : IDisposable ReadSqPackHook.Disable(); GetResourceSyncHook.Disable(); GetResourceAsyncHook.Disable(); + _incRefHook.Disable(); } public ResourceLoader( Penumbra _ ) { SignatureHelper.Initialise( this ); + _decRefHook = new Hook< ResourceHandleDecRef >( ( IntPtr )FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle.fpDecRef, + ResourceHandleDecRefDetour ); + _incRefHook = new Hook< ResourceHandleDestructor >( + ( IntPtr )FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle.fpIncRef, ResourceHandleIncRefDetour ); } // Event fired whenever a resource is requested.