diff --git a/OtterGui b/OtterGui index caa9e9b9..6fafc03b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit caa9e9b9a5dc3928eba10b315cf6a0f6f1d84b65 +Subproject commit 6fafc03b34971be7c0e74fd9a638d1ed642ea19a diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index 60e38aef..c681b230 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -9,11 +9,24 @@ namespace Penumbra.Collections.Cache; public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { public unsafe EqpEntry GetValues(CharacterArmor* armor) - => GetSingleValue(armor[0].Set, EquipSlot.Head) - | GetSingleValue(armor[1].Set, EquipSlot.Body) - | GetSingleValue(armor[2].Set, EquipSlot.Hands) - | GetSingleValue(armor[3].Set, EquipSlot.Legs) - | GetSingleValue(armor[4].Set, EquipSlot.Feet); + { + var bodyEntry = GetSingleValue(armor[1].Set, EquipSlot.Body); + var headEntry = bodyEntry.HasFlag(EqpEntry.BodyShowHead) + ? GetSingleValue(armor[0].Set, EquipSlot.Head) + : GetSingleValue(armor[1].Set, EquipSlot.Head); + var handEntry = bodyEntry.HasFlag(EqpEntry.BodyShowHand) + ? GetSingleValue(armor[2].Set, EquipSlot.Hands) + : GetSingleValue(armor[1].Set, EquipSlot.Hands); + var (legsEntry, legsId) = bodyEntry.HasFlag(EqpEntry.BodyShowLeg) + ? (GetSingleValue(armor[3].Set, EquipSlot.Legs), 3) + : (GetSingleValue(armor[1].Set, EquipSlot.Legs), 1); + var footEntry = legsEntry.HasFlag(EqpEntry.LegsShowFoot) + ? GetSingleValue(armor[4].Set, EquipSlot.Feet) + : GetSingleValue(armor[legsId].Set, EquipSlot.Feet); + + var combined = bodyEntry | headEntry | handEntry | legsEntry | footEntry; + return PostProcessFeet(PostProcessHands(combined)); + } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot) @@ -24,4 +37,30 @@ public sealed class EqpCache(MetaFileManager manager, ModCollection collection) protected override void Dispose(bool _) => Clear(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static EqpEntry PostProcessHands(EqpEntry entry) + { + if (!entry.HasFlag(EqpEntry.HandsHideForearm)) + return entry; + + var testFlag = entry.HasFlag(EqpEntry.HandsHideElbow) + ? entry.HasFlag(EqpEntry.BodyHideGlovesL) + : entry.HasFlag(EqpEntry.BodyHideGlovesM); + return testFlag + ? (entry | EqpEntry._4) & ~EqpEntry.BodyHideGlovesS + : entry & ~(EqpEntry._4 | EqpEntry.BodyHideGlovesS); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static EqpEntry PostProcessFeet(EqpEntry entry) + { + if (!entry.HasFlag(EqpEntry.FeetHideCalf)) + return entry; + + if (entry.HasFlag(EqpEntry.FeetHideKnee) || !entry.HasFlag(EqpEntry._20)) + return entry & ~(EqpEntry.LegsHideBootsS | EqpEntry.LegsHideBootsM); + + return (entry | EqpEntry.LegsHideBootsM) & ~EqpEntry.LegsHideBootsS; + } } diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index f6100b62..7faed6a2 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -31,7 +31,21 @@ public class Configuration : IPluginConfiguration, ISavable, IService public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; - public bool EnableMods { get; set; } = true; + public event Action? ModsEnabled; + + [JsonIgnore] + private bool _enableMods = true; + + public bool EnableMods + { + get => _enableMods; + set + { + _enableMods = value; + ModsEnabled?.Invoke(value); + } + } + public string ModDirectory { get; set; } = string.Empty; public string ExportDirectory { get; set; } = string.Empty; diff --git a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs index aaaaccd4..583a2ac5 100644 --- a/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqdpAccessoryHook.cs @@ -6,7 +6,7 @@ using Penumbra.Interop.PathResolving; namespace Penumbra.Interop.Hooks.Meta; -public unsafe class EqdpAccessoryHook : FastHook +public unsafe class EqdpAccessoryHook : FastHook, IDisposable { public delegate void Delegate(CharacterUtility* utility, EqdpEntry* entry, uint id, uint raceCode); @@ -15,7 +15,8 @@ public unsafe class EqdpAccessoryHook : FastHook public EqdpAccessoryHook(HookManager hooks, MetaState metaState) { _metaState = metaState; - Task = hooks.CreateHook("GetEqdpAccessoryEntry", "E8 ?? ?? ?? ?? 41 BF ?? ?? ?? ?? 83 FB", Detour, true); + Task = hooks.CreateHook("GetEqdpAccessoryEntry", "E8 ?? ?? ?? ?? 41 BF ?? ?? ?? ?? 83 FB", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uint raceCode) @@ -27,4 +28,7 @@ public unsafe class EqdpAccessoryHook : FastHook Penumbra.Log.Excessive( $"[GetEqdpAccessoryEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs index 2711f195..f5488f80 100644 --- a/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqdpEquipHook.cs @@ -6,7 +6,7 @@ using Penumbra.Interop.PathResolving; namespace Penumbra.Interop.Hooks.Meta; -public unsafe class EqdpEquipHook : FastHook +public unsafe class EqdpEquipHook : FastHook, IDisposable { public delegate void Delegate(CharacterUtility* utility, EqdpEntry* entry, uint id, uint raceCode); @@ -15,7 +15,8 @@ public unsafe class EqdpEquipHook : FastHook public EqdpEquipHook(HookManager hooks, MetaState metaState) { _metaState = metaState; - Task = hooks.CreateHook("GetEqdpEquipEntry", "E8 ?? ?? ?? ?? 85 DB 75 ?? F6 45", Detour, true); + Task = hooks.CreateHook("GetEqdpEquipEntry", "E8 ?? ?? ?? ?? 85 DB 75 ?? F6 45", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private void Detour(CharacterUtility* utility, EqdpEntry* entry, uint setId, uint raceCode) @@ -27,4 +28,7 @@ public unsafe class EqdpEquipHook : FastHook Penumbra.Log.Excessive( $"[GetEqdpEquipEntry] Invoked on 0x{(ulong)utility:X} with {setId}, {(GenderRace)raceCode}, returned {(ushort)*entry:B10}."); } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/EqpHook.cs b/Penumbra/Interop/Hooks/Meta/EqpHook.cs index 7107e26b..e96d8115 100644 --- a/Penumbra/Interop/Hooks/Meta/EqpHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EqpHook.cs @@ -5,7 +5,7 @@ using Penumbra.Interop.PathResolving; namespace Penumbra.Interop.Hooks.Meta; -public unsafe class EqpHook : FastHook +public unsafe class EqpHook : FastHook, IDisposable { public delegate void Delegate(CharacterUtility* utility, EqpEntry* flags, CharacterArmor* armor); @@ -14,7 +14,8 @@ public unsafe class EqpHook : FastHook public EqpHook(HookManager hooks, MetaState metaState) { _metaState = metaState; - Task = hooks.CreateHook("GetEqpFlags", "E8 ?? ?? ?? ?? 0F B6 44 24 ?? C0 E8", Detour, true); + Task = hooks.CreateHook("GetEqpFlags", "E8 ?? ?? ?? ?? 0F B6 44 24 ?? C0 E8", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private void Detour(CharacterUtility* utility, EqpEntry* flags, CharacterArmor* armor) @@ -31,4 +32,7 @@ public unsafe class EqpHook : FastHook Penumbra.Log.Excessive($"[GetEqpFlags] Invoked on 0x{(nint)utility:X} with 0x{(ulong)armor:X}, returned 0x{(ulong)*flags:X16}."); } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/EstHook.cs b/Penumbra/Interop/Hooks/Meta/EstHook.cs index 23931182..fa0bb3c5 100644 --- a/Penumbra/Interop/Hooks/Meta/EstHook.cs +++ b/Penumbra/Interop/Hooks/Meta/EstHook.cs @@ -6,7 +6,7 @@ using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Meta; -public class EstHook : FastHook +public class EstHook : FastHook, IDisposable { public delegate EstEntry Delegate(uint id, int estType, uint genderRace); @@ -14,14 +14,16 @@ public class EstHook : FastHook public EstHook(HookManager hooks, MetaState metaState) { - _metaState = metaState; - Task = hooks.CreateHook("GetEstEntry", "44 8B C9 83 EA ?? 74", Detour, true); + _metaState = metaState; + Task = hooks.CreateHook("GetEstEntry", "44 8B C9 83 EA ?? 74", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private EstEntry Detour(uint genderRace, int estType, uint id) { EstEntry ret; - if (_metaState.EstCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache } + if (_metaState.EstCollection.TryPeek(out var collection) + && collection is { Valid: true, ModCollection.MetaCache: { } cache } && cache.Est.TryGetValue(Convert(genderRace, estType, id), out var entry)) ret = entry.Entry; else @@ -46,4 +48,7 @@ public class EstHook : FastHook }; return new EstIdentifier(i, type, gr); } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/GmpHook.cs b/Penumbra/Interop/Hooks/Meta/GmpHook.cs index 256d8702..44d35f12 100644 --- a/Penumbra/Interop/Hooks/Meta/GmpHook.cs +++ b/Penumbra/Interop/Hooks/Meta/GmpHook.cs @@ -5,7 +5,7 @@ using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Meta; -public unsafe class GmpHook : FastHook +public unsafe class GmpHook : FastHook, IDisposable { public delegate nint Delegate(nint gmpResource, uint dividedHeadId); @@ -16,7 +16,8 @@ public unsafe class GmpHook : FastHook public GmpHook(HookManager hooks, MetaState metaState) { _metaState = metaState; - Task = hooks.CreateHook("GetGmpEntry", "E8 ?? ?? ?? ?? 48 85 C0 74 ?? 43 8D 0C", Detour, true); + Task = hooks.CreateHook("GetGmpEntry", "E8 ?? ?? ?? ?? 48 85 C0 74 ?? 43 8D 0C", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } /// @@ -27,7 +28,8 @@ public unsafe class GmpHook : FastHook private nint Detour(nint gmpResource, uint dividedHeadId) { nint ret; - if (_metaState.GmpCollection.TryPeek(out var collection) && collection.Collection is { Valid: true, ModCollection.MetaCache: { } cache } + if (_metaState.GmpCollection.TryPeek(out var collection) + && collection.Collection is { Valid: true, ModCollection.MetaCache: { } cache } && cache.Gmp.TryGetValue(new GmpIdentifier(collection.Id), out var entry)) { if (entry.Entry.Enabled) @@ -61,4 +63,7 @@ public unsafe class GmpHook : FastHook Marshal.FreeHGlobal((nint)Pointer); } } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs index 86759460..db22d90c 100644 --- a/Penumbra/Interop/Hooks/Meta/RspBustHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspBustHook.cs @@ -7,7 +7,7 @@ using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Meta; -public unsafe class RspBustHook : FastHook +public unsafe class RspBustHook : FastHook, IDisposable { public delegate float* Delegate(nint cmpResource, float* storage, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte bustSize); @@ -19,7 +19,8 @@ public unsafe class RspBustHook : FastHook { _metaState = metaState; _metaFileManager = metaFileManager; - Task = hooks.CreateHook("GetRspBust", "E8 ?? ?? ?? ?? F2 0F 10 44 24 ?? 8B 44 24", Detour, true); + Task = hooks.CreateHook("GetRspBust", "E8 ?? ?? ?? ?? F2 0F 10 44 24 ?? 8B 44 24", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private float* Detour(nint cmpResource, float* storage, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte bustSize) @@ -63,4 +64,7 @@ public unsafe class RspBustHook : FastHook $"[GetRspBust] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {bustSize}, returned {storage[0]}, {storage[1]}, {storage[2]}."); return ret; } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs b/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs index cf88c34a..dcb3f19c 100644 --- a/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspHeightHook.cs @@ -1,4 +1,3 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.Interop.PathResolving; @@ -8,7 +7,7 @@ using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Meta; -public class RspHeightHook : FastHook +public class RspHeightHook : FastHook, IDisposable { public delegate float Delegate(nint cmpResource, Race clan, byte gender, byte isSecondSubRace, byte bodyType, byte height); @@ -17,15 +16,18 @@ public class RspHeightHook : FastHook public RspHeightHook(HookManager hooks, MetaState metaState, MetaFileManager metaFileManager) { - _metaState = metaState; + _metaState = metaState; _metaFileManager = metaFileManager; - Task = hooks.CreateHook("GetRspHeight", "E8 ?? ?? ?? ?? 48 8B 8E ?? ?? ?? ?? 44 8B CF", Detour, true); + Task = hooks.CreateHook("GetRspHeight", "E8 ?? ?? ?? ?? 48 8B 8E ?? ?? ?? ?? 44 8B CF", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private unsafe float Detour(nint cmpResource, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte height) { float scale; - if (bodyType < 2 && _metaState.RspCollection.TryPeek(out var collection) && collection is { Valid: true, ModCollection.MetaCache: { } cache }) + if (bodyType < 2 + && _metaState.RspCollection.TryPeek(out var collection) + && collection is { Valid: true, ModCollection.MetaCache: { } cache }) { var clan = (SubRace)(((int)race - 1) * 2 + 1 + isSecondSubRace); var (minIdent, maxIdent) = gender == 0 @@ -66,4 +68,7 @@ public class RspHeightHook : FastHook $"[GetRspHeight] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {height}, returned {scale}."); return scale; } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/Hooks/Meta/RspTailHook.cs b/Penumbra/Interop/Hooks/Meta/RspTailHook.cs index e40f0161..8d333575 100644 --- a/Penumbra/Interop/Hooks/Meta/RspTailHook.cs +++ b/Penumbra/Interop/Hooks/Meta/RspTailHook.cs @@ -7,7 +7,7 @@ using Penumbra.Meta.Manipulations; namespace Penumbra.Interop.Hooks.Meta; -public class RspTailHook : FastHook +public class RspTailHook : FastHook, IDisposable { public delegate float Delegate(nint cmpResource, Race clan, byte gender, byte isSecondSubRace, byte bodyType, byte height); @@ -18,7 +18,8 @@ public class RspTailHook : FastHook { _metaState = metaState; _metaFileManager = metaFileManager; - Task = hooks.CreateHook("GetRspTail", "E8 ?? ?? ?? ?? 0F 28 F0 48 8B 05", Detour, true); + Task = hooks.CreateHook("GetRspTail", "E8 ?? ?? ?? ?? 0F 28 F0 48 8B 05", Detour, metaState.Config.EnableMods); + _metaState.Config.ModsEnabled += Toggle; } private unsafe float Detour(nint cmpResource, Race race, byte gender, byte isSecondSubRace, byte bodyType, byte tailLength) @@ -65,4 +66,7 @@ public class RspTailHook : FastHook $"[GetRspTail] Invoked on 0x{cmpResource:X} with {race}, {(Gender)(gender + 1)}, {isSecondSubRace == 1}, {bodyType}, {tailLength}, returned {scale}."); return scale; } + + public void Dispose() + => _metaState.Config.ModsEnabled -= Toggle; } diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index f7dcfc07..5eacbfb0 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -37,7 +37,7 @@ namespace Penumbra.Interop.PathResolving; // GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which is SetupVisor. public sealed unsafe class MetaState : IDisposable, IService { - private readonly Configuration _config; + public readonly Configuration Config; private readonly CommunicatorService _communicator; private readonly CollectionResolver _collectionResolver; private readonly ResourceLoader _resources; @@ -64,7 +64,7 @@ public sealed unsafe class MetaState : IDisposable, IService _resources = resources; _createCharacterBase = createCharacterBase; _characterUtility = characterUtility; - _config = config; + Config = config; _createCharacterBase.Subscribe(OnCreatingCharacterBase, CreateCharacterBase.Priority.MetaState); _createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.MetaState); } @@ -84,7 +84,7 @@ public sealed unsafe class MetaState : IDisposable, IService } public DecalReverter ResolveDecal(ResolveData resolve, bool which) - => new(_config, _characterUtility, _resources, resolve, which); + => new(Config, _characterUtility, _resources, resolve, which); public void Dispose() { @@ -99,7 +99,7 @@ public sealed unsafe class MetaState : IDisposable, IService _communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject, _lastCreatedCollection.ModCollection.Id, (nint)modelCharaId, (nint)customize, (nint)equipData); - var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection, + var decal = new DecalReverter(Config, _characterUtility, _resources, _lastCreatedCollection, UsesDecal(*(uint*)modelCharaId, (nint)customize)); RspCollection.Push(_lastCreatedCollection); _characterBaseCreateMetaChanges.Dispose(); // Should always be empty.