diff --git a/Penumbra/Interop/GameState.cs b/Penumbra/Interop/GameState.cs index 2552f1a7..7e7abcd8 100644 --- a/Penumbra/Interop/GameState.cs +++ b/Penumbra/Interop/GameState.cs @@ -72,7 +72,24 @@ public class GameState : IService #endregion - /// Return the correct resolve data from the stored data. + #region Subfiles + + public readonly ThreadLocal MtrlData = new(() => ResolveData.Invalid); + public readonly ThreadLocal AvfxData = new(() => ResolveData.Invalid); + + public readonly ConcurrentDictionary SubFileCollection = new(); + + public ResolveData LoadSubFileHelper(nint resourceHandle) + { + if (resourceHandle == nint.Zero) + return ResolveData.Invalid; + + return SubFileCollection.TryGetValue(resourceHandle, out var c) ? c : ResolveData.Invalid; + } + + #endregion + + /// Return the correct resolve data from the stored data. public unsafe bool HandleFiles(CollectionResolver resolver, ResourceType type, Utf8GamePath _, out ResolveData resolveData) { switch (type) diff --git a/Penumbra/Interop/Hooks/CharacterBaseDestructor.cs b/Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs similarity index 96% rename from Penumbra/Interop/Hooks/CharacterBaseDestructor.cs rename to Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs index 435ddea6..fc6dbfe6 100644 --- a/Penumbra/Interop/Hooks/CharacterBaseDestructor.cs +++ b/Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs @@ -4,7 +4,7 @@ using OtterGui.Classes; using OtterGui.Services; using Penumbra.UI.AdvancedWindow; -namespace Penumbra.Interop.Hooks; +namespace Penumbra.Interop.Hooks.Objects; public sealed unsafe class CharacterBaseDestructor : EventWrapperPtr, IHookService { diff --git a/Penumbra/Interop/Hooks/CharacterDestructor.cs b/Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs similarity index 96% rename from Penumbra/Interop/Hooks/CharacterDestructor.cs rename to Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs index 4a0e9367..6e10c5e3 100644 --- a/Penumbra/Interop/Hooks/CharacterDestructor.cs +++ b/Penumbra/Interop/Hooks/Objects/CharacterDestructor.cs @@ -4,7 +4,7 @@ using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData; -namespace Penumbra.Interop.Hooks; +namespace Penumbra.Interop.Hooks.Objects; public sealed unsafe class CharacterDestructor : EventWrapperPtr, IHookService { diff --git a/Penumbra/Interop/Hooks/CopyCharacter.cs b/Penumbra/Interop/Hooks/Objects/CopyCharacter.cs similarity index 96% rename from Penumbra/Interop/Hooks/CopyCharacter.cs rename to Penumbra/Interop/Hooks/Objects/CopyCharacter.cs index d2e8d816..7b730f84 100644 --- a/Penumbra/Interop/Hooks/CopyCharacter.cs +++ b/Penumbra/Interop/Hooks/Objects/CopyCharacter.cs @@ -3,7 +3,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using OtterGui.Classes; using OtterGui.Services; -namespace Penumbra.Interop.Hooks; +namespace Penumbra.Interop.Hooks.Objects; public sealed unsafe class CopyCharacter : EventWrapperPtr, IHookService { diff --git a/Penumbra/Interop/Hooks/CreateCharacterBase.cs b/Penumbra/Interop/Hooks/Objects/CreateCharacterBase.cs similarity index 94% rename from Penumbra/Interop/Hooks/CreateCharacterBase.cs rename to Penumbra/Interop/Hooks/Objects/CreateCharacterBase.cs index 7dbde666..299f312a 100644 --- a/Penumbra/Interop/Hooks/CreateCharacterBase.cs +++ b/Penumbra/Interop/Hooks/Objects/CreateCharacterBase.cs @@ -4,7 +4,7 @@ using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Structs; -namespace Penumbra.Interop.Hooks; +namespace Penumbra.Interop.Hooks.Objects; public sealed unsafe class CreateCharacterBase : EventWrapperPtr, IHookService { @@ -39,7 +39,7 @@ public sealed unsafe class CreateCharacterBase : EventWrapperPtr /// EnableDraw is what creates DrawObjects for gameObjects, @@ -12,12 +12,12 @@ namespace Penumbra.Interop.Hooks; public sealed unsafe class EnableDraw : IHookService { private readonly Task> _task; - private readonly GameState _state; + private readonly GameState _state; public EnableDraw(HookManager hooks, GameState state) { _state = state; - _task = hooks.CreateHook("Enable Draw", Sigs.EnableDraw, Detour, true); + _task = hooks.CreateHook("Enable Draw", Sigs.EnableDraw, Detour, true); } private delegate void Delegate(GameObject* gameObject); @@ -26,7 +26,7 @@ public sealed unsafe class EnableDraw : IHookService private void Detour(GameObject* gameObject) { _state.QueueGameObject(gameObject); - Penumbra.Log.Excessive($"[Enable Draw] Invoked on 0x{(nint) gameObject:X}."); + Penumbra.Log.Excessive($"[Enable Draw] Invoked on 0x{(nint)gameObject:X}."); _task.Result.Original.Invoke(gameObject); _state.DequeueGameObject(); } diff --git a/Penumbra/Interop/Hooks/WeaponReload.cs b/Penumbra/Interop/Hooks/Objects/WeaponReload.cs similarity index 98% rename from Penumbra/Interop/Hooks/WeaponReload.cs rename to Penumbra/Interop/Hooks/Objects/WeaponReload.cs index b931f8fb..31c6b883 100644 --- a/Penumbra/Interop/Hooks/WeaponReload.cs +++ b/Penumbra/Interop/Hooks/Objects/WeaponReload.cs @@ -4,7 +4,7 @@ using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Structs; -namespace Penumbra.Interop.Hooks; +namespace Penumbra.Interop.Hooks.Objects; public sealed unsafe class WeaponReload : EventWrapperPtr, IHookService { diff --git a/Penumbra/Interop/Hooks/Resources/ApricotResourceLoad.cs b/Penumbra/Interop/Hooks/Resources/ApricotResourceLoad.cs new file mode 100644 index 00000000..2e5698a3 --- /dev/null +++ b/Penumbra/Interop/Hooks/Resources/ApricotResourceLoad.cs @@ -0,0 +1,28 @@ +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using OtterGui.Services; +using Penumbra.GameData; + +namespace Penumbra.Interop.Hooks.Resources; + +public sealed unsafe class ApricotResourceLoad : FastHook +{ + private readonly GameState _gameState; + + public ApricotResourceLoad(HookManager hooks, GameState gameState) + { + _gameState = gameState; + Task = hooks.CreateHook("Load Apricot Resource", Sigs.ApricotResourceLoad, Detour, true); + } + + public delegate byte Delegate(ResourceHandle* handle, nint unk1, byte unk2); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private byte Detour(ResourceHandle* handle, nint unk1, byte unk2) + { + var last = _gameState.AvfxData.Value; + _gameState.AvfxData.Value = _gameState.LoadSubFileHelper((nint)handle); + var ret = Task.Result.Original(handle, unk1, unk2); + _gameState.AvfxData.Value = last; + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Resources/LoadMtrlShpk.cs b/Penumbra/Interop/Hooks/Resources/LoadMtrlShpk.cs new file mode 100644 index 00000000..5ef3bf37 --- /dev/null +++ b/Penumbra/Interop/Hooks/Resources/LoadMtrlShpk.cs @@ -0,0 +1,32 @@ +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using OtterGui.Services; +using Penumbra.GameData; +using Penumbra.Services; + +namespace Penumbra.Interop.Hooks.Resources; + +public sealed unsafe class LoadMtrlShpk : FastHook +{ + private readonly GameState _gameState; + private readonly CommunicatorService _communicator; + + public LoadMtrlShpk(HookManager hooks, GameState gameState, CommunicatorService communicator) + { + _gameState = gameState; + _communicator = communicator; + Task = hooks.CreateHook("Load Material Shaders", Sigs.LoadMtrlShpk, Detour, true); + } + + public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle); + + private byte Detour(MaterialResourceHandle* handle) + { + var last = _gameState.MtrlData.Value; + var mtrlData = _gameState.LoadSubFileHelper((nint)handle); + _gameState.MtrlData.Value = mtrlData; + var ret = Task.Result.Original(handle); + _gameState.MtrlData.Value = last; + _communicator.MtrlShpkLoaded.Invoke((nint)handle, mtrlData.AssociatedGameObject); + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Resources/LoadMtrlTex.cs b/Penumbra/Interop/Hooks/Resources/LoadMtrlTex.cs new file mode 100644 index 00000000..14a011ea --- /dev/null +++ b/Penumbra/Interop/Hooks/Resources/LoadMtrlTex.cs @@ -0,0 +1,28 @@ +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using OtterGui.Services; +using Penumbra.GameData; + +namespace Penumbra.Interop.Hooks.Resources; + +public sealed unsafe class LoadMtrlTex : FastHook +{ + private readonly GameState _gameState; + + public LoadMtrlTex(HookManager hooks, GameState gameState) + { + _gameState = gameState; + Task = hooks.CreateHook("Load Material Textures", Sigs.LoadMtrlTex, Detour, true); + } + + public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private byte Detour(MaterialResourceHandle* handle) + { + var last = _gameState.MtrlData.Value; + _gameState.MtrlData.Value = _gameState.LoadSubFileHelper((nint)handle); + var ret = Task.Result.Original(handle); + _gameState.MtrlData.Value = last; + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooks.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooks.cs new file mode 100644 index 00000000..8a52acd2 --- /dev/null +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooks.cs @@ -0,0 +1,38 @@ +using OtterGui.Services; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Resources; + +public sealed unsafe class ResolvePathHooks(HookManager hooks, CharacterBaseVTables vTables, PathState pathState) : IDisposable, IRequiredService +{ + // @formatter:off + private readonly ResolvePathHooksBase _human = new("Human", hooks, pathState, vTables.HumanVTable, ResolvePathHooksBase.Type.Human); + private readonly ResolvePathHooksBase _weapon = new("Weapon", hooks, pathState, vTables.WeaponVTable, ResolvePathHooksBase.Type.Other); + private readonly ResolvePathHooksBase _demiHuman = new("DemiHuman", hooks, pathState, vTables.DemiHumanVTable, ResolvePathHooksBase.Type.Other); + private readonly ResolvePathHooksBase _monster = new("Monster", hooks, pathState, vTables.MonsterVTable, ResolvePathHooksBase.Type.Other); + // @formatter:on + + public void Enable() + { + _human.Enable(); + _weapon.Enable(); + _demiHuman.Enable(); + _monster.Enable(); + } + + public void Disable() + { + _human.Disable(); + _weapon.Disable(); + _demiHuman.Disable(); + _monster.Disable(); + } + + public void Dispose() + { + _human.Dispose(); + _weapon.Dispose(); + _demiHuman.Dispose(); + _monster.Dispose(); + } +} diff --git a/Penumbra/Interop/PathResolving/ResolvePathHooks.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs similarity index 78% rename from Penumbra/Interop/PathResolving/ResolvePathHooks.cs rename to Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index 3be7ffdd..6b4abf90 100644 --- a/Penumbra/Interop/PathResolving/ResolvePathHooks.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -1,13 +1,14 @@ using Dalamud.Hooking; -using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.Collections; +using Penumbra.Interop.PathResolving; using Penumbra.Meta.Manipulations; -namespace Penumbra.Interop.PathResolving; +namespace Penumbra.Interop.Hooks.Resources; -public unsafe class ResolvePathHooks : IDisposable +public sealed unsafe class ResolvePathHooksBase : IDisposable { public enum Type { @@ -19,7 +20,9 @@ public unsafe class ResolvePathHooks : IDisposable private delegate nint NamedResolveDelegate(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint name); private delegate nint PerSlotResolveDelegate(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex); private delegate nint SingleResolveDelegate(nint drawObject, nint pathBuffer, nint pathBufferSize); + private delegate nint TmbResolveDelegate(nint drawObject, nint pathBuffer, nint pathBufferSize, nint timelineName); + // Kept separate from NamedResolveDelegate because the 5th parameter has out semantics here, instead of in. private delegate nint VfxResolveDelegate(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint unkOutParam); @@ -38,21 +41,24 @@ public unsafe class ResolvePathHooks : IDisposable private readonly PathState _parent; - public ResolvePathHooks(IGameInteropProvider interop, PathState parent, nint* vTable, Type type) + public ResolvePathHooksBase(string name, HookManager hooks, PathState parent, nint* vTable, Type type) { - _parent = parent; - _resolveDecalPathHook = Create(interop, vTable[83], ResolveDecal); - _resolveEidPathHook = Create(interop, vTable[85], ResolveEid); - _resolveImcPathHook = Create(interop, vTable[81], ResolveImc); - _resolveMPapPathHook = Create(interop, vTable[79], ResolveMPap); - _resolveMdlPathHook = Create(interop, vTable[73], type, ResolveMdl, ResolveMdlHuman); - _resolveMtrlPathHook = Create(interop, vTable[82], ResolveMtrl); - _resolvePapPathHook = Create(interop, vTable[76], type, ResolvePap, ResolvePapHuman); - _resolvePhybPathHook = Create(interop, vTable[75], type, ResolvePhyb, ResolvePhybHuman); - _resolveSklbPathHook = Create(interop, vTable[72], type, ResolveSklb, ResolveSklbHuman); - _resolveSkpPathHook = Create(interop, vTable[74], type, ResolveSkp, ResolveSkpHuman); - _resolveTmbPathHook = Create(interop, vTable[77], ResolveTmb); - _resolveVfxPathHook = Create(interop, vTable[84], ResolveVfx); + _parent = parent; + // @formatter:off + _resolveDecalPathHook = Create($"{name}.{nameof(ResolveDecal)}", hooks, vTable[83], ResolveDecal); + _resolveEidPathHook = Create( $"{name}.{nameof(ResolveEid)}", hooks, vTable[85], ResolveEid); + _resolveImcPathHook = Create($"{name}.{nameof(ResolveImc)}", hooks, vTable[81], ResolveImc); + _resolveMPapPathHook = Create( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[79], ResolveMPap); + _resolveMdlPathHook = Create($"{name}.{nameof(ResolveMdl)}", hooks, vTable[73], type, ResolveMdl, ResolveMdlHuman); + _resolveMtrlPathHook = Create( $"{name}.{nameof(ResolveMtrl)}", hooks, vTable[82], ResolveMtrl); + _resolvePapPathHook = Create( $"{name}.{nameof(ResolvePap)}", hooks, vTable[76], type, ResolvePap, ResolvePapHuman); + _resolvePhybPathHook = Create($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[75], type, ResolvePhyb, ResolvePhybHuman); + _resolveSklbPathHook = Create($"{name}.{nameof(ResolveSklb)}", hooks, vTable[72], type, ResolveSklb, ResolveSklbHuman); + _resolveSkpPathHook = Create($"{name}.{nameof(ResolveSkp)}", hooks, vTable[74], type, ResolveSkp, ResolveSkpHuman); + _resolveTmbPathHook = Create( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[77], ResolveTmb); + _resolveVfxPathHook = Create( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[84], ResolveVfx); + // @formatter:on + Enable(); } public void Enable() @@ -177,9 +183,8 @@ public unsafe class ResolvePathHooks : IDisposable { data = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true); if (_parent.InInternalResolve) - { return DisposableContainer.Empty; - } + return new DisposableContainer(data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Face), data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Body), data.ModCollection.TemporarilySetEstFile(_parent.CharacterUtility, EstManipulation.EstType.Hair), @@ -188,19 +193,19 @@ public unsafe class ResolvePathHooks : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static Hook Create(IGameInteropProvider interop, nint address, Type type, T other, T human) where T : Delegate + private static Hook Create(string name, HookManager hooks, nint address, Type type, T other, T human) where T : Delegate { var del = type switch { - Type.Human => human, - _ => other, + Type.Human => human, + _ => other, }; - return interop.HookFromAddress(address, del); + return hooks.CreateHook(name, address, del).Result; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static Hook Create(IGameInteropProvider interop, nint address, T del) where T : Delegate - => interop.HookFromAddress(address, del); + private static Hook Create(string name, HookManager hooks, nint address, T del) where T : Delegate + => hooks.CreateHook(name, address, del).Result; // Implementation diff --git a/Penumbra/Interop/Hooks/ResourceHandleDestructor.cs b/Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs similarity index 95% rename from Penumbra/Interop/Hooks/ResourceHandleDestructor.cs rename to Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs index 99eb1c23..776f2f92 100644 --- a/Penumbra/Interop/Hooks/ResourceHandleDestructor.cs +++ b/Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs @@ -2,10 +2,9 @@ using Dalamud.Hooking; using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData; -using Penumbra.Interop.Services; using Penumbra.Interop.Structs; -namespace Penumbra.Interop.Hooks; +namespace Penumbra.Interop.Hooks.Resources; public sealed unsafe class ResourceHandleDestructor : EventWrapperPtr, IHookService { diff --git a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs index 0b7bafe0..801c3bf0 100644 --- a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs +++ b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs @@ -16,11 +16,9 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase private readonly Texture** _colorTableTexture; private readonly SafeTextureHandle _originalColorTableTexture; - private Half[] _colorTable; - private bool _updatePending; + private bool _updatePending; - public Half[] ColorTable - => _colorTable; + public Half[] ColorTable { get; } public LiveColorTablePreviewer(IObjectTable objects, IFramework framework, MaterialInfo materialInfo) : base(objects, materialInfo) @@ -41,7 +39,7 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase if (_originalColorTableTexture == null) throw new InvalidOperationException("Material doesn't have a color table"); - _colorTable = new Half[TextureLength]; + ColorTable = new Half[TextureLength]; _updatePending = true; framework.Update += OnFrameworkUpdate; @@ -84,9 +82,9 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase return; bool success; - lock (_colorTable) + lock (ColorTable) { - fixed (Half* colorTable = _colorTable) + fixed (Half* colorTable = ColorTable) { success = texture.Texture->InitializeContents(colorTable); } @@ -105,9 +103,6 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase if (colorSetTextures == null) return false; - if (_colorTableTexture != colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.MaterialSlot)) - return false; - - return true; + return _colorTableTexture == colorSetTextures + (MaterialInfo.ModelSlot * 4 + MaterialInfo.MaterialSlot); } } diff --git a/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs index 972d81be..9ed7ca3d 100644 --- a/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs +++ b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewer.cs @@ -26,34 +26,29 @@ public sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase if (_shaderPackage == null) throw new InvalidOperationException("Material doesn't have a shader package"); - var material = Material; + _originalShPkFlags = Material->ShaderFlags; - _originalShPkFlags = material->ShaderFlags; + _originalMaterialParameter = Material->MaterialParameterCBuffer->TryGetBuffer().ToArray(); - _originalMaterialParameter = material->MaterialParameterCBuffer->TryGetBuffer().ToArray(); - - _originalSamplerFlags = new uint[material->TextureCount]; + _originalSamplerFlags = new uint[Material->TextureCount]; for (var i = 0; i < _originalSamplerFlags.Length; ++i) - _originalSamplerFlags[i] = material->Textures[i].SamplerFlags; + _originalSamplerFlags[i] = Material->Textures[i].SamplerFlags; } protected override void Clear(bool disposing, bool reset) { base.Clear(disposing, reset); - if (reset) - { - var material = Material; + if (!reset) + return; - material->ShaderFlags = _originalShPkFlags; + Material->ShaderFlags = _originalShPkFlags; + var materialParameter = Material->MaterialParameterCBuffer->TryGetBuffer(); + if (!materialParameter.IsEmpty) + _originalMaterialParameter.AsSpan().CopyTo(materialParameter); - var materialParameter = material->MaterialParameterCBuffer->TryGetBuffer(); - if (!materialParameter.IsEmpty) - _originalMaterialParameter.AsSpan().CopyTo(materialParameter); - - for (var i = 0; i < _originalSamplerFlags.Length; ++i) - material->Textures[i].SamplerFlags = _originalSamplerFlags[i]; - } + for (var i = 0; i < _originalSamplerFlags.Length; ++i) + Material->Textures[i].SamplerFlags = _originalSamplerFlags[i]; } public void SetShaderPackageFlags(uint shPkFlags) @@ -80,16 +75,16 @@ public sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase for (var i = 0; i < _shaderPackage->MaterialElementCount; ++i) { ref var parameter = ref _shaderPackage->MaterialElementsSpan[i]; - if (parameter.CRC == parameterCrc) - { - if ((parameter.Offset & 0x3) != 0 - || (parameter.Size & 0x3) != 0 - || (parameter.Offset + parameter.Size) >> 2 > buffer.Length) - return; + if (parameter.CRC != parameterCrc) + continue; - value.TryCopyTo(buffer.Slice(parameter.Offset >> 2, parameter.Size >> 2)[offset..]); + if ((parameter.Offset & 0x3) != 0 + || (parameter.Size & 0x3) != 0 + || (parameter.Offset + parameter.Size) >> 2 > buffer.Length) return; - } + + value.TryCopyTo(buffer.Slice(parameter.Offset >> 2, parameter.Size >> 2)[offset..]); + return; } } @@ -104,25 +99,24 @@ public sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase var samplers = _shaderPackage->Samplers; for (var i = 0; i < _shaderPackage->SamplerCount; ++i) { - if (samplers[i].CRC == samplerCrc) - { - id = samplers[i].Id; - found = true; - break; - } + if (samplers[i].CRC != samplerCrc) + continue; + + id = samplers[i].Id; + found = true; + break; } if (!found) return; - var material = Material; - for (var i = 0; i < material->TextureCount; ++i) + for (var i = 0; i < Material->TextureCount; ++i) { - if (material->Textures[i].Id == id) - { - material->Textures[i].SamplerFlags = (samplerFlags & 0xFFFFFDFF) | 0x000001C0; - break; - } + if (Material->Textures[i].Id != id) + continue; + + Material->Textures[i].SamplerFlags = (samplerFlags & 0xFFFFFDFF) | 0x000001C0; + break; } } @@ -139,9 +133,6 @@ public sealed unsafe class LiveMaterialPreviewer : LiveMaterialPreviewerBase if (shpkHandle == null) return false; - if (_shaderPackage != shpkHandle->ShaderPackage) - return false; - - return true; + return _shaderPackage == shpkHandle->ShaderPackage; } } diff --git a/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs index 86fee976..07986f52 100644 --- a/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs +++ b/Penumbra/Interop/MaterialPreview/LiveMaterialPreviewerBase.cs @@ -61,9 +61,6 @@ public abstract unsafe class LiveMaterialPreviewerBase : IDisposable if ((nint)DrawObject != MaterialInfo.GetDrawObject(gameObject)) return false; - if (Material != MaterialInfo.GetDrawObjectMaterial(DrawObject)) - return false; - - return true; + return Material == MaterialInfo.GetDrawObjectMaterial(DrawObject); } } diff --git a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs index ec0ddd29..686b5a86 100644 --- a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs +++ b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs @@ -24,22 +24,6 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy public nint GetDrawObject(nint address) => GetDrawObject(Type, address); - public static unsafe nint GetDrawObject(DrawObjectType type, nint address) - { - var gameObject = (Character*)address; - if (gameObject == null) - return nint.Zero; - - return type switch - { - DrawObjectType.Character => (nint)gameObject->GameObject.GetDrawObject(), - DrawObjectType.Mainhand => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).DrawObject, - DrawObjectType.Offhand => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject, - DrawObjectType.Vfx => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.Unk).DrawObject, - _ => nint.Zero, - }; - } - public unsafe Material* GetDrawObjectMaterial(IObjectTable objects) => GetDrawObjectMaterial((CharacterBase*)GetDrawObject(GetCharacter(objects))); @@ -103,4 +87,20 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy return result; } + + private static unsafe nint GetDrawObject(DrawObjectType type, nint address) + { + var gameObject = (Character*)address; + if (gameObject == null) + return nint.Zero; + + return type switch + { + DrawObjectType.Character => (nint)gameObject->GameObject.GetDrawObject(), + DrawObjectType.Mainhand => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).DrawObject, + DrawObjectType.Offhand => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject, + DrawObjectType.Vfx => (nint)gameObject->DrawData.Weapon(DrawDataContainer.WeaponSlot.Unk).DrawObject, + _ => nint.Zero, + }; + } } diff --git a/Penumbra/Interop/PathResolving/CutsceneService.cs b/Penumbra/Interop/PathResolving/CutsceneService.cs index c7b24bd7..2eeefbd8 100644 --- a/Penumbra/Interop/PathResolving/CutsceneService.cs +++ b/Penumbra/Interop/PathResolving/CutsceneService.cs @@ -2,7 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using OtterGui.Services; using Penumbra.GameData.Enums; -using Penumbra.Interop.Hooks; +using Penumbra.Interop.Hooks.Objects; namespace Penumbra.Interop.PathResolving; diff --git a/Penumbra/Interop/PathResolving/DrawObjectState.cs b/Penumbra/Interop/PathResolving/DrawObjectState.cs index 19c0fd10..dd4b03f2 100644 --- a/Penumbra/Interop/PathResolving/DrawObjectState.cs +++ b/Penumbra/Interop/PathResolving/DrawObjectState.cs @@ -6,6 +6,7 @@ using OtterGui.Services; using Penumbra.Interop.Hooks; using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; using Penumbra.GameData.Structs; +using Penumbra.Interop.Hooks.Objects; namespace Penumbra.Interop.PathResolving; diff --git a/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs b/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs index b944011d..32090f7c 100644 --- a/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs +++ b/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs @@ -5,7 +5,7 @@ using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Communication; using Penumbra.GameData.Actors; -using Penumbra.Interop.Hooks; +using Penumbra.Interop.Hooks.Objects; using Penumbra.Services; namespace Penumbra.Interop.PathResolving; diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 9d899648..a3400540 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -4,13 +4,13 @@ using Penumbra.Collections; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Interop.Hooks; using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.Services; using Penumbra.Services; using Penumbra.String.Classes; using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; +using Penumbra.Interop.Hooks.Objects; namespace Penumbra.Interop.PathResolving; diff --git a/Penumbra/Interop/PathResolving/PathState.cs b/Penumbra/Interop/PathResolving/PathState.cs index 6d7840d8..f4218e9c 100644 --- a/Penumbra/Interop/PathResolving/PathState.cs +++ b/Penumbra/Interop/PathResolving/PathState.cs @@ -1,34 +1,15 @@ -using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; using Penumbra.Collections; -using Penumbra.GameData; using Penumbra.Interop.Services; using Penumbra.String; namespace Penumbra.Interop.PathResolving; -public unsafe class PathState : IDisposable +public sealed class PathState(CollectionResolver collectionResolver, MetaState metaState, CharacterUtility characterUtility) + : IDisposable { - [Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] - private readonly nint* _humanVTable = null!; - - [Signature(Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)] - private readonly nint* _weaponVTable = null!; - - [Signature(Sigs.DemiHumanVTable, ScanType = ScanType.StaticAddress)] - private readonly nint* _demiHumanVTable = null!; - - [Signature(Sigs.MonsterVTable, ScanType = ScanType.StaticAddress)] - private readonly nint* _monsterVTable = null!; - - public readonly CollectionResolver CollectionResolver; - public readonly MetaState MetaState; - public readonly CharacterUtility CharacterUtility; - - private readonly ResolvePathHooks _human; - private readonly ResolvePathHooks _weapon; - private readonly ResolvePathHooks _demiHuman; - private readonly ResolvePathHooks _monster; + public readonly CollectionResolver CollectionResolver = collectionResolver; + public readonly MetaState MetaState = metaState; + public readonly CharacterUtility CharacterUtility = characterUtility; private readonly ThreadLocal _resolveData = new(() => ResolveData.Invalid, true); private readonly ThreadLocal _internalResolve = new(() => 0, false); @@ -39,31 +20,11 @@ public unsafe class PathState : IDisposable public bool InInternalResolve => _internalResolve.Value != 0u; - public PathState(CollectionResolver collectionResolver, MetaState metaState, CharacterUtility characterUtility, IGameInteropProvider interop) - { - interop.InitializeFromAttributes(this); - CollectionResolver = collectionResolver; - MetaState = metaState; - CharacterUtility = characterUtility; - _human = new ResolvePathHooks(interop, this, _humanVTable, ResolvePathHooks.Type.Human); - _weapon = new ResolvePathHooks(interop, this, _weaponVTable, ResolvePathHooks.Type.Other); - _demiHuman = new ResolvePathHooks(interop, this, _demiHumanVTable, ResolvePathHooks.Type.Other); - _monster = new ResolvePathHooks(interop, this, _monsterVTable, ResolvePathHooks.Type.Other); - _human.Enable(); - _weapon.Enable(); - _demiHuman.Enable(); - _monster.Enable(); - } - public void Dispose() { _resolveData.Dispose(); _internalResolve.Dispose(); - _human.Dispose(); - _weapon.Dispose(); - _demiHuman.Dispose(); - _monster.Dispose(); } public bool Consume(ByteString _, out ResolveData collection) @@ -86,9 +47,7 @@ public unsafe class PathState : IDisposable return path; if (!InInternalResolve) - { _resolveData.Value = collection.ToResolveData(gameObject); - } return path; } @@ -99,9 +58,7 @@ public unsafe class PathState : IDisposable return path; if (!InInternalResolve) - { _resolveData.Value = data; - } return path; } @@ -126,7 +83,7 @@ public unsafe class PathState : IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly void Dispose() + public void Dispose() { --_internalResolve.Value; } diff --git a/Penumbra/Interop/PathResolving/SubfileHelper.cs b/Penumbra/Interop/PathResolving/SubfileHelper.cs index 370118ea..2359c36e 100644 --- a/Penumbra/Interop/PathResolving/SubfileHelper.cs +++ b/Penumbra/Interop/PathResolving/SubfileHelper.cs @@ -1,17 +1,10 @@ -using Dalamud.Hooking; -using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; using Penumbra.Api.Enums; using Penumbra.Collections; -using Penumbra.GameData; -using Penumbra.Interop.Hooks; +using Penumbra.Interop.Hooks.Resources; using Penumbra.Interop.ResourceLoading; -using Penumbra.Interop.Services; using Penumbra.Interop.Structs; -using Penumbra.Services; using Penumbra.String; using Penumbra.String.Classes; -using Penumbra.Util; namespace Penumbra.Interop.PathResolving; @@ -20,49 +13,37 @@ namespace Penumbra.Interop.PathResolving; /// Those are loaded synchronously. /// Thus, we need to ensure the correct files are loaded when a material is loaded. /// -public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection> +public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection> { - private readonly PerformanceTracker _performance; + private readonly GameState _gameState; private readonly ResourceLoader _loader; private readonly ResourceHandleDestructor _resourceHandleDestructor; - private readonly CommunicatorService _communicator; - private readonly ThreadLocal _mtrlData = new(() => ResolveData.Invalid); - private readonly ThreadLocal _avfxData = new(() => ResolveData.Invalid); - - private readonly ConcurrentDictionary _subFileCollection = new(); - - public SubfileHelper(PerformanceTracker performance, ResourceLoader loader, CommunicatorService communicator, IGameInteropProvider interop, ResourceHandleDestructor resourceHandleDestructor) + public SubfileHelper(GameState gameState, ResourceLoader loader, ResourceHandleDestructor resourceHandleDestructor) { - interop.InitializeFromAttributes(this); - - _performance = performance; - _loader = loader; - _communicator = communicator; + _gameState = gameState; + _loader = loader; _resourceHandleDestructor = resourceHandleDestructor; - _loadMtrlShpkHook.Enable(); - _loadMtrlTexHook.Enable(); - _apricotResourceLoadHook.Enable(); - _loader.ResourceLoaded += SubfileContainerRequested; + _loader.ResourceLoaded += SubfileContainerRequested; _resourceHandleDestructor.Subscribe(ResourceDestroyed, ResourceHandleDestructor.Priority.SubfileHelper); } public IEnumerator> GetEnumerator() - => _subFileCollection.GetEnumerator(); + => _gameState.SubFileCollection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count - => _subFileCollection.Count; + => _gameState.SubFileCollection.Count; public ResolveData MtrlData - => _mtrlData.IsValueCreated ? _mtrlData.Value : ResolveData.Invalid; + => _gameState.MtrlData.IsValueCreated ? _gameState.MtrlData.Value : ResolveData.Invalid; public ResolveData AvfxData - => _avfxData.IsValueCreated ? _avfxData.Value : ResolveData.Invalid; + => _gameState.AvfxData.IsValueCreated ? _gameState.AvfxData.Value : ResolveData.Invalid; /// /// Check specifically for shpk and tex files whether we are currently in a material load, @@ -71,13 +52,13 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollectionFileSize == 0) - _subFileCollection[(nint)handle] = resolveData; + _gameState.SubFileCollection[(nint)handle] = resolveData; break; } } private void ResourceDestroyed(ResourceHandle* handle) - => _subFileCollection.TryRemove((nint)handle, out _); - - private delegate byte LoadMtrlFilesDelegate(nint mtrlResourceHandle); - - [Signature(Sigs.LoadMtrlTex, DetourName = nameof(LoadMtrlTexDetour))] - private readonly Hook _loadMtrlTexHook = null!; - - private byte LoadMtrlTexDetour(nint mtrlResourceHandle) - { - using var performance = _performance.Measure(PerformanceType.LoadTextures); - var last = _mtrlData.Value; - _mtrlData.Value = LoadFileHelper(mtrlResourceHandle); - var ret = _loadMtrlTexHook.Original(mtrlResourceHandle); - _mtrlData.Value = last; - return ret; - } - - [Signature(Sigs.LoadMtrlShpk, DetourName = nameof(LoadMtrlShpkDetour))] - private readonly Hook _loadMtrlShpkHook = null!; - - private byte LoadMtrlShpkDetour(nint mtrlResourceHandle) - { - using var performance = _performance.Measure(PerformanceType.LoadShaders); - var last = _mtrlData.Value; - var mtrlData = LoadFileHelper(mtrlResourceHandle); - _mtrlData.Value = mtrlData; - var ret = _loadMtrlShpkHook.Original(mtrlResourceHandle); - _mtrlData.Value = last; - _communicator.MtrlShpkLoaded.Invoke(mtrlResourceHandle, mtrlData.AssociatedGameObject); - return ret; - } - - private ResolveData LoadFileHelper(nint resourceHandle) - { - if (resourceHandle == nint.Zero) - return ResolveData.Invalid; - - return _subFileCollection.TryGetValue(resourceHandle, out var c) ? c : ResolveData.Invalid; - } - - - private delegate byte ApricotResourceLoadDelegate(nint handle, nint unk1, byte unk2); - - [Signature(Sigs.ApricotResourceLoad, DetourName = nameof(ApricotResourceLoadDetour))] - private readonly Hook _apricotResourceLoadHook = null!; - - private byte ApricotResourceLoadDetour(nint handle, nint unk1, byte unk2) - { - using var performance = _performance.Measure(PerformanceType.LoadApricotResources); - var last = _avfxData.Value; - _avfxData.Value = LoadFileHelper(handle); - var ret = _apricotResourceLoadHook.Original(handle, unk1, unk2); - _avfxData.Value = last; - return ret; - } + => _gameState.SubFileCollection.TryRemove((nint)handle, out _); } diff --git a/Penumbra/Interop/Services/SkinFixer.cs b/Penumbra/Interop/Services/SkinFixer.cs index 444b9a48..21331916 100644 --- a/Penumbra/Interop/Services/SkinFixer.cs +++ b/Penumbra/Interop/Services/SkinFixer.cs @@ -6,7 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using OtterGui.Classes; using Penumbra.Communication; using Penumbra.GameData; -using Penumbra.Interop.Hooks; +using Penumbra.Interop.Hooks.Resources; using Penumbra.Services; namespace Penumbra.Interop.Services; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs index 376bbcf7..b4801f5f 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs @@ -9,7 +9,7 @@ using OtterGui.Raii; using Penumbra.GameData.Data; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; -using Penumbra.Interop.Hooks; +using Penumbra.Interop.Hooks.Objects; using Penumbra.Interop.MaterialPreview; using Penumbra.String; using Penumbra.String.Classes; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 8d3e32f9..8b6ef331 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -14,7 +14,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.Import.Models; using Penumbra.Import.Textures; -using Penumbra.Interop.Hooks; +using Penumbra.Interop.Hooks.Objects; using Penumbra.Interop.ResourceTree; using Penumbra.Meta; using Penumbra.Mods;