mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Use better event in SkinFixer and some cleanup.
This commit is contained in:
parent
f238049750
commit
6d3e930440
5 changed files with 57 additions and 79 deletions
|
|
@ -85,26 +85,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event CreatedCharacterBaseDelegate? CreatedCharacterBase
|
public event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CheckInitialized();
|
|
||||||
_communicator.CreatedCharacterBase.Subscribe(new Action<nint, string, nint>(value),
|
|
||||||
Communication.CreatedCharacterBase.Priority.Api);
|
|
||||||
}
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CheckInitialized();
|
|
||||||
_communicator.CreatedCharacterBase.Unsubscribe(new Action<nint, string, nint>(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Valid
|
public bool Valid
|
||||||
=> _lumina != null;
|
=> _lumina != null;
|
||||||
|
|
@ -157,6 +138,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||||
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber, ModPathChanged.Priority.Api);
|
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber, ModPathChanged.Priority.Api);
|
||||||
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
|
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
|
||||||
|
_communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Dispose()
|
public unsafe void Dispose()
|
||||||
|
|
@ -167,6 +149,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
_resourceLoader.ResourceLoaded -= OnResourceLoaded;
|
_resourceLoader.ResourceLoaded -= OnResourceLoaded;
|
||||||
_communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber);
|
_communicator.ModPathChanged.Unsubscribe(ModPathChangeSubscriber);
|
||||||
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
|
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
|
||||||
|
_communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
|
||||||
_lumina = null;
|
_lumina = null;
|
||||||
_communicator = null!;
|
_communicator = null!;
|
||||||
_modManager = null!;
|
_modManager = null!;
|
||||||
|
|
@ -1189,4 +1172,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
|
|
||||||
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int _1, int _2, bool inherited)
|
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int _1, int _2, bool inherited)
|
||||||
=> ModSettingChanged?.Invoke(type, collection.Name, mod?.ModPath.Name ?? string.Empty, inherited);
|
=> ModSettingChanged?.Invoke(type, collection.Name, mod?.ModPath.Name ?? string.Empty, inherited);
|
||||||
|
|
||||||
|
private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject)
|
||||||
|
=> CreatedCharacterBase?.Invoke(gameObject, collection.Name, drawObject);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
|
||||||
namespace Penumbra.Communication;
|
namespace Penumbra.Communication;
|
||||||
|
|
||||||
/// <summary> <list type="number">
|
/// <summary> <list type="number">
|
||||||
/// <item>Parameter is the game object for which a draw object is created. </item>
|
/// <item>Parameter is the game object for which a draw object is created. </item>
|
||||||
/// <item>Parameter is the name of the applied collection. </item>
|
/// <item>Parameter is the applied collection. </item>
|
||||||
/// <item>Parameter is the created draw object. </item>
|
/// <item>Parameter is the created draw object. </item>
|
||||||
/// </list> </summary>
|
/// </list> </summary>
|
||||||
public sealed class CreatedCharacterBase : EventWrapper<Action<nint, string, nint>, CreatedCharacterBase.Priority>
|
public sealed class CreatedCharacterBase : EventWrapper<Action<nint, ModCollection, nint>, CreatedCharacterBase.Priority>
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="PenumbraApi.CreatedCharacterBase"/>
|
/// <seealso cref="PenumbraApi.CreatedCharacterBase"/>
|
||||||
Api = 0,
|
Api = int.MinValue,
|
||||||
|
|
||||||
|
/// <seealso cref="Interop.Services.SkinFixer.OnCharacterBaseCreated"/>
|
||||||
|
SkinFixer = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreatedCharacterBase()
|
public CreatedCharacterBase()
|
||||||
: base(nameof(CreatedCharacterBase))
|
: base(nameof(CreatedCharacterBase))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public void Invoke(nint gameObject, string appliedCollectionName, nint drawObject)
|
public void Invoke(nint gameObject, ModCollection appliedCollection, nint drawObject)
|
||||||
=> Invoke(this, gameObject, appliedCollectionName, drawObject);
|
=> Invoke(this, gameObject, appliedCollection, drawObject);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ public unsafe class MetaState : IDisposable
|
||||||
_characterBaseCreateMetaChanges = DisposableContainer.Empty;
|
_characterBaseCreateMetaChanges = DisposableContainer.Empty;
|
||||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
|
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
|
||||||
_communicator.CreatedCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
_communicator.CreatedCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
||||||
_lastCreatedCollection.ModCollection.Name, drawObject);
|
_lastCreatedCollection.ModCollection, drawObject);
|
||||||
_lastCreatedCollection = ResolveData.Invalid;
|
_lastCreatedCollection = ResolveData.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,18 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Communication;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.PathResolving;
|
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.ResourceLoading;
|
||||||
using Penumbra.Interop.SafeHandles;
|
using Penumbra.Interop.SafeHandles;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Services;
|
namespace Penumbra.Interop.Services;
|
||||||
|
|
||||||
public unsafe class SkinFixer : IDisposable
|
public sealed unsafe class SkinFixer : IDisposable
|
||||||
{
|
{
|
||||||
public static readonly Utf8GamePath SkinShpkPath =
|
public static readonly Utf8GamePath SkinShpkPath =
|
||||||
Utf8GamePath.FromSpan("shader/sm5/shpk/skin.shpk"u8, out var p) ? p : Utf8GamePath.Empty;
|
Utf8GamePath.FromSpan("shader/sm5/shpk/skin.shpk"u8, out var p) ? p : Utf8GamePath.Empty;
|
||||||
|
|
@ -34,60 +36,48 @@ public unsafe class SkinFixer : IDisposable
|
||||||
{
|
{
|
||||||
[FieldOffset(0x0)]
|
[FieldOffset(0x0)]
|
||||||
public Model* Model;
|
public Model* Model;
|
||||||
|
|
||||||
[FieldOffset(0x8)]
|
[FieldOffset(0x8)]
|
||||||
public uint MaterialIndex;
|
public uint MaterialIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Hook<OnRenderMaterialDelegate> _onRenderMaterialHook;
|
private readonly Hook<OnRenderMaterialDelegate> _onRenderMaterialHook;
|
||||||
|
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly GameEventManager _gameEvents;
|
||||||
private readonly GameEventManager _gameEvents;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly ResourceLoader _resources;
|
private readonly ResourceLoader _resources;
|
||||||
private readonly CharacterUtility _utility;
|
private readonly CharacterUtility _utility;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<nint /* CharacterBase* */, SafeResourceHandle> _skinShpks = new();
|
// CharacterBase to ShpkHandle
|
||||||
|
private readonly ConcurrentDictionary<nint, SafeResourceHandle> _skinShpks = new();
|
||||||
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
private bool _enabled = true;
|
|
||||||
private int _moddedSkinShpkCount = 0;
|
private int _moddedSkinShpkCount = 0;
|
||||||
private ulong _slowPathCallDelta = 0;
|
private ulong _slowPathCallDelta = 0;
|
||||||
public bool Enabled
|
|
||||||
{
|
public bool Enabled { get; internal set; } = true;
|
||||||
get => _enabled;
|
|
||||||
set => _enabled = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ModdedSkinShpkCount
|
public int ModdedSkinShpkCount
|
||||||
=> _moddedSkinShpkCount;
|
=> _moddedSkinShpkCount;
|
||||||
|
|
||||||
public SkinFixer(CollectionResolver collectionResolver, GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, DrawObjectState _)
|
public SkinFixer(GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, CommunicatorService communicator)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_collectionResolver = collectionResolver;
|
|
||||||
_gameEvents = gameEvents;
|
_gameEvents = gameEvents;
|
||||||
_resources = resources;
|
_resources = resources;
|
||||||
_utility = utility;
|
_utility = utility;
|
||||||
|
_communicator = communicator;
|
||||||
_onRenderMaterialHook = Hook<OnRenderMaterialDelegate>.FromAddress(_humanVTable[62], OnRenderHumanMaterial);
|
_onRenderMaterialHook = Hook<OnRenderMaterialDelegate>.FromAddress(_humanVTable[62], OnRenderHumanMaterial);
|
||||||
_gameEvents.CharacterBaseCreated += OnCharacterBaseCreated; // The dependency on DrawObjectState shall ensure that this handler is registered after its one.
|
_communicator.CreatedCharacterBase.Subscribe(OnCharacterBaseCreated, CreatedCharacterBase.Priority.SkinFixer);
|
||||||
_gameEvents.CharacterBaseDestructor += OnCharacterBaseDestructor;
|
_gameEvents.CharacterBaseDestructor += OnCharacterBaseDestructor;
|
||||||
_onRenderMaterialHook.Enable();
|
_onRenderMaterialHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
~SkinFixer()
|
|
||||||
{
|
|
||||||
Dispose(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
{
|
||||||
_onRenderMaterialHook.Dispose();
|
_onRenderMaterialHook.Dispose();
|
||||||
_gameEvents.CharacterBaseCreated -= OnCharacterBaseCreated;
|
_communicator.CreatedCharacterBase.Unsubscribe(OnCharacterBaseCreated);
|
||||||
_gameEvents.CharacterBaseDestructor -= OnCharacterBaseDestructor;
|
_gameEvents.CharacterBaseDestructor -= OnCharacterBaseDestructor;
|
||||||
foreach (var skinShpk in _skinShpks.Values)
|
foreach (var skinShpk in _skinShpks.Values)
|
||||||
skinShpk.Dispose();
|
skinShpk.Dispose();
|
||||||
|
|
@ -98,21 +88,21 @@ public unsafe class SkinFixer : IDisposable
|
||||||
public ulong GetAndResetSlowPathCallDelta()
|
public ulong GetAndResetSlowPathCallDelta()
|
||||||
=> Interlocked.Exchange(ref _slowPathCallDelta, 0);
|
=> Interlocked.Exchange(ref _slowPathCallDelta, 0);
|
||||||
|
|
||||||
private void OnCharacterBaseCreated(uint modelCharaId, nint customize, nint equipment, nint drawObject)
|
private void OnCharacterBaseCreated(nint gameObject, ModCollection collection, nint drawObject)
|
||||||
{
|
{
|
||||||
if (((CharacterBase*)drawObject)->GetModelType() != CharacterBase.ModelType.Human)
|
if (((CharacterBase*)drawObject)->GetModelType() != CharacterBase.ModelType.Human)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Task.Run(delegate
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var skinShpk = SafeResourceHandle.CreateInvalid();
|
var skinShpk = SafeResourceHandle.CreateInvalid();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
var data = collection.ToResolveData(gameObject);
|
||||||
if (data.Valid)
|
if (data.Valid)
|
||||||
{
|
{
|
||||||
var loadedShpk = _resources.LoadResolvedResource(ResourceCategory.Shader, ResourceType.Shpk, SkinShpkPath.Path, data);
|
var loadedShpk = _resources.LoadResolvedResource(ResourceCategory.Shader, ResourceType.Shpk, SkinShpkPath.Path, data);
|
||||||
skinShpk = new SafeResourceHandle((ResourceHandle*)loadedShpk, incRef: false);
|
skinShpk = new SafeResourceHandle((ResourceHandle*)loadedShpk, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -128,30 +118,31 @@ public unsafe class SkinFixer : IDisposable
|
||||||
Interlocked.Increment(ref _moddedSkinShpkCount);
|
Interlocked.Increment(ref _moddedSkinShpkCount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
skinShpk.Dispose();
|
skinShpk.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCharacterBaseDestructor(nint characterBase)
|
private void OnCharacterBaseDestructor(nint characterBase)
|
||||||
{
|
{
|
||||||
if (_skinShpks.Remove(characterBase, out var skinShpk))
|
if (!_skinShpks.Remove(characterBase, out var skinShpk))
|
||||||
{
|
return;
|
||||||
var handle = skinShpk.ResourceHandle;
|
|
||||||
skinShpk.Dispose();
|
var handle = skinShpk.ResourceHandle;
|
||||||
if ((nint)handle != _utility.DefaultSkinShpkResource)
|
skinShpk.Dispose();
|
||||||
Interlocked.Decrement(ref _moddedSkinShpkCount);
|
if ((nint)handle != _utility.DefaultSkinShpkResource)
|
||||||
}
|
Interlocked.Decrement(ref _moddedSkinShpkCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private nint OnRenderHumanMaterial(nint human, OnRenderMaterialParams* param)
|
private nint OnRenderHumanMaterial(nint human, OnRenderMaterialParams* param)
|
||||||
{
|
{
|
||||||
if (!_enabled || // Can be toggled on the debug tab.
|
// If we don't have any on-screen instances of modded skin.shpk, we don't need the slow path at all.
|
||||||
_moddedSkinShpkCount == 0 || // If we don't have any on-screen instances of modded skin.shpk, we don't need the slow path at all.
|
if (!Enabled || _moddedSkinShpkCount == 0 || !_skinShpks.TryGetValue(human, out var skinShpk) || skinShpk.IsInvalid)
|
||||||
!_skinShpks.TryGetValue(human, out var skinShpk))
|
|
||||||
return _onRenderMaterialHook!.Original(human, param);
|
return _onRenderMaterialHook!.Original(human, param);
|
||||||
|
|
||||||
var material = param->Model->Materials[param->MaterialIndex];
|
var material = param->Model->Materials[param->MaterialIndex];
|
||||||
var shpkResource = ((Structs.MtrlResource*)material->MaterialResourceHandle)->ShpkResourceHandle;
|
var shpkResource = ((Structs.MtrlResource*)material->MaterialResourceHandle)->ShpkResourceHandle;
|
||||||
if ((nint)shpkResource != (nint)skinShpk.ResourceHandle)
|
if ((nint)shpkResource != (nint)skinShpk.ResourceHandle)
|
||||||
return _onRenderMaterialHook!.Original(human, param);
|
return _onRenderMaterialHook!.Original(human, param);
|
||||||
|
|
@ -164,6 +155,7 @@ public unsafe class SkinFixer : IDisposable
|
||||||
// - Swapping path is taken up to hundreds of times a frame.
|
// - Swapping path is taken up to hundreds of times a frame.
|
||||||
// At the time of writing, the lock doesn't seem to have a noticeable impact in either framerate or CPU usage, but the swapping path shall still be avoided as much as possible.
|
// At the time of writing, the lock doesn't seem to have a noticeable impact in either framerate or CPU usage, but the swapping path shall still be avoided as much as possible.
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)skinShpk.ResourceHandle;
|
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)skinShpk.ResourceHandle;
|
||||||
|
|
@ -173,5 +165,6 @@ public unsafe class SkinFixer : IDisposable
|
||||||
{
|
{
|
||||||
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)_utility.DefaultSkinShpkResource;
|
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)_utility.DefaultSkinShpkResource;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
using static Lumina.Data.Parsing.Layer.LayerCommon;
|
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
|
|
@ -623,18 +622,14 @@ public class DebugTab : Window, ITab
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (resource == null)
|
if (resource == null)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
UiHelpers.Text(resource);
|
UiHelpers.Text(resource);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var data = (nint)ResourceHandle.GetData(resource);
|
var data = (nint)ResourceHandle.GetData(resource);
|
||||||
var length = ResourceHandle.GetLength(resource);
|
var length = ResourceHandle.GetLength(resource);
|
||||||
ImGui.Selectable($"0x{data:X}");
|
if (ImGui.Selectable($"0x{data:X}"))
|
||||||
if (ImGui.IsItemClicked())
|
|
||||||
{
|
{
|
||||||
if (data != nint.Zero && length > 0)
|
if (data != nint.Zero && length > 0)
|
||||||
ImGui.SetClipboardText(string.Join("\n",
|
ImGui.SetClipboardText(string.Join("\n",
|
||||||
|
|
@ -643,7 +638,7 @@ public class DebugTab : Window, ITab
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"{length}");
|
ImGui.TextUnformatted(length.ToString());
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (intern.Value != -1)
|
if (intern.Value != -1)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue