mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Move all meta hooks to own classes.
This commit is contained in:
parent
da019e729d
commit
68c782f0b9
11 changed files with 314 additions and 180 deletions
24
Penumbra/Interop/CharacterBaseVTables.cs
Normal file
24
Penumbra/Interop/CharacterBaseVTables.cs
Normal 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);
|
||||
}
|
||||
31
Penumbra/Interop/Hooks/Meta/CalculateHeight.cs
Normal file
31
Penumbra/Interop/Hooks/Meta/CalculateHeight.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
34
Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs
Normal file
34
Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
36
Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs
Normal file
36
Penumbra/Interop/Hooks/Meta/GetEqpIndirect.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
30
Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs
Normal file
30
Penumbra/Interop/Hooks/Meta/ModelLoadComplete.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
37
Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs
Normal file
37
Penumbra/Interop/Hooks/Meta/RspSetupCharacter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
35
Penumbra/Interop/Hooks/Meta/SetupVisor.cs
Normal file
35
Penumbra/Interop/Hooks/Meta/SetupVisor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
36
Penumbra/Interop/Hooks/Meta/UpdateModel.cs
Normal file
36
Penumbra/Interop/Hooks/Meta/UpdateModel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public sealed unsafe class CollectionResolver(
|
|||
{
|
||||
/// <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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue