mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Imc Fixes.
This commit is contained in:
parent
ac70f8db89
commit
7915d516e2
4 changed files with 112 additions and 31 deletions
101
Penumbra/Interop/MetaFileManager.cs
Normal file
101
Penumbra/Interop/MetaFileManager.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Memory;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Interop;
|
||||
|
||||
public unsafe class MetaFileManager : IDisposable
|
||||
{
|
||||
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.
|
||||
#if USE_IMC || USE_CMP || USE_EQDP || USE_EQP || USE_EST || USE_GMP
|
||||
[Signature( "E8 ?? ?? ?? ?? 41 B9 ?? ?? ?? ?? 4C 8B C0" )]
|
||||
public IntPtr GetFileSpaceAddress;
|
||||
#endif
|
||||
public IMemorySpace* GetFileSpace()
|
||||
=> ( ( delegate* unmanaged< IMemorySpace* > )GetFileSpaceAddress )();
|
||||
|
||||
public void* AllocateFileMemory( ulong length, ulong alignment = 0 )
|
||||
=> GetFileSpace()->Malloc( length, alignment );
|
||||
|
||||
public void* AllocateFileMemory( int length, int alignment = 0 )
|
||||
=> AllocateFileMemory( ( ulong )length, ( ulong )alignment );
|
||||
|
||||
|
||||
// We only need this for IMC files, since we need to hook their cleanup function.
|
||||
#if USE_IMC
|
||||
[Signature( "48 8D 05 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 89 03", ScanType = ScanType.StaticAddress )]
|
||||
public IntPtr* DefaultResourceHandleVTable;
|
||||
#endif
|
||||
|
||||
public delegate void ClearResource( ResourceHandle* resource );
|
||||
public Hook< ClearResource > ClearDefaultResourceHook = null!;
|
||||
|
||||
private readonly Dictionary< IntPtr, (IntPtr, int) > _originalImcData = 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 ) )
|
||||
{
|
||||
PluginLog.Debug( "Restoring data of {$Name:l} (0x{Resource}) to 0x{Data:X} and Length {Length} before deletion.",
|
||||
Utf8String.FromSpanUnsafe( resource->FileNameSpan(), true, null, null ), ( ulong )resource, ( ulong )data.Item1, data.Item2 );
|
||||
resource->SetData( data.Item1, data.Item2 );
|
||||
_originalImcData.Remove( ( IntPtr )resource );
|
||||
}
|
||||
|
||||
ClearDefaultResourceHook.Original( resource );
|
||||
}
|
||||
|
||||
// Called when a new IMC is manipulated to store its data.
|
||||
[Conditional( "USE_IMC" )]
|
||||
public void AddImcFile( ResourceHandle* resource, 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 ] = ( data, length );
|
||||
}
|
||||
|
||||
// Initialize the hook at VFunc 25, which is called when default resources (and IMC resources do not overwrite it) destroy their data.
|
||||
[Conditional( "USE_IMC" )]
|
||||
private void InitImc()
|
||||
{
|
||||
ClearDefaultResourceHook = new Hook< ClearResource >( DefaultResourceHandleVTable[ 25 ], ClearDefaultResourceDetour );
|
||||
ClearDefaultResourceHook.Enable();
|
||||
}
|
||||
|
||||
[Conditional( "USE_IMC" )]
|
||||
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, (data, length)) in _originalImcData )
|
||||
{
|
||||
var resource = ( ResourceHandle* )resourcePtr;
|
||||
resource->SetData( data, length );
|
||||
}
|
||||
|
||||
_originalImcData.Clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Newtonsoft.Json;
|
|||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
|
@ -68,7 +69,7 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
|
||||
public readonly Utf8GamePath Path;
|
||||
public readonly int NumParts;
|
||||
public bool ChangesSinceLoad = true;
|
||||
public bool ChangesSinceLoad = false;
|
||||
|
||||
public ReadOnlySpan< ImcEntry > Span
|
||||
=> new(( ImcEntry* )( Data + PreambleSize ), ( Length - PreambleSize ) / sizeof( ImcEntry ));
|
||||
|
|
@ -79,17 +80,6 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
private static ushort PartMask( byte* data )
|
||||
=> *( ushort* )( data + 2 );
|
||||
|
||||
private static ImcEntry* DefaultPartPtr( byte* data, int partIdx )
|
||||
{
|
||||
var flag = 1 << partIdx;
|
||||
if( ( PartMask( data ) & flag ) == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ( ImcEntry* )( data + PreambleSize ) + partIdx;
|
||||
}
|
||||
|
||||
private static ImcEntry* VariantPtr( byte* data, int partIdx, int variantIdx )
|
||||
{
|
||||
var flag = 1 << partIdx;
|
||||
|
|
@ -140,6 +130,7 @@ 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 );
|
||||
|
|
@ -173,8 +164,7 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
return false;
|
||||
}
|
||||
|
||||
*variantPtr = entry;
|
||||
ChangesSinceLoad = true;
|
||||
*variantPtr = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -187,8 +177,6 @@ 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 )
|
||||
|
|
@ -226,7 +214,7 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
}
|
||||
}
|
||||
|
||||
public void Replace( ResourceHandle* resource )
|
||||
public void Replace( ResourceHandle* resource, bool firstTime )
|
||||
{
|
||||
var (data, length) = resource->GetData();
|
||||
if( data == IntPtr.Zero )
|
||||
|
|
@ -234,18 +222,10 @@ public unsafe class ImcFile : MetaBaseFile
|
|||
return;
|
||||
}
|
||||
|
||||
var requiredLength = ActualLength;
|
||||
resource->SetData( ( IntPtr )Data, Length );
|
||||
if( length >= requiredLength )
|
||||
if( firstTime )
|
||||
{
|
||||
Functions.MemCpyUnchecked( ( void* )data, Data, requiredLength );
|
||||
Functions.MemSet( ( byte* )data + requiredLength, 0, length - requiredLength );
|
||||
return;
|
||||
Penumbra.MetaFileManager.AddImcFile( resource, data, length );
|
||||
}
|
||||
|
||||
MemoryHelper.GameFree( ref data, ( ulong )length );
|
||||
var file = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )requiredLength );
|
||||
Functions.MemCpyUnchecked( file, Data, requiredLength );
|
||||
resource->SetData( ( IntPtr )file, requiredLength );
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public unsafe class MetaBaseFile : IDisposable
|
|||
protected void AllocateData( int length )
|
||||
{
|
||||
Length = length;
|
||||
Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length );
|
||||
Data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( length );
|
||||
if( length > 0 )
|
||||
{
|
||||
GC.AddMemoryPressure( length );
|
||||
|
|
@ -53,7 +53,7 @@ public unsafe class MetaBaseFile : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
var data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )newLength );
|
||||
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
|
||||
if( newLength > Length )
|
||||
{
|
||||
Functions.MemCpyUnchecked( data, Data, Length );
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public partial class MetaManager
|
|||
{
|
||||
PluginLog.Debug( "Loaded {GamePath:l} from file and replaced with IMC from collection {Collection:l}.", path,
|
||||
collection.Name );
|
||||
file.Replace( fileDescriptor->ResourceHandle );
|
||||
file.Replace( fileDescriptor->ResourceHandle, true);
|
||||
file.ChangesSinceLoad = false;
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ public partial class MetaManager
|
|||
|
||||
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.Replace( resource, false );
|
||||
file.ChangesSinceLoad = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue