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;