From 68c782f0b9317a1230d1d1b529b378a1fb356117 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 1 Jan 2024 00:17:15 +0100 Subject: [PATCH] Move all meta hooks to own classes. --- Penumbra/Interop/CharacterBaseVTables.cs | 24 ++ .../Interop/Hooks/Meta/CalculateHeight.cs | 31 +++ .../Interop/Hooks/Meta/ChangeCustomize.cs | 34 +++ Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs | 36 +++ .../Interop/Hooks/Meta/ModelLoadComplete.cs | 30 +++ .../Interop/Hooks/Meta/RspSetupCharacter.cs | 37 +++ Penumbra/Interop/Hooks/Meta/SetupVisor.cs | 35 +++ Penumbra/Interop/Hooks/Meta/UpdateModel.cs | 36 +++ .../PathResolving/CollectionResolver.cs | 4 +- .../IdentifiedCollectionCache.cs | 4 +- Penumbra/Interop/PathResolving/MetaState.cs | 223 ++++-------------- 11 files changed, 314 insertions(+), 180 deletions(-) create mode 100644 Penumbra/Interop/CharacterBaseVTables.cs create mode 100644 Penumbra/Interop/Hooks/Meta/CalculateHeight.cs create mode 100644 Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs create mode 100644 Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs create mode 100644 Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs create mode 100644 Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs create mode 100644 Penumbra/Interop/Hooks/Meta/SetupVisor.cs create mode 100644 Penumbra/Interop/Hooks/Meta/UpdateModel.cs diff --git a/Penumbra/Interop/CharacterBaseVTables.cs b/Penumbra/Interop/CharacterBaseVTables.cs new file mode 100644 index 00000000..40b9a588 --- /dev/null +++ b/Penumbra/Interop/CharacterBaseVTables.cs @@ -0,0 +1,24 @@ +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using OtterGui.Services; +using Penumbra.GameData; + +namespace Penumbra.Interop; + +public sealed unsafe class CharacterBaseVTables : IService +{ + [Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] + public readonly nint* HumanVTable = null!; + + [Signature(Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)] + public readonly nint* WeaponVTable = null!; + + [Signature(Sigs.DemiHumanVTable, ScanType = ScanType.StaticAddress)] + public readonly nint* DemiHumanVTable = null!; + + [Signature(Sigs.MonsterVTable, ScanType = ScanType.StaticAddress)] + public readonly nint* MonsterVTable = null!; + + public CharacterBaseVTables(IGameInteropProvider interop) + => interop.InitializeFromAttributes(this); +} diff --git a/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs b/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs new file mode 100644 index 00000000..2fd87f6e --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/CalculateHeight.cs @@ -0,0 +1,31 @@ +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using OtterGui.Services; +using Penumbra.Interop.PathResolving; +using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; + +namespace Penumbra.Interop.Hooks.Meta; + +public sealed unsafe class CalculateHeight : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public CalculateHeight(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("Calculate Height", (nint)Character.MemberFunctionPointers.CalculateHeight, Detour, true); + } + + public delegate ulong Delegate(Character* character); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private ulong Detour(Character* character) + { + var collection = _collectionResolver.IdentifyCollection((GameObject*)character, true); + using var cmp = _metaState.ResolveRspData(collection.ModCollection); + var ret = Task.Result.Original.Invoke(character); + Penumbra.Log.Excessive($"[Calculate Height] Invoked on {(nint)character:X} -> {ret}."); + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs new file mode 100644 index 00000000..81f6d552 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs @@ -0,0 +1,34 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData; +using Penumbra.GameData.Structs; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + +public sealed unsafe class ChangeCustomize : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public ChangeCustomize(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("Change Customize", Sigs.ChangeCustomize, Detour, true); + } + + public delegate bool Delegate(Human* human, CustomizeArray* data, byte skipEquipment); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private bool Detour(Human* human, CustomizeArray* data, byte skipEquipment) + { + _metaState.CustomizeChangeCollection = _collectionResolver.IdentifyCollection((DrawObject*)human, true); + using var cmp = _metaState.ResolveRspData(_metaState.CustomizeChangeCollection.ModCollection); + using var decal1 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, true); + using var decal2 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, false); + var ret = Task.Result.Original.Invoke(human, data, skipEquipment); + Penumbra.Log.Excessive($"[Change Customize] Invoked on {(nint)human:X} with {(nint)data:X}, {skipEquipment} -> {ret}."); + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs b/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs new file mode 100644 index 00000000..c1b6eacc --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs @@ -0,0 +1,36 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + +public sealed unsafe class GetEqpIndirect : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public GetEqpIndirect(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("Get EQP Indirect", Sigs.GetEqpIndirect, Detour, true); + } + + public delegate void Delegate(DrawObject* drawObject); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void Detour(DrawObject* drawObject) + { + if ((*(byte*)(drawObject + Offsets.GetEqpIndirectSkip1) & 1) == 0 || *(ulong*)(drawObject + Offsets.GetEqpIndirectSkip2) == 0) + return; + + Penumbra.Log.Excessive($"[Get EQP Indirect] Invoked on {(nint)drawObject:X}."); + // Shortcut because this is also called all the time. + // Same thing is checked at the beginning of the original function. + + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + using var eqp = _metaState.ResolveEqpData(collection.ModCollection); + Task.Result.Original(drawObject); + } +} diff --git a/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs b/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs new file mode 100644 index 00000000..9f191fdd --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs @@ -0,0 +1,30 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + +public sealed unsafe class ModelLoadComplete : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public ModelLoadComplete(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState, CharacterBaseVTables vtables) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("Model Load Complete", vtables.HumanVTable[58], Detour, true); + } + + public delegate void Delegate(DrawObject* drawObject); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void Detour(DrawObject* drawObject) + { + Penumbra.Log.Excessive($"[Model Load Complete] Invoked on {(nint)drawObject:X}."); + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + using var eqp = _metaState.ResolveEqpData(collection.ModCollection); + using var eqdp = _metaState.ResolveEqdpData(collection.ModCollection, MetaState.GetDrawObjectGenderRace((nint)drawObject), true, true); + Task.Result.Original(drawObject); + } +} diff --git a/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs b/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs new file mode 100644 index 00000000..8f8f1d78 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs @@ -0,0 +1,37 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + +public sealed unsafe class RspSetupCharacter : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public RspSetupCharacter(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("RSP Setup Character", Sigs.RspSetupCharacter, Detour, true); + } + + public delegate void Delegate(DrawObject* drawObject, nint unk2, float unk3, nint unk4, byte unk5); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void Detour(DrawObject* drawObject, nint unk2, float unk3, nint unk4, byte unk5) + { + Penumbra.Log.Excessive($"[RSP Setup Character] Invoked on {(nint)drawObject:X} with {unk2}, {unk3}, {unk4}, {unk5}."); + // Skip if we are coming from ChangeCustomize. + if (_metaState.CustomizeChangeCollection.Valid) + { + Task.Result.Original.Invoke(drawObject, unk2, unk3, unk4, unk5); + return; + } + + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + using var cmp = _metaState.ResolveRspData(collection.ModCollection); + Task.Result.Original.Invoke(drawObject, unk2, unk3, unk4, unk5); + } +} diff --git a/Penumbra/Interop/Hooks/Meta/SetupVisor.cs b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs new file mode 100644 index 00000000..e451f118 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/SetupVisor.cs @@ -0,0 +1,35 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +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. +/// +public sealed unsafe class SetupVisor : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public SetupVisor(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("Setup Visor", Sigs.SetupVisor, Detour, true); + } + + public delegate byte Delegate(DrawObject* drawObject, ushort modelId, byte visorState); + + [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}."); + return ret; + } +} diff --git a/Penumbra/Interop/Hooks/Meta/UpdateModel.cs b/Penumbra/Interop/Hooks/Meta/UpdateModel.cs new file mode 100644 index 00000000..c169ead2 --- /dev/null +++ b/Penumbra/Interop/Hooks/Meta/UpdateModel.cs @@ -0,0 +1,36 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using OtterGui.Services; +using Penumbra.GameData; +using Penumbra.Interop.PathResolving; + +namespace Penumbra.Interop.Hooks.Meta; + +public sealed unsafe class UpdateModel : FastHook +{ + private readonly CollectionResolver _collectionResolver; + private readonly MetaState _metaState; + + public UpdateModel(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState) + { + _collectionResolver = collectionResolver; + _metaState = metaState; + Task = hooks.CreateHook("Update Model", Sigs.UpdateModel, Detour, true); + } + + public delegate void Delegate(DrawObject* drawObject); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void Detour(DrawObject* drawObject) + { + // Shortcut because this is called all the time. + // Same thing is checked at the beginning of the original function. + if (*(int*)(drawObject + Offsets.UpdateModelSkip) == 0) + return; + + Penumbra.Log.Excessive($"[Update Model] Invoked on {(nint)drawObject:X}."); + var collection = _collectionResolver.IdentifyCollection(drawObject, true); + using var eqp = _metaState.ResolveEqpData(collection.ModCollection); + using var eqdp = _metaState.ResolveEqdpData(collection.ModCollection, MetaState.GetDrawObjectGenderRace((nint)drawObject), true, true); + Task.Result.Original.Invoke(drawObject); + } +} diff --git a/Penumbra/Interop/PathResolving/CollectionResolver.cs b/Penumbra/Interop/PathResolving/CollectionResolver.cs index c649147a..1a715f13 100644 --- a/Penumbra/Interop/PathResolving/CollectionResolver.cs +++ b/Penumbra/Interop/PathResolving/CollectionResolver.cs @@ -24,12 +24,12 @@ public sealed unsafe class CollectionResolver( CollectionManager collectionManager, TempCollectionManager tempCollections, DrawObjectState drawObjectState, - HumanModelList humanModels) + HumanModelList humanModels) : IService { /// /// Get the collection applying to the current player character - /// or the Yourself or Default collection if no player exists. + /// or the 'Yourself' or 'Default' collection if no player exists. /// public ModCollection PlayerCollection() { diff --git a/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs b/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs index 3e7171f8..b944011d 100644 --- a/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs +++ b/Penumbra/Interop/PathResolving/IdentifiedCollectionCache.cs @@ -20,8 +20,8 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A public IdentifiedCollectionCache(IClientState clientState, CommunicatorService communicator, CharacterDestructor characterDestructor) { - _clientState = clientState; - _communicator = communicator; + _clientState = clientState; + _communicator = communicator; _characterDestructor = characterDestructor; _communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache); diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index 9ef291c7..9d899648 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -1,13 +1,7 @@ -using Dalamud.Hooking; -using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using Penumbra.Collections; using Penumbra.Api.Enums; -using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Hooks; @@ -15,10 +9,8 @@ using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.Services; using Penumbra.Services; using Penumbra.String.Classes; -using Penumbra.Util; using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; -using static Penumbra.GameData.Enums.GenderRace; namespace Penumbra.Interop.PathResolving; @@ -44,56 +36,40 @@ namespace Penumbra.Interop.PathResolving; // ChangeCustomize and RspSetupCharacter, which is hooked here, as well as Character.CalculateHeight. // GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which has a DrawObject as its first parameter. -public unsafe class MetaState : IDisposable +public sealed unsafe class MetaState : IDisposable { - [Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] - private readonly nint* _humanVTable = null!; - private readonly Configuration _config; private readonly CommunicatorService _communicator; - private readonly PerformanceTracker _performance; private readonly CollectionResolver _collectionResolver; private readonly ResourceLoader _resources; private readonly CharacterUtility _characterUtility; private readonly CreateCharacterBase _createCharacterBase; + public ResolveData CustomizeChangeCollection = ResolveData.Invalid; + private ResolveData _lastCreatedCollection = ResolveData.Invalid; - private ResolveData _customizeChangeCollection = ResolveData.Invalid; private DisposableContainer _characterBaseCreateMetaChanges = DisposableContainer.Empty; - public MetaState(PerformanceTracker performance, CommunicatorService communicator, CollectionResolver collectionResolver, - ResourceLoader resources, CreateCharacterBase createCharacterBase, CharacterUtility characterUtility, Configuration config, - IGameInteropProvider interop) + public MetaState(CommunicatorService communicator, CollectionResolver collectionResolver, + ResourceLoader resources, CreateCharacterBase createCharacterBase, CharacterUtility characterUtility, Configuration config) { - _performance = performance; _communicator = communicator; _collectionResolver = collectionResolver; _resources = resources; _createCharacterBase = createCharacterBase; _characterUtility = characterUtility; _config = config; - interop.InitializeFromAttributes(this); - _calculateHeightHook = - interop.HookFromAddress((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); - _onModelLoadCompleteHook = interop.HookFromAddress(_humanVTable[58], OnModelLoadCompleteDetour); - _getEqpIndirectHook.Enable(); - _updateModelsHook.Enable(); - _onModelLoadCompleteHook.Enable(); - _setupVisorHook.Enable(); - _rspSetupCharacterHook.Enable(); - _changeCustomize.Enable(); - _calculateHeightHook.Enable(); _createCharacterBase.Subscribe(OnCreatingCharacterBase, CreateCharacterBase.Priority.MetaState); - _createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.MetaState); + _createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.MetaState); } public bool HandleDecalFile(ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData) { if (type == ResourceType.Tex - && (_lastCreatedCollection.Valid || _customizeChangeCollection.Valid) + && (_lastCreatedCollection.Valid || CustomizeChangeCollection.Valid) && gamePath.Path.Substring("chara/common/texture/".Length).StartsWith("decal"u8)) { - resolveData = _lastCreatedCollection.Valid ? _lastCreatedCollection : _customizeChangeCollection; + resolveData = _lastCreatedCollection.Valid ? _lastCreatedCollection : CustomizeChangeCollection; return true; } @@ -102,30 +78,49 @@ public unsafe class MetaState : IDisposable } public DisposableContainer ResolveEqdpData(ModCollection collection, GenderRace race, bool equipment, bool accessory) - { - var races = race.Dependencies(); + => (equipment, accessory) switch + { + (true, true) => new DisposableContainer(race.Dependencies().SelectMany(r => new[] + { + collection.TemporarilySetEqdpFile(_characterUtility, r, false), + collection.TemporarilySetEqdpFile(_characterUtility, r, true), + })), + (true, false) => new DisposableContainer(race.Dependencies() + .Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, false))), + (false, true) => new DisposableContainer(race.Dependencies() + .Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, true))), + _ => DisposableContainer.Empty, + }; - var equipmentEnumerable = equipment - ? races.Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, false)) - : Array.Empty().AsEnumerable(); - var accessoryEnumerable = accessory - ? races.Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, true)) - : Array.Empty().AsEnumerable(); - return new DisposableContainer(equipmentEnumerable.Concat(accessoryEnumerable)); - } + public MetaList.MetaReverter ResolveEqpData(ModCollection collection) + => collection.TemporarilySetEqpFile(_characterUtility); + + public MetaList.MetaReverter ResolveGmpData(ModCollection collection) + => collection.TemporarilySetGmpFile(_characterUtility); + + public MetaList.MetaReverter ResolveRspData(ModCollection collection) + => collection.TemporarilySetCmpFile(_characterUtility); + + public DecalReverter ResolveDecal(ResolveData resolve, bool which) + => new(_config, _characterUtility, _resources, resolve, which); public static GenderRace GetHumanGenderRace(nint human) => (GenderRace)((Human*)human)->RaceSexId; + public static GenderRace GetDrawObjectGenderRace(nint drawObject) + { + var draw = (DrawObject*)drawObject; + if (draw->Object.GetObjectType() != ObjectType.CharacterBase) + return GenderRace.Unknown; + + var c = (CharacterBase*)drawObject; + return c->GetModelType() == CharacterBase.ModelType.Human + ? GetHumanGenderRace(drawObject) + : GenderRace.Unknown; + } + public void Dispose() { - _getEqpIndirectHook.Dispose(); - _updateModelsHook.Dispose(); - _onModelLoadCompleteHook.Dispose(); - _setupVisorHook.Dispose(); - _rspSetupCharacterHook.Dispose(); - _changeCustomize.Dispose(); - _calculateHeightHook.Dispose(); _createCharacterBase.Unsubscribe(OnCreatingCharacterBase); _createCharacterBase.Unsubscribe(OnCharacterBaseCreated); } @@ -135,10 +130,10 @@ public unsafe class MetaState : IDisposable _lastCreatedCollection = _collectionResolver.IdentifyLastGameObjectCollection(true); if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero) _communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject, - _lastCreatedCollection.ModCollection.Name, (nint) modelCharaId, (nint) customize, (nint) equipData); + _lastCreatedCollection.ModCollection.Name, (nint)modelCharaId, (nint)customize, (nint)equipData); var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection, - UsesDecal(*(uint*)modelCharaId, (nint) customize)); + UsesDecal(*(uint*)modelCharaId, (nint)customize)); var cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile(_characterUtility); _characterBaseCreateMetaChanges.Dispose(); // Should always be empty. _characterBaseCreateMetaChanges = new DisposableContainer(decal, cmp); @@ -154,134 +149,10 @@ public unsafe class MetaState : IDisposable _lastCreatedCollection = ResolveData.Invalid; } - private delegate void OnModelLoadCompleteDelegate(nint drawObject); - private readonly Hook _onModelLoadCompleteHook; - - private void OnModelLoadCompleteDetour(nint drawObject) - { - var collection = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - using var eqp = collection.ModCollection.TemporarilySetEqpFile(_characterUtility); - using var eqdp = ResolveEqdpData(collection.ModCollection, GetDrawObjectGenderRace(drawObject), true, true); - _onModelLoadCompleteHook.Original.Invoke(drawObject); - } - - private delegate void UpdateModelDelegate(nint drawObject); - - [Signature(Sigs.UpdateModel, DetourName = nameof(UpdateModelsDetour))] - private readonly Hook _updateModelsHook = null!; - - private void UpdateModelsDetour(nint drawObject) - { - // Shortcut because this is called all the time. - // Same thing is checked at the beginning of the original function. - if (*(int*)(drawObject + Offsets.UpdateModelSkip) == 0) - return; - - using var performance = _performance.Measure(PerformanceType.UpdateModels); - - var collection = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - using var eqp = collection.ModCollection.TemporarilySetEqpFile(_characterUtility); - using var eqdp = ResolveEqdpData(collection.ModCollection, GetDrawObjectGenderRace(drawObject), true, true); - _updateModelsHook.Original.Invoke(drawObject); - } - - private static GenderRace GetDrawObjectGenderRace(nint drawObject) - { - var draw = (DrawObject*)drawObject; - if (draw->Object.GetObjectType() != ObjectType.CharacterBase) - return Unknown; - - var c = (CharacterBase*)drawObject; - return c->GetModelType() == CharacterBase.ModelType.Human - ? GetHumanGenderRace(drawObject) - : Unknown; - } - - [Signature(Sigs.GetEqpIndirect, DetourName = nameof(GetEqpIndirectDetour))] - private readonly Hook _getEqpIndirectHook = null!; - - private void GetEqpIndirectDetour(nint drawObject) - { - // Shortcut because this is also called all the time. - // Same thing is checked at the beginning of the original function. - if ((*(byte*)(drawObject + Offsets.GetEqpIndirectSkip1) & 1) == 0 || *(ulong*)(drawObject + Offsets.GetEqpIndirectSkip2) == 0) - return; - - using var performance = _performance.Measure(PerformanceType.GetEqp); - var resolveData = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - using var eqp = resolveData.ModCollection.TemporarilySetEqpFile(_characterUtility); - _getEqpIndirectHook.Original(drawObject); - } - - - // 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. - private delegate byte SetupVisorDelegate(nint drawObject, ushort modelId, byte visorState); - - [Signature(Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))] - private readonly Hook _setupVisorHook = null!; - - private byte SetupVisorDetour(nint drawObject, ushort modelId, byte visorState) - { - using var performance = _performance.Measure(PerformanceType.SetupVisor); - var resolveData = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - using var gmp = resolveData.ModCollection.TemporarilySetGmpFile(_characterUtility); - return _setupVisorHook.Original(drawObject, modelId, visorState); - } - - // RSP - private delegate void RspSetupCharacterDelegate(nint drawObject, nint unk2, float unk3, nint unk4, byte unk5); - - [Signature(Sigs.RspSetupCharacter, DetourName = nameof(RspSetupCharacterDetour))] - private readonly Hook _rspSetupCharacterHook = null!; - - private void RspSetupCharacterDetour(nint drawObject, nint unk2, float unk3, nint unk4, byte unk5) - { - if (_customizeChangeCollection.Valid) - { - _rspSetupCharacterHook.Original(drawObject, unk2, unk3, unk4, unk5); - } - else - { - using var performance = _performance.Measure(PerformanceType.SetupCharacter); - var resolveData = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - using var cmp = resolveData.ModCollection.TemporarilySetCmpFile(_characterUtility); - _rspSetupCharacterHook.Original(drawObject, unk2, unk3, unk4, unk5); - } - } - - private delegate ulong CalculateHeightDelegate(Character* character); - - private readonly Hook _calculateHeightHook = null!; - - private ulong CalculateHeightDetour(Character* character) - { - var resolveData = _collectionResolver.IdentifyCollection((GameObject*)character, true); - using var cmp = resolveData.ModCollection.TemporarilySetCmpFile(_characterUtility); - return _calculateHeightHook.Original(character); - } - - private delegate bool ChangeCustomizeDelegate(nint human, nint data, byte skipEquipment); - - [Signature(Sigs.ChangeCustomize, DetourName = nameof(ChangeCustomizeDetour))] - private readonly Hook _changeCustomize = null!; - - private bool ChangeCustomizeDetour(nint human, nint data, byte skipEquipment) - { - using var performance = _performance.Measure(PerformanceType.ChangeCustomize); - _customizeChangeCollection = _collectionResolver.IdentifyCollection((DrawObject*)human, true); - using var cmp = _customizeChangeCollection.ModCollection.TemporarilySetCmpFile(_characterUtility); - using var decals = new DecalReverter(_config, _characterUtility, _resources, _customizeChangeCollection, true); - using var decal2 = new DecalReverter(_config, _characterUtility, _resources, _customizeChangeCollection, false); - var ret = _changeCustomize.Original(human, data, skipEquipment); - _customizeChangeCollection = ResolveData.Invalid; - return ret; - } - /// /// Check the customize array for the FaceCustomization byte and the last bit of that. /// Also check for humans. /// - public static bool UsesDecal(uint modelId, nint customizeData) + private static bool UsesDecal(uint modelId, nint customizeData) => modelId == 0 && ((byte*)customizeData)[12] > 0x7F; }