mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
915 lines
37 KiB
C#
915 lines
37 KiB
C#
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Windowing;
|
|
using Dalamud.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using ImGuiNET;
|
|
using OtterGui;
|
|
using OtterGui.Classes;
|
|
using OtterGui.Widgets;
|
|
using Penumbra.Api;
|
|
using Penumbra.Collections.Manager;
|
|
using Penumbra.GameData.Actors;
|
|
using Penumbra.GameData.Files;
|
|
using Penumbra.Import.Structs;
|
|
using Penumbra.Import.Textures;
|
|
using Penumbra.Interop.ResourceLoading;
|
|
using Penumbra.Interop.PathResolving;
|
|
using Penumbra.Interop.Structs;
|
|
using Penumbra.Mods;
|
|
using Penumbra.Mods.Manager;
|
|
using Penumbra.Services;
|
|
using Penumbra.String;
|
|
using Penumbra.UI.Classes;
|
|
using Penumbra.Util;
|
|
using static OtterGui.Raii.ImRaii;
|
|
using CharacterBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase;
|
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
|
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
|
using Penumbra.Interop.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
|
using ImGuiClip = OtterGui.ImGuiClip;
|
|
|
|
namespace Penumbra.UI.Tabs;
|
|
|
|
public class DebugTab : Window, ITab
|
|
{
|
|
private readonly StartTracker _timer;
|
|
private readonly PerformanceTracker _performance;
|
|
private readonly Configuration _config;
|
|
private readonly CollectionManager _collectionManager;
|
|
private readonly ModManager _modManager;
|
|
private readonly ValidityChecker _validityChecker;
|
|
private readonly HttpApi _httpApi;
|
|
private readonly ActorService _actorService;
|
|
private readonly DalamudServices _dalamud;
|
|
private readonly StainService _stains;
|
|
private readonly CharacterUtility _characterUtility;
|
|
private readonly ResidentResourceManager _residentResources;
|
|
private readonly ResourceManagerService _resourceManager;
|
|
private readonly PenumbraIpcProviders _ipc;
|
|
private readonly CollectionResolver _collectionResolver;
|
|
private readonly DrawObjectState _drawObjectState;
|
|
private readonly PathState _pathState;
|
|
private readonly SubfileHelper _subfileHelper;
|
|
private readonly IdentifiedCollectionCache _identifiedCollectionCache;
|
|
private readonly CutsceneService _cutsceneService;
|
|
private readonly ModImportManager _modImporter;
|
|
private readonly ImportPopup _importPopup;
|
|
private readonly FrameworkManager _framework;
|
|
private readonly TextureManager _textureManager;
|
|
private readonly SkinFixer _skinFixer;
|
|
private readonly IdentifierService _identifier;
|
|
|
|
public DebugTab(StartTracker timer, PerformanceTracker performance, Configuration config, CollectionManager collectionManager,
|
|
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorService actorService,
|
|
DalamudServices dalamud, StainService stains, CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
|
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
|
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
|
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
|
TextureManager textureManager, SkinFixer skinFixer, IdentifierService identifier)
|
|
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
|
{
|
|
IsOpen = true;
|
|
SizeConstraints = new WindowSizeConstraints
|
|
{
|
|
MinimumSize = new Vector2(200, 200),
|
|
MaximumSize = new Vector2(2000, 2000),
|
|
};
|
|
_timer = timer;
|
|
_performance = performance;
|
|
_config = config;
|
|
_collectionManager = collectionManager;
|
|
_validityChecker = validityChecker;
|
|
_modManager = modManager;
|
|
_httpApi = httpApi;
|
|
_actorService = actorService;
|
|
_dalamud = dalamud;
|
|
_stains = stains;
|
|
_characterUtility = characterUtility;
|
|
_residentResources = residentResources;
|
|
_resourceManager = resourceManager;
|
|
_ipc = ipc;
|
|
_collectionResolver = collectionResolver;
|
|
_drawObjectState = drawObjectState;
|
|
_pathState = pathState;
|
|
_subfileHelper = subfileHelper;
|
|
_identifiedCollectionCache = identifiedCollectionCache;
|
|
_cutsceneService = cutsceneService;
|
|
_modImporter = modImporter;
|
|
_importPopup = importPopup;
|
|
_framework = framework;
|
|
_textureManager = textureManager;
|
|
_skinFixer = skinFixer;
|
|
_identifier = identifier;
|
|
}
|
|
|
|
public ReadOnlySpan<byte> Label
|
|
=> "Debug"u8;
|
|
|
|
public bool IsVisible
|
|
=> _config.DebugMode && !_config.DebugSeparateWindow;
|
|
|
|
#if DEBUG
|
|
private const string DebugVersionString = "(Debug)";
|
|
#else
|
|
private const string DebugVersionString = "(Release)";
|
|
#endif
|
|
|
|
public void DrawContent()
|
|
{
|
|
using var child = Child("##DebugTab", -Vector2.One);
|
|
if (!child)
|
|
return;
|
|
|
|
DrawDebugTabGeneral();
|
|
DrawPerformanceTab();
|
|
ImGui.NewLine();
|
|
DrawPathResolverDebug();
|
|
ImGui.NewLine();
|
|
DrawActorsDebug();
|
|
ImGui.NewLine();
|
|
DrawCollectionCaches();
|
|
ImGui.NewLine();
|
|
DrawDebugCharacterUtility();
|
|
ImGui.NewLine();
|
|
DrawData();
|
|
ImGui.NewLine();
|
|
DrawDebugTabMetaLists();
|
|
ImGui.NewLine();
|
|
DrawResourceProblems();
|
|
ImGui.NewLine();
|
|
DrawPlayerModelInfo();
|
|
ImGui.NewLine();
|
|
DrawGlobalVariableInfo();
|
|
ImGui.NewLine();
|
|
DrawDebugTabIpc();
|
|
ImGui.NewLine();
|
|
}
|
|
|
|
|
|
private void DrawCollectionCaches()
|
|
{
|
|
if (!ImGui.CollapsingHeader(
|
|
$"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1} Caches)###Collections"))
|
|
return;
|
|
|
|
foreach (var collection in _collectionManager.Storage)
|
|
{
|
|
if (collection.HasCache)
|
|
{
|
|
using var color = PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
|
using var node = TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})");
|
|
if (!node)
|
|
continue;
|
|
|
|
color.Pop();
|
|
foreach (var (mod, paths, manips) in collection._cache!.ModData.Data.OrderBy(t => t.Item1.Name))
|
|
{
|
|
using var id = mod is TemporaryMod t ? PushId(t.Priority) : PushId(((Mod)mod).ModPath.Name);
|
|
using var node2 = TreeNode(mod.Name.Text);
|
|
if (!node2)
|
|
continue;
|
|
|
|
foreach (var path in paths)
|
|
|
|
TreeNode(path.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
|
|
|
foreach (var manip in manips)
|
|
TreeNode(manip.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using var color = PushColor(ImGuiCol.Text, ColorId.UndefinedMod.Value());
|
|
TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})",
|
|
ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary> Draw general information about mod and collection state. </summary>
|
|
private void DrawDebugTabGeneral()
|
|
{
|
|
if (!ImGui.CollapsingHeader("General"))
|
|
return;
|
|
|
|
var separateWindow = _config.DebugSeparateWindow;
|
|
if (ImGui.Checkbox("Draw as Separate Window", ref separateWindow))
|
|
{
|
|
IsOpen = true;
|
|
_config.DebugSeparateWindow = separateWindow;
|
|
_config.Save();
|
|
}
|
|
|
|
using (var table = Table("##DebugGeneralTable", 2, ImGuiTableFlags.SizingFixedFit))
|
|
{
|
|
if (table)
|
|
{
|
|
PrintValue("Penumbra Version", $"{_validityChecker.Version} {DebugVersionString}");
|
|
PrintValue("Git Commit Hash", _validityChecker.CommitHash);
|
|
PrintValue(TutorialService.SelectedCollection, _collectionManager.Active.Current.Name);
|
|
PrintValue(" has Cache", _collectionManager.Active.Current.HasCache.ToString());
|
|
PrintValue(TutorialService.DefaultCollection, _collectionManager.Active.Default.Name);
|
|
PrintValue(" has Cache", _collectionManager.Active.Default.HasCache.ToString());
|
|
PrintValue("Mod Manager BasePath", _modManager.BasePath.Name);
|
|
PrintValue("Mod Manager BasePath-Full", _modManager.BasePath.FullName);
|
|
PrintValue("Mod Manager BasePath IsRooted", Path.IsPathRooted(_config.ModDirectory).ToString());
|
|
PrintValue("Mod Manager BasePath Exists", Directory.Exists(_modManager.BasePath.FullName).ToString());
|
|
PrintValue("Mod Manager Valid", _modManager.Valid.ToString());
|
|
PrintValue("Web Server Enabled", _httpApi.Enabled.ToString());
|
|
}
|
|
}
|
|
|
|
var issues = _modManager.WithIndex().Count(p => p.Index != p.Value.Index);
|
|
using (var tree = TreeNode($"Mods ({issues} Issues)###Mods"))
|
|
{
|
|
if (tree)
|
|
{
|
|
using var table = Table("##DebugModsTable", 3, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
{
|
|
var lastIndex = -1;
|
|
foreach (var mod in _modManager)
|
|
{
|
|
PrintValue(mod.Name, mod.Index.ToString("D5"));
|
|
ImGui.TableNextColumn();
|
|
var index = mod.Index;
|
|
if (index != lastIndex + 1)
|
|
ImGui.TextUnformatted("!!!");
|
|
lastIndex = index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var tree = TreeNode("Mod Import"))
|
|
{
|
|
if (tree)
|
|
{
|
|
using var table = Table("##DebugModImport", 2, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
{
|
|
var importing = _modImporter.IsImporting(out var importer);
|
|
PrintValue("Is Importing", importing.ToString());
|
|
PrintValue("Importer State", (importer?.State ?? ImporterState.None).ToString());
|
|
PrintValue("Import Window Was Drawn", _importPopup.WasDrawn.ToString());
|
|
PrintValue("Import Popup Was Drawn", _importPopup.PopupWasDrawn.ToString());
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted("Import Batches");
|
|
ImGui.TableNextColumn();
|
|
foreach (var (batch, index) in _modImporter.ModBatches.WithIndex())
|
|
{
|
|
foreach (var mod in batch)
|
|
PrintValue(index.ToString(), mod);
|
|
}
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted("Addable Mods");
|
|
ImGui.TableNextColumn();
|
|
foreach (var mod in _modImporter.AddableMods)
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(mod.Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var tree = TreeNode("Framework"))
|
|
{
|
|
if (tree)
|
|
{
|
|
using var table = Table("##DebugFramework", 2, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
{
|
|
foreach (var important in _framework.Important)
|
|
PrintValue(important, "Immediate");
|
|
|
|
foreach (var (onTick, idx) in _framework.OnTick.WithIndex())
|
|
PrintValue(onTick, $"{idx + 1} Tick(s) From Now");
|
|
|
|
foreach (var (time, name) in _framework.Delayed)
|
|
{
|
|
var span = time - DateTime.UtcNow;
|
|
PrintValue(name, $"After {span.Minutes:D2}:{span.Seconds:D2}.{span.Milliseconds / 10:D2} (+ Ticks)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var tree = TreeNode($"Texture Manager {_textureManager.Tasks.Count}###Texture Manager"))
|
|
{
|
|
if (tree)
|
|
{
|
|
using var table = Table("##Tasks", 2, ImGuiTableFlags.RowBg);
|
|
if (table)
|
|
foreach (var task in _textureManager.Tasks)
|
|
{
|
|
ImGuiUtil.DrawTableColumn(task.Key.ToString()!);
|
|
ImGuiUtil.DrawTableColumn(task.Value.Item1.Status.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawPerformanceTab()
|
|
{
|
|
ImGui.NewLine();
|
|
if (ImGui.CollapsingHeader("Performance"))
|
|
return;
|
|
|
|
using (var start = TreeNode("Startup Performance", ImGuiTreeNodeFlags.DefaultOpen))
|
|
{
|
|
if (start)
|
|
{
|
|
_timer.Draw("##startTimer", TimingExtensions.ToName);
|
|
ImGui.NewLine();
|
|
}
|
|
}
|
|
|
|
_performance.Draw("##performance", "Enable Runtime Performance Tracking", TimingExtensions.ToName);
|
|
}
|
|
|
|
private unsafe void DrawActorsDebug()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Actors"))
|
|
return;
|
|
|
|
using var table = Table("##actors", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX);
|
|
if (!table)
|
|
return;
|
|
|
|
void DrawSpecial(string name, ActorIdentifier id)
|
|
{
|
|
if (!id.IsValid)
|
|
return;
|
|
|
|
ImGuiUtil.DrawTableColumn(name);
|
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
|
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(id));
|
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
|
}
|
|
|
|
DrawSpecial("Current Player", _actorService.AwaitedService.GetCurrentPlayer());
|
|
DrawSpecial("Current Inspect", _actorService.AwaitedService.GetInspectPlayer());
|
|
DrawSpecial("Current Card", _actorService.AwaitedService.GetCardPlayer());
|
|
DrawSpecial("Current Glamour", _actorService.AwaitedService.GetGlamourPlayer());
|
|
|
|
foreach (var obj in _dalamud.Objects)
|
|
{
|
|
ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}");
|
|
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);
|
|
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(identifier));
|
|
var id = obj.ObjectKind == ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.DataId}" : identifier.DataId.ToString();
|
|
ImGuiUtil.DrawTableColumn(id);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw information about which draw objects correspond to which game objects
|
|
/// and which paths are due to be loaded by which collection.
|
|
/// </summary>
|
|
private unsafe void DrawPathResolverDebug()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Path Resolver"))
|
|
return;
|
|
|
|
ImGui.TextUnformatted(
|
|
$"Last Game Object: 0x{_collectionResolver.IdentifyLastGameObjectCollection(true).AssociatedGameObject:X} ({_collectionResolver.IdentifyLastGameObjectCollection(true).ModCollection.Name})");
|
|
using (var drawTree = TreeNode("Draw Object to Object"))
|
|
{
|
|
if (drawTree)
|
|
{
|
|
using var table = Table("###DrawObjectResolverTable", 6, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
foreach (var (drawObject, (gameObjectPtr, child)) in _drawObjectState
|
|
.OrderBy(kvp => ((GameObject*)kvp.Value.Item1)->ObjectIndex)
|
|
.ThenBy(kvp => kvp.Value.Item2)
|
|
.ThenBy(kvp => kvp.Key))
|
|
{
|
|
var gameObject = (GameObject*)gameObjectPtr;
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted($"0x{drawObject:X}");
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(gameObject->ObjectIndex.ToString());
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(child ? "Child" : "Main");
|
|
ImGui.TableNextColumn();
|
|
var (address, name) = ($"0x{gameObjectPtr:X}", new ByteString(gameObject->Name).ToString());
|
|
ImGui.TextUnformatted(address);
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(name);
|
|
ImGui.TableNextColumn();
|
|
var collection = _collectionResolver.IdentifyCollection(gameObject, true);
|
|
ImGui.TextUnformatted(collection.ModCollection.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var pathTree = TreeNode("Path Collections"))
|
|
{
|
|
if (pathTree)
|
|
{
|
|
using var table = Table("###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
foreach (var data in _pathState.CurrentData)
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted($"{data.AssociatedGameObject:X}");
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(data.ModCollection.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var resourceTree = TreeNode("Subfile Collections"))
|
|
{
|
|
if (resourceTree)
|
|
{
|
|
using var table = Table("###ResourceCollectionResolverTable", 4, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
{
|
|
ImGuiUtil.DrawTableColumn("Current Mtrl Data");
|
|
ImGuiUtil.DrawTableColumn(_subfileHelper.MtrlData.ModCollection.Name);
|
|
ImGuiUtil.DrawTableColumn($"0x{_subfileHelper.MtrlData.AssociatedGameObject:X}");
|
|
ImGui.TableNextColumn();
|
|
|
|
ImGuiUtil.DrawTableColumn("Current Avfx Data");
|
|
ImGuiUtil.DrawTableColumn(_subfileHelper.AvfxData.ModCollection.Name);
|
|
ImGuiUtil.DrawTableColumn($"0x{_subfileHelper.AvfxData.AssociatedGameObject:X}");
|
|
ImGui.TableNextColumn();
|
|
|
|
ImGuiUtil.DrawTableColumn("Current Resources");
|
|
ImGuiUtil.DrawTableColumn(_subfileHelper.Count.ToString());
|
|
ImGui.TableNextColumn();
|
|
ImGui.TableNextColumn();
|
|
|
|
foreach (var (resource, resolve) in _subfileHelper)
|
|
{
|
|
ImGuiUtil.DrawTableColumn($"0x{resource:X}");
|
|
ImGuiUtil.DrawTableColumn(resolve.ModCollection.Name);
|
|
ImGuiUtil.DrawTableColumn($"0x{resolve.AssociatedGameObject:X}");
|
|
ImGuiUtil.DrawTableColumn($"{((ResourceHandle*)resource)->FileName()}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var identifiedTree = TreeNode("Identified Collections"))
|
|
{
|
|
if (identifiedTree)
|
|
{
|
|
using var table = Table("##PathCollectionsIdentifiedTable", 4, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
foreach (var (address, identifier, collection) in _identifiedCollectionCache
|
|
.OrderBy(kvp => ((GameObject*)kvp.Address)->ObjectIndex))
|
|
{
|
|
ImGuiUtil.DrawTableColumn($"{((GameObject*)address)->ObjectIndex}");
|
|
ImGuiUtil.DrawTableColumn($"0x{address:X}");
|
|
ImGuiUtil.DrawTableColumn(identifier.ToString());
|
|
ImGuiUtil.DrawTableColumn(collection.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var cutsceneTree = TreeNode("Cutscene Actors"))
|
|
{
|
|
if (cutsceneTree)
|
|
{
|
|
using var table = Table("###PCutsceneResolverTable", 2, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
foreach (var (idx, actor) in _cutsceneService.Actors)
|
|
{
|
|
ImGuiUtil.DrawTableColumn($"Cutscene Actor {idx}");
|
|
ImGuiUtil.DrawTableColumn(actor.Name.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var groupTree = TreeNode("Group"))
|
|
{
|
|
if (groupTree)
|
|
{
|
|
using var table = 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 = 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 = Table("###PBannerTable", 2, ImGuiTableFlags.SizingFixedFit);
|
|
if (table)
|
|
for (var i = 0; i < 8; ++i)
|
|
{
|
|
ref var c = ref agent->Data->CharacterArraySpan[i];
|
|
ImGuiUtil.DrawTableColumn($"Character {i}");
|
|
var name = c.Name1.ToString();
|
|
ImGuiUtil.DrawTableColumn(name.Length == 0 ? "NULL" : $"{name} ({c.WorldId})");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui.TextUnformatted("INACTIVE");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawData()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Game Data"))
|
|
return;
|
|
|
|
DrawEmotes();
|
|
DrawStainTemplates();
|
|
}
|
|
|
|
private string _emoteSearchFile = string.Empty;
|
|
private string _emoteSearchName = string.Empty;
|
|
|
|
private void DrawEmotes()
|
|
{
|
|
using var mainTree = TreeNode("Emotes");
|
|
if (!mainTree)
|
|
return;
|
|
|
|
ImGui.InputText("File Name", ref _emoteSearchFile, 256);
|
|
ImGui.InputText("Emote Name", ref _emoteSearchName, 256);
|
|
using var table = Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
|
new Vector2(-1, 12 * ImGui.GetTextLineHeightWithSpacing()));
|
|
if (!table)
|
|
return;
|
|
|
|
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
|
|
var dummy = ImGuiClip.FilteredClippedDraw(_identifier.AwaitedService.Emotes, skips,
|
|
p => p.Key.Contains(_emoteSearchFile, StringComparison.OrdinalIgnoreCase)
|
|
&& (_emoteSearchName.Length == 0
|
|
|| p.Value.Any(s => s.Name.ToDalamudString().TextValue.Contains(_emoteSearchName, StringComparison.OrdinalIgnoreCase))),
|
|
p =>
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(p.Key);
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(string.Join(", ", p.Value.Select(v => v.Name.ToDalamudString().TextValue)));
|
|
});
|
|
ImGuiClip.DrawEndDummy(dummy, ImGui.GetTextLineHeightWithSpacing());
|
|
}
|
|
|
|
private void DrawStainTemplates()
|
|
{
|
|
using var mainTree = TreeNode("Staining Templates");
|
|
if (!mainTree)
|
|
return;
|
|
|
|
foreach (var (key, data) in _stains.StmFile.Entries)
|
|
{
|
|
using var tree = TreeNode($"Template {key}");
|
|
if (!tree)
|
|
continue;
|
|
|
|
using var table = 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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw information about the character utility class from SE,
|
|
/// displaying all files, their sizes, the default files and the default sizes.
|
|
/// </summary>
|
|
private unsafe void DrawDebugCharacterUtility()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Character Utility"))
|
|
return;
|
|
|
|
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($"Materials with Modded skin.shpk: {_skinFixer.ModdedSkinShpkCount}");
|
|
}
|
|
|
|
using var table = Table("##CharacterUtility", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX);
|
|
if (!table)
|
|
return;
|
|
|
|
for (var idx = 0; idx < CharacterUtility.ReverseIndices.Length; ++idx)
|
|
{
|
|
var intern = CharacterUtility.ReverseIndices[idx];
|
|
var resource = _characterUtility.Address->Resource(idx);
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted($"[{idx}]");
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted($"0x{(ulong)resource:X}");
|
|
ImGui.TableNextColumn();
|
|
if (resource == null)
|
|
{
|
|
ImGui.TableNextRow();
|
|
continue;
|
|
}
|
|
|
|
UiHelpers.Text(resource);
|
|
ImGui.TableNextColumn();
|
|
var data = (nint)ResourceHandle.GetData(resource);
|
|
var length = ResourceHandle.GetLength(resource);
|
|
if (ImGui.Selectable($"0x{data:X}"))
|
|
if (data != nint.Zero && length > 0)
|
|
ImGui.SetClipboardText(string.Join("\n",
|
|
new ReadOnlySpan<byte>((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2"))));
|
|
|
|
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(length.ToString());
|
|
|
|
ImGui.TableNextColumn();
|
|
if (intern.Value != -1)
|
|
{
|
|
ImGui.Selectable($"0x{_characterUtility.DefaultResource(intern).Address:X}");
|
|
if (ImGui.IsItemClicked())
|
|
ImGui.SetClipboardText(string.Join("\n",
|
|
new ReadOnlySpan<byte>((byte*)_characterUtility.DefaultResource(intern).Address,
|
|
_characterUtility.DefaultResource(intern).Size).ToArray().Select(b => b.ToString("X2"))));
|
|
|
|
ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard.");
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted($"{_characterUtility.DefaultResource(intern).Size}");
|
|
}
|
|
else
|
|
{
|
|
ImGui.TableNextColumn();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawDebugTabMetaLists()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Metadata Changes"))
|
|
return;
|
|
|
|
using var table = Table("##DebugMetaTable", 3, ImGuiTableFlags.SizingFixedFit);
|
|
if (!table)
|
|
return;
|
|
|
|
foreach (var list in _characterUtility.Lists)
|
|
{
|
|
ImGuiUtil.DrawTableColumn(list.GlobalMetaIndex.ToString());
|
|
ImGuiUtil.DrawTableColumn(list.Entries.Count.ToString());
|
|
ImGuiUtil.DrawTableColumn(string.Join(", ", list.Entries.Select(e => $"0x{e.Data:X}")));
|
|
}
|
|
}
|
|
|
|
/// <summary> Draw information about the resident resource files. </summary>
|
|
private unsafe void DrawDebugResidentResources()
|
|
{
|
|
using var tree = TreeNode("Resident Resources");
|
|
if (!tree)
|
|
return;
|
|
|
|
if (_residentResources.Address == null || _residentResources.Address->NumResources == 0)
|
|
return;
|
|
|
|
using var table = Table("##ResidentResources", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
|
-Vector2.UnitX);
|
|
if (!table)
|
|
return;
|
|
|
|
for (var i = 0; i < _residentResources.Address->NumResources; ++i)
|
|
{
|
|
var resource = _residentResources.Address->ResourceList[i];
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted($"0x{(ulong)resource:X}");
|
|
ImGui.TableNextColumn();
|
|
UiHelpers.Text(resource);
|
|
}
|
|
}
|
|
|
|
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>
|
|
private 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;
|
|
|
|
DrawCopyableAddress("PlayerCharacter", player.Address);
|
|
|
|
var model = (CharacterBase*)((Character*)player.Address)->GameObject.GetDrawObject();
|
|
if (model == null)
|
|
return;
|
|
|
|
DrawCopyableAddress("CharacterBase", model);
|
|
|
|
using (var t1 = Table("##table", 2, ImGuiTableFlags.SizingFixedFit))
|
|
{
|
|
if (t1)
|
|
{
|
|
ImGuiUtil.DrawTableColumn("Flags");
|
|
ImGuiUtil.DrawTableColumn($"{model->UnkFlags_01:X2}");
|
|
ImGuiUtil.DrawTableColumn("Has Model In Slot Loaded");
|
|
ImGuiUtil.DrawTableColumn($"{model->HasModelInSlotLoaded:X8}");
|
|
ImGuiUtil.DrawTableColumn("Has Model Files In Slot Loaded");
|
|
ImGuiUtil.DrawTableColumn($"{model->HasModelFilesInSlotLoaded:X8}");
|
|
}
|
|
}
|
|
|
|
using var table = 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)
|
|
UiHelpers.Text(imc);
|
|
|
|
var mdl = (RenderModel*)model->Models[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();
|
|
{
|
|
UiHelpers.Text(mdl->ResourceHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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());
|
|
DrawDebugResidentResources();
|
|
}
|
|
|
|
/// <summary> Draw resources with unusual reference count. </summary>
|
|
private 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 = Table("##ProblemsTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
|
|
if (!table)
|
|
return;
|
|
|
|
_resourceManager.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)
|
|
UiHelpers.Text(name.BufferPtr, (int)name.Length);
|
|
else
|
|
fixed (byte* ptr = name.Buffer)
|
|
{
|
|
UiHelpers.Text(ptr, (int)name.Length);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/// <summary> Draw information about IPC options and availability. </summary>
|
|
private void DrawDebugTabIpc()
|
|
{
|
|
if (!ImGui.CollapsingHeader("IPC"))
|
|
{
|
|
_ipc.Tester.UnsubscribeEvents();
|
|
return;
|
|
}
|
|
|
|
_ipc.Tester.Draw();
|
|
}
|
|
|
|
/// <summary> Helper to print a property and its value in a 2-column table. </summary>
|
|
private static void PrintValue(string name, string value)
|
|
{
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(name);
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(value);
|
|
}
|
|
|
|
public override void Draw()
|
|
=> DrawContent();
|
|
|
|
public override bool DrawConditions()
|
|
=> _config.DebugMode && _config.DebugSeparateWindow;
|
|
|
|
public override void OnClose()
|
|
{
|
|
_config.DebugSeparateWindow = false;
|
|
_config.Save();
|
|
}
|
|
}
|