mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-16 13:44:17 +01:00
Take segmented read into consideration when changing CRC32 values after path replacement
This commit is contained in:
parent
d6d13594e0
commit
9dd12f4a71
1 changed files with 48 additions and 15 deletions
|
|
@ -1,12 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using FileMode = Penumbra.Interop.Structs.FileMode;
|
using FileMode = Penumbra.Interop.Structs.FileMode;
|
||||||
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
||||||
|
|
@ -17,31 +19,44 @@ public unsafe partial class ResourceLoader
|
||||||
{
|
{
|
||||||
// Resources can be obtained synchronously and asynchronously. We need to change behaviour in both cases.
|
// 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.
|
// Both work basically the same, so we can reduce the main work to one function used by both hooks.
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct GetResourceParameters
|
||||||
|
{
|
||||||
|
[FieldOffset( 16 )]
|
||||||
|
public uint SegmentOffset;
|
||||||
|
|
||||||
|
[FieldOffset( 20 )]
|
||||||
|
public uint SegmentLength;
|
||||||
|
|
||||||
|
public bool IsPartialRead => SegmentLength != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public delegate ResourceHandle* GetResourceSyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId,
|
public delegate ResourceHandle* GetResourceSyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId,
|
||||||
ResourceType* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown );
|
ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams );
|
||||||
|
|
||||||
[Signature( "E8 ?? ?? 00 00 48 8D 8F ?? ?? 00 00 48 89 87 ?? ?? 00 00", DetourName = "GetResourceSyncDetour" )]
|
[Signature( "E8 ?? ?? 00 00 48 8D 8F ?? ?? 00 00 48 89 87 ?? ?? 00 00", DetourName = "GetResourceSyncDetour" )]
|
||||||
public Hook< GetResourceSyncPrototype > GetResourceSyncHook = null!;
|
public Hook< GetResourceSyncPrototype > GetResourceSyncHook = null!;
|
||||||
|
|
||||||
public delegate ResourceHandle* GetResourceAsyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId,
|
public delegate ResourceHandle* GetResourceAsyncPrototype( ResourceManager* resourceManager, ResourceCategory* pCategoryId,
|
||||||
ResourceType* pResourceType, int* pResourceHash, byte* pPath, void* pUnknown, bool isUnknown );
|
ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams, bool isUnknown );
|
||||||
|
|
||||||
[Signature( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00", DetourName = "GetResourceAsyncDetour" )]
|
[Signature( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00", DetourName = "GetResourceAsyncDetour" )]
|
||||||
public Hook< GetResourceAsyncPrototype > GetResourceAsyncHook = null!;
|
public Hook< GetResourceAsyncPrototype > GetResourceAsyncHook = null!;
|
||||||
|
|
||||||
private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
|
private ResourceHandle* GetResourceSyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
|
||||||
int* resourceHash, byte* path, void* unk )
|
int* resourceHash, byte* path, GetResourceParameters* pGetResParams )
|
||||||
=> GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, unk, false );
|
=> GetResourceHandler( true, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, false );
|
||||||
|
|
||||||
private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
|
private ResourceHandle* GetResourceAsyncDetour( ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType,
|
||||||
int* resourceHash, byte* path, void* unk, bool isUnk )
|
int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
|
||||||
=> GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
=> GetResourceHandler( false, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
|
||||||
|
|
||||||
private ResourceHandle* CallOriginalHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
private ResourceHandle* CallOriginalHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||||
ResourceType* resourceType, int* resourceHash, byte* path, void* unk, bool isUnk )
|
ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
|
||||||
=> isSync
|
=> isSync
|
||||||
? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk )
|
? GetResourceSyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams )
|
||||||
: GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
: GetResourceAsyncHook.Original( resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
|
||||||
|
|
||||||
|
|
||||||
[Conditional( "DEBUG" )]
|
[Conditional( "DEBUG" )]
|
||||||
|
|
@ -56,15 +71,15 @@ public unsafe partial class ResourceLoader
|
||||||
private event Action< Utf8GamePath, FullPath?, object? >? PathResolved;
|
private event Action< Utf8GamePath, FullPath?, object? >? PathResolved;
|
||||||
|
|
||||||
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||||
ResourceType* resourceType, int* resourceHash, byte* path, void* unk, bool isUnk )
|
ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
|
||||||
{
|
{
|
||||||
if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
|
if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
|
||||||
{
|
{
|
||||||
PluginLog.Error( "Could not create GamePath from resource path." );
|
PluginLog.Error( "Could not create GamePath from resource path." );
|
||||||
return CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, unk, isUnk );
|
return CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
|
||||||
}
|
}
|
||||||
|
|
||||||
CompareHash( gamePath.Path.Crc32, *resourceHash, gamePath );
|
CompareHash( ComputeHash( gamePath.Path, pGetResParams ), *resourceHash, gamePath );
|
||||||
|
|
||||||
ResourceRequested?.Invoke( gamePath, isSync );
|
ResourceRequested?.Invoke( gamePath, isSync );
|
||||||
|
|
||||||
|
|
@ -73,15 +88,15 @@ public unsafe partial class ResourceLoader
|
||||||
PathResolved?.Invoke( gamePath, resolvedPath, data );
|
PathResolved?.Invoke( gamePath, resolvedPath, data );
|
||||||
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, pGetResParams, isUnk );
|
||||||
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data );
|
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data );
|
||||||
return retUnmodified;
|
return retUnmodified;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the hash and path with the correct one for the replacement.
|
// Replace the hash and path with the correct one for the replacement.
|
||||||
*resourceHash = resolvedPath.Value.InternalName.Crc32;
|
*resourceHash = ComputeHash( resolvedPath.Value.InternalName, pGetResParams );
|
||||||
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, pGetResParams, isUnk );
|
||||||
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data );
|
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data );
|
||||||
return retModified;
|
return retModified;
|
||||||
}
|
}
|
||||||
|
|
@ -228,4 +243,22 @@ public unsafe partial class ResourceLoader
|
||||||
GetResourceSyncHook.Dispose();
|
GetResourceSyncHook.Dispose();
|
||||||
GetResourceAsyncHook.Dispose();
|
GetResourceAsyncHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int ComputeHash( Utf8String path, GetResourceParameters* pGetResParams )
|
||||||
|
{
|
||||||
|
if( pGetResParams == null || !pGetResParams->IsPartialRead )
|
||||||
|
return path.Crc32;
|
||||||
|
|
||||||
|
// When the game requests file only partially, crc32 includes that information, in format of:
|
||||||
|
// path/to/file.ext.hex_offset.hex_size
|
||||||
|
// ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000
|
||||||
|
var pathWithSegmentInfo = Utf8String.Join(
|
||||||
|
0x2e,
|
||||||
|
path,
|
||||||
|
Utf8String.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ),
|
||||||
|
Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true )
|
||||||
|
);
|
||||||
|
Functions.ComputeCrc32AsciiLowerAndSize( pathWithSegmentInfo.Path, out var crc32, out _, out _ );
|
||||||
|
return crc32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue