Move all meta hooks to own classes.

This commit is contained in:
Ottermandias 2024-01-01 00:17:15 +01:00
parent da019e729d
commit 68c782f0b9
11 changed files with 314 additions and 180 deletions

View file

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

View file

@ -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<CalculateHeight.Delegate>
{
private readonly CollectionResolver _collectionResolver;
private readonly MetaState _metaState;
public CalculateHeight(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
{
_collectionResolver = collectionResolver;
_metaState = metaState;
Task = hooks.CreateHook<Delegate>("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;
}
}

View file

@ -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<ChangeCustomize.Delegate>
{
private readonly CollectionResolver _collectionResolver;
private readonly MetaState _metaState;
public ChangeCustomize(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
{
_collectionResolver = collectionResolver;
_metaState = metaState;
Task = hooks.CreateHook<Delegate>("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;
}
}

View file

@ -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<GetEqpIndirect.Delegate>
{
private readonly CollectionResolver _collectionResolver;
private readonly MetaState _metaState;
public GetEqpIndirect(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
{
_collectionResolver = collectionResolver;
_metaState = metaState;
Task = hooks.CreateHook<Delegate>("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);
}
}

View file

@ -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<ModelLoadComplete.Delegate>
{
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<Delegate>("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);
}
}

View file

@ -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<RspSetupCharacter.Delegate>
{
private readonly CollectionResolver _collectionResolver;
private readonly MetaState _metaState;
public RspSetupCharacter(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
{
_collectionResolver = collectionResolver;
_metaState = metaState;
Task = hooks.CreateHook<Delegate>("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);
}
}

View file

@ -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;
/// <summary>
/// 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.
/// </summary>
public sealed unsafe class SetupVisor : FastHook<SetupVisor.Delegate>
{
private readonly CollectionResolver _collectionResolver;
private readonly MetaState _metaState;
public SetupVisor(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
{
_collectionResolver = collectionResolver;
_metaState = metaState;
Task = hooks.CreateHook<Delegate>("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;
}
}

View file

@ -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<UpdateModel.Delegate>
{
private readonly CollectionResolver _collectionResolver;
private readonly MetaState _metaState;
public UpdateModel(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
{
_collectionResolver = collectionResolver;
_metaState = metaState;
Task = hooks.CreateHook<Delegate>("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);
}
}

View file

@ -24,12 +24,12 @@ public sealed unsafe class CollectionResolver(
CollectionManager collectionManager,
TempCollectionManager tempCollections,
DrawObjectState drawObjectState,
HumanModelList humanModels)
HumanModelList humanModels)
: IService
{
/// <summary>
/// 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.
/// </summary>
public ModCollection PlayerCollection()
{

View file

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

View file

@ -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<CalculateHeightDelegate>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
_onModelLoadCompleteHook = interop.HookFromAddress<OnModelLoadCompleteDelegate>(_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<IDisposable?>().AsEnumerable();
var accessoryEnumerable = accessory
? races.Select(r => collection.TemporarilySetEqdpFile(_characterUtility, r, true))
: Array.Empty<IDisposable?>().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<OnModelLoadCompleteDelegate> _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<UpdateModelDelegate> _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<OnModelLoadCompleteDelegate> _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<SetupVisorDelegate> _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<RspSetupCharacterDelegate> _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<CalculateHeightDelegate> _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<ChangeCustomizeDelegate> _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;
}
/// <summary>
/// Check the customize array for the FaceCustomization byte and the last bit of that.
/// Also check for humans.
/// </summary>
public static bool UsesDecal(uint modelId, nint customizeData)
private static bool UsesDecal(uint modelId, nint customizeData)
=> modelId == 0 && ((byte*)customizeData)[12] > 0x7F;
}