Move more hooks in own classes.

This commit is contained in:
Ottermandias 2024-01-11 15:31:25 +01:00
parent be588e2fa3
commit 91cea50f02
26 changed files with 274 additions and 262 deletions

View file

@ -72,7 +72,24 @@ public class GameState : IService
#endregion
/// <summary> Return the correct resolve data from the stored data. </summary>
#region Subfiles
public readonly ThreadLocal<ResolveData> MtrlData = new(() => ResolveData.Invalid);
public readonly ThreadLocal<ResolveData> AvfxData = new(() => ResolveData.Invalid);
public readonly ConcurrentDictionary<nint, ResolveData> 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
/// <summary> Return the correct resolve data from the stored data. </summary>
public unsafe bool HandleFiles(CollectionResolver resolver, ResourceType type, Utf8GamePath _, out ResolveData resolveData)
{
switch (type)

View file

@ -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<CharacterBase, CharacterBaseDestructor.Priority>, IHookService
{

View file

@ -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<Character, CharacterDestructor.Priority>, IHookService
{

View file

@ -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<Character, Character, CopyCharacter.Priority>, IHookService
{

View file

@ -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<ModelCharaId, CustomizeArray, CharacterArmor, CreateCharacterBase.Priority>, IHookService
{
@ -39,7 +39,7 @@ public sealed unsafe class CreateCharacterBase : EventWrapperPtr<ModelCharaId, C
private CharacterBase* Detour(ModelCharaId model, CustomizeArray* customize, CharacterArmor* equipment, byte unk)
{
Penumbra.Log.Verbose($"[{Name}] Triggered with model: {model.Id}, customize: 0x{(nint) customize:X}, equipment: 0x{(nint)equipment:X}, unk: {unk}.");
Penumbra.Log.Verbose($"[{Name}] Triggered with model: {model.Id}, customize: 0x{(nint)customize:X}, equipment: 0x{(nint)equipment:X}, unk: {unk}.");
Invoke(&model, customize, equipment);
var ret = _task.Result.Original(model, customize, equipment, unk);
_postEvent.Invoke(model, customize, equipment, ret);

View file

@ -3,7 +3,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
using OtterGui.Services;
using Penumbra.GameData;
namespace Penumbra.Interop.Hooks;
namespace Penumbra.Interop.Hooks.Objects;
/// <summary>
/// EnableDraw is what creates DrawObjects for gameObjects,
@ -12,12 +12,12 @@ namespace Penumbra.Interop.Hooks;
public sealed unsafe class EnableDraw : IHookService
{
private readonly Task<Hook<Delegate>> _task;
private readonly GameState _state;
private readonly GameState _state;
public EnableDraw(HookManager hooks, GameState state)
{
_state = state;
_task = hooks.CreateHook<Delegate>("Enable Draw", Sigs.EnableDraw, Detour, true);
_task = hooks.CreateHook<Delegate>("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();
}

View file

@ -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<DrawDataContainer, Character, CharacterWeapon, WeaponReload.Priority>, IHookService
{

View file

@ -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<ApricotResourceLoad.Delegate>
{
private readonly GameState _gameState;
public ApricotResourceLoad(HookManager hooks, GameState gameState)
{
_gameState = gameState;
Task = hooks.CreateHook<Delegate>("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;
}
}

View file

@ -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<LoadMtrlShpk.Delegate>
{
private readonly GameState _gameState;
private readonly CommunicatorService _communicator;
public LoadMtrlShpk(HookManager hooks, GameState gameState, CommunicatorService communicator)
{
_gameState = gameState;
_communicator = communicator;
Task = hooks.CreateHook<Delegate>("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;
}
}

View file

@ -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<LoadMtrlTex.Delegate>
{
private readonly GameState _gameState;
public LoadMtrlTex(HookManager hooks, GameState gameState)
{
_gameState = gameState;
Task = hooks.CreateHook<Delegate>("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;
}
}

View file

@ -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();
}
}

View file

@ -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<PerSlotResolveDelegate>(interop, vTable[83], ResolveDecal);
_resolveEidPathHook = Create<SingleResolveDelegate>(interop, vTable[85], ResolveEid);
_resolveImcPathHook = Create<PerSlotResolveDelegate>(interop, vTable[81], ResolveImc);
_resolveMPapPathHook = Create<MPapResolveDelegate>(interop, vTable[79], ResolveMPap);
_resolveMdlPathHook = Create<PerSlotResolveDelegate>(interop, vTable[73], type, ResolveMdl, ResolveMdlHuman);
_resolveMtrlPathHook = Create<NamedResolveDelegate>(interop, vTable[82], ResolveMtrl);
_resolvePapPathHook = Create<NamedResolveDelegate>(interop, vTable[76], type, ResolvePap, ResolvePapHuman);
_resolvePhybPathHook = Create<PerSlotResolveDelegate>(interop, vTable[75], type, ResolvePhyb, ResolvePhybHuman);
_resolveSklbPathHook = Create<PerSlotResolveDelegate>(interop, vTable[72], type, ResolveSklb, ResolveSklbHuman);
_resolveSkpPathHook = Create<PerSlotResolveDelegate>(interop, vTable[74], type, ResolveSkp, ResolveSkpHuman);
_resolveTmbPathHook = Create<TmbResolveDelegate>(interop, vTable[77], ResolveTmb);
_resolveVfxPathHook = Create<VfxResolveDelegate>(interop, vTable[84], ResolveVfx);
_parent = parent;
// @formatter:off
_resolveDecalPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveDecal)}", hooks, vTable[83], ResolveDecal);
_resolveEidPathHook = Create<SingleResolveDelegate>( $"{name}.{nameof(ResolveEid)}", hooks, vTable[85], ResolveEid);
_resolveImcPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveImc)}", hooks, vTable[81], ResolveImc);
_resolveMPapPathHook = Create<MPapResolveDelegate>( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[79], ResolveMPap);
_resolveMdlPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveMdl)}", hooks, vTable[73], type, ResolveMdl, ResolveMdlHuman);
_resolveMtrlPathHook = Create<NamedResolveDelegate>( $"{name}.{nameof(ResolveMtrl)}", hooks, vTable[82], ResolveMtrl);
_resolvePapPathHook = Create<NamedResolveDelegate>( $"{name}.{nameof(ResolvePap)}", hooks, vTable[76], type, ResolvePap, ResolvePapHuman);
_resolvePhybPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[75], type, ResolvePhyb, ResolvePhybHuman);
_resolveSklbPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveSklb)}", hooks, vTable[72], type, ResolveSklb, ResolveSklbHuman);
_resolveSkpPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveSkp)}", hooks, vTable[74], type, ResolveSkp, ResolveSkpHuman);
_resolveTmbPathHook = Create<TmbResolveDelegate>( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[77], ResolveTmb);
_resolveVfxPathHook = Create<VfxResolveDelegate>( $"{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<T> Create<T>(IGameInteropProvider interop, nint address, Type type, T other, T human) where T : Delegate
private static Hook<T> Create<T>(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<T> Create<T>(IGameInteropProvider interop, nint address, T del) where T : Delegate
=> interop.HookFromAddress(address, del);
private static Hook<T> Create<T>(string name, HookManager hooks, nint address, T del) where T : Delegate
=> hooks.CreateHook(name, address, del).Result;
// Implementation

View file

@ -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<ResourceHandle, ResourceHandleDestructor.Priority>, IHookService
{

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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,
};
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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> _resolveData = new(() => ResolveData.Invalid, true);
private readonly ThreadLocal<uint> _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;
}

View file

@ -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.
/// </summary>
public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePair<nint, ResolveData>>
public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePair<nint, ResolveData>>
{
private readonly PerformanceTracker _performance;
private readonly GameState _gameState;
private readonly ResourceLoader _loader;
private readonly ResourceHandleDestructor _resourceHandleDestructor;
private readonly CommunicatorService _communicator;
private readonly ThreadLocal<ResolveData> _mtrlData = new(() => ResolveData.Invalid);
private readonly ThreadLocal<ResolveData> _avfxData = new(() => ResolveData.Invalid);
private readonly ConcurrentDictionary<nint, ResolveData> _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<KeyValuePair<nint, ResolveData>> 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;
/// <summary>
/// Check specifically for shpk and tex files whether we are currently in a material load,
@ -71,13 +52,13 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePai
{
switch (type)
{
case ResourceType.Tex when _mtrlData.Value.Valid:
case ResourceType.Shpk when _mtrlData.Value.Valid:
collection = _mtrlData.Value;
case ResourceType.Tex when _gameState.MtrlData.Value.Valid:
case ResourceType.Shpk when _gameState.MtrlData.Value.Valid:
collection = _gameState.MtrlData.Value;
return true;
case ResourceType.Scd when _avfxData.Value.Valid:
case ResourceType.Atex when _avfxData.Value.Valid:
collection = _avfxData.Value;
case ResourceType.Scd when _gameState.AvfxData.Value.Valid:
case ResourceType.Atex when _gameState.AvfxData.Value.Valid:
collection = _gameState.AvfxData.Value;
return true;
}
@ -105,11 +86,8 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePai
public void Dispose()
{
_loader.ResourceLoaded -= SubfileContainerRequested;
_loader.ResourceLoaded -= SubfileContainerRequested;
_resourceHandleDestructor.Unsubscribe(ResourceDestroyed);
_loadMtrlShpkHook.Dispose();
_loadMtrlTexHook.Dispose();
_apricotResourceLoadHook.Dispose();
}
private void SubfileContainerRequested(ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath,
@ -120,66 +98,12 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePai
case ResourceType.Mtrl:
case ResourceType.Avfx:
if (handle->FileSize == 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<LoadMtrlFilesDelegate> _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<LoadMtrlFilesDelegate> _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<ApricotResourceLoadDelegate> _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 _);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;