Change texture handling.

This commit is contained in:
Ottermandias 2024-07-05 12:14:31 +02:00
parent 9fb8090781
commit 4026dd5867
3 changed files with 73 additions and 63 deletions

@ -1 +1 @@
Subproject commit 066637abe05c659b79d84f52e6db33487498f433 Subproject commit 19923f8d5649f11edcfae710c26d6273cf2e9d62

View file

@ -1,93 +1,110 @@
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using Lumina.Excel.GeneratedSheets2;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using FileMode = Penumbra.Interop.Structs.FileMode;
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
namespace Penumbra.Interop.Hooks.ResourceLoading; namespace Penumbra.Interop.Hooks.ResourceLoading;
public unsafe class TexMdlService : IDisposable, IRequiredService public unsafe class TexMdlService : IDisposable, IRequiredService
{ {
/// <summary>
/// We need to be able to obtain the requested LoD level.
/// This replicates the LoD behavior of a textures OnLoad function.
/// </summary>
private readonly struct LodService
{
public LodService(IGameInteropProvider interop)
=> interop.InitializeFromAttributes(this);
[Signature(Sigs.LodConfig)]
private readonly nint _lodConfig = nint.Zero;
public byte GetLod(TextureResourceHandle* handle)
{
if (handle->ChangeLod)
{
var config = *(byte*)_lodConfig + 0xE;
if (config == byte.MaxValue)
return 2;
}
return 0;
}
}
/// <summary> Custom ulong flag to signal our files as opposed to SE files. </summary> /// <summary> Custom ulong flag to signal our files as opposed to SE files. </summary>
public static readonly nint CustomFileFlag = new(0xDEADBEEF); public static readonly nint CustomFileFlag = new(0xDEADBEEF);
/// <summary> private readonly LodService _lodService;
/// We need to keep a list of all CRC64 hash values of our replaced Mdl and Tex files,
/// i.e. CRC32 of filename in the lower bytes, CRC32 of parent path in the upper bytes.
/// </summary>
public IReadOnlySet<ulong> CustomFileCrc
=> _customFileCrc;
public TexMdlService(IGameInteropProvider interop) public TexMdlService(IGameInteropProvider interop)
{ {
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_lodService = new LodService(interop);
if (HookSettings.ReplacementHooks) if (HookSettings.ReplacementHooks)
{ {
_checkFileStateHook.Enable(); _checkFileStateHook.Enable();
_loadMdlFileExternHook.Enable(); _loadMdlFileExternHook.Enable();
_textureSomethingHook.Enable(); _textureOnLoadHook.Enable();
_vf32Hook.Enable();
//_loadTexFileExternHook.Enable();
} }
} }
/// <summary> Add CRC64 if the given file is a model or texture file and has an associated path. </summary> /// <summary> Add CRC64 if the given file is a model or texture file and has an associated path. </summary>
public void AddCrc(ResourceType type, FullPath? path) public void AddCrc(ResourceType type, FullPath? path)
{ {
if (path.HasValue && type is ResourceType.Mdl) _ = type switch
_customFileCrc.Add(path.Value.Crc64); {
ResourceType.Mdl when path.HasValue => _customMdlCrc.Add(path.Value.Crc64),
ResourceType.Tex when path.HasValue => _customTexCrc.Add(path.Value.Crc64),
_ => false,
};
} }
/// <summary> Add a fixed CRC64 value. </summary>
public void AddCrc(ulong crc64)
=> _customFileCrc.Add(crc64);
public void Dispose() public void Dispose()
{ {
_checkFileStateHook.Dispose(); _checkFileStateHook.Dispose();
//_loadTexFileExternHook.Dispose();
_textureSomethingHook.Dispose();
_loadMdlFileExternHook.Dispose(); _loadMdlFileExternHook.Dispose();
_vf32Hook.Dispose(); _textureOnLoadHook.Dispose();
} }
private readonly HashSet<ulong> _customFileCrc = []; /// <summary>
/// We need to keep a list of all CRC64 hash values of our replaced Mdl and Tex files,
/// i.e. CRC32 of filename in the lower bytes, CRC32 of parent path in the upper bytes.
/// </summary>
private readonly HashSet<ulong> _customMdlCrc = [];
private readonly HashSet<ulong> _customTexCrc = [];
private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64); private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64);
private delegate nint TextureSomethingDelegate(TextureResourceHandle* handle, int lod, SeFileDescriptor* descriptor);
[Signature(Sigs.CheckFileState, DetourName = nameof(CheckFileStateDetour))] [Signature(Sigs.CheckFileState, DetourName = nameof(CheckFileStateDetour))]
private readonly Hook<CheckFileStatePrototype> _checkFileStateHook = null!; private readonly Hook<CheckFileStatePrototype> _checkFileStateHook = null!;
[Signature("E8 ?? ?? ?? ?? 0F B6 C8 EB ?? 4C 8B 83", DetourName = nameof(TextureSomethingDetour))] private readonly ThreadLocal<bool> _texReturnData = new(() => default);
private readonly Hook<TextureSomethingDelegate> _textureSomethingHook = null!;
private nint TextureSomethingDetour(TextureResourceHandle* handle, int lod, SeFileDescriptor* descriptor)
{
//Penumbra.Log.Information($"SomethingDetour {handle->Handle.FileName()}");
//if (!handle->Handle.GamePath(out var path) || !path.IsRooted())
return _textureSomethingHook.Original(handle, lod, descriptor);
descriptor->FileMode = FileMode.LoadUnpackedResource;
return _loadTexFileLocal.Invoke((ResourceHandle*)handle, lod, (nint)descriptor, true);
}
/// <summary> /// <summary>
/// The function that checks a files CRC64 to determine whether it is 'protected'. /// The function that checks a files CRC64 to determine whether it is 'protected'.
/// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag. /// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag for models.
/// Since Dawntrail inlined the RSF function for textures, we can not use the flag method here.
/// Instead, we signal the caller that this will fail and let it call the local function after intentionally failing.
/// </summary> /// </summary>
private nint CheckFileStateDetour(nint ptr, ulong crc64) private nint CheckFileStateDetour(nint ptr, ulong crc64)
=> _customFileCrc.Contains(crc64) ? CustomFileFlag : _checkFileStateHook.Original(ptr, crc64); {
if (_customMdlCrc.Contains(crc64))
return CustomFileFlag;
if (_customTexCrc.Contains(crc64))
_texReturnData.Value = true;
private delegate byte LoadTexFileLocalDelegate(ResourceHandle* handle, int unk1, nint unk2, bool unk3); return _checkFileStateHook.Original(ptr, crc64);
}
private delegate byte LoadTexFileLocalDelegate(TextureResourceHandle* handle, int unk1, SeFileDescriptor* unk2, bool unk3);
/// <summary> We use the local functions for our own files in the extern hook. </summary> /// <summary> We use the local functions for our own files in the extern hook. </summary>
[Signature(Sigs.LoadTexFileLocal)] [Signature(Sigs.LoadTexFileLocal)]
@ -99,36 +116,23 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
[Signature(Sigs.LoadMdlFileLocal)] [Signature(Sigs.LoadMdlFileLocal)]
private readonly LoadMdlFileLocalPrototype _loadMdlFileLocal = null!; private readonly LoadMdlFileLocalPrototype _loadMdlFileLocal = null!;
private delegate byte TexResourceHandleOnLoadPrototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2);
private delegate byte LoadTexFileExternPrototype(ResourceHandle* handle, int unk1, nint unk2, bool unk3, nint unk4); [Signature(Sigs.TexResourceHandleOnLoad, DetourName = nameof(OnLoadDetour))]
private readonly Hook<TexResourceHandleOnLoadPrototype> _textureOnLoadHook = null!;
private delegate byte TexResourceHandleVf32Prototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2); private byte OnLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
[Signature("40 53 55 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B D9", DetourName = nameof(Vf32Detour))]
private readonly Hook<TexResourceHandleVf32Prototype> _vf32Hook = null!;
private byte Vf32Detour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
{ {
//if (handle->Handle.GamePath(out var path) && path.IsRooted()) var ret = _textureOnLoadHook.Original(handle, descriptor, unk2);
//{ if (!_texReturnData.Value)
// Penumbra.Log.Information($"Replacing {descriptor->FileMode} with {FileMode.LoadSqPackResource} in VF32 for {path}.");
// descriptor->FileMode = FileMode.LoadSqPackResource;
//}
var ret = _vf32Hook.Original(handle, descriptor, unk2);
return ret; return ret;
// Function failed on a replaced texture, call local.
_texReturnData.Value = false;
return _loadTexFileLocal(handle, _lodService.GetLod(handle), descriptor, unk2 != 0);
} }
//[Signature(Sigs.LoadTexFileExtern, DetourName = nameof(LoadTexFileExternDetour))] private delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3);
//private readonly Hook<LoadTexFileExternPrototype> _loadTexFileExternHook = null!;
/// <summary> We hook the extern functions to just return the local one if given the custom flag as last argument. </summary>
//private byte LoadTexFileExternDetour(ResourceHandle* resourceHandle, int unk1, nint unk2, bool unk3, nint ptr)
// => ptr.Equals(CustomFileFlag)
// ? _loadTexFileLocal.Invoke(resourceHandle, unk1, unk2, unk3)
// : _loadTexFileExternHook.Original(resourceHandle, unk1, unk2, unk3, ptr);
public delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3);
[Signature(Sigs.LoadMdlFileExtern, DetourName = nameof(LoadMdlFileExternDetour))] [Signature(Sigs.LoadMdlFileExtern, DetourName = nameof(LoadMdlFileExternDetour))]
private readonly Hook<LoadMdlFileExternPrototype> _loadMdlFileExternHook = null!; private readonly Hook<LoadMdlFileExternPrototype> _loadMdlFileExternHook = null!;

View file

@ -14,6 +14,12 @@ public unsafe struct TextureResourceHandle
[FieldOffset(0x0)] [FieldOffset(0x0)]
public CsHandle.TextureResourceHandle CsHandle; public CsHandle.TextureResourceHandle CsHandle;
[FieldOffset(0x104)]
public byte SomeLodFlag;
public bool ChangeLod
=> (SomeLodFlag & 1) != 0;
} }
public enum LoadState : byte public enum LoadState : byte