diff --git a/Penumbra/Collections/Cache/EqdpCache.cs b/Penumbra/Collections/Cache/EqdpCache.cs index 7139bb72..c63403ae 100644 --- a/Penumbra/Collections/Cache/EqdpCache.cs +++ b/Penumbra/Collections/Cache/EqdpCache.cs @@ -26,7 +26,7 @@ public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) Manager.SetFile(_eqdpFiles[i], index); } - public override void ResetFiles() + public void ResetFiles() { foreach (var t in CharacterUtilityData.EqdpIndices) Manager.SetFile(null, t); @@ -62,7 +62,7 @@ public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) return Manager.TemporarilySetFile(_eqdpFiles[i], idx); } - public override void Reset() + public void Reset() { foreach (var file in _eqdpFiles.OfType()) { diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 32c4c0ae..b1e03943 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -1,7 +1,5 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -10,24 +8,11 @@ namespace Penumbra.Collections.Cache; public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private ExpandedEqpFile? _eqpFile; - public override void SetFiles() - => Manager.SetFile(_eqpFile, MetaIndex.Eqp); - - public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.Eqp); + { } protected override void IncorporateChangesInternal() - { - if (GetFile() is not { } file) - return; - - foreach (var (identifier, (_, entry)) in this) - Apply(file, identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed EQP manipulations."); - } + { } public unsafe EqpEntry GetValues(CharacterArmor* armor) => GetSingleValue(armor[0].Set, EquipSlot.Head) @@ -40,29 +25,14 @@ public sealed class EqpCache(MetaFileManager manager, ModCollection collection) private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot) => TryGetValue(new EqpIdentifier(id, slot), out var pair) ? pair.Entry : ExpandedEqpFile.GetDefault(Manager, id) & Eqp.Mask(slot); - public MetaList.MetaReverter TemporarilySetFile() - => Manager.TemporarilySetFile(_eqpFile, MetaIndex.Eqp); - - public override void Reset() - { - if (_eqpFile == null) - return; - - _eqpFile.Reset(Keys.Select(identifier => identifier.SetId)); - Clear(); - } + public void Reset() + => Clear(); protected override void ApplyModInternal(EqpIdentifier identifier, EqpEntry entry) - { - if (GetFile() is { } file) - Apply(file, identifier, entry); - } + { } protected override void RevertModInternal(EqpIdentifier identifier) - { - if (GetFile() is { } file) - Apply(file, identifier, ExpandedEqpFile.GetDefault(Manager, identifier.SetId)); - } + { } public static bool Apply(ExpandedEqpFile file, EqpIdentifier identifier, EqpEntry entry) { @@ -76,20 +46,5 @@ public sealed class EqpCache(MetaFileManager manager, ModCollection collection) } protected override void Dispose(bool _) - { - _eqpFile?.Dispose(); - _eqpFile = null; - Clear(); - } - - private ExpandedEqpFile? GetFile() - { - if (_eqpFile != null) - return _eqpFile; - - if (!Manager.CharacterUtility.Ready) - return null; - - return _eqpFile = new ExpandedEqpFile(Manager); - } + => Clear(); } diff --git a/Penumbra/Collections/Cache/EstCache.cs b/Penumbra/Collections/Cache/EstCache.cs index 845ff128..6a9fa909 100644 --- a/Penumbra/Collections/Cache/EstCache.cs +++ b/Penumbra/Collections/Cache/EstCache.cs @@ -54,7 +54,7 @@ public sealed class EstCache(MetaFileManager manager, ModCollection collection) return Manager.TemporarilySetFile(file, idx); } - public override void ResetFiles() + public void ResetFiles() { Manager.SetFile(null, MetaIndex.FaceEst); Manager.SetFile(null, MetaIndex.HairEst); @@ -80,7 +80,7 @@ public sealed class EstCache(MetaFileManager manager, ModCollection collection) : EstFile.GetDefault(Manager, identifier); } - public override void Reset() + public void Reset() { _estFaceFile?.Reset(); _estHairFile?.Reset(); diff --git a/Penumbra/Collections/Cache/GmpCache.cs b/Penumbra/Collections/Cache/GmpCache.cs index 1475ffd5..0beb51d8 100644 --- a/Penumbra/Collections/Cache/GmpCache.cs +++ b/Penumbra/Collections/Cache/GmpCache.cs @@ -1,6 +1,4 @@ using Penumbra.GameData.Structs; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -9,48 +7,20 @@ namespace Penumbra.Collections.Cache; public sealed class GmpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private ExpandedGmpFile? _gmpFile; - public override void SetFiles() - => Manager.SetFile(_gmpFile, MetaIndex.Gmp); - - public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.Gmp); + { } protected override void IncorporateChangesInternal() - { - if (GetFile() is not { } file) - return; + { } - foreach (var (identifier, (_, entry)) in this) - Apply(file, identifier, entry); - - Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed GMP manipulations."); - } - - public MetaList.MetaReverter TemporarilySetFile() - => Manager.TemporarilySetFile(_gmpFile, MetaIndex.Gmp); - - public override void Reset() - { - if (_gmpFile == null) - return; - - _gmpFile.Reset(Keys.Select(identifier => identifier.SetId)); - Clear(); - } + public void Reset() + => Clear(); protected override void ApplyModInternal(GmpIdentifier identifier, GmpEntry entry) - { - if (GetFile() is { } file) - Apply(file, identifier, entry); - } + { } protected override void RevertModInternal(GmpIdentifier identifier) - { - if (GetFile() is { } file) - Apply(file, identifier, ExpandedGmpFile.GetDefault(Manager, identifier.SetId)); - } + { } public static bool Apply(ExpandedGmpFile file, GmpIdentifier identifier, GmpEntry entry) { @@ -63,20 +33,5 @@ public sealed class GmpCache(MetaFileManager manager, ModCollection collection) } protected override void Dispose(bool _) - { - _gmpFile?.Dispose(); - _gmpFile = null; - Clear(); - } - - private ExpandedGmpFile? GetFile() - { - if (_gmpFile != null) - return _gmpFile; - - if (!Manager.CharacterUtility.Ready) - return null; - - return _gmpFile = new ExpandedGmpFile(Manager); - } + => Clear(); } diff --git a/Penumbra/Collections/Cache/IMetaCache.cs b/Penumbra/Collections/Cache/IMetaCache.cs index eacbdcc2..dd218b48 100644 --- a/Penumbra/Collections/Cache/IMetaCache.cs +++ b/Penumbra/Collections/Cache/IMetaCache.cs @@ -27,8 +27,6 @@ public abstract class MetaCacheBase } public abstract void SetFiles(); - public abstract void Reset(); - public abstract void ResetFiles(); public bool ApplyMod(IMod source, TIdentifier identifier, TEntry entry) { diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 3d05e793..a9daf795 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -38,7 +38,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) Collection._cache!.ForceFile(path, PathDataHandler.CreateImc(path.Path, Collection)); } - public override void ResetFiles() + public void ResetFiles() { foreach (var (path, _) in _imcFiles) Collection._cache!.ForceFile(path, FullPath.Empty); @@ -56,7 +56,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) } - public override void Reset() + public void Reset() { foreach (var (path, (file, set)) in _imcFiles) { diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 45a85d0f..c8a116eb 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -148,9 +148,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) public MetaList.MetaReverter? TemporarilySetEqdpFile(GenderRace genderRace, bool accessory) => Eqdp.TemporarilySetFile(genderRace, accessory); - public MetaList.MetaReverter TemporarilySetGmpFile() - => Gmp.TemporarilySetFile(); - public MetaList.MetaReverter TemporarilySetCmpFile() => Rsp.TemporarilySetFile(); diff --git a/Penumbra/Collections/Cache/RspCache.cs b/Penumbra/Collections/Cache/RspCache.cs index 3889d6f1..8a5fe97d 100644 --- a/Penumbra/Collections/Cache/RspCache.cs +++ b/Penumbra/Collections/Cache/RspCache.cs @@ -13,9 +13,6 @@ public sealed class RspCache(MetaFileManager manager, ModCollection collection) public override void SetFiles() => Manager.SetFile(_cmpFile, MetaIndex.HumanCmp); - public override void ResetFiles() - => Manager.SetFile(null, MetaIndex.HumanCmp); - protected override void IncorporateChangesInternal() { if (GetFile() is not { } file) @@ -30,7 +27,7 @@ public sealed class RspCache(MetaFileManager manager, ModCollection collection) public MetaList.MetaReverter TemporarilySetFile() => Manager.TemporarilySetFile(_cmpFile, MetaIndex.HumanCmp); - public override void Reset() + public void Reset() { if (_cmpFile == null) return; diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 3e3386ea..8701e3bb 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -100,10 +100,6 @@ public partial class ModCollection return idx >= 0 ? utility.TemporarilyResetResource(idx) : null; } - public MetaList.MetaReverter TemporarilySetGmpFile(CharacterUtility utility) - => _cache?.Meta.TemporarilySetGmpFile() - ?? utility.TemporarilyResetResource(MetaIndex.Gmp); - public MetaList.MetaReverter TemporarilySetCmpFile(CharacterUtility utility) => _cache?.Meta.TemporarilySetCmpFile() ?? utility.TemporarilyResetResource(MetaIndex.HumanCmp); diff --git a/Penumbra/Interop/Hooks/Meta/GmpHook.cs b/Penumbra/Interop/Hooks/Meta/GmpHook.cs new file mode 100644 index 00000000..60966fb7 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/GmpHook.cs @@ -0,0 +1,64 @@ +using OtterGui.Services; +using Penumbra.Interop.PathResolving; +using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; + +namespace Penumbra.Interop.Hooks.Meta; + +public unsafe class GmpHook : FastHook +{ + public delegate nint Delegate(nint gmpResource, uint dividedHeadId); + + private readonly MetaState _metaState; + + private static readonly Finalizer StablePointer = new(); + + public GmpHook(HookManager hooks, MetaState metaState) + { + _metaState = metaState; + Task = hooks.CreateHook("GetGmpEntry", "E8 ?? ?? ?? ?? 48 85 C0 74 ?? 43 8D 0C", Detour, true); + } + + /// + /// This function returns a pointer to the correct block in the GMP file, if it exists - cf. . + /// To work around this, we just have a single stable ulong accessible and offset the pointer to this by the required distance, + /// which is defined by the modulo of the original ID and the block size, if we return our own custom gmp entry. + /// + private nint Detour(nint gmpResource, uint dividedHeadId) + { + nint ret; + if (_metaState.GmpCollection is { Valid: true, ModCollection.MetaCache: { } cache } + && cache.Gmp.TryGetValue(new GmpIdentifier(_metaState.UndividedGmpId), out var entry)) + { + if (entry.Entry.Enabled) + { + *StablePointer.Pointer = entry.Entry.Value; + // This function already gets the original ID divided by the block size, so we can compute the modulo with a single multiplication and addition. + // We then go backwards from our pointer because this gets added by the calling functions. + ret = (nint)(StablePointer.Pointer - (_metaState.UndividedGmpId.Id - dividedHeadId * ExpandedEqpGmpBase.BlockSize)); + } + else + { + ret = nint.Zero; + } + } + else + { + ret = Task.Result.Original(gmpResource, dividedHeadId); + } + + Penumbra.Log.Excessive($"[GetGmpFlags] Invoked on 0x{gmpResource:X} with {dividedHeadId}, returned {ret:X10}."); + return ret; + } + + /// Allocate and clean up our single stable ulong pointer. + private class Finalizer + { + public readonly ulong* Pointer = (ulong*)Marshal.AllocHGlobal(8); + + ~Finalizer() + { + Marshal.FreeHGlobal((nint)Pointer); + } + } +} diff --git a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs index e451f118..a3e56d7f 100644 --- a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs +++ b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs @@ -1,10 +1,11 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Services; -using Penumbra.GameData; -using Penumbra.Interop.PathResolving; - -namespace Penumbra.Interop.Hooks.Meta; - +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.Collections; +using Penumbra.GameData; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + /// /// GMP. This gets called every time when changing visor state, and it accesses the gmp file itself, /// but it only applies a changed gmp file after a redraw for some reason. @@ -26,10 +27,11 @@ public sealed unsafe class SetupVisor : FastHook [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private byte Detour(DrawObject* drawObject, ushort modelId, byte visorState) { - var collection = _collectionResolver.IdentifyCollection(drawObject, true); - using var gmp = _metaState.ResolveGmpData(collection.ModCollection); - var ret = Task.Result.Original.Invoke(drawObject, modelId, visorState); - Penumbra.Log.Excessive($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); + _metaState.GmpCollection = _collectionResolver.IdentifyCollection(drawObject, true); + _metaState.UndividedGmpId = modelId; + var ret = Task.Result.Original.Invoke(drawObject, modelId, visorState); + Penumbra.Log.Information($"[Setup Visor] Invoked on {(nint)drawObject:X} with {modelId}, {visorState} -> {ret}."); + _metaState.GmpCollection = ResolveData.Invalid; return ret; } } diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index de7912e0..c8ebe18f 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -95,9 +95,6 @@ public sealed unsafe class MetaState : IDisposable _ => DisposableContainer.Empty, }; - public MetaList.MetaReverter ResolveGmpData(ModCollection collection) - => collection.TemporarilySetGmpFile(_characterUtility); - public MetaList.MetaReverter ResolveRspData(ModCollection collection) => collection.TemporarilySetCmpFile(_characterUtility); diff --git a/Penumbra/Interop/Services/MetaList.cs b/Penumbra/Interop/Services/MetaList.cs index e956040b..dc472b8e 100644 --- a/Penumbra/Interop/Services/MetaList.cs +++ b/Penumbra/Interop/Services/MetaList.cs @@ -102,30 +102,19 @@ public unsafe class MetaList : IDisposable ResetResourceInternal(); } - public sealed class MetaReverter : IDisposable + public sealed class MetaReverter(MetaList metaList, nint data, int length) : IDisposable { public static readonly MetaReverter Disabled = new(null!) { Disposed = true }; - public readonly MetaList MetaList; - public readonly nint Data; - public readonly int Length; + public readonly MetaList MetaList = metaList; + public readonly nint Data = data; + public readonly int Length = length; public readonly bool Resetter; public bool Disposed; - public MetaReverter(MetaList metaList, nint data, int length) - { - MetaList = metaList; - Data = data; - Length = length; - } - public MetaReverter(MetaList metaList) - { - MetaList = metaList; - Data = nint.Zero; - Length = 0; - Resetter = true; - } + : this(metaList, nint.Zero, 0) + => Resetter = true; public void Dispose() { diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index a7470b75..c47c84ef 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -15,10 +15,10 @@ namespace Penumbra.Meta.Files; /// public unsafe class ExpandedEqpGmpBase : MetaBaseFile { - protected const int BlockSize = 160; - protected const int NumBlocks = 64; - protected const int EntrySize = 8; - protected const int MaxSize = BlockSize * NumBlocks * EntrySize; + public const int BlockSize = 160; + public const int NumBlocks = 64; + public const int EntrySize = 8; + public const int MaxSize = BlockSize * NumBlocks * EntrySize; public const int Count = BlockSize * NumBlocks;