mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
1100 lines
40 KiB
C#
1100 lines
40 KiB
C#
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
using CheapLoc;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Bindings.ImPlot;
|
|
using Dalamud.Configuration.Internal;
|
|
using Dalamud.Console;
|
|
using Dalamud.Data;
|
|
using Dalamud.Game.Addon.Lifecycle;
|
|
using Dalamud.Game.ClientState;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Keys;
|
|
using Dalamud.Game.Gui;
|
|
using Dalamud.Game.Internal;
|
|
using Dalamud.Hooking;
|
|
using Dalamud.Interface.Animation.EasingFunctions;
|
|
using Dalamud.Interface.Colors;
|
|
using Dalamud.Interface.Internal.Windows;
|
|
using Dalamud.Interface.Internal.Windows.Data;
|
|
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
|
using Dalamud.Interface.Internal.Windows.SelfTest;
|
|
using Dalamud.Interface.Internal.Windows.Settings;
|
|
using Dalamud.Interface.Internal.Windows.StyleEditor;
|
|
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
|
using Dalamud.Interface.Style;
|
|
using Dalamud.Interface.Textures;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using Dalamud.Logging.Internal;
|
|
using Dalamud.Plugin.Internal;
|
|
using Dalamud.Storage.Assets;
|
|
using Dalamud.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using Serilog.Events;
|
|
|
|
namespace Dalamud.Interface.Internal;
|
|
|
|
/// <summary>
|
|
/// This plugin implements all the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
|
|
/// </summary>
|
|
[ServiceManager.EarlyLoadedService]
|
|
internal class DalamudInterface : IInternalDisposableService
|
|
{
|
|
private const float CreditsDarkeningMaxAlpha = 0.8f;
|
|
|
|
private static readonly ModuleLog Log = new("DUI");
|
|
|
|
private readonly Dalamud dalamud;
|
|
private readonly DalamudConfiguration configuration;
|
|
private readonly InterfaceManager interfaceManager;
|
|
|
|
private readonly ChangelogWindow changelogWindow;
|
|
private readonly ColorDemoWindow colorDemoWindow;
|
|
private readonly ComponentDemoWindow componentDemoWindow;
|
|
private readonly DataWindow dataWindow;
|
|
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
|
|
private readonly ConsoleWindow consoleWindow;
|
|
private readonly PluginStatWindow pluginStatWindow;
|
|
private readonly PluginInstallerWindow pluginWindow;
|
|
private readonly SettingsWindow settingsWindow;
|
|
private readonly SelfTestWindow selfTestWindow;
|
|
private readonly StyleEditorWindow styleEditorWindow;
|
|
private readonly TitleScreenMenuWindow titleScreenMenuWindow;
|
|
private readonly ProfilerWindow profilerWindow;
|
|
private readonly BranchSwitcherWindow branchSwitcherWindow;
|
|
private readonly HitchSettingsWindow hitchSettingsWindow;
|
|
|
|
private bool isCreditsDarkening = false;
|
|
private OutCubic creditsDarkeningAnimation = new(TimeSpan.FromSeconds(10));
|
|
|
|
#if DEBUG
|
|
private bool isImGuiDrawDevMenu = true;
|
|
#else
|
|
private bool isImGuiDrawDevMenu = false;
|
|
#endif
|
|
|
|
#if BOOT_AGING
|
|
private bool signaledBoot = false;
|
|
#endif
|
|
|
|
private bool isImGuiDrawDemoWindow = false;
|
|
private bool isImPlotDrawDemoWindow = false;
|
|
private bool isImGuiTestWindowsInMonospace = false;
|
|
private bool isImGuiDrawMetricsWindow = false;
|
|
|
|
[ServiceManager.ServiceConstructor]
|
|
private DalamudInterface(
|
|
Dalamud dalamud,
|
|
DalamudConfiguration configuration,
|
|
FontAtlasFactory fontAtlasFactory,
|
|
InterfaceManager interfaceManager,
|
|
PluginImageCache pluginImageCache,
|
|
DalamudAssetManager dalamudAssetManager,
|
|
Game.Framework framework,
|
|
ClientState clientState,
|
|
TitleScreenMenu titleScreenMenu,
|
|
GameGui gameGui,
|
|
ConsoleManager consoleManager,
|
|
AddonLifecycle addonLifecycle)
|
|
{
|
|
this.dalamud = dalamud;
|
|
this.configuration = configuration;
|
|
this.interfaceManager = interfaceManager;
|
|
|
|
this.WindowSystem = new WindowSystem("DalamudCore");
|
|
|
|
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
|
|
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
|
|
this.dataWindow = new DataWindow() { IsOpen = false };
|
|
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
|
|
this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup };
|
|
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
|
this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false };
|
|
this.settingsWindow = new SettingsWindow() { IsOpen = false };
|
|
this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
|
|
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
|
this.titleScreenMenuWindow = new TitleScreenMenuWindow(
|
|
clientState,
|
|
configuration,
|
|
dalamudAssetManager,
|
|
fontAtlasFactory,
|
|
framework,
|
|
gameGui,
|
|
titleScreenMenu,
|
|
consoleManager,
|
|
addonLifecycle) { IsOpen = false };
|
|
this.changelogWindow = new ChangelogWindow(
|
|
this.titleScreenMenuWindow,
|
|
fontAtlasFactory,
|
|
dalamudAssetManager,
|
|
gameGui,
|
|
framework) { IsOpen = false };
|
|
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
|
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
|
|
this.hitchSettingsWindow = new HitchSettingsWindow() { IsOpen = false };
|
|
|
|
this.WindowSystem.AddWindow(this.changelogWindow);
|
|
this.WindowSystem.AddWindow(this.colorDemoWindow);
|
|
this.WindowSystem.AddWindow(this.componentDemoWindow);
|
|
this.WindowSystem.AddWindow(this.dataWindow);
|
|
this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow);
|
|
this.WindowSystem.AddWindow(this.consoleWindow);
|
|
this.WindowSystem.AddWindow(this.pluginStatWindow);
|
|
this.WindowSystem.AddWindow(this.pluginWindow);
|
|
this.WindowSystem.AddWindow(this.settingsWindow);
|
|
this.WindowSystem.AddWindow(this.selfTestWindow);
|
|
this.WindowSystem.AddWindow(this.styleEditorWindow);
|
|
this.WindowSystem.AddWindow(this.titleScreenMenuWindow);
|
|
this.WindowSystem.AddWindow(this.profilerWindow);
|
|
this.WindowSystem.AddWindow(this.branchSwitcherWindow);
|
|
this.WindowSystem.AddWindow(this.hitchSettingsWindow);
|
|
|
|
this.interfaceManager.ShowAsserts = configuration.ImGuiAssertsEnabledAtStartup ?? false;
|
|
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
|
|
|
|
this.interfaceManager.Draw += this.OnDraw;
|
|
|
|
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(
|
|
_ =>
|
|
{
|
|
titleScreenMenu.AddEntryCore(
|
|
Loc.Localize("TSMDalamudPlugins", "Plugin Installer"),
|
|
new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)),
|
|
this.OpenPluginInstaller);
|
|
titleScreenMenu.AddEntryCore(
|
|
Loc.Localize("TSMDalamudSettings", "Dalamud Settings"),
|
|
new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)),
|
|
this.OpenSettings);
|
|
|
|
titleScreenMenu.AddEntryCore(
|
|
"Toggle Dev Menu",
|
|
new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)),
|
|
() => Service<DalamudInterface>.GetNullable()?.ToggleDevMenu(),
|
|
VirtualKey.SHIFT);
|
|
|
|
if (!configuration.DalamudBetaKind.IsNullOrEmpty())
|
|
{
|
|
titleScreenMenu.AddEntryCore(
|
|
Loc.Localize("TSMDalamudDevMenu", "Developer Menu"),
|
|
new ForwardingSharedImmediateTexture(dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall)),
|
|
() => this.isImGuiDrawDevMenu = true);
|
|
}
|
|
});
|
|
|
|
this.creditsDarkeningAnimation.Point1 = Vector2.Zero;
|
|
this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha);
|
|
|
|
// This is temporary, until we know the repercussions of vtable hooking mode
|
|
consoleManager.AddCommand(
|
|
"dalamud.interface.swapchain_mode",
|
|
"Set swapchain hooking mode",
|
|
(string mode) =>
|
|
{
|
|
switch (mode)
|
|
{
|
|
case "vtable":
|
|
this.configuration.SwapChainHookMode = SwapChainHelper.HookMode.VTable;
|
|
break;
|
|
case "bytecode":
|
|
this.configuration.SwapChainHookMode = SwapChainHelper.HookMode.ByteCode;
|
|
break;
|
|
default:
|
|
Log.Error("Unknown swapchain mode: {Mode}", mode);
|
|
return false;
|
|
}
|
|
|
|
this.configuration.QueueSave();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
private delegate nint CrashDebugDelegate(nint self);
|
|
|
|
/// <summary>
|
|
/// Gets the number of frames since Dalamud has loaded.
|
|
/// </summary>
|
|
public ulong FrameCount { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="WindowSystem"/> controlling all Dalamud-internal windows.
|
|
/// </summary>
|
|
public WindowSystem WindowSystem { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the /xldev menu is open.
|
|
/// </summary>
|
|
public bool IsDevMenuOpen
|
|
{
|
|
get => this.isImGuiDrawDevMenu;
|
|
set => this.isImGuiDrawDevMenu = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the plugin installer is open.
|
|
/// </summary>
|
|
public bool IsPluginInstallerOpen
|
|
{
|
|
get => this.pluginWindow.IsOpen;
|
|
set => this.pluginWindow.IsOpen = value;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
void IInternalDisposableService.DisposeService()
|
|
{
|
|
this.interfaceManager.Draw -= this.OnDraw;
|
|
|
|
this.WindowSystem.Windows.OfType<IDisposable>().AggregateToDisposable().Dispose();
|
|
this.WindowSystem.RemoveAllWindows();
|
|
|
|
this.changelogWindow.Dispose();
|
|
this.consoleWindow.Dispose();
|
|
this.pluginWindow.Dispose();
|
|
this.titleScreenMenuWindow.Dispose();
|
|
}
|
|
|
|
#region Open
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="ChangelogWindow"/>.
|
|
/// </summary>
|
|
public void OpenChangelogWindow() => this.changelogWindow.IsOpen = true;
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="ColorDemoWindow"/>.
|
|
/// </summary>
|
|
public void OpenColorsDemoWindow() => this.colorDemoWindow.IsOpen = true;
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="ComponentDemoWindow"/>.
|
|
/// </summary>
|
|
public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true;
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="DataWindow"/>.
|
|
/// </summary>
|
|
/// <param name="dataKind">The data kind to switch to after opening.</param>
|
|
public void OpenDataWindow(string? dataKind = null)
|
|
{
|
|
this.dataWindow.IsOpen = true;
|
|
if (dataKind != null && this.dataWindow.IsOpen)
|
|
{
|
|
this.dataWindow.SetDataKind(dataKind);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the dev menu bar.
|
|
/// </summary>
|
|
public void OpenDevMenu() => this.isImGuiDrawDevMenu = true;
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="GamepadModeNotifierWindow"/>.
|
|
/// </summary>
|
|
public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true;
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="ConsoleWindow"/>.
|
|
/// </summary>
|
|
public void OpenLogWindow()
|
|
{
|
|
this.consoleWindow.IsOpen = true;
|
|
this.consoleWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="PluginStatWindow"/>.
|
|
/// </summary>
|
|
public void OpenPluginStats()
|
|
{
|
|
this.pluginStatWindow.IsOpen = true;
|
|
this.pluginStatWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="PluginInstallerWindow"/> on the plugin installed.
|
|
/// </summary>
|
|
public void OpenPluginInstaller()
|
|
{
|
|
this.pluginWindow.OpenTo(this.configuration.PluginInstallerOpen);
|
|
this.pluginWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="PluginInstallerWindow"/> on the specified page.
|
|
/// </summary>
|
|
/// <param name="kind">The page of the installer to open.</param>
|
|
public void OpenPluginInstallerTo(PluginInstallerOpenKind kind)
|
|
{
|
|
this.pluginWindow.OpenTo(kind);
|
|
this.pluginWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="SettingsWindow"/>.
|
|
/// </summary>
|
|
public void OpenSettings()
|
|
{
|
|
this.settingsWindow.IsOpen = true;
|
|
this.settingsWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="SettingsWindow"/> on the specified tab.
|
|
/// </summary>
|
|
/// <param name="kind">The tab of the settings to open.</param>
|
|
public void OpenSettingsTo(SettingsOpenKind kind)
|
|
{
|
|
this.settingsWindow.OpenTo(kind);
|
|
this.settingsWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="SelfTestWindow"/>.
|
|
/// </summary>
|
|
public void OpenSelfTest()
|
|
{
|
|
this.selfTestWindow.IsOpen = true;
|
|
this.selfTestWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="StyleEditorWindow"/>.
|
|
/// </summary>
|
|
public void OpenStyleEditor()
|
|
{
|
|
this.styleEditorWindow.IsOpen = true;
|
|
this.styleEditorWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="ProfilerWindow"/>.
|
|
/// </summary>
|
|
public void OpenProfiler()
|
|
{
|
|
this.profilerWindow.IsOpen = true;
|
|
this.profilerWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="HitchSettingsWindow"/>.
|
|
/// </summary>
|
|
public void OpenHitchSettings()
|
|
{
|
|
this.hitchSettingsWindow.IsOpen = true;
|
|
this.hitchSettingsWindow.BringToFront();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the <see cref="BranchSwitcherWindow"/>.
|
|
/// </summary>
|
|
public void OpenBranchSwitcher()
|
|
{
|
|
this.branchSwitcherWindow.IsOpen = true;
|
|
this.branchSwitcherWindow.BringToFront();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Close
|
|
|
|
/// <summary>
|
|
/// Closes the <see cref="GamepadModeNotifierWindow"/>.
|
|
/// </summary>
|
|
public void CloseGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = false;
|
|
|
|
#endregion
|
|
|
|
#region Toggle
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="ChangelogWindow"/>.
|
|
/// </summary>
|
|
public void ToggleChangelogWindow() => this.changelogWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="ColorDemoWindow"/>.
|
|
/// </summary>
|
|
public void ToggleColorsDemoWindow() => this.colorDemoWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="ComponentDemoWindow"/>.
|
|
/// </summary>
|
|
public void ToggleComponentDemoWindow() => this.componentDemoWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="DataWindow"/>.
|
|
/// </summary>
|
|
/// <param name="dataKind">The data kind to switch to after opening.</param>
|
|
public void ToggleDataWindow(string? dataKind = null)
|
|
{
|
|
this.dataWindow.Toggle();
|
|
if (dataKind != null && this.dataWindow.IsOpen)
|
|
{
|
|
this.dataWindow.SetDataKind(dataKind);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles the dev menu bar.
|
|
/// </summary>
|
|
public void ToggleDevMenu() => this.isImGuiDrawDevMenu ^= true;
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="GamepadModeNotifierWindow"/>.
|
|
/// </summary>
|
|
public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="ConsoleWindow"/>.
|
|
/// </summary>
|
|
public void ToggleLogWindow() => this.consoleWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="PluginStatWindow"/>.
|
|
/// </summary>
|
|
public void TogglePluginStatsWindow() => this.pluginStatWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="PluginInstallerWindow"/>.
|
|
/// </summary>
|
|
/// <param name="kind">The page of the installer to open.</param>
|
|
public void TogglePluginInstallerWindowTo(PluginInstallerOpenKind kind) => this.pluginWindow.ToggleTo(kind);
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="SettingsWindow"/>.
|
|
/// </summary>
|
|
public void ToggleSettingsWindow() => this.settingsWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="SelfTestWindow"/>.
|
|
/// </summary>
|
|
public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="StyleEditorWindow"/>.
|
|
/// </summary>
|
|
public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="ProfilerWindow"/>.
|
|
/// </summary>
|
|
public void ToggleProfilerWindow() => this.profilerWindow.Toggle();
|
|
|
|
/// <summary>
|
|
/// Toggles the <see cref="BranchSwitcherWindow"/>.
|
|
/// </summary>
|
|
public void ToggleBranchSwitcher() => this.branchSwitcherWindow.Toggle();
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Sets the current search text for the plugin installer.
|
|
/// </summary>
|
|
/// <param name="text">The search term.</param>
|
|
public void SetPluginInstallerSearchText(string text)
|
|
{
|
|
this.pluginWindow.SetSearchText(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the current search text for the settings window.
|
|
/// </summary>
|
|
/// <param name="text">The search term.</param>
|
|
public void SetSettingsSearchText(string text)
|
|
{
|
|
this.settingsWindow.SetSearchText(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggle the screen darkening effect used for the credits.
|
|
/// </summary>
|
|
/// <param name="status">Whether or not to turn the effect on.</param>
|
|
public void SetCreditsDarkeningAnimation(bool status)
|
|
{
|
|
this.isCreditsDarkening = status;
|
|
|
|
if (status)
|
|
this.creditsDarkeningAnimation.Restart();
|
|
}
|
|
|
|
private void OnDraw()
|
|
{
|
|
this.FrameCount++;
|
|
|
|
#if BOOT_AGING
|
|
if (this.frameCount > 500 && !this.signaledBoot)
|
|
{
|
|
this.signaledBoot = true;
|
|
|
|
System.Threading.Tasks.Task.Run(async () =>
|
|
{
|
|
using var client = new System.Net.Http.HttpClient();
|
|
await client.PostAsync("http://localhost:1415/aging/success", new System.Net.Http.StringContent(string.Empty));
|
|
});
|
|
}
|
|
#endif
|
|
|
|
try
|
|
{
|
|
this.DrawHiddenDevMenuOpener();
|
|
this.DrawDevMenu();
|
|
|
|
if (Service<GameGui>.Get().GameUiHidden)
|
|
return;
|
|
|
|
this.WindowSystem.Draw();
|
|
|
|
if (this.isImGuiTestWindowsInMonospace)
|
|
ImGui.PushFont(InterfaceManager.MonoFont);
|
|
|
|
if (this.isImGuiDrawDemoWindow)
|
|
ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow);
|
|
|
|
if (this.isImPlotDrawDemoWindow)
|
|
ImPlot.ShowDemoWindow(ref this.isImPlotDrawDemoWindow);
|
|
|
|
if (this.isImGuiDrawMetricsWindow)
|
|
ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow);
|
|
|
|
if (this.isImGuiTestWindowsInMonospace)
|
|
ImGui.PopFont();
|
|
|
|
if (this.isCreditsDarkening)
|
|
this.DrawCreditsDarkeningAnimation();
|
|
|
|
// Release focus of any ImGui window if we click into the game.
|
|
var io = ImGui.GetIO();
|
|
if (!io.WantCaptureMouse && (global::Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
|
|
{
|
|
unsafe
|
|
{
|
|
ImGui.SetWindowFocus((byte*)null);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error during OnDraw");
|
|
}
|
|
}
|
|
|
|
private void DrawCreditsDarkeningAnimation()
|
|
{
|
|
using var style1 = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding, 0f);
|
|
using var style2 = ImRaii.PushStyle(ImGuiStyleVar.WindowBorderSize, 0f);
|
|
using var color = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0));
|
|
|
|
ImGui.SetNextWindowPos(ImGuiHelpers.MainViewport.Pos);
|
|
ImGui.SetNextWindowSize(ImGuiHelpers.MainViewport.Size);
|
|
ImGuiHelpers.ForceNextWindowMainViewport();
|
|
|
|
this.creditsDarkeningAnimation.Update();
|
|
ImGui.SetNextWindowBgAlpha(Math.Min(this.creditsDarkeningAnimation.EasedPoint.X, CreditsDarkeningMaxAlpha));
|
|
|
|
ImGui.Begin(
|
|
"###CreditsDarkenWindow",
|
|
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoMove |
|
|
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBringToFrontOnFocus |
|
|
ImGuiWindowFlags.NoNav);
|
|
ImGui.End();
|
|
}
|
|
|
|
private void DrawHiddenDevMenuOpener()
|
|
{
|
|
var condition = Service<Condition>.Get();
|
|
|
|
if (!this.isImGuiDrawDevMenu && !condition.Any())
|
|
{
|
|
using var color = ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero);
|
|
color.Push(ImGuiCol.ButtonActive, Vector4.Zero);
|
|
color.Push(ImGuiCol.ButtonHovered, Vector4.Zero);
|
|
color.Push(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1));
|
|
color.Push(ImGuiCol.Border, new Vector4(0, 0, 0, 1));
|
|
color.Push(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1));
|
|
color.Push(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 1));
|
|
|
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
|
style.Push(ImGuiStyleVar.WindowBorderSize, 0);
|
|
style.Push(ImGuiStyleVar.FrameBorderSize, 0);
|
|
|
|
var windowPos = ImGui.GetMainViewport().Pos + new Vector2(20);
|
|
ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always);
|
|
ImGui.SetNextWindowBgAlpha(1);
|
|
|
|
if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings))
|
|
{
|
|
ImGui.SetNextItemWidth(40);
|
|
if (ImGui.Button("###devMenuOpener", new Vector2(20, 20)))
|
|
this.isImGuiDrawDevMenu = true;
|
|
|
|
ImGui.End();
|
|
}
|
|
|
|
if (EnvironmentConfiguration.DalamudForceMinHook)
|
|
{
|
|
ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always);
|
|
ImGui.SetNextWindowBgAlpha(1);
|
|
|
|
if (ImGui.Begin(
|
|
"Disclaimer",
|
|
ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground |
|
|
ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove |
|
|
ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoMouseInputs |
|
|
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings))
|
|
{
|
|
ImGui.TextColored(ImGuiColors.DalamudRed, "Is force MinHook!");
|
|
}
|
|
|
|
ImGui.End();
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void DrawDevMenu()
|
|
{
|
|
if (this.isImGuiDrawDevMenu)
|
|
{
|
|
if (ImGui.BeginMainMenuBar())
|
|
{
|
|
var pluginManager = Service<PluginManager>.Get();
|
|
|
|
if (ImGui.BeginMenu("Dalamud"))
|
|
{
|
|
ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu);
|
|
var devBarAtStartup = this.configuration.DevBarOpenAtStartup;
|
|
if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup))
|
|
{
|
|
this.configuration.DevBarOpenAtStartup ^= true;
|
|
this.configuration.QueueSave();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Open Log window"))
|
|
{
|
|
this.OpenLogWindow();
|
|
}
|
|
|
|
if (ImGui.BeginMenu("Set log level..."))
|
|
{
|
|
foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>())
|
|
{
|
|
if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel))
|
|
{
|
|
EntryPoint.LogLevelSwitch.MinimumLevel = logLevel;
|
|
this.configuration.LogLevel = logLevel;
|
|
this.configuration.QueueSave();
|
|
}
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
var logSynchronously = this.configuration.LogSynchronously;
|
|
if (ImGui.MenuItem("Log Synchronously", (byte*)null, ref logSynchronously))
|
|
{
|
|
this.configuration.LogSynchronously = logSynchronously;
|
|
this.configuration.QueueSave();
|
|
|
|
EntryPoint.InitLogging(
|
|
this.dalamud.StartInfo.LogPath!,
|
|
this.dalamud.StartInfo.BootShowConsole,
|
|
this.configuration.LogSynchronously,
|
|
this.dalamud.StartInfo.LogName);
|
|
}
|
|
|
|
var antiDebug = Service<AntiDebug>.Get();
|
|
if (ImGui.MenuItem("Disable Debugging Protections", (byte*)null, antiDebug.IsEnabled))
|
|
{
|
|
var newEnabled = !antiDebug.IsEnabled;
|
|
if (newEnabled)
|
|
antiDebug.Enable();
|
|
else
|
|
antiDebug.Disable();
|
|
|
|
this.configuration.IsAntiAntiDebugEnabled = newEnabled;
|
|
this.configuration.QueueSave();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Open Data window"))
|
|
{
|
|
this.OpenDataWindow();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Settings window"))
|
|
{
|
|
this.OpenSettings();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Changelog window"))
|
|
{
|
|
this.OpenChangelogWindow();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Components Demo"))
|
|
{
|
|
this.OpenComponentDemoWindow();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Colors Demo"))
|
|
{
|
|
this.OpenColorsDemoWindow();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Self-Test"))
|
|
{
|
|
this.OpenSelfTest();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Style Editor"))
|
|
{
|
|
this.OpenStyleEditor();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Profiler"))
|
|
{
|
|
this.OpenProfiler();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Open Hitch Settings"))
|
|
{
|
|
this.OpenHitchSettings();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Unload Dalamud"))
|
|
{
|
|
Service<Dalamud>.Get().Unload();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Restart game"))
|
|
{
|
|
Dalamud.RestartGame();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Kill game"))
|
|
{
|
|
Process.GetCurrentProcess().Kill();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.BeginMenu("Crash game"))
|
|
{
|
|
if (ImGui.MenuItem("Access Violation"))
|
|
{
|
|
Marshal.ReadByte(IntPtr.Zero);
|
|
}
|
|
|
|
if (ImGui.MenuItem("Set UiModule to NULL"))
|
|
{
|
|
unsafe
|
|
{
|
|
var framework = Framework.Instance();
|
|
framework->UIModule = (UIModule*)0;
|
|
}
|
|
}
|
|
|
|
if (ImGui.MenuItem("Set UiModule to invalid ptr"))
|
|
{
|
|
unsafe
|
|
{
|
|
var framework = Framework.Instance();
|
|
framework->UIModule = (UIModule*)0x12345678;
|
|
}
|
|
}
|
|
|
|
if (ImGui.MenuItem("Deref nullptr in Hook"))
|
|
{
|
|
unsafe
|
|
{
|
|
var hook = Hook<CrashDebugDelegate>.FromAddress(
|
|
(nint)UIModule.StaticVirtualTablePointer->GetUIInputData,
|
|
self =>
|
|
{
|
|
_ = *(byte*)0;
|
|
return (nint)UIModule.Instance()->GetUIInputData();
|
|
});
|
|
hook.Enable();
|
|
}
|
|
}
|
|
|
|
if (ImGui.MenuItem("Cause CLR fastfail"))
|
|
{
|
|
unsafe void CauseFastFail()
|
|
{
|
|
// ReSharper disable once NotAccessedVariable
|
|
var texture = Unsafe.AsRef<AtkTexture>((void*)0x12345678);
|
|
texture.TextureType = TextureType.Crest;
|
|
}
|
|
|
|
Service<Game.Framework>.Get().RunOnTick(CauseFastFail);
|
|
}
|
|
|
|
if (ImGui.MenuItem("Cause ImGui assert"))
|
|
{
|
|
ImGui.PopStyleVar();
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Report crashes at shutdown", (byte*)null, this.configuration.ReportShutdownCrashes))
|
|
{
|
|
this.configuration.ReportShutdownCrashes = !this.configuration.ReportShutdownCrashes;
|
|
this.configuration.QueueSave();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Open Dalamud branch switcher"))
|
|
{
|
|
this.OpenBranchSwitcher();
|
|
}
|
|
|
|
ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false);
|
|
ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false);
|
|
ImGui.MenuItem($"CLR: {Environment.Version}", false);
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (ImGui.BeginMenu("GUI"))
|
|
{
|
|
ImGui.MenuItem("Use Monospace font for following windows", string.Empty, ref this.isImGuiTestWindowsInMonospace);
|
|
ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow);
|
|
ImGui.MenuItem("Draw ImPlot demo", string.Empty, ref this.isImPlotDrawDemoWindow);
|
|
ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow);
|
|
|
|
ImGui.Separator();
|
|
|
|
var showAsserts = this.interfaceManager.ShowAsserts;
|
|
if (ImGui.MenuItem("Enable assert popups", string.Empty, ref showAsserts))
|
|
{
|
|
this.interfaceManager.ShowAsserts = showAsserts;
|
|
}
|
|
|
|
var enableVerboseAsserts = this.interfaceManager.EnableVerboseAssertLogging;
|
|
if (ImGui.MenuItem("Enable verbose assert logging", string.Empty, ref enableVerboseAsserts))
|
|
{
|
|
this.interfaceManager.EnableVerboseAssertLogging = enableVerboseAsserts;
|
|
}
|
|
|
|
var assertsEnabled = this.configuration.ImGuiAssertsEnabledAtStartup ?? false;
|
|
if (ImGui.MenuItem("Enable asserts at startup", (byte*)null, assertsEnabled))
|
|
{
|
|
this.configuration.ImGuiAssertsEnabledAtStartup = !assertsEnabled;
|
|
this.configuration.QueueSave();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Clear focus"))
|
|
{
|
|
ImGui.SetWindowFocus((byte*)null);
|
|
}
|
|
|
|
if (ImGui.MenuItem("Clear stacks"))
|
|
{
|
|
this.interfaceManager.ClearStacks();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Dump style"))
|
|
{
|
|
var info = string.Empty;
|
|
var style = StyleModelV1.Get();
|
|
var enCulture = new CultureInfo("en-US");
|
|
|
|
foreach (var propertyInfo in typeof(StyleModel).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
if (propertyInfo.PropertyType == typeof(Vector2))
|
|
{
|
|
var vec2 = (Vector2)propertyInfo.GetValue(style)!;
|
|
info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n";
|
|
}
|
|
else
|
|
{
|
|
info += $"{propertyInfo.Name} = {propertyInfo.GetValue(style)},\n";
|
|
}
|
|
}
|
|
|
|
info += "Colors = new Dictionary<string, Vector4>()\n";
|
|
info += "{\n";
|
|
|
|
foreach (var color in style.Colors)
|
|
{
|
|
info +=
|
|
$"{{\"{color.Key}\", new Vector4({color.Value.X.ToString(enCulture)}f, {color.Value.Y.ToString(enCulture)}f, {color.Value.Z.ToString(enCulture)}f, {color.Value.W.ToString(enCulture)}f)}},\n";
|
|
}
|
|
|
|
info += "},";
|
|
|
|
Log.Information(info);
|
|
}
|
|
|
|
if (ImGui.MenuItem("Show dev bar info", (byte*)null, this.configuration.ShowDevBarInfo))
|
|
{
|
|
this.configuration.ShowDevBarInfo = !this.configuration.ShowDevBarInfo;
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Show loading window"))
|
|
{
|
|
var dialog = new LoadingDialog();
|
|
dialog.Show();
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (ImGui.BeginMenu("Game"))
|
|
{
|
|
if (ImGui.MenuItem("Use in-game default ExceptionHandler"))
|
|
{
|
|
this.dalamud.UseDefaultExceptionHandler();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Use in-game debug ExceptionHandler"))
|
|
{
|
|
this.dalamud.UseDebugExceptionHandler();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Disable in-game ExceptionHandler"))
|
|
{
|
|
this.dalamud.UseNoExceptionHandler();
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (ImGui.BeginMenu("Plugins"))
|
|
{
|
|
if (ImGui.MenuItem("Open Plugin installer"))
|
|
{
|
|
this.OpenPluginInstaller();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Clear cached images/icons"))
|
|
{
|
|
this.pluginWindow?.ClearIconCache();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Open Plugin Stats"))
|
|
{
|
|
this.OpenPluginStats();
|
|
}
|
|
|
|
if (ImGui.MenuItem("Print plugin info"))
|
|
{
|
|
foreach (var plugin in pluginManager.InstalledPlugins)
|
|
{
|
|
// TODO: some more here, state maybe?
|
|
Log.Information($"{plugin.Name}");
|
|
}
|
|
}
|
|
|
|
if (ImGui.MenuItem("Scan dev plugins"))
|
|
{
|
|
pluginManager.ScanDevPlugins();
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
if (ImGui.MenuItem("Load all API levels", (byte*)null, pluginManager.LoadAllApiLevels))
|
|
{
|
|
pluginManager.LoadAllApiLevels = !pluginManager.LoadAllApiLevels;
|
|
}
|
|
|
|
if (ImGui.MenuItem("Load blacklisted plugins", (byte*)null, pluginManager.LoadBannedPlugins))
|
|
{
|
|
pluginManager.LoadBannedPlugins = !pluginManager.LoadBannedPlugins;
|
|
}
|
|
|
|
if (pluginManager.SafeMode && ImGui.MenuItem("Disable Safe Mode"))
|
|
{
|
|
pluginManager.SafeMode = false;
|
|
}
|
|
|
|
ImGui.Separator();
|
|
ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false);
|
|
ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count(), false);
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (ImGui.BeginMenu("Localization"))
|
|
{
|
|
var localization = Service<Localization>.Get();
|
|
|
|
if (ImGui.MenuItem("Export localizable"))
|
|
{
|
|
localization.ExportLocalizable(true);
|
|
}
|
|
|
|
if (ImGui.BeginMenu("Load language..."))
|
|
{
|
|
if (ImGui.MenuItem("From Fallbacks"))
|
|
{
|
|
localization.SetupWithFallbacks();
|
|
}
|
|
|
|
if (ImGui.MenuItem("From UICulture"))
|
|
{
|
|
localization.SetupWithUiCulture();
|
|
}
|
|
|
|
foreach (var applicableLangCode in Localization.ApplicableLangCodes)
|
|
{
|
|
if (ImGui.MenuItem($"Applicable: {applicableLangCode}"))
|
|
{
|
|
localization.SetupWithLangCode(applicableLangCode);
|
|
}
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
ImGui.EndMenu();
|
|
}
|
|
|
|
if (Service<GameGui>.Get().GameUiHidden)
|
|
ImGui.BeginMenu("UI is hidden...", false);
|
|
|
|
if (this.configuration.ShowDevBarInfo)
|
|
{
|
|
ImGui.PushFont(InterfaceManager.MonoFont);
|
|
|
|
ImGui.BeginMenu(Util.GetScmVersion(), false);
|
|
ImGui.BeginMenu(this.FrameCount.ToString("000000"), false);
|
|
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
|
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
|
|
|
var videoMem = this.interfaceManager.GetD3dMemoryInfo();
|
|
ImGui.BeginMenu(
|
|
!videoMem.HasValue ? "V:???" : $"V:{Util.FormatBytes(videoMem.Value.Used)}",
|
|
false);
|
|
|
|
ImGui.PopFont();
|
|
}
|
|
|
|
ImGui.EndMainMenuBar();
|
|
}
|
|
}
|
|
}
|
|
}
|