mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Skin Fixer (fixes modding of skin.shpk)
This commit is contained in:
parent
4f71065d67
commit
ead88f9fa6
8 changed files with 302 additions and 30 deletions
|
|
@ -33,6 +33,7 @@ public unsafe partial class CharacterUtility : IDisposable
|
||||||
public event Action LoadingFinished;
|
public event Action LoadingFinished;
|
||||||
public nint DefaultTransparentResource { get; private set; }
|
public nint DefaultTransparentResource { get; private set; }
|
||||||
public nint DefaultDecalResource { get; private set; }
|
public nint DefaultDecalResource { get; private set; }
|
||||||
|
public nint DefaultSkinShpkResource { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The relevant indices depend on which meta manipulations we allow for.
|
/// The relevant indices depend on which meta manipulations we allow for.
|
||||||
|
|
@ -102,6 +103,12 @@ public unsafe partial class CharacterUtility : IDisposable
|
||||||
anyMissing |= DefaultDecalResource == nint.Zero;
|
anyMissing |= DefaultDecalResource == nint.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DefaultSkinShpkResource == nint.Zero)
|
||||||
|
{
|
||||||
|
DefaultSkinShpkResource = (nint)Address->SkinShpkResource;
|
||||||
|
anyMissing |= DefaultSkinShpkResource == nint.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
if (anyMissing)
|
if (anyMissing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -140,15 +147,16 @@ public unsafe partial class CharacterUtility : IDisposable
|
||||||
|
|
||||||
/// <summary> Return all relevant resources to the default resource. </summary>
|
/// <summary> Return all relevant resources to the default resource. </summary>
|
||||||
public void ResetAll()
|
public void ResetAll()
|
||||||
{
|
{
|
||||||
if (!Ready)
|
if (!Ready)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var list in _lists)
|
foreach (var list in _lists)
|
||||||
list.Dispose();
|
list.Dispose();
|
||||||
|
|
||||||
Address->TransparentTexResource = (TextureResourceHandle*)DefaultTransparentResource;
|
Address->TransparentTexResource = (TextureResourceHandle*)DefaultTransparentResource;
|
||||||
Address->DecalTexResource = (TextureResourceHandle*)DefaultDecalResource;
|
Address->DecalTexResource = (TextureResourceHandle*)DefaultDecalResource;
|
||||||
|
Address->SkinShpkResource = (ResourceHandle*)DefaultSkinShpkResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
161
Penumbra/Interop/Services/SkinFixer.cs
Normal file
161
Penumbra/Interop/Services/SkinFixer.cs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Interop.ResourceLoading;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Services;
|
||||||
|
|
||||||
|
public unsafe class SkinFixer : IDisposable
|
||||||
|
{
|
||||||
|
public static readonly Utf8GamePath SkinShpkPath =
|
||||||
|
Utf8GamePath.FromSpan("shader/sm5/shpk/skin.shpk"u8, out var p) ? p : Utf8GamePath.Empty;
|
||||||
|
|
||||||
|
[Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
|
||||||
|
private readonly nint* _humanVTable = null!;
|
||||||
|
|
||||||
|
private delegate nint OnRenderMaterialDelegate(nint drawObject, OnRenderMaterialParams* param);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private struct OnRenderMaterialParams
|
||||||
|
{
|
||||||
|
[FieldOffset(0x0)]
|
||||||
|
public Model* Model;
|
||||||
|
[FieldOffset(0x8)]
|
||||||
|
public uint MaterialIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Hook<OnRenderMaterialDelegate> _onRenderMaterialHook;
|
||||||
|
|
||||||
|
private readonly CollectionResolver _collectionResolver;
|
||||||
|
private readonly GameEventManager _gameEvents;
|
||||||
|
private readonly ResourceLoader _resources;
|
||||||
|
private readonly CharacterUtility _utility;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<nint /* CharacterBase* */, nint /* ResourceHandle* */> _skinShpks = new();
|
||||||
|
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
private bool _enabled = true;
|
||||||
|
private int _moddedSkinShpkCount = 0;
|
||||||
|
private ulong _slowPathCallDelta = 0;
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get => _enabled;
|
||||||
|
set => _enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ModdedSkinShpkCount
|
||||||
|
=> _moddedSkinShpkCount;
|
||||||
|
|
||||||
|
public SkinFixer(CollectionResolver collectionResolver, GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, DrawObjectState _)
|
||||||
|
{
|
||||||
|
SignatureHelper.Initialise(this);
|
||||||
|
_collectionResolver = collectionResolver;
|
||||||
|
_gameEvents = gameEvents;
|
||||||
|
_resources = resources;
|
||||||
|
_utility = utility;
|
||||||
|
_onRenderMaterialHook = Hook<OnRenderMaterialDelegate>.FromAddress(_humanVTable[62], OnRenderHumanMaterial);
|
||||||
|
_gameEvents.CharacterBaseCreated += OnCharacterBaseCreated; // The dependency on DrawObjectState shall ensure that this handler is registered after its one.
|
||||||
|
_gameEvents.CharacterBaseDestructor += OnCharacterBaseDestructor;
|
||||||
|
_onRenderMaterialHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
~SkinFixer()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
_onRenderMaterialHook.Dispose();
|
||||||
|
_gameEvents.CharacterBaseCreated -= OnCharacterBaseCreated;
|
||||||
|
_gameEvents.CharacterBaseDestructor -= OnCharacterBaseDestructor;
|
||||||
|
foreach (var skinShpk in _skinShpks.Values)
|
||||||
|
if (skinShpk != nint.Zero)
|
||||||
|
((ResourceHandle*)skinShpk)->DecRef();
|
||||||
|
_skinShpks.Clear();
|
||||||
|
_moddedSkinShpkCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetAndResetSlowPathCallDelta()
|
||||||
|
=> Interlocked.Exchange(ref _slowPathCallDelta, 0);
|
||||||
|
|
||||||
|
private void OnCharacterBaseCreated(uint modelCharaId, nint customize, nint equipment, nint drawObject)
|
||||||
|
{
|
||||||
|
if (((CharacterBase*)drawObject)->GetModelType() != CharacterBase.ModelType.Human)
|
||||||
|
return;
|
||||||
|
|
||||||
|
nint skinShpk;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||||
|
skinShpk = data.Valid ? (nint)_resources.LoadResolvedResource(ResourceCategory.Shader, ResourceType.Shpk, SkinShpkPath.Path, data) : nint.Zero;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"Error while resolving skin.shpk for human {drawObject:X}: {e}");
|
||||||
|
skinShpk = nint.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinShpk != nint.Zero && _skinShpks.TryAdd(drawObject, skinShpk) && skinShpk != _utility.DefaultSkinShpkResource)
|
||||||
|
Interlocked.Increment(ref _moddedSkinShpkCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCharacterBaseDestructor(nint characterBase)
|
||||||
|
{
|
||||||
|
if (_skinShpks.Remove(characterBase, out var skinShpk) && skinShpk != nint.Zero)
|
||||||
|
{
|
||||||
|
((ResourceHandle*)skinShpk)->DecRef();
|
||||||
|
if (skinShpk != _utility.DefaultSkinShpkResource)
|
||||||
|
Interlocked.Decrement(ref _moddedSkinShpkCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private nint OnRenderHumanMaterial(nint human, OnRenderMaterialParams* param)
|
||||||
|
{
|
||||||
|
if (!_enabled || // Can be toggled on the debug tab.
|
||||||
|
_moddedSkinShpkCount == 0 || // If we don't have any on-screen instances of modded skin.shpk, we don't need the slow path at all.
|
||||||
|
!_skinShpks.TryGetValue(human, out var skinShpk) || skinShpk == nint.Zero)
|
||||||
|
return _onRenderMaterialHook!.Original(human, param);
|
||||||
|
|
||||||
|
var material = param->Model->Materials[param->MaterialIndex];
|
||||||
|
var shpkResource = ((Structs.MtrlResource*)material->MaterialResourceHandle)->ShpkResourceHandle;
|
||||||
|
if ((nint)shpkResource != skinShpk)
|
||||||
|
return _onRenderMaterialHook!.Original(human, param);
|
||||||
|
|
||||||
|
Interlocked.Increment(ref _slowPathCallDelta);
|
||||||
|
|
||||||
|
// Performance considerations:
|
||||||
|
// - This function is called from several threads simultaneously, hence the need for synchronization in the swapping path ;
|
||||||
|
// - Function is called each frame for each material on screen, after culling, i. e. up to thousands of times a frame in crowded areas ;
|
||||||
|
// - 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.
|
||||||
|
lock (_lock)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)skinShpk;
|
||||||
|
return _onRenderMaterialHook!.Original(human, param);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_utility.Address->SkinShpkResource = (Structs.ResourceHandle*)_utility.DefaultSkinShpkResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ public unsafe struct CharacterUtilityData
|
||||||
{
|
{
|
||||||
public const int IndexTransparentTex = 72;
|
public const int IndexTransparentTex = 72;
|
||||||
public const int IndexDecalTex = 73;
|
public const int IndexDecalTex = 73;
|
||||||
|
public const int IndexSkinShpk = 76;
|
||||||
|
|
||||||
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames< MetaIndex >()
|
public static readonly MetaIndex[] EqdpIndices = Enum.GetNames< MetaIndex >()
|
||||||
.Zip( Enum.GetValues< MetaIndex >() )
|
.Zip( Enum.GetValues< MetaIndex >() )
|
||||||
|
|
@ -17,8 +18,8 @@ public unsafe struct CharacterUtilityData
|
||||||
.Select( n => n.Second ).ToArray();
|
.Select( n => n.Second ).ToArray();
|
||||||
|
|
||||||
public const int TotalNumResources = 87;
|
public const int TotalNumResources = 87;
|
||||||
|
|
||||||
/// <summary> Obtain the index for the eqdp file corresponding to the given race code and accessory. </summary>
|
/// <summary> Obtain the index for the eqdp file corresponding to the given race code and accessory. </summary>
|
||||||
public static MetaIndex EqdpIdx( GenderRace raceCode, bool accessory )
|
public static MetaIndex EqdpIdx( GenderRace raceCode, bool accessory )
|
||||||
=> +( int )raceCode switch
|
=> +( int )raceCode switch
|
||||||
{
|
{
|
||||||
|
|
@ -95,5 +96,8 @@ public unsafe struct CharacterUtilityData
|
||||||
[FieldOffset( 8 + IndexDecalTex * 8 )]
|
[FieldOffset( 8 + IndexDecalTex * 8 )]
|
||||||
public TextureResourceHandle* DecalTexResource;
|
public TextureResourceHandle* DecalTexResource;
|
||||||
|
|
||||||
|
[FieldOffset( 8 + IndexSkinShpk * 8 )]
|
||||||
|
public ResourceHandle* SkinShpkResource;
|
||||||
|
|
||||||
// not included resources have no known use case.
|
// not included resources have no known use case.
|
||||||
}
|
}
|
||||||
|
|
@ -8,8 +8,11 @@ public unsafe struct MtrlResource
|
||||||
[FieldOffset( 0x00 )]
|
[FieldOffset( 0x00 )]
|
||||||
public ResourceHandle Handle;
|
public ResourceHandle Handle;
|
||||||
|
|
||||||
|
[FieldOffset( 0xC8 )]
|
||||||
|
public ShaderPackageResourceHandle* ShpkResourceHandle;
|
||||||
|
|
||||||
[FieldOffset( 0xD0 )]
|
[FieldOffset( 0xD0 )]
|
||||||
public ushort* TexSpace; // Contains the offsets for the tex files inside the string list.
|
public TextureEntry* TexSpace; // Contains the offsets for the tex files inside the string list.
|
||||||
|
|
||||||
[FieldOffset( 0xE0 )]
|
[FieldOffset( 0xE0 )]
|
||||||
public byte* StringList;
|
public byte* StringList;
|
||||||
|
|
@ -24,8 +27,21 @@ public unsafe struct MtrlResource
|
||||||
=> StringList + ShpkOffset;
|
=> StringList + ShpkOffset;
|
||||||
|
|
||||||
public byte* TexString( int idx )
|
public byte* TexString( int idx )
|
||||||
=> StringList + *( TexSpace + 4 + idx * 8 );
|
=> StringList + TexSpace[idx].PathOffset;
|
||||||
|
|
||||||
public bool TexIsDX11( int idx )
|
public bool TexIsDX11( int idx )
|
||||||
=> *(TexSpace + 5 + idx * 8) >= 0x8000;
|
=> TexSpace[idx].Flags >= 0x8000;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
||||||
|
public struct TextureEntry
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x00 )]
|
||||||
|
public TextureResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
|
[FieldOffset( 0x08 )]
|
||||||
|
public ushort PathOffset;
|
||||||
|
|
||||||
|
[FieldOffset( 0x0A )]
|
||||||
|
public ushort Flags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -18,12 +20,22 @@ public unsafe struct TextureResourceHandle
|
||||||
public IntPtr Unk;
|
public IntPtr Unk;
|
||||||
|
|
||||||
[FieldOffset( 0x118 )]
|
[FieldOffset( 0x118 )]
|
||||||
public IntPtr KernelTexture;
|
public Texture* KernelTexture;
|
||||||
|
|
||||||
[FieldOffset( 0x20 )]
|
[FieldOffset( 0x20 )]
|
||||||
public IntPtr NewKernelTexture;
|
public IntPtr NewKernelTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public unsafe struct ShaderPackageResourceHandle
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x0 )]
|
||||||
|
public ResourceHandle Handle;
|
||||||
|
|
||||||
|
[FieldOffset( 0xB0 )]
|
||||||
|
public ShaderPackage* ShaderPackage;
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout( LayoutKind.Explicit )]
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
public unsafe struct ResourceHandle
|
public unsafe struct ResourceHandle
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ using Penumbra.Collections.Manager;
|
||||||
using Penumbra.UI.Tabs;
|
using Penumbra.UI.Tabs;
|
||||||
using ChangedItemClick = Penumbra.Communication.ChangedItemClick;
|
using ChangedItemClick = Penumbra.Communication.ChangedItemClick;
|
||||||
using ChangedItemHover = Penumbra.Communication.ChangedItemHover;
|
using ChangedItemHover = Penumbra.Communication.ChangedItemHover;
|
||||||
using OtterGui.Tasks;
|
using OtterGui.Tasks;
|
||||||
|
|
||||||
namespace Penumbra;
|
namespace Penumbra;
|
||||||
|
|
||||||
public class Penumbra : IDalamudPlugin
|
public class Penumbra : IDalamudPlugin
|
||||||
|
|
@ -81,6 +81,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
_services.GetRequiredService<PathResolver>();
|
_services.GetRequiredService<PathResolver>();
|
||||||
}
|
}
|
||||||
|
_services.GetRequiredService<SkinFixer>();
|
||||||
|
|
||||||
SetupInterface();
|
SetupInterface();
|
||||||
SetupApi();
|
SetupApi();
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,8 @@ public static class ServiceManager
|
||||||
=> services.AddSingleton<ResourceLoader>()
|
=> services.AddSingleton<ResourceLoader>()
|
||||||
.AddSingleton<ResourceWatcher>()
|
.AddSingleton<ResourceWatcher>()
|
||||||
.AddSingleton<ResourceTreeFactory>()
|
.AddSingleton<ResourceTreeFactory>()
|
||||||
.AddSingleton<MetaFileManager>();
|
.AddSingleton<MetaFileManager>()
|
||||||
|
.AddSingleton<SkinFixer>();
|
||||||
|
|
||||||
private static IServiceCollection AddResolvers(this IServiceCollection services)
|
private static IServiceCollection AddResolvers(this IServiceCollection services)
|
||||||
=> services.AddSingleton<AnimationHookService>()
|
=> services.AddSingleton<AnimationHookService>()
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ using CharacterBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBa
|
||||||
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
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 FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
using static Lumina.Data.Parsing.Layer.LayerCommon;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
|
|
@ -63,6 +66,7 @@ public class DebugTab : Window, ITab
|
||||||
private readonly ImportPopup _importPopup;
|
private readonly ImportPopup _importPopup;
|
||||||
private readonly FrameworkManager _framework;
|
private readonly FrameworkManager _framework;
|
||||||
private readonly TextureManager _textureManager;
|
private readonly TextureManager _textureManager;
|
||||||
|
private readonly SkinFixer _skinFixer;
|
||||||
|
|
||||||
public DebugTab(StartTracker timer, PerformanceTracker performance, Configuration config, CollectionManager collectionManager,
|
public DebugTab(StartTracker timer, PerformanceTracker performance, Configuration config, CollectionManager collectionManager,
|
||||||
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorService actorService,
|
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorService actorService,
|
||||||
|
|
@ -70,7 +74,7 @@ public class DebugTab : Window, ITab
|
||||||
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
||||||
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
||||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||||
TextureManager textureManager)
|
TextureManager textureManager, SkinFixer skinFixer)
|
||||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse, false)
|
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse, false)
|
||||||
{
|
{
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
|
@ -103,6 +107,7 @@ public class DebugTab : Window, ITab
|
||||||
_importPopup = importPopup;
|
_importPopup = importPopup;
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
_textureManager = textureManager;
|
_textureManager = textureManager;
|
||||||
|
_skinFixer = skinFixer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -144,6 +149,8 @@ public class DebugTab : Window, ITab
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawPlayerModelInfo();
|
DrawPlayerModelInfo();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
DrawGlobalVariableInfo();
|
||||||
|
ImGui.NewLine();
|
||||||
DrawDebugTabIpc();
|
DrawDebugTabIpc();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
}
|
}
|
||||||
|
|
@ -338,7 +345,7 @@ public class DebugTab : Window, ITab
|
||||||
if (!ImGui.CollapsingHeader("Actors"))
|
if (!ImGui.CollapsingHeader("Actors"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using var table = Table("##actors", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
using var table = Table("##actors", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
||||||
-Vector2.UnitX);
|
-Vector2.UnitX);
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
@ -350,6 +357,7 @@ public class DebugTab : Window, ITab
|
||||||
|
|
||||||
ImGuiUtil.DrawTableColumn(name);
|
ImGuiUtil.DrawTableColumn(name);
|
||||||
ImGuiUtil.DrawTableColumn(string.Empty);
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
||||||
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
||||||
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(id));
|
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(id));
|
||||||
ImGuiUtil.DrawTableColumn(string.Empty);
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
@ -363,6 +371,7 @@ public class DebugTab : Window, ITab
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}");
|
ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}");
|
||||||
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");
|
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");
|
||||||
|
ImGuiUtil.DrawTableColumn((obj.Address == nint.Zero) ? string.Empty : $"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}");
|
||||||
var identifier = _actorService.AwaitedService.FromObject(obj, false, true, false);
|
var identifier = _actorService.AwaitedService.FromObject(obj, false, true, false);
|
||||||
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(identifier));
|
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(identifier));
|
||||||
var id = obj.ObjectKind == ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.DataId}" : identifier.DataId.ToString();
|
var id = obj.ObjectKind == ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.DataId}" : identifier.DataId.ToString();
|
||||||
|
|
@ -582,45 +591,76 @@ public class DebugTab : Window, ITab
|
||||||
if (!ImGui.CollapsingHeader("Character Utility"))
|
if (!ImGui.CollapsingHeader("Character Utility"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using var table = Table("##CharacterUtility", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
var enableSkinFixer = _skinFixer.Enabled;
|
||||||
|
if (ImGui.Checkbox("Enable Skin Fixer", ref enableSkinFixer))
|
||||||
|
_skinFixer.Enabled = enableSkinFixer;
|
||||||
|
|
||||||
|
if (enableSkinFixer)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted($"\u0394 Slow-Path Calls: {_skinFixer.GetAndResetSlowPathCallDelta()}");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted($"Draw Objects with Modded skin.shpk: {_skinFixer.ModdedSkinShpkCount}");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var table = Table("##CharacterUtility", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
||||||
-Vector2.UnitX);
|
-Vector2.UnitX);
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i)
|
for (var idx = 0; idx < CharacterUtility.ReverseIndices.Length; ++idx)
|
||||||
{
|
{
|
||||||
var idx = CharacterUtility.RelevantIndices[i];
|
var intern = CharacterUtility.ReverseIndices[idx];
|
||||||
var intern = new CharacterUtility.InternalIndex(i);
|
|
||||||
var resource = _characterUtility.Address->Resource(idx);
|
var resource = _characterUtility.Address->Resource(idx);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted($"[{idx}]");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"0x{(ulong)resource:X}");
|
ImGui.TextUnformatted($"0x{(ulong)resource:X}");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
if (resource == null)
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
UiHelpers.Text(resource);
|
UiHelpers.Text(resource);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Selectable($"0x{resource->GetData().Data:X}");
|
var data = (nint)ResourceHandle.GetData(resource);
|
||||||
|
var length = ResourceHandle.GetLength(resource);
|
||||||
|
ImGui.Selectable($"0x{data:X}");
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
{
|
{
|
||||||
var (data, length) = resource->GetData();
|
|
||||||
if (data != nint.Zero && length > 0)
|
if (data != nint.Zero && length > 0)
|
||||||
ImGui.SetClipboardText(string.Join("\n",
|
ImGui.SetClipboardText(string.Join("\n",
|
||||||
new ReadOnlySpan<byte>((byte*)data, length).ToArray().Select(b => b.ToString("X2"))));
|
new ReadOnlySpan<byte>((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted($"{length}");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"{resource->GetData().Length}");
|
if (intern.Value != -1)
|
||||||
ImGui.TableNextColumn();
|
{
|
||||||
ImGui.Selectable($"0x{_characterUtility.DefaultResource(intern).Address:X}");
|
ImGui.Selectable($"0x{_characterUtility.DefaultResource(intern).Address:X}");
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
ImGui.SetClipboardText(string.Join("\n",
|
ImGui.SetClipboardText(string.Join("\n",
|
||||||
new ReadOnlySpan<byte>((byte*)_characterUtility.DefaultResource(intern).Address,
|
new ReadOnlySpan<byte>((byte*)_characterUtility.DefaultResource(intern).Address,
|
||||||
_characterUtility.DefaultResource(intern).Size).ToArray().Select(b => b.ToString("X2"))));
|
_characterUtility.DefaultResource(intern).Size).ToArray().Select(b => b.ToString("X2"))));
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"{_characterUtility.DefaultResource(intern).Size}");
|
ImGui.TextUnformatted($"{_characterUtility.DefaultResource(intern).Size}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ImGui.TableNextColumn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -665,6 +705,18 @@ public class DebugTab : Window, ITab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawCopyableAddress(string label, nint address)
|
||||||
|
{
|
||||||
|
using (var _ = PushFont(UiBuilder.MonoFont))
|
||||||
|
if (ImGui.Selectable($"0x{address:X16} {label}"))
|
||||||
|
ImGui.SetClipboardText($"{address:X16}");
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip("Click to copy address to clipboard.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void DrawCopyableAddress(string label, void* address)
|
||||||
|
=> DrawCopyableAddress(label, (nint)address);
|
||||||
|
|
||||||
/// <summary> Draw information about the models, materials and resources currently loaded by the local player. </summary>
|
/// <summary> Draw information about the models, materials and resources currently loaded by the local player. </summary>
|
||||||
private unsafe void DrawPlayerModelInfo()
|
private unsafe void DrawPlayerModelInfo()
|
||||||
{
|
{
|
||||||
|
|
@ -673,10 +725,14 @@ public class DebugTab : Window, ITab
|
||||||
if (!ImGui.CollapsingHeader($"Player Model Info: {name}##Draw") || player == null)
|
if (!ImGui.CollapsingHeader($"Player Model Info: {name}##Draw") || player == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
DrawCopyableAddress("PlayerCharacter", player.Address);
|
||||||
|
|
||||||
var model = (CharacterBase*)((Character*)player.Address)->GameObject.GetDrawObject();
|
var model = (CharacterBase*)((Character*)player.Address)->GameObject.GetDrawObject();
|
||||||
if (model == null)
|
if (model == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
DrawCopyableAddress("CharacterBase", model);
|
||||||
|
|
||||||
using (var t1 = Table("##table", 2, ImGuiTableFlags.SizingFixedFit))
|
using (var t1 = Table("##table", 2, ImGuiTableFlags.SizingFixedFit))
|
||||||
{
|
{
|
||||||
if (t1)
|
if (t1)
|
||||||
|
|
@ -730,6 +786,19 @@ public class DebugTab : Window, ITab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Draw information about some game global variables. </summary>
|
||||||
|
private unsafe void DrawGlobalVariableInfo()
|
||||||
|
{
|
||||||
|
var header = ImGui.CollapsingHeader("Global Variables");
|
||||||
|
ImGuiUtil.HoverTooltip("Draw information about global variables. Can provide useful starting points for a memory viewer.");
|
||||||
|
if (!header)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawCopyableAddress("CharacterUtility", _characterUtility.Address);
|
||||||
|
DrawCopyableAddress("ResidentResourceManager", _residentResources.Address);
|
||||||
|
DrawCopyableAddress("Device", Device.Instance());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Draw resources with unusual reference count. </summary>
|
/// <summary> Draw resources with unusual reference count. </summary>
|
||||||
private unsafe void DrawResourceProblems()
|
private unsafe void DrawResourceProblems()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue