Fix IMC handling.

This commit is contained in:
Ottermandias 2022-03-19 21:48:51 +01:00
parent d07355c0f8
commit d03a3168b0
8 changed files with 77 additions and 41 deletions

View file

@ -19,13 +19,13 @@ public unsafe partial class ResourceLoader
// Gather some debugging data about penumbra-loaded objects.
public struct DebugData
{
public ResourceHandle* OriginalResource;
public ResourceHandle* ManipulatedResource;
public Utf8GamePath OriginalPath;
public FullPath ManipulatedPath;
public ResourceCategory Category;
public object? ResolverInfo;
public uint Extension;
public Structs.ResourceHandle* OriginalResource;
public Structs.ResourceHandle* ManipulatedResource;
public Utf8GamePath OriginalPath;
public FullPath ManipulatedPath;
public ResourceCategory Category;
public object? ResolverInfo;
public uint Extension;
}
private readonly SortedDictionary< FullPath, DebugData > _debugList = new();
@ -44,7 +44,8 @@ public unsafe partial class ResourceLoader
ResourceLoaded -= AddModifiedDebugInfo;
}
private void AddModifiedDebugInfo( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, object? resolverInfo )
private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
object? resolverInfo )
{
if( manipulatedPath == null )
{
@ -55,7 +56,7 @@ public unsafe partial class ResourceLoader
var originalResource = FindResource( handle->Category, handle->FileType, crc );
_debugList[ manipulatedPath.Value ] = new DebugData()
{
OriginalResource = originalResource,
OriginalResource = ( Structs.ResourceHandle* )originalResource,
ManipulatedResource = handle,
Category = handle->Category,
Extension = handle->FileType,
@ -172,8 +173,8 @@ public unsafe partial class ResourceLoader
{
_deleteList.Add( ( data.ManipulatedPath, data with
{
OriginalResource = regularResource,
ManipulatedResource = modifiedResource,
OriginalResource = ( Structs.ResourceHandle* )regularResource,
ManipulatedResource = ( Structs.ResourceHandle* )modifiedResource,
} ) );
}
}
@ -195,7 +196,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( ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
private static void LogResource( Structs.ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, object? _ )
{
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})" );

View file

@ -72,7 +72,7 @@ public unsafe partial class ResourceLoader
if( resolvedPath == null )
{
var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
ResourceLoaded?.Invoke( retUnmodified, gamePath, null, data );
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data );
return retUnmodified;
}
@ -80,7 +80,7 @@ public unsafe partial class ResourceLoader
*resourceHash = resolvedPath.Value.InternalName.Crc32;
path = resolvedPath.Value.InternalName.Path;
var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
ResourceLoaded?.Invoke( retModified, gamePath, resolvedPath.Value, data );
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data );
return retModified;
}

View file

