diff --git a/Penumbra.GameData b/Penumbra.GameData
index 066637ab..19923f8d 160000
--- a/Penumbra.GameData
+++ b/Penumbra.GameData
@@ -1 +1 @@
-Subproject commit 066637abe05c659b79d84f52e6db33487498f433
+Subproject commit 19923f8d5649f11edcfae710c26d6273cf2e9d62
diff --git a/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs b/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs
index 28ad7aa4..793d15af 100644
--- a/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs
+++ b/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs
@@ -1,93 +1,110 @@
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
-using Lumina.Excel.GeneratedSheets2;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.Interop.Structs;
using Penumbra.String.Classes;
-using FileMode = Penumbra.Interop.Structs.FileMode;
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
namespace Penumbra.Interop.Hooks.ResourceLoading;
public unsafe class TexMdlService : IDisposable, IRequiredService
{
+ ///
+ /// We need to be able to obtain the requested LoD level.
+ /// This replicates the LoD behavior of a textures OnLoad function.
+ ///
+ 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;
+ }
+ }
+
/// Custom ulong flag to signal our files as opposed to SE files.
public static readonly nint CustomFileFlag = new(0xDEADBEEF);
- ///
- /// 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.
- ///
- public IReadOnlySet CustomFileCrc
- => _customFileCrc;
+ private readonly LodService _lodService;
public TexMdlService(IGameInteropProvider interop)
{
interop.InitializeFromAttributes(this);
+ _lodService = new LodService(interop);
if (HookSettings.ReplacementHooks)
{
_checkFileStateHook.Enable();
_loadMdlFileExternHook.Enable();
- _textureSomethingHook.Enable();
- _vf32Hook.Enable();
- //_loadTexFileExternHook.Enable();
+ _textureOnLoadHook.Enable();
}
}
/// Add CRC64 if the given file is a model or texture file and has an associated path.
public void AddCrc(ResourceType type, FullPath? path)
{
- if (path.HasValue && type is ResourceType.Mdl)
- _customFileCrc.Add(path.Value.Crc64);
+ _ = type switch
+ {
+ ResourceType.Mdl when path.HasValue => _customMdlCrc.Add(path.Value.Crc64),
+ ResourceType.Tex when path.HasValue => _customTexCrc.Add(path.Value.Crc64),
+ _ => false,
+ };
}
- /// Add a fixed CRC64 value.
- public void AddCrc(ulong crc64)
- => _customFileCrc.Add(crc64);
-
public void Dispose()
{
_checkFileStateHook.Dispose();
- //_loadTexFileExternHook.Dispose();
- _textureSomethingHook.Dispose();
_loadMdlFileExternHook.Dispose();
- _vf32Hook.Dispose();
+ _textureOnLoadHook.Dispose();
}
- private readonly HashSet _customFileCrc = [];
+ ///
+ /// 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.
+ ///
+ private readonly HashSet _customMdlCrc = [];
+
+ private readonly HashSet _customTexCrc = [];
private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64);
- private delegate nint TextureSomethingDelegate(TextureResourceHandle* handle, int lod, SeFileDescriptor* descriptor);
-
[Signature(Sigs.CheckFileState, DetourName = nameof(CheckFileStateDetour))]
private readonly Hook _checkFileStateHook = null!;
- [Signature("E8 ?? ?? ?? ?? 0F B6 C8 EB ?? 4C 8B 83", DetourName = nameof(TextureSomethingDetour))]
- private readonly Hook _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);
- }
+ private readonly ThreadLocal _texReturnData = new(() => default);
///
/// 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.
///
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);
/// We use the local functions for our own files in the extern hook.
[Signature(Sigs.LoadTexFileLocal)]
@@ -99,36 +116,23 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
[Signature(Sigs.LoadMdlFileLocal)]
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 _textureOnLoadHook = null!;
- private delegate byte TexResourceHandleVf32Prototype(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 _vf32Hook = null!;
-
- private byte Vf32Detour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
+ private byte OnLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
{
- //if (handle->Handle.GamePath(out var path) && path.IsRooted())
- //{
- // Penumbra.Log.Information($"Replacing {descriptor->FileMode} with {FileMode.LoadSqPackResource} in VF32 for {path}.");
- // descriptor->FileMode = FileMode.LoadSqPackResource;
- //}
+ var ret = _textureOnLoadHook.Original(handle, descriptor, unk2);
+ if (!_texReturnData.Value)
+ return ret;
- var ret = _vf32Hook.Original(handle, descriptor, unk2);
- 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 readonly Hook _loadTexFileExternHook = null!;
-
- /// We hook the extern functions to just return the local one if given the custom flag as last argument.
- //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);
-
+ private delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3);
[Signature(Sigs.LoadMdlFileExtern, DetourName = nameof(LoadMdlFileExternDetour))]
private readonly Hook _loadMdlFileExternHook = null!;
diff --git a/Penumbra/Interop/Structs/ResourceHandle.cs b/Penumbra/Interop/Structs/ResourceHandle.cs
index 058b9004..6e428f25 100644
--- a/Penumbra/Interop/Structs/ResourceHandle.cs
+++ b/Penumbra/Interop/Structs/ResourceHandle.cs
@@ -14,6 +14,12 @@ public unsafe struct TextureResourceHandle
[FieldOffset(0x0)]
public CsHandle.TextureResourceHandle CsHandle;
+
+ [FieldOffset(0x104)]
+ public byte SomeLodFlag;
+
+ public bool ChangeLod
+ => (SomeLodFlag & 1) != 0;
}
public enum LoadState : byte