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. // Gather some debugging data about penumbra-loaded objects.
public struct DebugData public struct DebugData
{ {
public ResourceHandle* OriginalResource; public Structs.ResourceHandle* OriginalResource;
public ResourceHandle* ManipulatedResource; public Structs.ResourceHandle* ManipulatedResource;
public Utf8GamePath OriginalPath; public Utf8GamePath OriginalPath;
public FullPath ManipulatedPath; public FullPath ManipulatedPath;
public ResourceCategory Category; public ResourceCategory Category;
public object? ResolverInfo; public object? ResolverInfo;
public uint Extension; public uint Extension;
} }
private readonly SortedDictionary< FullPath, DebugData > _debugList = new(); private readonly SortedDictionary< FullPath, DebugData > _debugList = new();
@ -44,7 +44,8 @@ public unsafe partial class ResourceLoader
ResourceLoaded -= AddModifiedDebugInfo; 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 ) if( manipulatedPath == null )
{ {
@ -55,7 +56,7 @@ public unsafe partial class ResourceLoader
var originalResource = FindResource( handle->Category, handle->FileType, crc ); var originalResource = FindResource( handle->Category, handle->FileType, crc );
_debugList[ manipulatedPath.Value ] = new DebugData() _debugList[ manipulatedPath.Value ] = new DebugData()
{ {
OriginalResource = originalResource, OriginalResource = ( Structs.ResourceHandle* )originalResource,
ManipulatedResource = handle, ManipulatedResource = handle,
Category = handle->Category, Category = handle->Category,
Extension = handle->FileType, Extension = handle->FileType,
@ -172,8 +173,8 @@ public unsafe partial class ResourceLoader
{ {
_deleteList.Add( ( data.ManipulatedPath, data with _deleteList.Add( ( data.ManipulatedPath, data with
{ {
OriginalResource = regularResource, OriginalResource = ( Structs.ResourceHandle* )regularResource,
ManipulatedResource = modifiedResource, ManipulatedResource = ( Structs.ResourceHandle* )modifiedResource,
} ) ); } ) );
} }
} }
@ -195,7 +196,7 @@ public unsafe partial class ResourceLoader
private static void LogPath( Utf8GamePath path, bool synchronous ) private static void LogPath( Utf8GamePath path, bool synchronous )
=> PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" ); => PluginLog.Information( $"[ResourceLoader] Requested {path} {( synchronous ? "synchronously." : "asynchronously." )}" );
private static void LogResource( 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(); var pathString = manipulatedPath != null ? $"custom file {manipulatedPath} instead of {path}" : path.ToString();
PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" ); PluginLog.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );

View file

@ -72,7 +72,7 @@ public unsafe partial class ResourceLoader
if( resolvedPath == null ) if( resolvedPath == null )
{ {
var retUnmodified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); 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; return retUnmodified;
} }
@ -80,7 +80,7 @@ public unsafe partial class ResourceLoader
*resourceHash = resolvedPath.Value.InternalName.Crc32; *resourceHash = resolvedPath.Value.InternalName.Crc32;
path = resolvedPath.Value.InternalName.Path; path = resolvedPath.Value.InternalName.Path;
var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk ); 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; return retModified;
} }

View file

@ -105,7 +105,7 @@ public unsafe partial class ResourceLoader : IDisposable
// Event fired whenever a resource is returned. // Event fired whenever a resource is returned.
// If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource. // If the path was manipulated by penumbra, manipulatedPath will be the file path of the loaded resource.
// resolveData is additional data returned by the current ResolvePath function and is user-defined. // resolveData is additional data returned by the current ResolvePath function 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 ); object? resolveData );
public event ResourceLoadedDelegate? ResourceLoaded; public event ResourceLoadedDelegate? ResourceLoaded;

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.Interop.Loader; using Penumbra.Interop.Loader;
@ -57,7 +58,6 @@ public partial class MetaManager
public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod ) public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod )
{ {
const uint imcExt = 0x00696D63;
#if USE_IMC #if USE_IMC
if( !Manipulations.TryAdd( m, mod ) ) if( !Manipulations.TryAdd( m, mod ) )
{ {
@ -82,12 +82,6 @@ public partial class MetaManager
_collection.Cache.ResolvedFiles[ path ] = fullPath; _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; return true;
#else #else
return false; return false;
@ -109,22 +103,25 @@ public partial class MetaManager
[Conditional( "USE_IMC" )] [Conditional( "USE_IMC" )]
private unsafe void SetupDelegate() private unsafe void SetupDelegate()
{ {
Penumbra.ResourceLoader.ResourceLoadCustomization = ImcHandler; Penumbra.ResourceLoader.ResourceLoadCustomization = ImcLoadHandler;
Penumbra.ResourceLoader.ResourceLoaded += ImcResourceHandler;
} }
[Conditional( "USE_IMC" )] [Conditional( "USE_IMC" )]
private unsafe void RestoreDelegate() private unsafe void RestoreDelegate()
{ {
if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcHandler ) if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcLoadHandler )
{ {
Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate; Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate;
} }
Penumbra.ResourceLoader.ResourceLoaded -= ImcResourceHandler;
} }
private FullPath CreateImcPath( Utf8GamePath path ) private FullPath CreateImcPath( Utf8GamePath path )
=> new($"|{_collection.Name}|{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 ) SeFileDescriptor* fileDescriptor, int priority, bool isSync )
{ {
var split = gamePath.Path.Split( ( byte )'|', 2, true ); var split = gamePath.Path.Split( ( byte )'|', 2, true );
@ -137,12 +134,33 @@ public partial class MetaManager
&& collection.Cache.MetaManipulations.Imc.Files.TryGetValue( && collection.Cache.MetaManipulations.Imc.Files.TryGetValue(
Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) ) 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.Replace( fileDescriptor->ResourceHandle );
file.ChangesSinceLoad = false;
} }
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
return ret; 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -25,7 +26,7 @@ public class CollectionManager
private readonly ModManager _manager; private readonly ModManager _manager;
public string CollectionChangedTo { get; private set; } = string.Empty; 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 Dictionary< string, ModCollection > CharacterCollection { get; } = new();
public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty; public ModCollection CurrentCollection { get; private set; } = ModCollection.Empty;