@ -105,7 +105,7 @@ 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.
public delegate void ResourceLoadedDelegate( ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
public delegate void ResourceLoadedDelegate( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
object? resolveData );
public event ResourceLoadedDelegate? ResourceLoaded;

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Enums;
@ -33,11 +34,11 @@ public unsafe class ObjectReloader : IDisposable
public static DrawState* ActorDrawState( GameObject actor )
=> ( DrawState* )( actor.Address + 0x0104 );
private static delegate*< IntPtr, void > GetDisableDraw( GameObject actor )
=> ( ( delegate*< IntPtr, void >** )actor.Address )[ 0 ][ 17 ];
private static void DisableDraw( GameObject actor )
=> ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 17 ]( actor.Address );
private static delegate*< IntPtr, void > GetEnableDraw( GameObject actor )
=> ( ( delegate*< IntPtr, void >** )actor.Address )[ 0 ][ 16 ];
private static void EnableDraw( GameObject actor )
=> ( ( delegate* unmanaged< IntPtr, void >** )actor.Address )[ 0 ][ 16 ]( actor.Address );
public ObjectReloader( ModManager mods )
=> _mods = mods;
@ -57,14 +58,14 @@ public unsafe class ObjectReloader : IDisposable
_changedSettings = false;
}
private unsafe void WriteInvisible( GameObject actor, int actorIdx )
private void WriteInvisible( GameObject actor, int actorIdx )
{
_currentObjectStartState = *ActorDrawState( actor );
*ActorDrawState( actor ) |= DrawState.Invisibility;
if( _inGPose )
{
GetDisableDraw( actor )( actor.Address );
DisableDraw( actor );
}
}
@ -95,7 +96,7 @@ public unsafe class ObjectReloader : IDisposable
if( _inGPose )
{
GetEnableDraw( actor )( actor.Address );
EnableDraw( actor );
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
namespace Penumbra.Interop.Structs;
@ -40,18 +41,30 @@ public unsafe struct ResourceHandle
[FieldOffset( 0x00 )]
public void** VTable;
[FieldOffset( 0x08 )]
public ResourceCategory Category;
[FieldOffset( 0x0C )]
public uint FileType;
[FieldOffset( 0x10 )]
public uint Id;
[FieldOffset( 0x48 )]
public byte* FileNameData;
[FieldOffset( 0x58 )]
public int FileNameLength;
[FieldOffset( 0xAC )]
public uint RefCount;
// May return null.
public static byte* GetData( ResourceHandle* handle )
=> ( ( delegate*< ResourceHandle*, byte* > )handle->VTable[ 23 ] )( handle );
=> ( ( delegate* unmanaged< ResourceHandle*, byte* > )handle->VTable[ 23 ] )( handle );
public static ulong GetLength( ResourceHandle* handle )
=> ( ( delegate*< ResourceHandle*, ulong > )handle->VTable[ 17 ] )( handle );
=> ( ( delegate* unmanaged< ResourceHandle*, ulong > )handle->VTable[ 17 ] )( handle );
// Only use these if you know what you are doing.

View file

@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Numerics;
using Dalamud.Logging;
using Dalamud.Memory;
@ -8,7 +7,6 @@ using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Files;
@ -68,8 +66,9 @@ public unsafe class ImcFile : MetaBaseFile
public int Count
=> CountInternal( Data );
public readonly int NumParts;
public readonly Utf8GamePath Path;
public readonly int NumParts;
public bool ChangesSinceLoad = true;
private static int CountInternal( byte* data )
=> *( ushort* )data;
@ -179,7 +178,8 @@ public unsafe class ImcFile : MetaBaseFile
return false;
}
*variantPtr = entry;
*variantPtr = entry;
ChangesSinceLoad = true;
return true;
}
@ -192,6 +192,8 @@ public unsafe class ImcFile : MetaBaseFile
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
}
ChangesSinceLoad = true;
}
public ImcFile( Utf8GamePath path )
@ -209,9 +211,8 @@ public unsafe class ImcFile : MetaBaseFile
fixed( byte* ptr = file.Data )
{
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
AllocateData( file.Data.Length + sizeof( ImcEntry ) * 100 * NumParts );
AllocateData( file.Data.Length );
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
Functions.MemSet( Data + file.Data.Length, 0, sizeof( ImcEntry ) * 100 * NumParts );
}
}
@ -239,6 +240,7 @@ public unsafe class ImcFile : MetaBaseFile
}
var requiredLength = ActualLength;
resource->SetData( (IntPtr) Data, Length );
if( length >= requiredLength )
{
Functions.MemCpyUnchecked( ( void* )data, Data, requiredLength );

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString;
using Penumbra.Interop.Loader;
@ -57,7 +58,6 @@ public partial class MetaManager
public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod )
{
const uint imcExt = 0x00696D63;
#if USE_IMC
if( !Manipulations.TryAdd( m, mod ) )
{
@ -82,12 +82,6 @@ public partial class MetaManager
_collection.Cache.ResolvedFiles[ path ] = fullPath;
}
var resource = ResourceLoader.FindResource( ResourceCategory.Chara, imcExt, ( uint )path.Path.Crc32 );
if( resource != null )
{
file.Replace( ( ResourceHandle* )resource );
}
return true;
#else
return false;
@ -109,22 +103,25 @@ public partial class MetaManager
[Conditional( "USE_IMC" )]
private unsafe void SetupDelegate()
{
Penumbra.ResourceLoader.ResourceLoadCustomization = ImcHandler;
Penumbra.ResourceLoader.ResourceLoadCustomization = ImcLoadHandler;
Penumbra.ResourceLoader.ResourceLoaded += ImcResourceHandler;
}
[Conditional( "USE_IMC" )]
private unsafe void RestoreDelegate()
{
if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcHandler )
if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcLoadHandler )
{
Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate;
}
Penumbra.ResourceLoader.ResourceLoaded -= ImcResourceHandler;
}
private FullPath CreateImcPath( Utf8GamePath path )
=> new($"|{_collection.Name}|{path}");
private static unsafe byte ImcHandler( Utf8GamePath gamePath, ResourceManager* resourceManager,
private static unsafe byte ImcLoadHandler( Utf8GamePath gamePath, ResourceManager* resourceManager,
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
{
var split = gamePath.Path.Split( ( byte )'|', 2, true );
@ -137,12 +134,33 @@ public partial class MetaManager
&& collection.Cache.MetaManipulations.Imc.Files.TryGetValue(
Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) )
{
PluginLog.Debug( "Loaded {GamePath:l} from file and replaced with IMC from collection {Collection:l}.", gamePath,
collection.Name );
file.Replace( fileDescriptor->ResourceHandle );
file.ChangesSinceLoad = false;
}
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
return ret;
}
private static unsafe void ImcResourceHandler( ResourceHandle* resource, Utf8GamePath gamePath, FullPath? _2, object? resolveData )
{
// Only check imcs.
if( resource->FileType != 0x00696D63
|| resolveData is not ModCollection collection
|| collection.Cache == null
|| !collection.Cache.MetaManipulations.Imc.Files.TryGetValue( gamePath, out var file )
|| !file.ChangesSinceLoad )
{
return;
}
PluginLog.Debug( "File {GamePath:l} was already loaded but IMC in collection {Collection:l} was changed, so reloaded.", gamePath,
collection.Name );
file.Replace( resource );
file.ChangesSinceLoad = false;
}
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -25,7 +26,7 @@ public class CollectionManager
private readonly ModManager _manager;
public string CollectionChangedTo { get; private set; } = string.Empty;
public Dictionary< string, ModCollection > Collections { get; } = new();
public Dictionary< string, ModCollection > Collections { get; } = new(StringComparer.InvariantCultureIgnoreCase);
public Dictionary< string, ModCollection > CharacterCollection { get; } = new();
public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty;