Maybe improve IMC handling.

This commit is contained in:
Ottermandias 2022-07-14 20:27:47 +02:00
parent 23a08f30c4
commit e261b4c0c5
4 changed files with 21 additions and 132 deletions

View file

@ -1,38 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using Penumbra.GameData.ByteString;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
namespace Penumbra.Interop;
public unsafe class MetaFileManager : IDisposable
public unsafe class MetaFileManager
{
public MetaFileManager()
{
SignatureHelper.Initialise( this );
InitImc();
}
public void Dispose()
{
DisposeImc();
}
// Allocate in the games space for file storage.
// We only need this if using any meta file.
[Signature( "E8 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 4C 8B C0" )]
public IntPtr GetFileSpaceAddress;
private readonly IntPtr _getFileSpaceAddress = IntPtr.Zero;
public IMemorySpace* GetFileSpace()
=> ( ( delegate* unmanaged< IMemorySpace* > )GetFileSpaceAddress )();
=> ( ( delegate* unmanaged< IMemorySpace* > )_getFileSpaceAddress )();
public void* AllocateFileMemory( ulong length, ulong alignment = 0 )
=> GetFileSpace()->Malloc( length, alignment );
@ -40,87 +25,12 @@ public unsafe class MetaFileManager : IDisposable
public void* AllocateFileMemory( int length, int alignment = 0 )
=> AllocateFileMemory( ( ulong )length, ( ulong )alignment );
public void* AllocateDefaultMemory( ulong length, ulong alignment = 0 )
=> GetFileSpace()->Malloc( length, alignment );
// We only need this for IMC files, since we need to hook their cleanup function.
[Signature( "48 8D 05 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 89 03", ScanType = ScanType.StaticAddress )]
public IntPtr* DefaultResourceHandleVTable;
public void* AllocateDefaultMemory( int length, int alignment = 0 )
=> IMemorySpace.GetDefaultSpace()->Malloc( ( ulong )length, ( ulong )alignment );
public delegate void ClearResource( ResourceHandle* resource );
public Hook< ClearResource > ClearDefaultResourceHook = null!;
private readonly Dictionary< IntPtr, (ImcFile, IntPtr, int) > _originalImcData = new();
private readonly Dictionary< ImcFile, IntPtr > _currentUse = new();
// We store the original data of loaded IMCs so that we can restore it before they get destroyed,
// similar to the other meta files, just with arbitrary destruction.
private void ClearDefaultResourceDetour( ResourceHandle* resource )
{
if( _originalImcData.TryGetValue( ( IntPtr )resource, out var data ) )
{
ClearImcData( resource, data.Item1, data.Item2, data.Item3);
}
ClearDefaultResourceHook.Original( resource );
}
// Reset all files from a given IMC cache if they exist.
public void ResetByFile( ImcFile file )
{
if( !_currentUse.TryGetValue( file, out var resource ) )
{
return;
}
if( _originalImcData.TryGetValue( resource, out var data ) )
{
ClearImcData((ResourceHandle*) resource, file, data.Item2, data.Item3 );
}
else
{
_currentUse.Remove( file );
}
}
// Clear a single IMC resource and reset it to its original data.
private void ClearImcData( ResourceHandle* resource, ImcFile file, IntPtr data, int length)
{
var name = new FullPath( Utf8String.FromSpanUnsafe( resource->FileNameSpan(), true ).ToString() );
PluginLog.Debug( "Restoring data of {$Name:l} (0x{Resource}) to 0x{Data:X} and Length {Length} before deletion.", name,
( ulong )resource, ( ulong )data, length );
resource->SetData( data, length );
_originalImcData.Remove( ( IntPtr )resource );
_currentUse.Remove( file );
}
// Called when a new IMC is manipulated to store its data.
public void AddImcFile( ResourceHandle* resource, ImcFile file, IntPtr data, int length)
{
PluginLog.Debug( "Storing data 0x{Data:X} of Length {Length} for {$Name:l} (0x{Resource:X}).", ( ulong )data, length,
Utf8String.FromSpanUnsafe( resource->FileNameSpan(), true, null, null ), ( ulong )resource );
_originalImcData[ ( IntPtr )resource ] = ( file, data, length );
_currentUse[ file ] = ( IntPtr )resource;
}
// Initialize the hook at VFunc 25, which is called when default resources (and IMC resources do not overwrite it) destroy their data.
private void InitImc()
{
ClearDefaultResourceHook = Hook< ClearResource >.FromAddress( DefaultResourceHandleVTable[ 25 ], ClearDefaultResourceDetour );
ClearDefaultResourceHook.Enable();
}
private void DisposeImc()
{
ClearDefaultResourceHook.Disable();
ClearDefaultResourceHook.Dispose();
// Restore all IMCs to their default values on dispose.
// This should only be relevant when testing/disabling/reenabling penumbra.
foreach( var (resourcePtr, (file, data, length)) in _originalImcData )
{
var resource = ( ResourceHandle* )resourcePtr;
resource->SetData( data, length );
}
_originalImcData.Clear();
_currentUse.Clear();
}
public void Free( IntPtr ptr, int length )
=> IMemorySpace.Free( ( void* )ptr, ( ulong )length );
}

View file

@ -128,7 +128,6 @@ public unsafe class ImcFile : MetaBaseFile
var newLength = ( ( ( ActualLength - 1 ) >> 7 ) + 1 ) << 7;
PluginLog.Verbose( "Resized IMC {Path} from {Length} to {NewLength}.", Path, Length, newLength );
ResizeResources( newLength );
ChangesSinceLoad = true;
}
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
@ -218,18 +217,18 @@ public unsafe class ImcFile : MetaBaseFile
}
}
public void Replace( ResourceHandle* resource, bool firstTime )
public void Replace( ResourceHandle* resource )
{
var (data, length) = resource->GetData();
if( data == IntPtr.Zero )
var newData = Penumbra.MetaFileManager.AllocateDefaultMemory( Length, 8 );
if( newData == null )
{
PluginLog.Error("Could not replace loaded IMC data at 0x{Data:X}, allocation failed." );
return;
}
Functions.MemCpyUnchecked( newData, Data, Length );
resource->SetData( ( IntPtr )Data, Length );
if( firstTime )
{
Penumbra.MetaFileManager.AddImcFile( resource, this, data, length );
}
Penumbra.MetaFileManager.Free( data, length );
resource->SetData( ( IntPtr )newData, Length );
}
}

View file

@ -119,9 +119,9 @@ public partial class MetaManager
{
foreach( var file in _imcFiles.Values )
{
Penumbra.MetaFileManager.ResetByFile( file );
file.Dispose();
}
_imcFiles.Clear();
_imcManipulations.Clear();
RestoreImcDelegate();
@ -132,7 +132,6 @@ public partial class MetaManager
if( _imcManagerCount++ == 0 )
{
Penumbra.ResourceLoader.ResourceLoadCustomization += ImcLoadHandler;
Penumbra.ResourceLoader.ResourceLoaded += ImcResourceHandler;
}
}
@ -141,7 +140,6 @@ public partial class MetaManager
if( --_imcManagerCount == 0 )
{
Penumbra.ResourceLoader.ResourceLoadCustomization -= ImcLoadHandler;
Penumbra.ResourceLoader.ResourceLoaded -= ImcResourceHandler;
}
}
@ -163,33 +161,16 @@ public partial class MetaManager
var lastUnderscore = split.LastIndexOf( ( byte )'_' );
var name = lastUnderscore == -1 ? split.ToString() : split.Substring( 0, lastUnderscore ).ToString();
if( Penumbra.CollectionManager.ByName( name, out var collection )
if( ( Penumbra.TempMods.Collections.TryGetValue( name, out var collection )
|| Penumbra.CollectionManager.ByName( name, out collection ) )
&& collection.HasCache
&& collection.MetaCache!._imcFiles.TryGetValue( Utf8GamePath.FromSpan( path.Span, out var p ) ? p : Utf8GamePath.Empty, out var file ) )
{
PluginLog.Debug( "Loaded {GamePath:l} from file and replaced with IMC from collection {Collection:l}.", path,
collection.Name );
file.Replace( fileDescriptor->ResourceHandle, true );
file.ChangesSinceLoad = false;
collection.AnonymizedName );
file.Replace( fileDescriptor->ResourceHandle );
}
return true;
}
private static unsafe void ImcResourceHandler( ResourceHandle* resource, Utf8GamePath gamePath, FullPath? _2, object? resolveData )
{
// Only check imcs.
if( resource->FileType != ResourceType.Imc
|| resolveData is not ModCollection { HasCache: true } collection
|| !collection.MetaCache!._imcFiles.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, false );
file.ChangesSinceLoad = false;
}
}

View file

@ -258,7 +258,6 @@ public class Penumbra : IDalamudPlugin
PathResolver.Dispose();
ResourceLogger.Dispose();
MetaFileManager.Dispose();
ResourceLoader.Dispose();
CharacterUtility.Dispose();