mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-01-01 05:13:43 +01:00
Change resolving to possibly work correctly for all materials and load specific materials for each collection.
This commit is contained in:
parent
b6ed27e235
commit
1e5776a481
16 changed files with 408 additions and 172 deletions
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using FileMode = Penumbra.Interop.Structs.FileMode;
|
||||
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
||||
|
|
@ -16,27 +18,27 @@ public unsafe partial class ResourceLoader
|
|||
// Resources can be obtained synchronously and asynchronously. We need to change behaviour in both cases.
|
||||
// Both work basically the same, so we can reduce the main work to one function used by both hooks.
|
||||
public delegate ResourceHandle* GetResourceSyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId,
|
||||
uint* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown );
|
||||
ResourceType* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown );
|
||||
|
||||
[Signature( "E8 ?? ?? 00 00 48 8D 8F ?? ?? 00 00 48 89 87 ?? ?? 00 00", DetourName = "GetResourceSyncDetour" )]
|
||||
public Hook< GetResourceSyncPrototype > GetResourceSyncHook = null!;
|
||||
|
||||
public delegate ResourceHandle* GetResourceAsyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId,
|
||||
uint* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown, bool isUnknown );
|
||||
ResourceType* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown, bool isUnknown );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00", DetourName = "GetResourceAsyncDetour" )]
|
||||
public Hook< GetResourceAsyncPrototype > GetResourceAsyncHook = null!;
|
||||
|
||||
private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, uint* resourceType,
|
||||
private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
|
||||
int* resourceHash, byte* path, void* unk )
|
||||
=> GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, unk, false );
|
||||
|
||||
private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, uint* resourceType,
|
||||
private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
|
||||
int* resourceHash, byte* path, void* unk, bool isUnk )
|
||||
=> GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
||||
|
||||
private ResourceHandle* CallOriginalHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||
uint* resourceType, int* resourceHash, byte* path, void* unk, bool isUnk )
|
||||
ResourceType* resourceType, int* resourceHash, byte* path, void* unk, bool isUnk )
|
||||
=> isSync
|
||||
? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk )
|
||||
: GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
||||
|
|
@ -53,7 +55,8 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
private event Action< Utf8GamePath, FullPath?, object? >? PathResolved;
|
||||
|
||||
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, uint* resourceType,
|
||||
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||
ResourceType* resourceType,
|
||||
int* resourceHash, byte* path, void* unk, bool isUnk )
|
||||
{
|
||||
if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
|
||||
|
|
@ -67,7 +70,7 @@ public unsafe partial class ResourceLoader
|
|||
ResourceRequested?.Invoke( gamePath, isSync );
|
||||
|
||||
// If no replacements are being made, we still want to be able to trigger the event.
|
||||
var (resolvedPath, data) = DoReplacements ? ResolvePath( gamePath.ToLower() ) : ( null, null );
|
||||
var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash );
|
||||
PathResolved?.Invoke( gamePath, resolvedPath, data );
|
||||
if( resolvedPath == null )
|
||||
{
|
||||
|
|
@ -85,6 +88,37 @@ public unsafe partial class ResourceLoader
|
|||
}
|
||||
|
||||
|
||||
// Use the default method of path replacement.
|
||||
public static (FullPath?, object?) DefaultResolver( Utf8GamePath path )
|
||||
{
|
||||
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
|
||||
return ( resolved, null );
|
||||
}
|
||||
|
||||
// Try all resolve path subscribers or use the default replacer.
|
||||
private (FullPath?, object?) ResolvePath( Utf8GamePath path, ResourceCategory category, ResourceType resourceType, int resourceHash )
|
||||
{
|
||||
if( !DoReplacements )
|
||||
{
|
||||
return ( null, null );
|
||||
}
|
||||
|
||||
path = path.ToLower();
|
||||
if( ResolvePathCustomization != null )
|
||||
{
|
||||
foreach( var resolver in ResolvePathCustomization.GetInvocationList() )
|
||||
{
|
||||
if( ( ( ResolvePathDelegate )resolver ).Invoke( path, category, resourceType, resourceHash, out var ret ) )
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultResolver( path );
|
||||
}
|
||||
|
||||
|
||||
// We need to use the ReadFile function to load local, uncompressed files instead of loading them from the SqPacks.
|
||||
public delegate byte ReadFileDelegate( ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority,
|
||||
bool isSync );
|
||||
|
|
@ -111,63 +145,82 @@ public unsafe partial class ResourceLoader
|
|||
return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
||||
}
|
||||
|
||||
var valid = Utf8GamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false );
|
||||
byte ret;
|
||||
// The internal buffer size does not allow for more than 260 characters.
|
||||
// We use the IsRooted check to signify paths replaced by us pointing to the local filesystem instead of an SqPack.
|
||||
if( !valid || !gamePath.IsRooted() )
|
||||
if( !Utf8GamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false ) )
|
||||
{
|
||||
if( valid && ResourceLoadCustomization != null && gamePath.Path[ 0 ] == ( byte )'|' )
|
||||
{
|
||||
ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync );
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
||||
FileLoaded?.Invoke( gamePath.Path, ret != 0, false );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specify that we are loading unpacked files from the drive.
|
||||
// We need to copy the actual file path in UTF16 (Windows-Unicode) on two locations,
|
||||
// but since we only allow ASCII in the game paths, this is just a matter of upcasting.
|
||||
fileDescriptor->FileMode = FileMode.LoadUnpackedResource;
|
||||
|
||||
var fd = stackalloc byte[0x20 + 2 * gamePath.Length + 0x16];
|
||||
fileDescriptor->FileDescriptor = fd;
|
||||
var fdPtr = ( char* )( fd + 0x21 );
|
||||
for( var i = 0; i < gamePath.Length; ++i )
|
||||
{
|
||||
( &fileDescriptor->Utf16FileName )[ i ] = ( char )gamePath.Path[ i ];
|
||||
fdPtr[ i ] = ( char )gamePath.Path[ i ];
|
||||
}
|
||||
|
||||
( &fileDescriptor->Utf16FileName )[ gamePath.Length ] = '\0';
|
||||
fdPtr[ gamePath.Length ] = '\0';
|
||||
|
||||
// Use the SE ReadFile function.
|
||||
ret = ReadFile( resourceManager, fileDescriptor, priority, isSync );
|
||||
FileLoaded?.Invoke( gamePath.Path, ret != 0, true );
|
||||
return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
||||
}
|
||||
|
||||
byte ret = 0;
|
||||
// Paths starting with a '|' are handled separately to allow for special treatment.
|
||||
// They are expected to also have a closing '|'.
|
||||
if( ResourceLoadCustomization == null || gamePath.Path[ 0 ] != ( byte )'|' )
|
||||
{
|
||||
return DefaultLoadResource( gamePath.Path, resourceManager, fileDescriptor, priority, isSync );
|
||||
}
|
||||
|
||||
// Split the path into the special-treatment part (between the first and second '|')
|
||||
// and the actual path.
|
||||
var split = gamePath.Path.Split( ( byte )'|', 3, false );
|
||||
fileDescriptor->ResourceHandle->FileNameData = split[ 2 ].Path;
|
||||
fileDescriptor->ResourceHandle->FileNameLength = split[ 2 ].Length;
|
||||
var funcFound = ResourceLoadCustomization.GetInvocationList()
|
||||
.Any( f => ( ( ResourceLoadCustomizationDelegate )f )
|
||||
.Invoke( split[ 1 ], split[ 2 ], resourceManager, fileDescriptor, priority, isSync, out ret ) );
|
||||
|
||||
if( !funcFound )
|
||||
{
|
||||
ret = DefaultLoadResource( split[ 2 ], resourceManager, fileDescriptor, priority, isSync );
|
||||
}
|
||||
|
||||
// Return original resource handle path so that they can be loaded separately.
|
||||
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
|
||||
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Customize file loading for any GamePaths that start with "|".
|
||||
public delegate byte ResourceLoadCustomizationDelegate( Utf8GamePath gamePath, ResourceManager* resourceManager,
|
||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync );
|
||||
|
||||
public ResourceLoadCustomizationDelegate? ResourceLoadCustomization;
|
||||
|
||||
|
||||
// Use the default method of path replacement.
|
||||
public static (FullPath?, object?) DefaultReplacer( Utf8GamePath path )
|
||||
// Load the resource from an SqPack and trigger the FileLoaded event.
|
||||
private byte DefaultResourceLoad( Utf8String path, ResourceManager* resourceManager,
|
||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
||||
{
|
||||
var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path );
|
||||
return ( resolved, null );
|
||||
var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
||||
FileLoaded?.Invoke( path, ret != 0, false );
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Load the resource from a path on the users hard drives.
|
||||
private byte DefaultRootedResourceLoad( Utf8String gamePath, ResourceManager* resourceManager,
|
||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
||||
{
|
||||
// Specify that we are loading unpacked files from the drive.
|
||||
// We need to copy the actual file path in UTF16 (Windows-Unicode) on two locations,
|
||||
// but since we only allow ASCII in the game paths, this is just a matter of upcasting.
|
||||
fileDescriptor->FileMode = FileMode.LoadUnpackedResource;
|
||||
|
||||
var fd = stackalloc byte[0x20 + 2 * gamePath.Length + 0x16];
|
||||
fileDescriptor->FileDescriptor = fd;
|
||||
var fdPtr = ( char* )( fd + 0x21 );
|
||||
for( var i = 0; i < gamePath.Length; ++i )
|
||||
{
|
||||
( &fileDescriptor->Utf16FileName )[ i ] = ( char )gamePath.Path[ i ];
|
||||
fdPtr[ i ] = ( char )gamePath.Path[ i ];
|
||||
}
|
||||
|
||||
( &fileDescriptor->Utf16FileName )[ gamePath.Length ] = '\0';
|
||||
fdPtr[ gamePath.Length ] = '\0';
|
||||
|
||||
// Use the SE ReadFile function.
|
||||
var ret = ReadFile( resourceManager, fileDescriptor, priority, isSync );
|
||||
FileLoaded?.Invoke( gamePath, ret != 0, true );
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Load a resource by its path. If it is rooted, it will be loaded from the drive, otherwise from the SqPack.
|
||||
internal byte DefaultLoadResource( Utf8String gamePath, ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority,
|
||||
bool isSync )
|
||||
=> Utf8GamePath.IsRooted( gamePath )
|
||||
? DefaultRootedResourceLoad( gamePath, resourceManager, fileDescriptor, priority, isSync )
|
||||
: DefaultResourceLoad( gamePath, resourceManager, fileDescriptor, priority, isSync );
|
||||
|
||||
private void DisposeHooks()
|
||||
{
|
||||
DisableHooks();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue