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