mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
670 lines
No EOL
26 KiB
C#
670 lines
No EOL
26 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using ImGuiNET;
|
|
using OtterGui;
|
|
using OtterGui.Raii;
|
|
using Penumbra.GameData.Actors;
|
|
using Penumbra.GameData.Files;
|
|
using Penumbra.Interop.Loader;
|
|
using Penumbra.Interop.Resolver;
|
|
using Penumbra.Interop.Structs;
|
|
using Penumbra.String;
|
|
using Penumbra.Util;
|
|
using static OtterGui.Raii.ImRaii;
|
|
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
|
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
|
|
|
namespace Penumbra.UI;
|
|
|
|
public partial class ConfigWindow
|
|
{
|
|
private class DebugTab
|
|
{
|
|
private readonly ConfigWindow _window;
|
|
|
|
public DebugTab( ConfigWindow window )
|
|
=> _window = window;
|
|
|
|
#if DEBUG
|
|
private const string DebugVersionString = "(Debug)";
|
|
#else
|
|
private const string DebugVersionString = "(Release)";
|
|
#endif
|
|
|
|
public void Draw()
|
|
{
|
|
if( !Penumbra.Config.DebugMode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var tab = ImRaii.TabItem( "Debug" );
|
|
if( !tab )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var child = ImRaii.Child( "##DebugTab", -Vector2.One );
|
|
if( !child )
|
|
{
|
|
return;
|
|
}
|
|
|
|
DrawDebugTabGeneral();
|
|
DrawPerformanceTab();
|
|
ImGui.NewLine();
|
|
DrawDebugTabReplacedResources();
|
|
ImGui.NewLine();
|
|
DrawPathResolverDebug();
|
|
ImGui.NewLine();
|
|
DrawActorsDebug();
|
|
ImGui.NewLine();
|
|
DrawDebugCharacterUtility();
|
|
ImGui.NewLine();
|
|
DrawStainTemplates();
|
|
ImGui.NewLine();
|
|
DrawDebugTabMetaLists();
|
|
ImGui.NewLine();
|
|
DrawDebugResidentResources();
|
|
ImGui.NewLine();
|
|
DrawResourceProblems();
|
|
ImGui.NewLine();
|
|
DrawPlayerModelInfo();
|
|
ImGui.NewLine();
|
|
DrawDebugTabIpc();
|
|
ImGui.NewLine();
|
|
}
|
|
|
|
// Draw general information about mod and collection state.
|
|
private void DrawDebugTabGeneral()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "General" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##DebugGeneralTable", 2, ImGuiTableFlags.SizingFixedFit,
|
|
new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 1 ) );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
var manager = Penumbra.ModManager;
|
|
PrintValue( "Penumbra Version", $"{Penumbra.Version} {DebugVersionString}" );
|
|
PrintValue( "Git Commit Hash", Penumbra.CommitHash );
|
|
PrintValue( SelectedCollection, Penumbra.CollectionManager.Current.Name );
|
|
PrintValue( " has Cache", Penumbra.CollectionManager.Current.HasCache.ToString() );
|
|
PrintValue( DefaultCollection, Penumbra.CollectionManager.Default.Name );
|
|
PrintValue( " has Cache", Penumbra.CollectionManager.Default.HasCache.ToString() );
|
|
PrintValue( "Mod Manager BasePath", manager.BasePath.Name );
|
|
PrintValue( "Mod Manager BasePath-Full", manager.BasePath.FullName );
|
|
PrintValue( "Mod Manager BasePath IsRooted", Path.IsPathRooted( Penumbra.Config.ModDirectory ).ToString() );
|
|
PrintValue( "Mod Manager BasePath Exists", Directory.Exists( manager.BasePath.FullName ).ToString() );
|
|
PrintValue( "Mod Manager Valid", manager.Valid.ToString() );
|
|
PrintValue( "Path Resolver Enabled", _window._penumbra.PathResolver.Enabled.ToString() );
|
|
PrintValue( "Web Server Enabled", ( _window._penumbra.WebServer != null ).ToString() );
|
|
}
|
|
|
|
[Conditional( "DEBUG" )]
|
|
private static void DrawPerformanceTab()
|
|
{
|
|
ImGui.NewLine();
|
|
if( !ImGui.CollapsingHeader( "Performance" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Penumbra.Performance.Draw( "##performance", "Enable Performance Tracking", PerformanceTypeExtensions.ToName );
|
|
}
|
|
|
|
// Draw all resources currently replaced by Penumbra and (if existing) the resources they replace.
|
|
// Resources are collected by iterating through the
|
|
private static unsafe void DrawDebugTabReplacedResources()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Replaced Resources" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Penumbra.ResourceLoader.UpdateDebugInfo();
|
|
|
|
if( Penumbra.ResourceLoader.DebugList.Count == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##ReplacedResources", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach( var data in Penumbra.ResourceLoader.DebugList.Values.ToArray() )
|
|
{
|
|
if( data.ManipulatedPath.Crc64 == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var refCountManip = data.ManipulatedResource == null ? 0 : data.ManipulatedResource->RefCount;
|
|
var refCountOrig = data.OriginalResource == null ? 0 : data.OriginalResource->RefCount;
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( data.ManipulatedPath.ToString() );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( ( ( ulong )data.ManipulatedResource ).ToString( "X" ) );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( refCountManip.ToString() );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( data.OriginalPath.ToString() );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( ( ( ulong )data.OriginalResource ).ToString( "X" ) );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( refCountOrig.ToString() );
|
|
}
|
|
}
|
|
|
|
private static unsafe void DrawActorsDebug()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Actors" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##actors", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void DrawSpecial( string name, ActorIdentifier id )
|
|
{
|
|
if( !id.IsValid )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGuiUtil.DrawTableColumn( name );
|
|
ImGuiUtil.DrawTableColumn( string.Empty );
|
|
ImGuiUtil.DrawTableColumn( Penumbra.Actors.ToString( id ) );
|
|
ImGuiUtil.DrawTableColumn( string.Empty );
|
|
}
|
|
|
|
DrawSpecial( "Current Player", Penumbra.Actors.GetCurrentPlayer() );
|
|
DrawSpecial( "Current Inspect", Penumbra.Actors.GetInspectPlayer() );
|
|
DrawSpecial( "Current Card", Penumbra.Actors.GetCardPlayer() );
|
|
DrawSpecial( "Current Glamour", Penumbra.Actors.GetGlamourPlayer() );
|
|
|
|
foreach( var obj in Dalamud.Objects )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( $"{( ( GameObject* )obj.Address )->ObjectIndex}" );
|
|
ImGuiUtil.DrawTableColumn( $"0x{obj.Address:X}" );
|
|
var identifier = Penumbra.Actors.FromObject( obj, false, true );
|
|
ImGuiUtil.DrawTableColumn( Penumbra.Actors.ToString( identifier ) );
|
|
var id = obj.ObjectKind == ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.DataId}" : identifier.DataId.ToString();
|
|
ImGuiUtil.DrawTableColumn( id );
|
|
}
|
|
}
|
|
|
|
// Draw information about which draw objects correspond to which game objects
|
|
// and which paths are due to be loaded by which collection.
|
|
private unsafe void DrawPathResolverDebug()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Path Resolver" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui.TextUnformatted(
|
|
$"Last Game Object: 0x{_window._penumbra.PathResolver.LastGameObject:X} ({_window._penumbra.PathResolver.LastGameObjectData.ModCollection.Name})" );
|
|
using( var drawTree = ImRaii.TreeNode( "Draw Object to Object" ) )
|
|
{
|
|
if( drawTree )
|
|
{
|
|
using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
foreach( var (ptr, (c, idx)) in _window._penumbra.PathResolver.DrawObjectMap )
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( ptr.ToString( "X" ) );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( idx.ToString() );
|
|
ImGui.TableNextColumn();
|
|
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
|
var (address, name) =
|
|
obj != null ? ( $"0x{( ulong )obj:X}", new ByteString( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
|
ImGui.TextUnformatted( address );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( name );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( c.ModCollection.Name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using( var pathTree = ImRaii.TreeNode( "Path Collections" ) )
|
|
{
|
|
if( pathTree )
|
|
{
|
|
using var table = ImRaii.Table( "###PathCollectionResolverTable", 3, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
foreach( var (path, collection) in _window._penumbra.PathResolver.PathCollections )
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( collection.ModCollection.Name );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( collection.AssociatedGameObject.ToString( "X" ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using( var resourceTree = ImRaii.TreeNode( "Subfile Collections" ) )
|
|
{
|
|
if( resourceTree )
|
|
{
|
|
using var table = ImRaii.Table( "###ResourceCollectionResolverTable", 3, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( "Current Mtrl Data" );
|
|
ImGuiUtil.DrawTableColumn( _window._penumbra.PathResolver.CurrentMtrlData.ModCollection.Name );
|
|
ImGuiUtil.DrawTableColumn( $"0x{_window._penumbra.PathResolver.CurrentMtrlData.AssociatedGameObject:X}" );
|
|
|
|
ImGuiUtil.DrawTableColumn( "Current Avfx Data" );
|
|
ImGuiUtil.DrawTableColumn( _window._penumbra.PathResolver.CurrentAvfxData.ModCollection.Name );
|
|
ImGuiUtil.DrawTableColumn( $"0x{_window._penumbra.PathResolver.CurrentAvfxData.AssociatedGameObject:X}" );
|
|
|
|
ImGuiUtil.DrawTableColumn( "Current Resources" );
|
|
ImGuiUtil.DrawTableColumn( _window._penumbra.PathResolver.SubfileCount.ToString() );
|
|
ImGui.TableNextColumn();
|
|
|
|
foreach( var (resource, resolve) in _window._penumbra.PathResolver.ResourceCollections )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( $"0x{resource:X}" );
|
|
ImGuiUtil.DrawTableColumn( resolve.ModCollection.Name );
|
|
ImGuiUtil.DrawTableColumn( $"0x{resolve.AssociatedGameObject:X}" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using( var identifiedTree = ImRaii.TreeNode( "Identified Collections" ) )
|
|
{
|
|
if( identifiedTree )
|
|
{
|
|
using var table = ImRaii.Table( "##PathCollectionsIdentifiedTable", 3, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
foreach( var (address, identifier, collection) in PathResolver.IdentifiedCache )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( $"0x{address:X}" );
|
|
ImGuiUtil.DrawTableColumn( identifier.ToString() );
|
|
ImGuiUtil.DrawTableColumn( collection.Name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using( var cutsceneTree = ImRaii.TreeNode( "Cutscene Actors" ) )
|
|
{
|
|
if( cutsceneTree )
|
|
{
|
|
using var table = ImRaii.Table( "###PCutsceneResolverTable", 2, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
foreach( var (idx, actor) in _window._penumbra.PathResolver.CutsceneActors )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( $"Cutscene Actor {idx}" );
|
|
ImGuiUtil.DrawTableColumn( actor.Name.ToString() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using( var groupTree = ImRaii.TreeNode( "Group" ) )
|
|
{
|
|
if( groupTree )
|
|
{
|
|
using var table = ImRaii.Table( "###PGroupTable", 2, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( "Group Members" );
|
|
ImGuiUtil.DrawTableColumn( GroupManager.Instance()->MemberCount.ToString() );
|
|
for( var i = 0; i < 8; ++i )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( $"Member #{i}" );
|
|
var member = GroupManager.Instance()->GetPartyMemberByIndex( i );
|
|
ImGuiUtil.DrawTableColumn( member == null ? "NULL" : new ByteString( member->Name ).ToString() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using( var bannerTree = ImRaii.TreeNode( "Party Banner" ) )
|
|
{
|
|
if( bannerTree )
|
|
{
|
|
var agent = &AgentBannerParty.Instance()->AgentBannerInterface;
|
|
if( agent->Data == null )
|
|
agent = &AgentBannerMIP.Instance()->AgentBannerInterface;
|
|
if( agent->Data != null )
|
|
{
|
|
using var table = ImRaii.Table( "###PBannerTable", 2, ImGuiTableFlags.SizingFixedFit );
|
|
if( table )
|
|
{
|
|
for( var i = 0; i < 8; ++i )
|
|
{
|
|
var c = agent->Character( i );
|
|
ImGuiUtil.DrawTableColumn( $"Character {i}" );
|
|
var name = c->Name1.ToString();
|
|
ImGuiUtil.DrawTableColumn( name.Length == 0 ? "NULL" : $"{name} ({c->WorldId})" );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui.TextUnformatted( "INACTIVE" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static unsafe void DrawStainTemplates()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Staining Templates" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach( var (key, data) in Penumbra.StainManager.StmFile.Entries )
|
|
{
|
|
using var tree = ImRaii.TreeNode( $"Template {key}" );
|
|
if( !tree )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##table", 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg );
|
|
if( !table )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for( var i = 0; i < StmFile.StainingTemplateEntry.NumElements; ++i )
|
|
{
|
|
var (r, g, b) = data.DiffuseEntries[ i ];
|
|
ImGuiUtil.DrawTableColumn( $"{r:F6} | {g:F6} | {b:F6}" );
|
|
|
|
( r, g, b ) = data.SpecularEntries[ i ];
|
|
ImGuiUtil.DrawTableColumn( $"{r:F6} | {g:F6} | {b:F6}" );
|
|
|
|
( r, g, b ) = data.EmissiveEntries[ i ];
|
|
ImGuiUtil.DrawTableColumn( $"{r:F6} | {g:F6} | {b:F6}" );
|
|
|
|
var a = data.SpecularPowerEntries[ i ];
|
|
ImGuiUtil.DrawTableColumn( $"{a:F6}" );
|
|
|
|
a = data.GlossEntries[ i ];
|
|
ImGuiUtil.DrawTableColumn( $"{a:F6}" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw information about the character utility class from SE,
|
|
// displaying all files, their sizes, the default files and the default sizes.
|
|
public static unsafe void DrawDebugCharacterUtility()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Character Utility" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##CharacterUtility", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i )
|
|
{
|
|
var idx = CharacterUtility.RelevantIndices[ i ];
|
|
var intern = new CharacterUtility.InternalIndex( i );
|
|
var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resource( idx );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( $"0x{( ulong )resource:X}" );
|
|
ImGui.TableNextColumn();
|
|
Text( resource );
|
|
ImGui.TableNextColumn();
|
|
ImGui.Selectable( $"0x{resource->GetData().Data:X}" );
|
|
if( ImGui.IsItemClicked() )
|
|
{
|
|
var (data, length) = resource->GetData();
|
|
if( data != IntPtr.Zero && length > 0 )
|
|
{
|
|
ImGui.SetClipboardText( string.Join( "\n",
|
|
new ReadOnlySpan< byte >( ( byte* )data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
|
|
}
|
|
}
|
|
|
|
ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." );
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( $"{resource->GetData().Length}" );
|
|
ImGui.TableNextColumn();
|
|
ImGui.Selectable( $"0x{Penumbra.CharacterUtility.DefaultResource( intern ).Address:X}" );
|
|
if( ImGui.IsItemClicked() )
|
|
{
|
|
ImGui.SetClipboardText( string.Join( "\n",
|
|
new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResource( intern ).Address,
|
|
Penumbra.CharacterUtility.DefaultResource( intern ).Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
|
|
}
|
|
|
|
ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." );
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( $"{Penumbra.CharacterUtility.DefaultResource( intern ).Size}" );
|
|
}
|
|
}
|
|
|
|
private static void DrawDebugTabMetaLists()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Metadata Changes" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##DebugMetaTable", 3, ImGuiTableFlags.SizingFixedFit );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach( var list in Penumbra.CharacterUtility.Lists )
|
|
{
|
|
ImGuiUtil.DrawTableColumn( list.GlobalIndex.ToString() );
|
|
ImGuiUtil.DrawTableColumn( list.Entries.Count.ToString() );
|
|
ImGuiUtil.DrawTableColumn( string.Join( ", ", list.Entries.Select( e => $"0x{e.Data:X}" ) ) );
|
|
}
|
|
}
|
|
|
|
// Draw information about the resident resource files.
|
|
public unsafe void DrawDebugResidentResources()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "Resident Resources" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( Penumbra.ResidentResources.Address == null || Penumbra.ResidentResources.Address->NumResources == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##ResidentResources", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for( var i = 0; i < Penumbra.ResidentResources.Address->NumResources; ++i )
|
|
{
|
|
var resource = Penumbra.ResidentResources.Address->ResourceList[ i ];
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( $"0x{( ulong )resource:X}" );
|
|
ImGui.TableNextColumn();
|
|
Text( resource );
|
|
}
|
|
}
|
|
|
|
// Draw information about the models, materials and resources currently loaded by the local player.
|
|
private static unsafe void DrawPlayerModelInfo()
|
|
{
|
|
var player = Dalamud.ClientState.LocalPlayer;
|
|
var name = player?.Name.ToString() ?? "NULL";
|
|
if( !ImGui.CollapsingHeader( $"Player Model Info: {name}##Draw" ) || player == null )
|
|
{
|
|
return;
|
|
}
|
|
|
|
var model = ( CharacterBase* )( ( Character* )player.Address )->GameObject.GetDrawObject();
|
|
if( model == null )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( $"##{name}DrawTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableHeader( "Slot" );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableHeader( "Imc Ptr" );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableHeader( "Imc File" );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableHeader( "Model Ptr" );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableHeader( "Model File" );
|
|
|
|
for( var i = 0; i < model->SlotCount; ++i )
|
|
{
|
|
var imc = ( ResourceHandle* )model->IMCArray[ i ];
|
|
ImGui.TableNextRow();
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( $"Slot {i}" );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( imc == null ? "NULL" : $"0x{( ulong )imc:X}" );
|
|
ImGui.TableNextColumn();
|
|
if( imc != null )
|
|
{
|
|
Text( imc );
|
|
}
|
|
|
|
var mdl = ( RenderModel* )model->ModelArray[ i ];
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( mdl == null ? "NULL" : $"0x{( ulong )mdl:X}" );
|
|
if( mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ImGui.TableNextColumn();
|
|
{
|
|
Text( mdl->ResourceHandle );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw resources with unusual reference count.
|
|
private static unsafe void DrawResourceProblems()
|
|
{
|
|
var header = ImGui.CollapsingHeader( "Resource Problems" );
|
|
ImGuiUtil.HoverTooltip( "Draw resources with unusually high reference count to detect overflows." );
|
|
if( !header )
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var table = ImRaii.Table( "##ProblemsTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit );
|
|
if( !table )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ResourceLoader.IterateResources( ( _, r ) =>
|
|
{
|
|
if( r->RefCount < 10000 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( r->Category.ToString() );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( r->FileType.ToString( "X" ) );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( r->Id.ToString( "X" ) );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( ( ( ulong )r ).ToString( "X" ) );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( r->RefCount.ToString() );
|
|
ImGui.TableNextColumn();
|
|
ref var name = ref r->FileName;
|
|
if( name.Capacity > 15 )
|
|
{
|
|
ImGuiNative.igTextUnformatted( name.BufferPtr, name.BufferPtr + name.Length );
|
|
}
|
|
else
|
|
{
|
|
fixed( byte* ptr = name.Buffer )
|
|
{
|
|
ImGuiNative.igTextUnformatted( ptr, ptr + name.Length );
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
|
|
|
|
// Draw information about IPC options and availability.
|
|
private void DrawDebugTabIpc()
|
|
{
|
|
if( !ImGui.CollapsingHeader( "IPC" ) )
|
|
{
|
|
_window._penumbra.IpcProviders.Tester.UnsubscribeEvents();
|
|
return;
|
|
}
|
|
|
|
_window._penumbra.IpcProviders.Tester.Draw();
|
|
}
|
|
|
|
// Helper to print a property and its value in a 2-column table.
|
|
private static void PrintValue( string name, string value )
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( name );
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted( value );
|
|
}
|
|
}
|
|
} |