diff --git a/.gitattributes b/.gitattributes
index 1ff0c4230..6d2ce70f2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -17,7 +17,7 @@
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
-# the diff markers are never inserted). Diff markers may cause the following
+# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
@@ -46,9 +46,9 @@
###############################################################################
# diff behavior for common document formats
-#
+#
# Convert binary document formats to text before diffing them. This feature
-# is only available from the command line. Turn it on by uncommenting the
+# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
@@ -61,3 +61,5 @@
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
+
+imgui/**/Generated/**/*.cs linguist-generated=true
diff --git a/.gitignore b/.gitignore
index 7023fb37f..b6a32a3ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -327,4 +327,7 @@ ASALocalRun/
*.nvuser
# MFractors (Xamarin productivity tool) working folder
-.mfractor/
\ No newline at end of file
+.mfractor/
+
+# HexaGen generated files
+#imgui/**/Generated/**/*
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index 227653d48..0d12df2b8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "lib/ImGuiScene"]
- path = lib/ImGuiScene
- url = https://github.com/goatcorp/ImGuiScene
[submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs
url = https://github.com/aers/FFXIVClientStructs
@@ -19,3 +16,9 @@
[submodule "lib/cimguizmo"]
path = lib/cimguizmo
url = https://github.com/goatcorp/gc-cimguizmo
+[submodule "lib/Hexa.NET.ImGui"]
+ path = lib/Hexa.NET.ImGui
+ url = https://github.com/goatcorp/Hexa.NET.ImGui.git
+[submodule "lib/Lumina.Excel"]
+ path = lib/Lumina.Excel
+ url = https://github.com/NotAdam/Lumina.Excel.git
diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj
index c18045027..a15601af4 100644
--- a/Dalamud.Boot/Dalamud.Boot.vcxproj
+++ b/Dalamud.Boot/Dalamud.Boot.vcxproj
@@ -49,7 +49,6 @@
truetruestdcpp20
- MultiThreadedDebugpch.hProgramDatabaseCPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
@@ -66,6 +65,7 @@
truefalse
+ MultiThreadedDebugDLL_DEBUG;%(PreprocessorDefinitions)Use26812
@@ -80,6 +80,7 @@
truetrue
+ MultiThreadedDLLNDEBUG;%(PreprocessorDefinitions)Use26812
@@ -203,7 +204,6 @@
-
diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp
index 35ad42212..5be8f97d0 100644
--- a/Dalamud.Boot/DalamudStartInfo.cpp
+++ b/Dalamud.Boot/DalamudStartInfo.cpp
@@ -117,6 +117,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
config.NoLoadThirdPartyPlugins = json.value("NoLoadThirdPartyPlugins", config.NoLoadThirdPartyPlugins);
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
+ config.BootDebugDirectX = json.value("BootDebugDirectX", config.BootDebugDirectX);
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
config.BootDisableFallbackConsole = json.value("BootDisableFallbackConsole", config.BootDisableFallbackConsole);
config.BootWaitMessageBox = json.value("BootWaitMessageBox", config.BootWaitMessageBox);
diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h
index 05ff7b23e..0eeaddeed 100644
--- a/Dalamud.Boot/DalamudStartInfo.h
+++ b/Dalamud.Boot/DalamudStartInfo.h
@@ -55,6 +55,7 @@ struct DalamudStartInfo {
bool NoLoadThirdPartyPlugins;
std::string BootLogPath;
+ bool BootDebugDirectX = false;
bool BootShowConsole = false;
bool BootDisableFallbackConsole = false;
WaitMessageboxFlags BootWaitMessageBox = WaitMessageboxFlags::None;
diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp
index b72063855..5e73962ec 100644
--- a/Dalamud.Boot/dllmain.cpp
+++ b/Dalamud.Boot/dllmain.cpp
@@ -1,6 +1,10 @@
#include "pch.h"
+#include
+#include
+
#include "DalamudStartInfo.h"
+#include "hooks.h"
#include "logging.h"
#include "utils.h"
#include "veh.h"
@@ -90,6 +94,69 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
+ if (g_startInfo.BootDebugDirectX) {
+ logging::I("Enabling DirectX Debugging.");
+
+ const auto hD3D11 = GetModuleHandleW(L"d3d11.dll");
+ const auto hDXGI = GetModuleHandleW(L"dxgi.dll");
+ const auto pfnD3D11CreateDevice = static_cast(
+ hD3D11 ? static_cast(GetProcAddress(hD3D11, "D3D11CreateDevice")) : nullptr);
+ if (pfnD3D11CreateDevice) {
+ static hooks::direct_hook s_hookD3D11CreateDevice(
+ "d3d11.dll!D3D11CreateDevice",
+ pfnD3D11CreateDevice);
+ s_hookD3D11CreateDevice.set_detour([](
+ IDXGIAdapter* pAdapter,
+ D3D_DRIVER_TYPE DriverType,
+ HMODULE Software,
+ UINT Flags,
+ const D3D_FEATURE_LEVEL* pFeatureLevels,
+ UINT FeatureLevels,
+ UINT SDKVersion,
+ ID3D11Device** ppDevice,
+ D3D_FEATURE_LEVEL* pFeatureLevel,
+ ID3D11DeviceContext** ppImmediateContext
+ ) -> HRESULT {
+ return s_hookD3D11CreateDevice.call_original(
+ pAdapter,
+ DriverType,
+ Software,
+ (Flags & ~D3D11_CREATE_DEVICE_PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY) | D3D11_CREATE_DEVICE_DEBUG,
+ pFeatureLevels,
+ FeatureLevels,
+ SDKVersion,
+ ppDevice,
+ pFeatureLevel,
+ ppImmediateContext);
+ });
+ } else {
+ logging::W("Could not find d3d11!D3D11CreateDevice.");
+ }
+
+ const auto pfnCreateDXGIFactory = static_cast(
+ hDXGI ? static_cast(GetProcAddress(hDXGI, "CreateDXGIFactory")) : nullptr);
+ const auto pfnCreateDXGIFactory1 = static_cast(
+ hDXGI ? static_cast(GetProcAddress(hDXGI, "CreateDXGIFactory1")) : nullptr);
+ static const auto pfnCreateDXGIFactory2 = static_cast(
+ hDXGI ? static_cast(GetProcAddress(hDXGI, "CreateDXGIFactory2")) : nullptr);
+ if (pfnCreateDXGIFactory2) {
+ static hooks::direct_hook s_hookCreateDXGIFactory(
+ "dxgi.dll!CreateDXGIFactory",
+ pfnCreateDXGIFactory);
+ static hooks::direct_hook s_hookCreateDXGIFactory1(
+ "dxgi.dll!CreateDXGIFactory1",
+ pfnCreateDXGIFactory1);
+ s_hookCreateDXGIFactory.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
+ return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
+ });
+ s_hookCreateDXGIFactory1.set_detour([](REFIID riid, _COM_Outptr_ void **ppFactory) -> HRESULT {
+ return pfnCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, riid, ppFactory);
+ });
+ } else {
+ logging::W("Could not find dxgi!CreateDXGIFactory2.");
+ }
+ }
+
if (minHookLoaded) {
logging::I("Applying fixes...");
xivfixes::apply_all(true);
diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs
index 680dce217..a0d7f8b0b 100644
--- a/Dalamud.Common/DalamudStartInfo.cs
+++ b/Dalamud.Common/DalamudStartInfo.cs
@@ -101,6 +101,11 @@ public record DalamudStartInfo
///
public bool BootShowConsole { get; set; }
+ ///
+ /// Gets or sets a value indicating whether to enable D3D11 and DXGI debugging if possible.
+ ///
+ public bool BootDebugDirectX { get; set; }
+
///
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
///
diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
index f2c8cbc5a..0c6a86e8d 100644
--- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
+++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj
@@ -26,7 +26,6 @@
- all
@@ -38,15 +37,6 @@
false
-
- false
-
-
- false
-
-
- false
-
diff --git a/Dalamud.CorePlugin/PluginWindow.cs b/Dalamud.CorePlugin/PluginWindow.cs
index 27be82f41..3a17ea065 100644
--- a/Dalamud.CorePlugin/PluginWindow.cs
+++ b/Dalamud.CorePlugin/PluginWindow.cs
@@ -1,8 +1,8 @@
using System;
using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Windowing;
-using ImGuiNET;
namespace Dalamud.CorePlugin
{
diff --git a/Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj b/Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
index c293e258c..1c89f4ff7 100644
--- a/Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
+++ b/Dalamud.Injector.Boot/Dalamud.Injector.Boot.vcxproj
@@ -39,7 +39,6 @@
truetruestdcpplatest
- MultiThreadedDebugpch.hProgramDatabaseCPPDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
@@ -56,6 +55,7 @@
truefalse
+ MultiThreadedDebugDLL_DEBUG;%(PreprocessorDefinitions)
@@ -67,6 +67,7 @@
truetrue
+ MultiThreadedDLLNDEBUG;%(PreprocessorDefinitions)
@@ -107,4 +108,4 @@
-
\ No newline at end of file
+
diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs
index cd2127355..4f876102e 100644
--- a/Dalamud.Injector/EntryPoint.cs
+++ b/Dalamud.Injector/EntryPoint.cs
@@ -91,6 +91,7 @@ namespace Dalamud.Injector
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
// Remove already handled arguments
+ args.Remove("--debug-directx");
args.Remove("--console");
args.Remove("--msgbox1");
args.Remove("--msgbox2");
@@ -464,6 +465,7 @@ namespace Dalamud.Injector
startInfo.LogName ??= string.Empty;
// Set boot defaults
+ startInfo.BootDebugDirectX = args.Contains("--debug-directx");
startInfo.BootShowConsole = args.Contains("--console");
startInfo.BootEnableEtw = args.Contains("--etw");
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
diff --git a/Dalamud.sln b/Dalamud.sln
index 5b1eb9d30..fe1c9b19d 100644
--- a/Dalamud.sln
+++ b/Dalamud.sln
@@ -32,12 +32,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Test", "Dalamud.Tes
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.CorePlugin", "Dalamud.CorePlugin\Dalamud.CorePlugin.csproj", "{4AFDB34A-7467-4D41-B067-53BC4101D9D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\FFXIVClientStructs\FFXIVClientStructs\FFXIVClientStructs.csproj", "{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}"
@@ -68,6 +62,24 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimplot", "external\cimplot
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cimguizmo", "external\cimguizmo\cimguizmo.vcxproj", "{F258347D-31BE-4605-98CE-40E43BDF6F9D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImGui", "imgui\Dalamud.Bindings.ImGui\Dalamud.Bindings.ImGui.csproj", "{B0AA8737-33A3-4766-8CBE-A48F2EF283BA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImGuizmo", "imgui\Dalamud.Bindings.ImGuizmo\Dalamud.Bindings.ImGuizmo.csproj", "{5E6EDD75-AE95-43A6-9D67-95B840EB4B71}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bindings.ImPlot", "imgui\Dalamud.Bindings.ImPlot\Dalamud.Bindings.ImPlot.csproj", "{9C70BD06-D52C-425E-9C14-5D66BC6046EF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bindings", "Bindings", "{A217B3DF-607A-4EFB-B107-3C4809348043}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StandaloneImGuiTestbed", "imgui\StandaloneImGuiTestbed\StandaloneImGuiTestbed.csproj", "{4702A911-2513-478C-A434-2776393FDE77}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiScene", "imgui\ImGuiScene\ImGuiScene.csproj", "{66753AC7-0029-4373-9CC4-7760B1F46141}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lumina", "Lumina", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel.Generator", "lib\Lumina.Excel\src\Lumina.Excel.Generator\Lumina.Excel.Generator.csproj", "{5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumina.Excel", "lib\Lumina.Excel\src\Lumina.Excel\Lumina.Excel.csproj", "{88FB719B-EB41-73C5-8D25-C03E0C69904F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -98,18 +110,6 @@ Global
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
- {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
- {0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
- {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
- {0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
- {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
- {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
- {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
- {C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
- {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
- {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
- {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
- {2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
@@ -154,6 +154,34 @@ Global
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Debug|Any CPU.Build.0 = Debug|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.ActiveCfg = Release|x64
{F258347D-31BE-4605-98CE-40E43BDF6F9D}.Release|Any CPU.Build.0 = Release|x64
+ {B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Debug|Any CPU.Build.0 = Debug|x64
+ {B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Release|Any CPU.ActiveCfg = Release|x64
+ {B0AA8737-33A3-4766-8CBE-A48F2EF283BA}.Release|Any CPU.Build.0 = Release|x64
+ {5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Debug|Any CPU.Build.0 = Debug|x64
+ {5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Release|Any CPU.ActiveCfg = Release|x64
+ {5E6EDD75-AE95-43A6-9D67-95B840EB4B71}.Release|Any CPU.Build.0 = Release|x64
+ {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Debug|Any CPU.Build.0 = Debug|x64
+ {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.ActiveCfg = Release|x64
+ {9C70BD06-D52C-425E-9C14-5D66BC6046EF}.Release|Any CPU.Build.0 = Release|x64
+ {4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {4702A911-2513-478C-A434-2776393FDE77}.Debug|Any CPU.Build.0 = Debug|x64
+ {4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.ActiveCfg = Release|x64
+ {4702A911-2513-478C-A434-2776393FDE77}.Release|Any CPU.Build.0 = Release|x64
+ {66753AC7-0029-4373-9CC4-7760B1F46141}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {66753AC7-0029-4373-9CC4-7760B1F46141}.Debug|Any CPU.Build.0 = Debug|x64
+ {66753AC7-0029-4373-9CC4-7760B1F46141}.Release|Any CPU.ActiveCfg = Release|x64
+ {66753AC7-0029-4373-9CC4-7760B1F46141}.Release|Any CPU.Build.0 = Release|x64
+ {5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {88FB719B-EB41-73C5-8D25-C03E0C69904F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {88FB719B-EB41-73C5-8D25-C03E0C69904F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -161,9 +189,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{5B832F73-5F54-4ADC-870F-D0095EF72C9A} = {19775C83-7117-4A5F-AA00-18889F46A490}
{8874326B-E755-4D13-90B4-59AB263A3E6B} = {19775C83-7117-4A5F-AA00-18889F46A490}
- {0483026E-C6CE-4B1A-AA68-46544C08140B} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
- {C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
- {2F7FF0A8-B619-4572-86C7-71E46FE22FB8} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{4AFDB34A-7467-4D41-B067-53BC4101D9D0} = {8F079208-C227-4D96-9427-2BEBE0003944}
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
{E0D51896-604F-4B40-8CFE-51941607B3A1} = {8BBACF2D-7AB8-4610-A115-0E363D35C291}
@@ -175,6 +200,14 @@ Global
{8BBACF2D-7AB8-4610-A115-0E363D35C291} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
{76CAA246-C405-4A8C-B0AE-F4A0EF3D4E16} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
{F258347D-31BE-4605-98CE-40E43BDF6F9D} = {DBE5345E-6594-4A59-B183-1C3D5592269D}
+ {B0AA8737-33A3-4766-8CBE-A48F2EF283BA} = {A217B3DF-607A-4EFB-B107-3C4809348043}
+ {5E6EDD75-AE95-43A6-9D67-95B840EB4B71} = {A217B3DF-607A-4EFB-B107-3C4809348043}
+ {9C70BD06-D52C-425E-9C14-5D66BC6046EF} = {A217B3DF-607A-4EFB-B107-3C4809348043}
+ {4702A911-2513-478C-A434-2776393FDE77} = {A217B3DF-607A-4EFB-B107-3C4809348043}
+ {66753AC7-0029-4373-9CC4-7760B1F46141} = {A217B3DF-607A-4EFB-B107-3C4809348043}
+ {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
+ {5A44DF0C-C9DA-940F-4D6B-4A11D13AEA3D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {88FB719B-EB41-73C5-8D25-C03E0C69904F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79B65AC9-C940-410E-AB61-7EA7E12C7599}
diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
index 8dafd897e..08bbeb938 100644
--- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs
+++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs
@@ -23,6 +23,7 @@ using Dalamud.Utility;
using Newtonsoft.Json;
using Serilog;
using Serilog.Events;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud.Configuration.Internal;
@@ -152,23 +153,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
///
public float GlobalUiScale { get; set; } = 1.0f;
- ///
- /// Gets or sets a value indicating whether to use AXIS fonts from the game.
- ///
- [Obsolete($"See {nameof(DefaultFontSpec)}")]
- public bool UseAxisFontsFromGame { get; set; } = true;
-
///
/// Gets or sets the default font spec.
///
public IFontSpec? DefaultFontSpec { get; set; }
- ///
- /// Gets or sets the gamma value to apply for Dalamud fonts. Do not use.
- ///
- [Obsolete("It happens that nobody touched this setting", true)]
- public float FontGammaLevel { get; set; } = 1.4f;
-
/// Gets or sets the opacity of the IME state indicator.
/// 0 will hide the state indicator. 1 will make the state indicator fully visible. Values outside the
/// range will be clamped to [0, 1].
@@ -604,11 +593,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
{
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
var winAnimEnabled = 0;
- var success = NativeFunctions.SystemParametersInfo(
- (uint)NativeFunctions.AccessibilityParameter.SPI_GETCLIENTAREAANIMATION,
- 0,
- ref winAnimEnabled,
- 0);
+ var success = false;
+ unsafe
+ {
+ success = Windows.Win32.PInvoke.SystemParametersInfo(
+ SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETCLIENTAREAANIMATION,
+ 0,
+ &winAnimEnabled,
+ 0);
+ }
if (!success)
{
diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs
index e406c3502..a411883d5 100644
--- a/Dalamud/Dalamud.cs
+++ b/Dalamud/Dalamud.cs
@@ -13,8 +13,9 @@ using Dalamud.Plugin.Internal;
using Dalamud.Storage;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
-using PInvoke;
using Serilog;
+using Windows.Win32.Foundation;
+using Windows.Win32.Security;
#if DEBUG
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
@@ -29,7 +30,7 @@ namespace Dalamud;
/// The main Dalamud class containing all subsystems.
///
[ServiceManager.ProvidedService]
-internal sealed class Dalamud : IServiceType
+internal sealed unsafe class Dalamud : IServiceType
{
#region Internals
@@ -79,7 +80,7 @@ internal sealed class Dalamud : IServiceType
{
Log.Verbose("=============== GAME THREAD KICKOFF ===============");
Timings.Event("Game thread kickoff");
- NativeFunctions.SetEvent(mainThreadContinueEvent);
+ Windows.Win32.PInvoke.SetEvent(new HANDLE(mainThreadContinueEvent));
}
void HandleServiceInitFailure(Task t)
@@ -116,9 +117,9 @@ internal sealed class Dalamud : IServiceType
HandleServiceInitFailure(t);
});
- this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero);
- NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter);
- Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}");
+ this.DefaultExceptionFilter = SetExceptionHandler(nint.Zero);
+ SetExceptionHandler(this.DefaultExceptionFilter);
+ Log.Debug($"SE default exception filter at {new IntPtr(this.DefaultExceptionFilter):X}");
var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF";
this.DebugExceptionFilter = Service.Get().ScanText(debugSig);
@@ -170,8 +171,9 @@ internal sealed class Dalamud : IServiceType
if (!reportCrashesSetting && !pmHasDevPlugins)
{
// Leaking on purpose for now
- var attribs = Kernel32.SECURITY_ATTRIBUTES.Create();
- Kernel32.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE");
+ var attribs = default(SECURITY_ATTRIBUTES);
+ attribs.nLength = (uint)Unsafe.SizeOf();
+ Windows.Win32.PInvoke.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE");
}
this.unloadSignal.Set();
@@ -189,27 +191,29 @@ internal sealed class Dalamud : IServiceType
/// Replace the current exception handler with the default one.
///
internal void UseDefaultExceptionHandler() =>
- this.SetExceptionHandler(this.DefaultExceptionFilter);
+ SetExceptionHandler(this.DefaultExceptionFilter);
///
/// Replace the current exception handler with a debug one.
///
internal void UseDebugExceptionHandler() =>
- this.SetExceptionHandler(this.DebugExceptionFilter);
+ SetExceptionHandler(this.DebugExceptionFilter);
///
/// Disable the current exception handler.
///
internal void UseNoExceptionHandler() =>
- this.SetExceptionHandler(nint.Zero);
+ SetExceptionHandler(nint.Zero);
///
/// Helper function to set the exception handler.
///
- private void SetExceptionHandler(nint newFilter)
+ private static nint SetExceptionHandler(nint newFilter)
{
- var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter);
- Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, oldFilter);
+ var oldFilter =
+ Windows.Win32.PInvoke.SetUnhandledExceptionFilter((delegate* unmanaged[Stdcall])newFilter);
+ Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, (nint)oldFilter);
+ return (nint)oldFilter;
}
private void SetupClientStructsResolver(DirectoryInfo cacheDir)
diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj
index 3fce08dd3..a02780438 100644
--- a/Dalamud/Dalamud.csproj
+++ b/Dalamud/Dalamud.csproj
@@ -6,7 +6,7 @@
XIV Launcher addon framework
- 12.0.1.5
+ 13.0.0.0$(DalamudVersion)$(DalamudVersion)$(DalamudVersion)
@@ -35,6 +35,10 @@
trueannotationstrue
+
+
+
@@ -64,12 +68,13 @@
-
-
-
+
+ all
+
+
@@ -87,13 +92,23 @@
+
+
+ imgui-frag.hlsl.bytes
+
+
+ imgui-vertex.hlsl.bytes
+
+
+
+
+
+
-
-
-
+
diff --git a/Dalamud/Dalamud.csproj.DotSettings b/Dalamud/Dalamud.csproj.DotSettings
index 9089754a3..e723e4da1 100644
--- a/Dalamud/Dalamud.csproj.DotSettings
+++ b/Dalamud/Dalamud.csproj.DotSettings
@@ -1,3 +1,3 @@
-True
-300000
+True
+ True
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index 47591f821..9fc09a56b 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -1,6 +1,8 @@
+using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
+using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -15,12 +17,11 @@ using Dalamud.Storage;
using Dalamud.Support;
using Dalamud.Utility;
using Newtonsoft.Json;
-using PInvoke;
using Serilog;
using Serilog.Core;
using Serilog.Events;
-
-using static Dalamud.NativeFunctions;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud;
@@ -58,7 +59,7 @@ public sealed class EntryPoint
var info = JsonConvert.DeserializeObject(infoStr)!;
if ((info.BootWaitMessageBox & 4) != 0)
- MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
+ Windows.Win32.PInvoke.MessageBox(HWND.Null, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MESSAGEBOX_STYLE.MB_OK);
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
}
@@ -135,6 +136,8 @@ public sealed class EntryPoint
/// Event used to signal the main thread to continue.
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
{
+ NativeLibrary.Load(Path.Combine(info.WorkingDirectory!, "cimgui.dll"));
+
// Setup logger
InitLogging(info.LogPath!, info.BootShowConsole, true, info.LogName);
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
@@ -259,10 +262,12 @@ public sealed class EntryPoint
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
var searchPath = $".;{symbolPath}";
- // Remove any existing Symbol Handler and Init a new one with our search path added
- SymCleanup(GetCurrentProcess());
+ var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
- if (!SymInitialize(GetCurrentProcess(), searchPath, true))
+ // Remove any existing Symbol Handler and Init a new one with our search path added
+ Windows.Win32.PInvoke.SymCleanup(currentProcess);
+
+ if (!Windows.Win32.PInvoke.SymInitialize(currentProcess, searchPath, true))
throw new Win32Exception();
}
catch (Exception ex)
@@ -304,14 +309,14 @@ public sealed class EntryPoint
// ignored
}
- const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
- var result = MessageBoxW(
- Process.GetCurrentProcess().MainWindowHandle,
+ const MESSAGEBOX_STYLE flags = MESSAGEBOX_STYLE.MB_YESNO | MESSAGEBOX_STYLE.MB_ICONERROR | MESSAGEBOX_STYLE.MB_SYSTEMMODAL;
+ var result = Windows.Win32.PInvoke.MessageBox(
+ new HWND(Process.GetCurrentProcess().MainWindowHandle),
$"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\n{ex.GetType().Name}\n{info}\n\n{pluginInfo}More information has been recorded separately{supportText}.\n\nDo you want to disable all plugins the next time you start the game?",
"Dalamud",
flags);
- if (result == (int)User32.MessageBoxResult.IDYES)
+ if (result == MESSAGEBOX_RESULT.IDYES)
{
Log.Information("User chose to disable plugins on next launch...");
var config = Service.Get();
diff --git a/Dalamud/Game/Addon/Events/AddonEventEntry.cs b/Dalamud/Game/Addon/Events/AddonEventEntry.cs
index 50b9c7ec4..30d0465dc 100644
--- a/Dalamud/Game/Addon/Events/AddonEventEntry.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventEntry.cs
@@ -1,4 +1,4 @@
-using Dalamud.Plugin.Services;
+using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -33,17 +33,10 @@ internal unsafe class AddonEventEntry
///
public required nint Node { get; init; }
- ///
- /// Gets the handler that gets called when this event is triggered.
- ///
- [Obsolete("Use AddonEventDelegate Delegate instead")]
- public IAddonEventManager.AddonEventHandler Handler { get; init; }
-
///
/// Gets the delegate that gets called when this event is triggered.
///
- [Api13ToDo("Make this field required")]
- public IAddonEventManager.AddonEventDelegate Delegate { get; init; }
+ public required IAddonEventManager.AddonEventDelegate Delegate { get; init; }
///
/// Gets the unique id for this event.
diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs
index 0990c1f5f..945197e2b 100644
--- a/Dalamud/Game/Addon/Events/AddonEventManager.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs
@@ -73,29 +73,6 @@ internal unsafe class AddonEventManager : IInternalDisposableService
this.addonLifecycle.UnregisterListener(this.finalizeEventListener);
}
- ///
- /// Registers an event handler for the specified addon, node, and type.
- ///
- /// Unique ID for this plugin.
- /// The parent addon for this event.
- /// The node that will trigger this event.
- /// The event type for this event.
- /// The handler to call when event is triggered.
- /// IAddonEventHandle used to remove the event.
- internal IAddonEventHandle? AddEvent(Guid pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
- {
- if (this.pluginEventControllers.TryGetValue(pluginId, out var controller))
- {
- return controller.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
- }
- else
- {
- Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
- }
-
- return null;
- }
-
///
/// Registers an event handler for the specified addon, node, and type.
///
@@ -260,10 +237,6 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo
}).Wait();
}
- ///
- public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
- => this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler);
-
///
public IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventDelegate eventDelegate)
=> this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventDelegate);
diff --git a/Dalamud/Game/Addon/Events/AddonEventType.cs b/Dalamud/Game/Addon/Events/AddonEventType.cs
index cd04152ca..2c88c797b 100644
--- a/Dalamud/Game/Addon/Events/AddonEventType.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventType.cs
@@ -121,12 +121,6 @@ public enum AddonEventType : byte
///
ListItemClick = 35,
- ///
- /// AtkComponentList Toggle.
- ///
- [Obsolete("Use ListItemClick")]
- ListItemToggle = 35,
-
///
/// AtkComponentList Double Click.
///
diff --git a/Dalamud/Game/Addon/Events/AddonEventData.cs b/Dalamud/Game/Addon/Events/EventDataTypes/AddonEventData.cs
similarity index 59%
rename from Dalamud/Game/Addon/Events/AddonEventData.cs
rename to Dalamud/Game/Addon/Events/EventDataTypes/AddonEventData.cs
index 3a5c05660..423bf5eb9 100644
--- a/Dalamud/Game/Addon/Events/AddonEventData.cs
+++ b/Dalamud/Game/Addon/Events/EventDataTypes/AddonEventData.cs
@@ -1,10 +1,32 @@
-namespace Dalamud.Game.Addon.Events;
+namespace Dalamud.Game.Addon.Events.EventDataTypes;
///
/// Object representing data that is relevant in handling native events.
///
public class AddonEventData
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AddonEventData()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Other event data to copy.
+ internal AddonEventData(AddonEventData eventData)
+ {
+ this.AtkEventType = eventData.AtkEventType;
+ this.Param = eventData.Param;
+ this.AtkEventPointer = eventData.AtkEventPointer;
+ this.AtkEventDataPointer = eventData.AtkEventDataPointer;
+ this.AddonPointer = eventData.AddonPointer;
+ this.NodeTargetPointer = eventData.NodeTargetPointer;
+ this.AtkEventListener = eventData.AtkEventListener;
+ }
+
///
/// Gets the AtkEventType for this event.
///
@@ -18,8 +40,8 @@ public class AddonEventData
///
/// Gets the pointer to the AtkEvent object for this event.
///
- /// Note: This is not a pointer to the AtkEventData object.
- /// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event.
+ /// Note: This is not a pointer to the AtkEventData object.
+ /// Warning: AtkEvent->Node has been modified to be the AtkUnitBase*, and AtkEvent->Target has been modified to be the AtkResNode* that triggered this event.
public nint AtkEventPointer { get; internal set; }
///
diff --git a/Dalamud/Game/Addon/Events/EventDataTypes/AddonMouseEventData.cs b/Dalamud/Game/Addon/Events/EventDataTypes/AddonMouseEventData.cs
new file mode 100644
index 000000000..27d56c287
--- /dev/null
+++ b/Dalamud/Game/Addon/Events/EventDataTypes/AddonMouseEventData.cs
@@ -0,0 +1,82 @@
+using System.Numerics;
+
+using FFXIVClientStructs.FFXIV.Component.GUI;
+
+using AtkMouseData = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData;
+using ModifierFlag = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData.ModifierFlag;
+
+namespace Dalamud.Game.Addon.Events.EventDataTypes;
+
+///
+public unsafe class AddonMouseEventData : AddonEventData
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal AddonMouseEventData()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Other event data to copy.
+ internal AddonMouseEventData(AddonEventData eventData)
+ : base(eventData)
+ {
+ }
+
+ ///
+ /// Gets a value indicating whether the event was a Left Mouse Click.
+ ///
+ public bool IsLeftClick => this.MouseData.ButtonId is 0;
+
+ ///
+ /// Gets a value indicating whether the event was a Right Mouse Click.
+ ///
+ public bool IsRightClick => this.MouseData.ButtonId is 1;
+
+ ///
+ /// Gets a value indicating whether there are any modifiers set such as alt, control, shift, or dragging.
+ ///
+ public bool IsNoModifier => this.MouseData.Modifier is 0;
+
+ ///
+ /// Gets a value indicating whether alt was being held when this event triggered.
+ ///
+ public bool IsAltHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Alt);
+
+ ///
+ /// Gets a value indicating whether control was being held when this event triggered.
+ ///
+ public bool IsControlHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Ctrl);
+
+ ///
+ /// Gets a value indicating whether shift was being held when this event triggered.
+ ///
+ public bool IsShiftHeld => this.MouseData.Modifier.HasFlag(ModifierFlag.Shift);
+
+ ///
+ /// Gets a value indicating whether this event is a mouse drag or not.
+ ///
+ public bool IsDragging => this.MouseData.Modifier.HasFlag(ModifierFlag.Dragging);
+
+ ///
+ /// Gets a value indicating whether the event was a scroll up.
+ ///
+ public bool IsScrollUp => this.MouseData.WheelDirection is 1;
+
+ ///
+ /// Gets a value indicating whether the event was a scroll down.
+ ///
+ public bool IsScrollDown => this.MouseData.WheelDirection is -1;
+
+ ///
+ /// Gets the position of the mouse when this event was triggered.
+ ///
+ public Vector2 Position => new(this.MouseData.PosX, this.MouseData.PosY);
+
+ private AtkEventData* AtkEventData => (AtkEventData*)this.AtkEventDataPointer;
+
+ private AtkMouseData MouseData => this.AtkEventData->MouseData;
+}
diff --git a/Dalamud/Game/Addon/Events/PluginEventController.cs b/Dalamud/Game/Addon/Events/PluginEventController.cs
index 0b1491e77..afaee9966 100644
--- a/Dalamud/Game/Addon/Events/PluginEventController.cs
+++ b/Dalamud/Game/Addon/Events/PluginEventController.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
+using Dalamud.Game.Addon.Events.EventDataTypes;
using Dalamud.Game.Gui;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
@@ -29,49 +30,6 @@ internal unsafe class PluginEventController : IDisposable
private List Events { get; } = new();
- ///
- /// Adds a tracked event.
- ///
- /// The Parent addon for the event.
- /// The Node for the event.
- /// The Event Type.
- /// The delegate to call when invoking this event.
- /// IAddonEventHandle used to remove the event.
- [Obsolete("Use AddEvent that uses AddonEventDelegate instead of AddonEventHandler")]
- public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
- {
- var node = (AtkResNode*)atkResNode;
- var addon = (AtkUnitBase*)atkUnitBase;
- var eventType = (AtkEventType)atkEventType;
- var eventId = this.GetNextParamKey();
- var eventGuid = Guid.NewGuid();
-
- var eventHandle = new AddonEventHandle
- {
- AddonName = addon->NameString,
- ParamKey = eventId,
- EventType = atkEventType,
- EventGuid = eventGuid,
- };
-
- var eventEntry = new AddonEventEntry
- {
- Addon = atkUnitBase,
- Handler = handler,
- Delegate = null,
- Node = atkResNode,
- EventType = atkEventType,
- ParamKey = eventId,
- Handle = eventHandle,
- };
-
- Log.Verbose($"Adding Event. {eventEntry.LogString}");
- this.EventListener.RegisterEvent(addon, node, eventType, eventId);
- this.Events.Add(eventEntry);
-
- return eventHandle;
- }
-
///
/// Adds a tracked event.
///
@@ -100,7 +58,6 @@ internal unsafe class PluginEventController : IDisposable
{
Addon = atkUnitBase,
Delegate = eventDelegate,
- Handler = null,
Node = atkResNode,
EventType = atkEventType,
ParamKey = eventId,
@@ -184,7 +141,7 @@ internal unsafe class PluginEventController : IDisposable
if (currentAddonPointer != eventEntry.Addon) return;
// Make sure the addon is not unloaded
- var atkUnitBase = (AtkUnitBase*)currentAddonPointer;
+ var atkUnitBase = currentAddonPointer.Struct;
if (atkUnitBase->UldManager.LoadedState == AtkLoadState.Unloaded) return;
// Does this addon contain the node this event is for? (by address)
@@ -230,7 +187,6 @@ internal unsafe class PluginEventController : IDisposable
this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey);
}
- [Api13ToDo("Remove invoke from eventInfo.Handler, and remove nullability from eventInfo.Delegate?.Invoke")]
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr)
{
try
@@ -238,10 +194,7 @@ internal unsafe class PluginEventController : IDisposable
if (eventPtr is null) return;
if (this.Events.FirstOrDefault(handler => handler.ParamKey == eventParam) is not { } eventInfo) return;
- // We stored the AtkUnitBase* in EventData->Node, and EventData->Target contains the node that triggered the event.
- eventInfo.Handler?.Invoke((AddonEventType)eventType, (nint)eventPtr->Node, (nint)eventPtr->Target);
-
- eventInfo.Delegate?.Invoke((AddonEventType)eventType, new AddonEventData
+ eventInfo.Delegate.Invoke((AddonEventType)eventType, new AddonEventData
{
AddonPointer = (nint)eventPtr->Node,
NodeTargetPointer = (nint)eventPtr->Target,
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
index 36083337e..c008db08f 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs
@@ -1,8 +1,4 @@
-using System.Runtime.CompilerServices;
-
-using Dalamud.Memory;
-
-using FFXIVClientStructs.FFXIV.Component.GUI;
+using Dalamud.Game.NativeWrapper;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
@@ -17,7 +13,6 @@ public abstract unsafe class AddonArgs
public const string InvalidAddon = "NullAddon";
private string? addonName;
- private IntPtr addon;
///
/// Gets the name of the addon this args referrers to.
@@ -27,10 +22,10 @@ public abstract unsafe class AddonArgs
///
/// Gets the pointer to the addons AtkUnitBase.
///
- public nint Addon
+ public AtkUnitBasePtr Addon
{
- get => this.AddonInternal;
- init => this.AddonInternal = value;
+ get;
+ internal set;
}
///
@@ -38,42 +33,32 @@ public abstract unsafe class AddonArgs
///
public abstract AddonArgsType Type { get; }
- ///
- /// Gets or sets the pointer to the addons AtkUnitBase.
- ///
- internal nint AddonInternal
- {
- get => this.addon;
- set
- {
- this.addon = value;
-
- // Note: always clear addonName on updating the addon being pointed.
- // Same address may point to a different addon.
- this.addonName = null;
- }
- }
-
///
/// Checks if addon name matches the given span of char.
///
/// The name to check.
/// Whether it is the case.
- internal bool IsAddon(ReadOnlySpan name)
+ internal bool IsAddon(string name)
{
- if (this.Addon == nint.Zero) return false;
- if (name.Length is 0 or > 0x20)
+ if (this.Addon.IsNull)
return false;
- var addonPointer = (AtkUnitBase*)this.Addon;
- if (addonPointer->Name[0] == 0) return false;
+ if (name.Length is 0 or > 32)
+ return false;
- // note: might want to rewrite this to just compare to NameString
- return MemoryHelper.EqualsZeroTerminatedString(
- name,
- (nint)Unsafe.AsPointer(ref addonPointer->Name[0]),
- null,
- 0x20);
+ if (string.IsNullOrEmpty(this.Addon.Name))
+ return false;
+
+ return name == this.Addon.Name;
+ }
+
+ ///
+ /// Clears this AddonArgs values.
+ ///
+ internal virtual void Clear()
+ {
+ this.addonName = null;
+ this.Addon = 0;
}
///
@@ -82,11 +67,13 @@ public abstract unsafe class AddonArgs
/// The name of the addon for this object. when invalid.
private string GetAddonName()
{
- if (this.Addon == nint.Zero) return InvalidAddon;
+ if (this.Addon.IsNull) return InvalidAddon;
- var addonPointer = (AtkUnitBase*)this.Addon;
- if (addonPointer->Name[0] == 0) return InvalidAddon;
+ var name = this.Addon.Name;
- return this.addonName ??= addonPointer->NameString;
+ if (string.IsNullOrEmpty(name))
+ return InvalidAddon;
+
+ return this.addonName ??= name;
}
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
index a557b0cb3..980fe4f2f 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonReceiveEventArgs.cs
@@ -41,4 +41,14 @@ public class AddonReceiveEventArgs : AddonArgs, ICloneable
///
object ICloneable.Clone() => this.Clone();
+
+ ///
+ internal override void Clear()
+ {
+ base.Clear();
+ this.AtkEventType = default;
+ this.EventParam = default;
+ this.AtkEvent = default;
+ this.Data = default;
+ }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
index 6e1b11ead..d28631c3c 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs
@@ -38,4 +38,12 @@ public class AddonRefreshArgs : AddonArgs, ICloneable
///
object ICloneable.Clone() => this.Clone();
+
+ ///
+ internal override void Clear()
+ {
+ base.Clear();
+ this.AtkValueCount = default;
+ this.AtkValues = default;
+ }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs
index 26357abb0..e87a980fd 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRequestedUpdateArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
///
/// Addon argument data for OnRequestedUpdate events.
@@ -31,4 +31,12 @@ public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
///
object ICloneable.Clone() => this.Clone();
+
+ ///
+ internal override void Clear()
+ {
+ base.Clear();
+ this.NumberArrayData = default;
+ this.StringArrayData = default;
+ }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
index 19c93ce25..0dd9ecee2 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs
@@ -1,4 +1,4 @@
-using FFXIVClientStructs.FFXIV.Component.GUI;
+using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
@@ -38,4 +38,12 @@ public class AddonSetupArgs : AddonArgs, ICloneable
///
object ICloneable.Clone() => this.Clone();
+
+ ///
+ internal override void Clear()
+ {
+ base.Clear();
+ this.AtkValueCount = default;
+ this.AtkValues = default;
+ }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs
index cc34a7531..a263f6ae4 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonUpdateArgs.cs
@@ -35,4 +35,11 @@ public class AddonUpdateArgs : AddonArgs, ICloneable
///
object ICloneable.Clone() => this.Clone();
+
+ ///
+ internal override void Clear()
+ {
+ base.Clear();
+ this.TimeDeltaInternal = default;
+ }
}
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
index b9179edde..b44ab8764 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
@@ -238,7 +238,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
}
using var returner = this.argsPool.Rent(out AddonSetupArgs arg);
- arg.AddonInternal = (nint)addon;
+ arg.Clear();
+ arg.Addon = (nint)addon;
arg.AtkValueCount = valueCount;
arg.AtkValues = (nint)values;
this.InvokeListenersSafely(AddonEvent.PreSetup, arg);
@@ -270,7 +271,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
}
using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg);
- arg.AddonInternal = (nint)atkUnitBase[0];
+ arg.Clear();
+ arg.Addon = (nint)atkUnitBase[0];
this.InvokeListenersSafely(AddonEvent.PreFinalize, arg);
try
@@ -286,7 +288,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
private void OnAddonDraw(AtkUnitBase* addon)
{
using var returner = this.argsPool.Rent(out AddonDrawArgs arg);
- arg.AddonInternal = (nint)addon;
+ arg.Clear();
+ arg.Addon = (nint)addon;
this.InvokeListenersSafely(AddonEvent.PreDraw, arg);
try
@@ -304,7 +307,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
{
using var returner = this.argsPool.Rent(out AddonUpdateArgs arg);
- arg.AddonInternal = (nint)addon;
+ arg.Clear();
+ arg.Addon = (nint)addon;
arg.TimeDeltaInternal = delta;
this.InvokeListenersSafely(AddonEvent.PreUpdate, arg);
@@ -325,7 +329,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
var result = false;
using var returner = this.argsPool.Rent(out AddonRefreshArgs arg);
- arg.AddonInternal = (nint)addon;
+ arg.Clear();
+ arg.Addon = (nint)addon;
arg.AtkValueCount = valueCount;
arg.AtkValues = (nint)values;
this.InvokeListenersSafely(AddonEvent.PreRefresh, arg);
@@ -348,7 +353,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg);
- arg.AddonInternal = (nint)addon;
+ arg.Clear();
+ arg.Addon = (nint)addon;
arg.NumberArrayData = (nint)numberArrayData;
arg.StringArrayData = (nint)stringArrayData;
this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg);
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs
index a66440b25..0d2bcc7f2 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Hooking;
@@ -86,7 +86,8 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
}
using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg);
- arg.AddonInternal = (nint)addon;
+ arg.Clear();
+ arg.Addon = (nint)addon;
arg.AtkEventType = (byte)eventType;
arg.EventParam = eventParam;
arg.AtkEvent = (IntPtr)atkEvent;
diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs
index 61cd88a05..c57dd70b8 100644
--- a/Dalamud/Game/ChatHandlers.cs
+++ b/Dalamud/Game/ChatHandlers.cs
@@ -119,8 +119,6 @@ internal partial class ChatHandlers : IServiceType
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !Util.AssemblyVersion.StartsWith(this.configuration.LastVersion))
{
var linkPayload = chatGui.AddChatLinkHandler(
- "dalamud",
- 8459324,
(_, _) => dalamudInterface.OpenPluginInstallerTo(PluginInstallerOpenKind.Changelogs));
var updateMessage = new SeStringBuilder()
diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs
index 244989476..89dd8b8b1 100644
--- a/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs
+++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteEntry.cs
@@ -97,7 +97,7 @@ internal sealed class AetheryteEntry : IAetheryteEntry
public uint GilCost => this.data.GilCost;
///
- public bool IsFavourite => this.data.IsFavourite != 0;
+ public bool IsFavourite => this.data.IsFavourite;
///
public bool IsSharedHouse => this.data.IsSharedHouse;
diff --git a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
index ef6649d7d..19451dd5c 100644
--- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
+++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs
@@ -63,6 +63,12 @@ public enum ConditionFlag
///
/// Unable to execute command while mounted.
///
+ RidingPillion = 10,
+
+ ///
+ /// Unable to execute command while mounted.
+ ///
+ [Obsolete("Renamed to RidingPillion", true)]
Mounted2 = 10,
///
@@ -188,12 +194,6 @@ public enum ConditionFlag
///
ExecutingCraftingAction = 40,
- ///
- /// Unable to execute command while crafting.
- ///
- [Obsolete("Renamed to ExecutingCraftingAction.")]
- Crafting40 = 40,
-
///
/// Unable to execute command while preparing to craft.
///
@@ -205,12 +205,6 @@ public enum ConditionFlag
/// Includes fishing.
ExecutingGatheringAction = 42,
- ///
- /// Unable to execute command while gathering.
- ///
- [Obsolete("Renamed to ExecutingGatheringAction.")]
- Gathering42 = 42,
-
///
/// Unable to execute command while fishing.
///
@@ -235,12 +229,6 @@ public enum ConditionFlag
///
Jumping = 48,
- ///
- /// Unable to execute command while auto-run is active.
- ///
- [Obsolete("To avoid confusion, renamed to UsingChocoboTaxi.")]
- AutorunActive = 49,
-
///
/// Unable to execute command while auto-run is active.
///
@@ -282,12 +270,6 @@ public enum ConditionFlag
///
BoundByDuty56 = 56,
- ///
- /// Unable to execute command at this time.
- ///
- [Obsolete("Renamed to MountOrOrnamentTransition.")]
- Unknown57 = 57,
-
///
/// Unable to execute command at this time.
///
@@ -449,6 +431,12 @@ public enum ConditionFlag
///
/// Unable to execute command in this state.
///
+ MountImmobile = 88,
+
+ ///
+ /// Unable to execute command in this state.
+ ///
+ [Obsolete("Renamed to MountImmobile", true)]
InThisState88 = 88,
///
@@ -461,12 +449,6 @@ public enum ConditionFlag
///
RolePlaying = 90,
- ///
- /// Unable to execute command while bound by duty.
- ///
- [Obsolete("Use InDutyQueue")]
- BoundToDuty97 = 91,
-
///
/// Unable to execute command while bound by duty.
/// Specifically triggered when you are in a queue for a duty but not inside a duty.
@@ -483,12 +465,6 @@ public enum ConditionFlag
///
WaitingToVisitOtherWorld = 93,
- ///
- /// Unable to execute command while using a parasol.
- ///
- [Obsolete("Renamed to UsingFashionAccessory.")]
- UsingParasol = 94,
-
///
/// Unable to execute command while using a fashion accessory.
///
diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs
index 2da2dde9d..504b690c3 100644
--- a/Dalamud/Game/ClientState/Fates/Fate.cs
+++ b/Dalamud/Game/ClientState/Fates/Fate.cs
@@ -3,7 +3,6 @@ using System.Numerics;
using Dalamud.Data;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
-using Dalamud.Utility;
using Lumina.Excel;
@@ -69,12 +68,6 @@ public interface IFate : IEquatable
///
byte Progress { get; }
- ///
- /// Gets a value indicating whether this has a EXP bonus.
- ///
- [Obsolete($"Use {nameof(HasBonus)} instead")]
- bool HasExpBonus { get; }
-
///
/// Gets a value indicating whether this has a bonus.
///
@@ -222,10 +215,6 @@ internal unsafe partial class Fate : IFate
///
public byte Progress => this.Struct->Progress;
- ///
- [Api13ToDo("Remove")]
- public bool HasExpBonus => this.HasBonus;
-
///
public bool HasBonus => this.Struct->IsBonus;
diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
index 5237c6f0c..ab4f8a03f 100644
--- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
@@ -1,13 +1,11 @@
using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services;
-
using FFXIVClientStructs.FFXIV.Client.System.Input;
-
-using ImGuiNET;
using Serilog;
namespace Dalamud.Game.ClientState.GamePad;
diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs
index f97385fce..84c1b5693 100644
--- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs
+++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs
@@ -66,6 +66,24 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
///
public int Length => objectTableLength;
+ ///
+ public IEnumerable PlayerObjects => this.GetPlayerObjects();
+
+ ///
+ public IEnumerable CharacterManagerObjects => this.GetObjectsInRange(..199);
+
+ ///
+ public IEnumerable ClientObjects => this.GetObjectsInRange(200..448);
+
+ ///
+ public IEnumerable EventObjects => this.GetObjectsInRange(449..488);
+
+ ///
+ public IEnumerable StandObjects => this.GetObjectsInRange(489..628);
+
+ ///
+ public IEnumerable ReactionEventObjects => this.GetObjectsInRange(629..728);
+
///
public IGameObject? this[int index]
{
@@ -146,6 +164,28 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
};
}
+ private IEnumerable GetPlayerObjects()
+ {
+ for (var index = 0; index < 200; index += 2)
+ {
+ if (this[index] is IBattleChara { ObjectKind: ObjectKind.Player } gameObject)
+ {
+ yield return gameObject;
+ }
+ }
+ }
+
+ private IEnumerable GetObjectsInRange(Range range)
+ {
+ for (var index = range.Start.Value; index <= range.End.Value; index++)
+ {
+ if (this[index] is { } gameObject)
+ {
+ yield return gameObject;
+ }
+ }
+ }
+
/// Stores an object table entry, with preallocated concrete types.
/// Initializes a new instance of the struct.
/// A pointer to the object table entry this entry should be pointing to.
diff --git a/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs b/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs
index efd8b5b3b..238c81a72 100644
--- a/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs
+++ b/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs
@@ -77,10 +77,10 @@ internal unsafe class BattleChara : Character, IBattleChara
public StatusList StatusList => new(this.Struct->GetStatusManager());
///
- public bool IsCasting => this.Struct->GetCastInfo()->IsCasting > 0;
+ public bool IsCasting => this.Struct->GetCastInfo()->IsCasting;
///
- public bool IsCastInterruptible => this.Struct->GetCastInfo()->Interruptible > 0;
+ public bool IsCastInterruptible => this.Struct->GetCastInfo()->Interruptible;
///
public byte CastActionType => (byte)this.Struct->GetCastInfo()->ActionType;
diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs
index cf620a7ef..65b752808 100644
--- a/Dalamud/Game/ClientState/Party/PartyMember.cs
+++ b/Dalamud/Game/ClientState/Party/PartyMember.cs
@@ -128,7 +128,7 @@ internal unsafe class PartyMember : IPartyMember
///
/// Gets the position of the party member.
///
- public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z);
+ public Vector3 Position => this.Struct->Position;
///
/// Gets the content ID of the party member.
diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs
index c3493ce55..2775f8f9b 100644
--- a/Dalamud/Game/ClientState/Statuses/Status.cs
+++ b/Dalamud/Game/ClientState/Statuses/Status.cs
@@ -55,7 +55,7 @@ public unsafe class Status
///
/// Gets the source ID of this status.
///
- public uint SourceId => this.Struct->SourceId;
+ public uint SourceId => this.Struct->SourceObject.ObjectId;
///
/// Gets the source actor associated with this status.
diff --git a/Dalamud/Game/Config/SystemConfigOption.cs b/Dalamud/Game/Config/SystemConfigOption.cs
index 154992637..dcfe16751 100644
--- a/Dalamud/Game/Config/SystemConfigOption.cs
+++ b/Dalamud/Game/Config/SystemConfigOption.cs
@@ -1031,6 +1031,13 @@ public enum SystemConfigOption
[GameConfigOption("TitleScreenType", ConfigType.UInt)]
TitleScreenType,
+ ///
+ /// System option with the internal name DisplayObjectLimitType2.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("DisplayObjectLimitType2", ConfigType.UInt)]
+ DisplayObjectLimitType2,
+
///
/// System option with the internal name AccessibilitySoundVisualEnable.
/// This option is a UInt.
@@ -1115,6 +1122,13 @@ public enum SystemConfigOption
[GameConfigOption("CameraZoom", ConfigType.UInt)]
CameraZoom,
+ ///
+ /// System option with the internal name DynamicAroundRangeMode.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("DynamicAroundRangeMode", ConfigType.UInt)]
+ DynamicAroundRangeMode,
+
///
/// System option with the internal name DynamicRezoType.
/// This option is a UInt.
diff --git a/Dalamud/Game/Config/UiConfigOption.cs b/Dalamud/Game/Config/UiConfigOption.cs
index 1a59b8945..f6a9aaa21 100644
--- a/Dalamud/Game/Config/UiConfigOption.cs
+++ b/Dalamud/Game/Config/UiConfigOption.cs
@@ -2032,6 +2032,13 @@ public enum UiConfigOption
[GameConfigOption("NamePlateDispJobIconInInstanceOther", ConfigType.UInt)]
NamePlateDispJobIconInInstanceOther,
+ ///
+ /// UiConfig option with the internal name LogNamePlateDispEnemyCast.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogNamePlateDispEnemyCast", ConfigType.UInt)]
+ LogNamePlateDispEnemyCast,
+
///
/// UiConfig option with the internal name ActiveInfo.
/// This option is a UInt.
@@ -2690,6 +2697,594 @@ public enum UiConfigOption
[GameConfigOption("LogColorOtherClass", ConfigType.UInt)]
LogColorOtherClass,
+ ///
+ /// UiConfig option with the internal name LogChatBubbleEnableChatBubble.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleEnableChatBubble", ConfigType.UInt)]
+ LogChatBubbleEnableChatBubble,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShowMax.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShowMax", ConfigType.UInt)]
+ LogChatBubbleShowMax,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShowOwnMessage.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShowOwnMessage", ConfigType.UInt)]
+ LogChatBubbleShowOwnMessage,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShowDuringBattle.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShowDuringBattle", ConfigType.UInt)]
+ LogChatBubbleShowDuringBattle,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleSizeType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleSizeType", ConfigType.UInt)]
+ LogChatBubbleSizeType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShowLargePvP.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShowLargePvP", ConfigType.UInt)]
+ LogChatBubbleShowLargePvP,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShowQuickChat.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShowQuickChat", ConfigType.UInt)]
+ LogChatBubbleShowQuickChat,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleDispRowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleDispRowType", ConfigType.UInt)]
+ LogChatBubbleDispRowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleSayShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleSayShowType", ConfigType.UInt)]
+ LogChatBubbleSayShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleSayFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleSayFontColor", ConfigType.UInt)]
+ LogChatBubbleSayFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleSayWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleSayWindowColor", ConfigType.UInt)]
+ LogChatBubbleSayWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleYellShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleYellShowType", ConfigType.UInt)]
+ LogChatBubbleYellShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleYellFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleYellFontColor", ConfigType.UInt)]
+ LogChatBubbleYellFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleYellWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleYellWindowColor", ConfigType.UInt)]
+ LogChatBubbleYellWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShoutShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShoutShowType", ConfigType.UInt)]
+ LogChatBubbleShoutShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShoutFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShoutFontColor", ConfigType.UInt)]
+ LogChatBubbleShoutFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleShoutWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleShoutWindowColor", ConfigType.UInt)]
+ LogChatBubbleShoutWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleTellShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleTellShowType", ConfigType.UInt)]
+ LogChatBubbleTellShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleTellFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleTellFontColor", ConfigType.UInt)]
+ LogChatBubbleTellFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleTellWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleTellWindowColor", ConfigType.UInt)]
+ LogChatBubbleTellWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubblePartyShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubblePartyShowType", ConfigType.UInt)]
+ LogChatBubblePartyShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubblePartyFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubblePartyFontColor", ConfigType.UInt)]
+ LogChatBubblePartyFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubblePartyWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubblePartyWindowColor", ConfigType.UInt)]
+ LogChatBubblePartyWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleAllianceShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleAllianceShowType", ConfigType.UInt)]
+ LogChatBubbleAllianceShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleAllianceFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleAllianceFontColor", ConfigType.UInt)]
+ LogChatBubbleAllianceFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleAllianceWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleAllianceWindowColor", ConfigType.UInt)]
+ LogChatBubbleAllianceWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleFcShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleFcShowType", ConfigType.UInt)]
+ LogChatBubbleFcShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleFcFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleFcFontColor", ConfigType.UInt)]
+ LogChatBubbleFcFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleFcWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleFcWindowColor", ConfigType.UInt)]
+ LogChatBubbleFcWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleBeginnerShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleBeginnerShowType", ConfigType.UInt)]
+ LogChatBubbleBeginnerShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleBeginnerFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleBeginnerFontColor", ConfigType.UInt)]
+ LogChatBubbleBeginnerFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleBeginnerWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleBeginnerWindowColor", ConfigType.UInt)]
+ LogChatBubbleBeginnerWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubblePvpteamShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubblePvpteamShowType", ConfigType.UInt)]
+ LogChatBubblePvpteamShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubblePvpteamFontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubblePvpteamFontColor", ConfigType.UInt)]
+ LogChatBubblePvpteamFontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubblePvpteamWindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubblePvpteamWindowColor", ConfigType.UInt)]
+ LogChatBubblePvpteamWindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs1ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs1ShowType", ConfigType.UInt)]
+ LogChatBubbleLs1ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs1FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs1FontColor", ConfigType.UInt)]
+ LogChatBubbleLs1FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs1WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs1WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs1WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs2ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs2ShowType", ConfigType.UInt)]
+ LogChatBubbleLs2ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs2FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs2FontColor", ConfigType.UInt)]
+ LogChatBubbleLs2FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs2WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs2WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs2WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs3ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs3ShowType", ConfigType.UInt)]
+ LogChatBubbleLs3ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs3FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs3FontColor", ConfigType.UInt)]
+ LogChatBubbleLs3FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs3WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs3WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs3WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs4ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs4ShowType", ConfigType.UInt)]
+ LogChatBubbleLs4ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs4FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs4FontColor", ConfigType.UInt)]
+ LogChatBubbleLs4FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs4WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs4WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs4WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs5ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs5ShowType", ConfigType.UInt)]
+ LogChatBubbleLs5ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs5FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs5FontColor", ConfigType.UInt)]
+ LogChatBubbleLs5FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs5WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs5WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs5WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs6ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs6ShowType", ConfigType.UInt)]
+ LogChatBubbleLs6ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs6FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs6FontColor", ConfigType.UInt)]
+ LogChatBubbleLs6FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs6WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs6WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs6WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs7ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs7ShowType", ConfigType.UInt)]
+ LogChatBubbleLs7ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs7FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs7FontColor", ConfigType.UInt)]
+ LogChatBubbleLs7FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs7WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs7WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs7WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs8ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs8ShowType", ConfigType.UInt)]
+ LogChatBubbleLs8ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs8FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs8FontColor", ConfigType.UInt)]
+ LogChatBubbleLs8FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleLs8WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleLs8WindowColor", ConfigType.UInt)]
+ LogChatBubbleLs8WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls1ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls1ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls1ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls1FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls1FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls1FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls1WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls1WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls1WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls2ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls2ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls2ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls2FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls2FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls2FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls2WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls2WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls2WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls3ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls3ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls3ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls3FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls3FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls3FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls3WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls3WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls3WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls4ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls4ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls4ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls4FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls4FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls4FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls4WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls4WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls4WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls5ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls5ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls5ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls5FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls5FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls5FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls5WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls5WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls5WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls6ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls6ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls6ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls6FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls6FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls6FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls6WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls6WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls6WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls7ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls7ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls7ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls7FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls7FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls7FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls7WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls7WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls7WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls8ShowType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls8ShowType", ConfigType.UInt)]
+ LogChatBubbleCwls8ShowType,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls8FontColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls8FontColor", ConfigType.UInt)]
+ LogChatBubbleCwls8FontColor,
+
+ ///
+ /// UiConfig option with the internal name LogChatBubbleCwls8WindowColor.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogChatBubbleCwls8WindowColor", ConfigType.UInt)]
+ LogChatBubbleCwls8WindowColor,
+
+ ///
+ /// UiConfig option with the internal name LogPermeationRateInput.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("LogPermeationRateInput", ConfigType.UInt)]
+ LogPermeationRateInput,
+
///
/// UiConfig option with the internal name ChatType.
/// This option is a UInt.
@@ -3453,6 +4048,27 @@ public enum UiConfigOption
[GameConfigOption("GPoseMotionFilterAction", ConfigType.UInt)]
GPoseMotionFilterAction,
+ ///
+ /// UiConfig option with the internal name GPoseRollRotationCameraCorrection.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("GPoseRollRotationCameraCorrection", ConfigType.UInt)]
+ GPoseRollRotationCameraCorrection,
+
+ ///
+ /// UiConfig option with the internal name GPoseInitCameraFocus.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("GPoseInitCameraFocus", ConfigType.UInt)]
+ GPoseInitCameraFocus,
+
+ ///
+ /// UiConfig option with the internal name GposePortraitRotateType.
+ /// This option is a UInt.
+ ///
+ [GameConfigOption("GposePortraitRotateType", ConfigType.UInt)]
+ GposePortraitRotateType,
+
///
/// UiConfig option with the internal name LsListSortPriority.
/// This option is a UInt.
diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs
index 022968c7e..72b8ef8d1 100644
--- a/Dalamud/Game/Gui/ChatGui.cs
+++ b/Dalamud/Game/Gui/ChatGui.cs
@@ -12,6 +12,7 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
+using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@@ -50,6 +51,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
private readonly DalamudConfiguration configuration = Service.Get();
private ImmutableDictionary<(string PluginName, uint CommandId), Action>? dalamudLinkHandlersCopy;
+ private uint dalamudChatHandlerId = 1000;
[ServiceManager.ServiceConstructor]
private ChatGui()
@@ -161,6 +163,42 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
#endregion
+ #region Chat Links
+
+ ///
+ /// Register a chat link handler.
+ ///
+ /// Internal use only.
+ /// The action to be executed.
+ /// Returns an SeString payload for the link.
+ public DalamudLinkPayload AddChatLinkHandler(Action commandAction)
+ {
+ return this.AddChatLinkHandler("Dalamud", this.dalamudChatHandlerId++, commandAction);
+ }
+
+ ///
+ /// Internal use only.
+ public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction)
+ {
+ return this.AddChatLinkHandler("Dalamud", commandId, commandAction);
+ }
+
+ ///
+ /// Internal use only.
+ public void RemoveChatLinkHandler(uint commandId)
+ {
+ this.RemoveChatLinkHandler("Dalamud", commandId);
+ }
+
+ ///
+ /// Internal use only.
+ public void RemoveChatLinkHandler()
+ {
+ this.RemoveChatLinkHandler("Dalamud");
+ }
+
+ #endregion
+
///
/// Process a chat queue.
///
@@ -220,7 +258,6 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
/// The ID of the command to run.
/// The command action itself.
/// A payload for handling.
- [Api13ToDo("Plugins should not specify their own command IDs here. We should assign them ourselves.")]
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction)
{
var payload = new DalamudLinkPayload { Plugin = pluginName, CommandId = commandId };
@@ -478,11 +515,15 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
[ServiceManager.ServiceDependency]
private readonly ChatGui chatGuiService = Service.Get();
+ private readonly LocalPlugin plugin;
+
///
/// Initializes a new instance of the class.
///
- internal ChatGuiPluginScoped()
+ /// The plugin.
+ internal ChatGuiPluginScoped(LocalPlugin plugin)
{
+ this.plugin = plugin;
this.chatGuiService.ChatMessage += this.OnMessageForward;
this.chatGuiService.CheckMessageHandled += this.OnCheckMessageForward;
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
@@ -524,6 +565,18 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
this.ChatMessageUnhandled = null;
}
+ ///
+ public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction)
+ => this.chatGuiService.AddChatLinkHandler(this.plugin.InternalName, commandId, commandAction);
+
+ ///
+ public void RemoveChatLinkHandler(uint commandId)
+ => this.chatGuiService.RemoveChatLinkHandler(this.plugin.InternalName, commandId);
+
+ ///
+ public void RemoveChatLinkHandler()
+ => this.chatGuiService.RemoveChatLinkHandler(this.plugin.InternalName);
+
///
public void Print(XivChatEntry chat)
=> this.chatGuiService.Print(chat);
diff --git a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs
index fb78e6b80..7512f4160 100644
--- a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs
+++ b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs
@@ -451,14 +451,14 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
case ContextMenuType.Default:
{
var ownerAddonId = ((AgentContext*)this.SelectedAgent)->OwnerAddon;
- module->OpenAddon(this.AddonContextSubNameId, (uint)valueCount, values, this.SelectedAgent, 71, checked((ushort)ownerAddonId), 4);
+ module->OpenAddon(this.AddonContextSubNameId, (uint)valueCount, values, &this.SelectedAgent->AtkEventInterface, 71, checked((ushort)ownerAddonId), 4);
break;
}
case ContextMenuType.Inventory:
{
var ownerAddonId = ((AgentInventoryContext*)this.SelectedAgent)->OwnerAddonId;
- module->OpenAddon(this.AddonContextSubNameId, (uint)valueCount, values, this.SelectedAgent, 0, checked((ushort)ownerAddonId), 4);
+ module->OpenAddon(this.AddonContextSubNameId, (uint)valueCount, values, &this.SelectedAgent->AtkEventInterface, 0, checked((ushort)ownerAddonId), 4);
break;
}
diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs
index 6f3f9a8dd..dac34f38d 100644
--- a/Dalamud/Game/Gui/Dtr/DtrBar.cs
+++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs
@@ -5,6 +5,7 @@ using System.Threading;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Addon.Events;
+using Dalamud.Game.Addon.Events.EventDataTypes;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.Text.SeStringHandling;
@@ -330,7 +331,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
this.entriesReadOnlyCopy = null;
}
- private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR").ToPointer();
+ private AtkUnitBase* GetDtr() => this.gameGui.GetAddonByName("_DTR").Struct;
private void Update(IFramework unused)
{
@@ -427,7 +428,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
private void FixCollision(AddonEvent eventType, AddonArgs addonInfo)
{
- var addon = (AtkUnitBase*)addonInfo.Addon;
+ var addon = addonInfo.Addon.Struct;
if (addon->RootNode is null || addon->UldManager.NodeList is null) return;
float minX = addon->RootNode->Width;
@@ -585,8 +586,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
newTextNode->LineSpacing = 12;
newTextNode->AlignmentFontType = 5;
newTextNode->FontSize = 14;
- newTextNode->TextFlags = (byte)TextFlags.Edge;
- newTextNode->TextFlags2 = 0;
+ newTextNode->TextFlags = TextFlags.Edge;
if (this.emptyString == null)
this.emptyString = Utf8String.FromString(" ");
@@ -596,24 +596,13 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
newTextNode->TextColor = new ByteColor { R = 255, G = 255, B = 255, A = 255 };
newTextNode->EdgeColor = new ByteColor { R = 142, G = 106, B = 12, A = 255 };
- // ICreatable was restored, this may be necessary if AtkUldManager.CreateAtkTextNode(); is used instead of Create
- // // Memory is filled with random data after being created, zero out some things to avoid issues.
- // newTextNode->UnkPtr_1 = null;
- // newTextNode->SelectStart = 0;
- // newTextNode->SelectEnd = 0;
- // newTextNode->FontCacheHandle = 0;
- // newTextNode->CharSpacing = 0;
- // newTextNode->BackgroundColor = new ByteColor { R = 0, G = 0, B = 0, A = 0 };
- // newTextNode->TextId = 0;
- // newTextNode->SheetType = 0;
-
return newTextNode;
}
- private void DtrEventHandler(AddonEventType atkEventType, IntPtr atkUnitBase, IntPtr atkResNode)
+ private void DtrEventHandler(AddonEventType atkEventType, AddonEventData eventData)
{
- var addon = (AtkUnitBase*)atkUnitBase;
- var node = (AtkResNode*)atkResNode;
+ var addon = (AtkUnitBase*)eventData.AddonPointer;
+ var node = (AtkResNode*)eventData.NodeTargetPointer;
DtrBarEntry? dtrBarEntry = null;
this.entriesLock.EnterReadLock();
@@ -652,7 +641,7 @@ internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
break;
case AddonEventType.MouseClick:
- dtrBarEntry.OnClick.Invoke();
+ dtrBarEntry.OnClick?.Invoke(new AddonMouseEventData(eventData));
break;
}
}
diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs
index 26708eb4c..54847705d 100644
--- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs
+++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs
@@ -1,4 +1,5 @@
using Dalamud.Configuration.Internal;
+using Dalamud.Game.Addon.Events.EventDataTypes;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility;
@@ -42,12 +43,6 @@ public interface IReadOnlyDtrBarEntry
/// Gets a value indicating whether the user has hidden this entry from view through the Dalamud settings.
///
public bool UserHidden { get; }
-
- ///
- /// Triggers the click action of this entry.
- ///
- /// True, if a click action was registered and executed.
- public bool TriggerClickAction();
}
///
@@ -71,9 +66,9 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry
public new bool Shown { get; set; }
///
- /// Gets or sets a action to be invoked when the user clicks on the dtr entry.
+ /// Gets or sets an action to be invoked when the user clicks on the dtr entry.
///
- public Action? OnClick { get; set; }
+ public Action? OnClick { get; set; }
///
/// Remove this entry from the bar.
@@ -122,10 +117,8 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
///
public SeString? Tooltip { get; set; }
- ///
- /// Gets or sets a action to be invoked when the user clicks on the dtr entry.
- ///
- public Action? OnClick { get; set; }
+ ///
+ public Action? OnClick { get; set; }
///
public bool HasClickAction => this.OnClick != null;
@@ -145,7 +138,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
}
///
- [Api13ToDo("Maybe make this config scoped to internalname?")]
+ [Api13ToDo("Maybe make this config scoped to internal name?")]
public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false;
///
@@ -178,16 +171,6 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
///
internal LocalPlugin? OwnerPlugin { get; set; }
- ///
- public bool TriggerClickAction()
- {
- if (this.OnClick == null)
- return false;
-
- this.OnClick.Invoke();
- return true;
- }
-
///
/// Remove this entry from the bar.
/// You will need to re-acquire it from DtrBar to reuse it.
diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs
index ada9021c4..415dd44c6 100644
--- a/Dalamud/Game/Gui/GameGui.cs
+++ b/Dalamud/Game/Gui/GameGui.cs
@@ -1,5 +1,7 @@
using System.Runtime.InteropServices;
+using Dalamud.Bindings.ImGui;
+using Dalamud.Game.NativeWrapper;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Hooking;
using Dalamud.Interface.Utility;
@@ -18,8 +20,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using FFXIVClientStructs.FFXIV.Component.GUI;
-using ImGuiNET;
-
using Vector2 = System.Numerics.Vector2;
using Vector3 = System.Numerics.Vector3;
using Vector4 = System.Numerics.Vector4;
@@ -167,79 +167,59 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
}
///
- public IntPtr GetUIModule()
+ public UIModulePtr GetUIModule()
{
- var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance();
- if (framework == null)
- return IntPtr.Zero;
-
- var uiModule = framework->GetUIModule();
- if (uiModule == null)
- return IntPtr.Zero;
-
- return (IntPtr)uiModule;
+ return (nint)UIModule.Instance();
}
///
- public IntPtr GetAddonByName(string name, int index = 1)
+ public AtkUnitBasePtr GetAddonByName(string name, int index = 1)
{
- var atkStage = AtkStage.Instance();
- if (atkStage == null)
- return IntPtr.Zero;
+ var unitManager = RaptureAtkUnitManager.Instance();
+ if (unitManager == null)
+ return 0;
- var unitMgr = atkStage->RaptureAtkUnitManager;
- if (unitMgr == null)
- return IntPtr.Zero;
-
- var addon = unitMgr->GetAddonByName(name, index);
- if (addon == null)
- return IntPtr.Zero;
-
- return (IntPtr)addon;
+ return (nint)unitManager->GetAddonByName(name, index);
}
///
- public IntPtr FindAgentInterface(string addonName)
+ public AgentInterfacePtr GetAgentById(int id)
+ {
+ var agentModule = AgentModule.Instance();
+ if (agentModule == null || id < 0 || id >= agentModule->Agents.Length)
+ return 0;
+
+ return (nint)agentModule->Agents[id].Value;
+ }
+
+ ///
+ public AgentInterfacePtr FindAgentInterface(string addonName)
{
var addon = this.GetAddonByName(addonName);
return this.FindAgentInterface(addon);
}
///
- public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon);
-
- ///
- public IntPtr FindAgentInterface(IntPtr addonPtr)
+ public AgentInterfacePtr FindAgentInterface(AtkUnitBasePtr addon)
{
- if (addonPtr == IntPtr.Zero)
- return IntPtr.Zero;
+ if (addon.IsNull)
+ return 0;
- var uiModule = (UIModule*)this.GetUIModule();
- if (uiModule == null)
- return IntPtr.Zero;
-
- var agentModule = uiModule->GetAgentModule();
+ var agentModule = AgentModule.Instance();
if (agentModule == null)
- return IntPtr.Zero;
-
- var addon = (AtkUnitBase*)addonPtr;
- var addonId = addon->ParentId == 0 ? addon->Id : addon->ParentId;
+ return 0;
+ var addonId = addon.ParentId == 0 ? addon.Id : addon.ParentId;
if (addonId == 0)
- return IntPtr.Zero;
+ return 0;
- var index = 0;
- while (true)
+ foreach (AgentInterface* agent in agentModule->Agents)
{
- var agent = agentModule->GetAgentByInternalId((AgentId)index++);
- if (agent == uiModule || agent == null)
- break;
-
- if (agent->AddonId == addonId)
- return new IntPtr(agent);
+ if (agent != null && agent->AddonId == addonId)
+ return (nint)agent;
}
- return IntPtr.Zero;
+ return 0;
}
///
@@ -315,7 +295,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
return ret;
}
- private void HandleActionHoverDetour(AgentActionDetail* hoverState, FFXIVClientStructs.FFXIV.Client.UI.Agent.ActionKind actionKind, uint actionId, int a4, byte a5)
+ private void HandleActionHoverDetour(AgentActionDetail* hoverState, FFXIVClientStructs.FFXIV.Client.UI.Agent.ActionKind actionKind, uint actionId, int a4, bool a5)
{
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
this.HoveredAction.ActionKind = (HoverActionKind)actionKind;
@@ -454,25 +434,25 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
=> this.gameGuiService.ScreenToWorld(screenPos, out worldPos, rayDistance);
///
- public IntPtr GetUIModule()
+ public UIModulePtr GetUIModule()
=> this.gameGuiService.GetUIModule();
///
- public IntPtr GetAddonByName(string name, int index = 1)
+ public AtkUnitBasePtr GetAddonByName(string name, int index = 1)
=> this.gameGuiService.GetAddonByName(name, index);
///
- public IntPtr FindAgentInterface(string addonName)
+ public AgentInterfacePtr GetAgentById(int id)
+ => this.gameGuiService.GetAgentById(id);
+
+ ///
+ public AgentInterfacePtr FindAgentInterface(string addonName)
=> this.gameGuiService.FindAgentInterface(addonName);
///
- public unsafe IntPtr FindAgentInterface(void* addon)
+ public AgentInterfacePtr FindAgentInterface(AtkUnitBasePtr addon)
=> this.gameGuiService.FindAgentInterface(addon);
- ///
- public IntPtr FindAgentInterface(IntPtr addonPtr)
- => this.gameGuiService.FindAgentInterface(addonPtr);
-
private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled);
private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId);
diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs
index cd84b996b..7f83f180c 100644
--- a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs
+++ b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs
@@ -72,7 +72,7 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
///
public unsafe void RequestRedraw()
{
- var addon = (AddonNamePlate*)this.gameGui.GetAddonByName("NamePlate");
+ var addon = (AddonNamePlate*)(nint)this.gameGui.GetAddonByName("NamePlate");
if (addon != null)
{
addon->DoFullUpdate = 1;
diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs
index 475892205..5d6130cc1 100644
--- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs
+++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs
@@ -127,7 +127,7 @@ public enum JobFlags : ulong
RedMage = 1ul << 24,
///
- /// Blue mage (BLM).
+ /// Blue mage (BLU).
///
BlueMage = 1ul << 25,
diff --git a/Dalamud/Game/Internal/Completion.cs b/Dalamud/Game/Internal/Completion.cs
deleted file mode 100644
index 01c9c99c5..000000000
--- a/Dalamud/Game/Internal/Completion.cs
+++ /dev/null
@@ -1,322 +0,0 @@
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-
-using Dalamud.Game.Command;
-using Dalamud.Game.Text.SeStringHandling;
-using Dalamud.Hooking;
-using Dalamud.Plugin.Services;
-using Dalamud.Utility;
-
-using FFXIVClientStructs.FFXIV.Client.System.String;
-using FFXIVClientStructs.FFXIV.Client.UI;
-using FFXIVClientStructs.FFXIV.Component.Completion;
-using FFXIVClientStructs.FFXIV.Component.GUI;
-
-namespace Dalamud.Game.Internal;
-
-///
-/// This class adds dalamud and plugin commands to the chat box's autocompletion.
-///
-[ServiceManager.EarlyLoadedService]
-internal sealed unsafe class Completion : IInternalDisposableService
-{
- // 0xFF is a magic group number that causes CompletionModule's internals to treat entries
- // as raw strings instead of as lookups into an EXD sheet
- private const int GroupNumber = 0xFF;
-
- [ServiceManager.ServiceDependency]
- private readonly CommandManager commandManager = Service.Get();
-
- [ServiceManager.ServiceDependency]
- private readonly Framework framework = Service.Get();
-
- private readonly Dictionary cachedCommands = [];
- private readonly ConcurrentQueue addedCommands = [];
-
- private EntryStrings? dalamudCategory;
-
- private Hook? getSelection;
-
- // This is marked volatile since we set and check it from different threads. Instead of using a synchronization
- // primitive, a volatile is sufficient since the absolute worst case is that we delay one extra frame to reset
- // the list, which is fine
- private volatile bool needsClear;
- private bool disposed;
- private nint wantedVtblPtr;
-
- ///
- /// Initializes a new instance of the class.
- ///
- [ServiceManager.ServiceConstructor]
- internal Completion()
- {
- this.commandManager.CommandAdded += this.OnCommandAdded;
- this.commandManager.CommandRemoved += this.OnCommandRemoved;
-
- this.framework.Update += this.OnUpdate;
- }
-
- /// Finalizes an instance of the class.
- ~Completion() => this.Dispose(false);
-
- ///
- void IInternalDisposableService.DisposeService() => this.Dispose(true);
-
- private static AtkUnitBase* FindOwningAddon(AtkComponentTextInput* component)
- {
- if (component == null) return null;
-
- var node = (AtkResNode*)component->OwnerNode;
- if (node == null) return null;
-
- while (node->ParentNode != null)
- node = node->ParentNode;
-
- foreach (var addon in RaptureAtkUnitManager.Instance()->AllLoadedUnitsList.Entries)
- {
- if (addon.Value->RootNode == node)
- return addon;
- }
-
- return null;
- }
-
- private AtkComponentTextInput* GetActiveTextInput()
- {
- var mod = RaptureAtkModule.Instance();
- if (mod == null) return null;
-
- var basePtr = mod->TextInput.TargetTextInputEventInterface;
- if (basePtr == null) return null;
-
- // Once CS has an implementation for multiple inheritance, we can remove this sig from dalamud
- // as well as the nasty pointer arithmetic below. But for now, we need to do this manually.
- // The AtkTextInputEventInterface* is the secondary base class for AtkComponentTextInput*
- // so the pointer is sizeof(AtkComponentInputBase) into the object. We verify that we're looking
- // at the object we think we are by confirming the pointed-to vtbl matches the known secondary vtbl for
- // AtkComponentTextInput, and if it does, we can shift the pointer back to get the start of our text input
- if (this.wantedVtblPtr == 0)
- {
- this.wantedVtblPtr =
- Service.Get().GetStaticAddressFromSig(
- "48 89 01 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 8B 48 68",
- 4);
- }
-
- var vtblPtr = *(nint*)basePtr;
- if (vtblPtr != this.wantedVtblPtr) return null;
-
- // This needs to be updated if the layout/base order of AtkComponentTextInput changes
- return (AtkComponentTextInput*)((AtkComponentInputBase*)basePtr - 1);
- }
-
- private bool AllowCompletion(string cmd)
- {
- // this is one of our commands, let's see if we should allow this to be completed
- var component = this.GetActiveTextInput();
-
- // ContainingAddon or ContainingAddon2 aren't always populated, but they
- // seem to be in any case where this is actually a completable AtkComponentTextInput
- // In the worst case, we can walk the AtkNode tree- but let's try the easy pointers first
- var addon = component->ContainingAddon;
- if (addon == null) addon = component->ContainingAddon2;
- if (addon == null) addon = FindOwningAddon(component);
-
- if (addon == null || addon->NameString != "ChatLog")
- {
- // we don't know what addon is completing, or we know it isn't ChatLog
- // either way, we should just reject this completion
- return false;
- }
-
- // We're in ChatLog, so check if this is the start of the text input
- // AtkComponentTextInput->UnkText1 is the evaluated version of the current text
- // so if the command starts with that, then either it's empty or a prefix completion.
- // In either case, we're happy to allow completion.
- return cmd.StartsWith(component->UnkText1.StringPtr.ExtractText());
- }
-
- private void Dispose(bool disposing)
- {
- if (this.disposed)
- return;
-
- if (disposing)
- {
- this.getSelection?.Disable();
- this.getSelection?.Dispose();
- this.framework.Update -= this.OnUpdate;
- this.commandManager.CommandAdded -= this.OnCommandAdded;
- this.commandManager.CommandRemoved -= this.OnCommandRemoved;
-
- this.dalamudCategory?.Dispose();
- this.ClearCachedCommands();
- }
-
- this.disposed = true;
- }
-
- private void OnCommandAdded(object? sender, CommandManager.CommandEventArgs e)
- {
- if (e.CommandInfo.ShowInHelp)
- this.addedCommands.Enqueue(e.Command);
- }
-
- private void OnCommandRemoved(object? sender, CommandManager.CommandEventArgs e) => this.needsClear = true;
-
- private void OnUpdate(IFramework fw)
- {
- var atkModule = RaptureAtkModule.Instance();
- if (atkModule == null) return;
-
- var textInput = &atkModule->TextInput;
-
- if (textInput->CompletionModule == null) return;
-
- // Before we change _anything_ we need to check the state of the UI- if the completion list is open
- // changes to the underlying data are extremely unsafe, so we'll just wait until the next frame
- // worst case, someone tries to complete a command that _just_ got unloaded so it won't do anything
- // but that's the same as making a typo, really
- if (textInput->CompletionDepth > 0) return;
-
- // Create the category for Dalamud commands.
- // This needs to be done here, since we cannot create Utf8Strings before the game
- // has initialized (no allocator set up yet).
- this.dalamudCategory ??= new EntryStrings("【Dalamud】");
-
- this.LoadCommands(textInput->CompletionModule);
- }
-
- private CategoryData* EnsureCategoryData(CompletionModule* module)
- {
- if (module == null) return null;
-
- if (this.getSelection == null)
- {
- this.getSelection = Hook.FromAddress(
- (IntPtr)module->VirtualTable->GetSelection,
- this.GetSelectionDetour);
- this.getSelection.Enable();
- }
-
- for (var i = 0; i < module->CategoryNames.Count; i++)
- {
- if (module->CategoryNames[i].AsReadOnlySeStringSpan().ContainsText("【Dalamud】"u8))
- {
- return module->CategoryData[i];
- }
- }
-
- // Create the category since we don't have one
- var categoryData = (CategoryData*)Memory.MemoryHelper.GameAllocateDefault((ulong)sizeof(CategoryData));
- categoryData->Ctor(GroupNumber, 0xFF);
- module->AddCategoryData(GroupNumber, this.dalamudCategory!.Display->StringPtr,
- this.dalamudCategory.Match->StringPtr, categoryData);
-
- return categoryData;
- }
-
- private void ClearCachedCommands()
- {
- if (this.cachedCommands.Count == 0)
- return;
-
- foreach (var entry in this.cachedCommands.Values)
- {
- entry.Dispose();
- }
-
- this.cachedCommands.Clear();
- }
-
- private void LoadCommands(CompletionModule* completionModule)
- {
- if (completionModule == null) return;
- if (completionModule->CategoryNames.Count == 0) return; // We want this data populated first
-
- if (this.needsClear && this.cachedCommands.Count > 0)
- {
- this.needsClear = false;
- completionModule->ClearCompletionData();
- this.ClearCachedCommands();
- return;
- }
-
- var catData = this.EnsureCategoryData(completionModule);
- if (catData == null) return;
-
- if (catData->CompletionData.Count == 0)
- {
- var inputCommands = this.commandManager.Commands.Where(pair => pair.Value.ShowInHelp);
- foreach (var (cmd, _) in inputCommands)
- AddEntry(cmd);
- catData->SortEntries();
-
- return;
- }
-
- var needsSort = false;
- while (this.addedCommands.TryDequeue(out var cmd))
- {
- needsSort = true;
- AddEntry(cmd);
- }
-
- if (needsSort)
- catData->SortEntries();
-
- return;
-
- void AddEntry(string cmd)
- {
- if (this.cachedCommands.ContainsKey(cmd)) return;
-
- var cmdStr = new EntryStrings(cmd);
- this.cachedCommands.Add(cmd, cmdStr);
- completionModule->AddCompletionEntry(
- GroupNumber,
- 0xFF,
- cmdStr.Display->StringPtr,
- cmdStr.Match->StringPtr,
- 0xFF);
- }
- }
-
- private int GetSelectionDetour(CompletionModule* thisPtr, CategoryData.CompletionDataStruct* dataStructs, int index, Utf8String* outputString, Utf8String* outputDisplayString)
- {
- var ret = this.getSelection!.Original.Invoke(thisPtr, dataStructs, index, outputString, outputDisplayString);
- if (ret != -2 || outputString == null) return ret;
-
- // -2 means it was a plain text final selection, so it might be ours
- // Unfortunately, the code that uses this string mangles the color macro for some reason...
- // We'll just strip those out since we don't need the color in the chatbox
- var txt = outputString->StringPtr.ExtractText();
- if (!this.cachedCommands.ContainsKey(txt))
- return ret;
-
- if (!this.AllowCompletion(txt))
- {
- outputString->Clear();
- if (outputDisplayString != null) outputDisplayString->Clear();
- return ret;
- }
-
- outputString->SetString(txt + " ");
- return ret;
- }
-
- private class EntryStrings(string command) : IDisposable
- {
- public Utf8String* Display { get; } =
- Utf8String.FromSequence(new SeStringBuilder().AddUiForeground(command, 539).BuiltString.EncodeWithNullTerminator());
-
- public Utf8String* Match { get; } = Utf8String.FromString(command);
-
- public void Dispose()
- {
- this.Display->Dtor(true);
- this.Match->Dtor(true);
- }
- }
-}
diff --git a/Dalamud/Game/Internal/DalamudCompletion.cs b/Dalamud/Game/Internal/DalamudCompletion.cs
new file mode 100644
index 000000000..ec5652b3c
--- /dev/null
+++ b/Dalamud/Game/Internal/DalamudCompletion.cs
@@ -0,0 +1,279 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using Dalamud.Game.Command;
+using Dalamud.Hooking;
+using Dalamud.Utility;
+
+using FFXIVClientStructs.FFXIV.Client.System.Memory;
+using FFXIVClientStructs.FFXIV.Client.System.String;
+using FFXIVClientStructs.FFXIV.Client.UI;
+using FFXIVClientStructs.FFXIV.Component.Completion;
+using FFXIVClientStructs.FFXIV.Component.GUI;
+
+using Lumina.Text;
+
+namespace Dalamud.Game.Internal;
+
+///
+/// This class adds Dalamud and plugin commands to the chat box's autocompletion.
+///
+[ServiceManager.EarlyLoadedService]
+internal sealed unsafe class DalamudCompletion : IInternalDisposableService
+{
+ // 0xFF is a magic group number that causes CompletionModule's internals to treat entries
+ // as raw strings instead of as lookups into an EXD sheet
+ private const int GroupNumber = 0xFF;
+
+ [ServiceManager.ServiceDependency]
+ private readonly CommandManager commandManager = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly Framework framework = Service.Get();
+
+ private readonly Dictionary cachedCommands = [];
+
+ private EntryStrings? dalamudCategory;
+
+ private Hook openSuggestionsHook;
+ private Hook? getSelectionHook;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [ServiceManager.ServiceConstructor]
+ internal DalamudCompletion()
+ {
+ this.framework.RunOnTick(this.Setup);
+ }
+
+ ///
+ void IInternalDisposableService.DisposeService()
+ {
+ this.openSuggestionsHook?.Disable();
+ this.openSuggestionsHook?.Dispose();
+
+ this.getSelectionHook?.Disable();
+ this.getSelectionHook?.Dispose();
+
+ this.dalamudCategory?.Dispose();
+
+ this.ClearCachedCommands();
+ }
+
+ private void Setup()
+ {
+ var uiModule = UIModule.Instance();
+ if (uiModule == null || uiModule->FrameCount == 0)
+ {
+ this.framework.RunOnTick(this.Setup);
+ return;
+ }
+
+ this.dalamudCategory = new EntryStrings("【Dalamud】");
+
+ this.openSuggestionsHook = Hook.FromAddress(
+ (nint)AtkTextInput.MemberFunctionPointers.OpenCompletion,
+ this.OpenSuggestionsDetour);
+
+ this.getSelectionHook = Hook.FromAddress(
+ (nint)uiModule->CompletionModule.VirtualTable->GetSelection,
+ this.GetSelectionDetour);
+
+ this.openSuggestionsHook.Enable();
+ this.getSelectionHook.Enable();
+ }
+
+ private void OpenSuggestionsDetour(AtkTextInput* thisPtr)
+ {
+ this.UpdateCompletionData();
+ this.openSuggestionsHook!.Original(thisPtr);
+ }
+
+ private int GetSelectionDetour(CompletionModule* thisPtr, CategoryData.CompletionDataStruct* dataStructs, int index, Utf8String* outputString, Utf8String* outputDisplayString)
+ {
+ var ret = this.getSelectionHook!.Original.Invoke(thisPtr, dataStructs, index, outputString, outputDisplayString);
+ this.HandleInsert(ret, outputString, outputDisplayString);
+ return ret;
+ }
+
+ private void UpdateCompletionData()
+ {
+ if (!this.TryGetActiveTextInput(out var component, out var addon))
+ {
+ if (this.HasDalamudCategory())
+ this.ResetCompletionData();
+
+ return;
+ }
+
+ var uiModule = UIModule.Instance();
+ if (uiModule == null)
+ return;
+
+ this.ResetCompletionData();
+ this.ClearCachedCommands();
+
+ var currentText = component->UnkText1.StringPtr.ExtractText();
+
+ var commands = this.commandManager.Commands
+ .Where(kv => kv.Value.ShowInHelp && (currentText.Length == 0 || kv.Key.StartsWith(currentText)))
+ .OrderBy(kv => kv.Key);
+
+ if (!commands.Any())
+ return;
+
+ var categoryData = (CategoryData*)IMemorySpace.GetDefaultSpace()->Malloc((ulong)sizeof(CategoryData), 0x08);
+ categoryData->Ctor(GroupNumber, 0xFF);
+
+ uiModule->CompletionModule.AddCategoryData(
+ GroupNumber,
+ this.dalamudCategory!.Display->StringPtr,
+ this.dalamudCategory.Match->StringPtr, categoryData);
+
+ foreach (var (cmd, info) in commands)
+ {
+ if (!this.cachedCommands.TryGetValue(cmd, out var entryString))
+ this.cachedCommands.Add(cmd, entryString = new EntryStrings(cmd));
+
+ uiModule->CompletionModule.AddCompletionEntry(
+ GroupNumber,
+ 0xFF,
+ entryString.Display->StringPtr,
+ entryString.Match->StringPtr,
+ 0xFF);
+ }
+
+ categoryData->SortEntries();
+ }
+
+ private void HandleInsert(int ret, Utf8String* outputString, Utf8String* outputDisplayString)
+ {
+ // -2 means it was a plain text final selection, so it might be ours.
+ if (ret != -2 || outputString == null)
+ return;
+
+ // Strip out color payloads that we added to the string.
+ var txt = outputString->StringPtr.ExtractText();
+ if (!this.cachedCommands.ContainsKey(txt))
+ return;
+
+ if (!this.TryGetActiveTextInput(out _, out _))
+ {
+ outputString->Clear();
+
+ if (outputDisplayString != null)
+ outputDisplayString->Clear();
+
+ return;
+ }
+
+ outputString->SetString(txt + ' ');
+ }
+
+ private bool TryGetActiveTextInput(out AtkComponentTextInput* component, out AtkUnitBase* addon)
+ {
+ component = null;
+ addon = null;
+
+ var raptureAtkModule = RaptureAtkModule.Instance();
+ if (raptureAtkModule == null)
+ return false;
+
+ var textInputEventInterface = raptureAtkModule->TextInput.TargetTextInputEventInterface;
+ if (textInputEventInterface == null)
+ return false;
+
+ var ownerNode = textInputEventInterface->GetOwnerNode();
+ if (ownerNode == null || ownerNode->GetNodeType() != NodeType.Component)
+ return false;
+
+ var componentNode = (AtkComponentNode*)ownerNode;
+ var componentBase = componentNode->Component;
+ if (componentBase == null || componentBase->GetComponentType() != ComponentType.TextInput)
+ return false;
+
+ component = (AtkComponentTextInput*)componentBase;
+
+ addon = component->ContainingAddon;
+
+ if (addon == null)
+ addon = component->ContainingAddon2;
+
+ if (addon == null)
+ addon = RaptureAtkUnitManager.Instance()->GetAddonByNode((AtkResNode*)component->OwnerNode);
+
+ return addon != null && addon->NameString == "ChatLog";
+ }
+
+ private bool HasDalamudCategory()
+ {
+ var uiModule = UIModule.Instance();
+ if (uiModule == null)
+ return false;
+
+ for (var i = 0; i < uiModule->CompletionModule.CategoryNames.Count; i++)
+ {
+ if (uiModule->CompletionModule.CategoryNames[i].AsReadOnlySeStringSpan().ContainsText("【Dalamud】"u8))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void ResetCompletionData()
+ {
+ var uiModule = UIModule.Instance();
+ if (uiModule == null)
+ return;
+
+ uiModule->CompletionModule.ClearCompletionData();
+
+ // This happens in UIModule.Update. Just repeat it to fill CompletionData back up with defaults.
+ uiModule->CompletionModule.Update(
+ &uiModule->CompletionSheetName,
+ &uiModule->CompletionOpenIconMacro,
+ &uiModule->CompletionCloseIconMacro,
+ 0);
+ }
+
+ private void ClearCachedCommands()
+ {
+ foreach (var entry in this.cachedCommands.Values)
+ {
+ entry.Dispose();
+ }
+
+ this.cachedCommands.Clear();
+ }
+
+ private class EntryStrings : IDisposable
+ {
+ public EntryStrings(string command)
+ {
+ var rssb = SeStringBuilder.SharedPool.Get();
+
+ this.Display = Utf8String.FromSequence(rssb
+ .PushColorType(539)
+ .Append(command)
+ .PopColorType()
+ .GetViewAsSpan());
+
+ SeStringBuilder.SharedPool.Return(rssb);
+
+ this.Match = Utf8String.FromString(command);
+ }
+
+ public Utf8String* Display { get; }
+
+ public Utf8String* Match { get; }
+
+ public void Dispose()
+ {
+ this.Display->Dtor(true);
+ this.Match->Dtor(true);
+ }
+ }
+}
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 085200742..af35262d6 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -1,7 +1,9 @@
+using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Dalamud.Data;
+using Dalamud.Game.Inventory.Records;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
@@ -160,6 +162,29 @@ public unsafe struct GameInventoryItem : IEquatable
}
}
+ ///
+ /// Gets a list of materia entries for this item. Exists as a user-friendly interface to and
+ /// .
+ ///
+ public IReadOnlyList MateriaEntries
+ {
+ get
+ {
+ if (ItemUtil.IsEventItem(this.BaseItemId) || this.IsMateriaUsedForDate)
+ return [];
+
+ var result = new List();
+ for (byte i = 0; i < this.InternalItem.GetMateriaCount(); i++)
+ {
+ var entry = new MateriaEntry(this.InternalItem.GetMateriaId(i), this.InternalItem.GetMateriaGrade(i));
+ if (entry.IsValid())
+ result.Add(entry);
+ }
+
+ return result;
+ }
+ }
+
///
/// Gets the address of native inventory item in the game.
/// Can be 0 if this instance of does not point to a valid set of container type and slot.
diff --git a/Dalamud/Game/Inventory/Records/MateriaEntry.cs b/Dalamud/Game/Inventory/Records/MateriaEntry.cs
new file mode 100644
index 000000000..4c7528123
--- /dev/null
+++ b/Dalamud/Game/Inventory/Records/MateriaEntry.cs
@@ -0,0 +1,42 @@
+using Dalamud.Data;
+
+using Lumina.Excel;
+using Lumina.Excel.Sheets;
+
+namespace Dalamud.Game.Inventory.Records;
+
+///
+/// A record to hold easy information about a given piece of Materia.
+///
+public record MateriaEntry
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID of the materia.
+ /// The grade of the materia.
+ public MateriaEntry(ushort typeId, byte gradeValue)
+ {
+ this.Type = LuminaUtils.CreateRef(typeId);
+ this.Grade = LuminaUtils.CreateRef(gradeValue);
+ }
+
+ ///
+ /// Gets the Lumina row for this Materia.
+ ///
+ public RowRef Type { get; }
+
+ ///
+ /// Gets the Lumina row for this Materia's grade.
+ ///
+ public RowRef Grade { get; }
+
+ ///
+ /// Checks if this MateriaEntry is valid.
+ ///
+ /// True if valid, false otherwise.
+ internal bool IsValid()
+ {
+ return this.Type.IsValid && this.Grade.IsValid;
+ }
+}
diff --git a/Dalamud/Game/NativeWrapper/AgentInterfacePtr.cs b/Dalamud/Game/NativeWrapper/AgentInterfacePtr.cs
new file mode 100644
index 000000000..b5a6375a9
--- /dev/null
+++ b/Dalamud/Game/NativeWrapper/AgentInterfacePtr.cs
@@ -0,0 +1,96 @@
+using System.Runtime.InteropServices;
+
+using FFXIVClientStructs.FFXIV.Client.UI;
+using FFXIVClientStructs.FFXIV.Client.UI.Agent;
+
+namespace Dalamud.Game.NativeWrapper;
+
+///
+/// A readonly wrapper for AgentInterface.
+///
+/// The address to the AgentInterface.
+[StructLayout(LayoutKind.Explicit, Size = 0x08)]
+public readonly unsafe struct AgentInterfacePtr(nint address) : IEquatable
+{
+ ///
+ /// The address to the AgentInterface.
+ ///
+ [FieldOffset(0x00)]
+ public readonly nint Address = address;
+
+ ///
+ /// Gets a value indicating whether the underlying pointer is a nullptr.
+ ///
+ public readonly bool IsNull => this.Address == 0;
+
+ ///
+ /// Gets a value indicating whether the agents addon is visible.
+ ///
+ public readonly AtkUnitBasePtr Addon
+ {
+ get
+ {
+ if (this.IsNull)
+ return 0;
+
+ var raptureAtkUnitManager = RaptureAtkUnitManager.Instance();
+ if (raptureAtkUnitManager == null)
+ return 0;
+
+ return (nint)raptureAtkUnitManager->GetAddonById(this.AddonId);
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the agent is active.
+ ///
+ public readonly ushort AddonId => (ushort)(this.IsNull ? 0 : this.Struct->GetAddonId());
+
+ ///
+ /// Gets a value indicating whether the agent is active.
+ ///
+ public readonly bool IsAgentActive => !this.IsNull && this.Struct->IsAgentActive();
+
+ ///
+ /// Gets a value indicating whether the agents addon is ready.
+ ///
+ public readonly bool IsAddonReady => !this.IsNull && this.Struct->IsAddonReady();
+
+ ///
+ /// Gets a value indicating whether the agents addon is visible.
+ ///
+ public readonly bool IsAddonShown => !this.IsNull && this.Struct->IsAddonShown();
+
+ ///
+ /// Gets the AgentInterface*.
+ ///
+ /// Internal use only.
+ internal readonly AgentInterface* Struct => (AgentInterface*)this.Address;
+
+ public static implicit operator nint(AgentInterfacePtr wrapper) => wrapper.Address;
+
+ public static implicit operator AgentInterfacePtr(nint address) => new(address);
+
+ public static implicit operator AgentInterfacePtr(void* ptr) => new((nint)ptr);
+
+ public static bool operator ==(AgentInterfacePtr left, AgentInterfacePtr right) => left.Address == right.Address;
+
+ public static bool operator !=(AgentInterfacePtr left, AgentInterfacePtr right) => left.Address != right.Address;
+
+ ///
+ /// Focuses the AtkUnitBase.
+ ///
+ /// true when the addon was focused, false otherwise.
+ public readonly bool FocusAddon() => this.IsNull && this.Struct->FocusAddon();
+
+ /// Determines whether the specified AgentInterfacePtr is equal to the current AgentInterfacePtr.
+ /// The AgentInterfacePtr to compare with the current AgentInterfacePtr.
+ /// true if the specified AgentInterfacePtr is equal to the current AgentInterfacePtr; otherwise, false.
+ public readonly bool Equals(AgentInterfacePtr other) => this.Address == other.Address;
+
+ ///
+ public override readonly bool Equals(object obj) => obj is AgentInterfacePtr wrapper && this.Equals(wrapper);
+
+ ///
+ public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode();
+}
diff --git a/Dalamud/Game/NativeWrapper/AtkUnitBasePtr.cs b/Dalamud/Game/NativeWrapper/AtkUnitBasePtr.cs
new file mode 100644
index 000000000..d4ebf4d3b
--- /dev/null
+++ b/Dalamud/Game/NativeWrapper/AtkUnitBasePtr.cs
@@ -0,0 +1,171 @@
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+using FFXIVClientStructs.FFXIV.Component.GUI;
+using FFXIVClientStructs.Interop;
+
+namespace Dalamud.Game.NativeWrapper;
+
+///
+/// A readonly wrapper for AtkUnitBase.
+///
+/// The address to the AtkUnitBase.
+[StructLayout(LayoutKind.Explicit, Size = 0x08)]
+public readonly unsafe struct AtkUnitBasePtr(nint address) : IEquatable
+{
+ ///
+ /// The address to the AtkUnitBase.
+ ///
+ [FieldOffset(0x00)]
+ public readonly nint Address = address;
+
+ ///
+ /// Gets a value indicating whether the underlying pointer is a nullptr.
+ ///
+ public readonly bool IsNull => this.Address == 0;
+
+ ///
+ /// Gets a value indicating whether the OnSetup function has been called.
+ ///
+ public readonly bool IsReady => !this.IsNull && this.Struct->IsReady;
+
+ ///
+ /// Gets a value indicating whether the AtkUnitBase is visible.
+ ///
+ public readonly bool IsVisible => !this.IsNull && this.Struct->IsVisible;
+
+ ///
+ /// Gets the name.
+ ///
+ public readonly string Name => this.IsNull ? string.Empty : this.Struct->NameString;
+
+ ///
+ /// Gets the id.
+ ///
+ public readonly ushort Id => this.IsNull ? (ushort)0 : this.Struct->Id;
+
+ ///
+ /// Gets the parent id.
+ ///
+ public readonly ushort ParentId => this.IsNull ? (ushort)0 : this.Struct->ParentId;
+
+ ///
+ /// Gets the host id.
+ ///
+ public readonly ushort HostId => this.IsNull ? (ushort)0 : this.Struct->HostId;
+
+ ///
+ /// Gets the scale.
+ ///
+ public readonly float Scale => this.IsNull ? 0f : this.Struct->Scale;
+
+ ///
+ /// Gets the x-position.
+ ///
+ public readonly short X => this.IsNull ? (short)0 : this.Struct->X;
+
+ ///
+ /// Gets the y-position.
+ ///
+ public readonly short Y => this.IsNull ? (short)0 : this.Struct->Y;
+
+ ///
+ /// Gets the width.
+ ///
+ public readonly float Width => this.IsNull ? 0f : this.Struct->GetScaledWidth(false);
+
+ ///
+ /// Gets the height.
+ ///
+ public readonly float Height => this.IsNull ? 0f : this.Struct->GetScaledHeight(false);
+
+ ///
+ /// Gets the scaled width.
+ ///
+ public readonly float ScaledWidth => this.IsNull ? 0f : this.Struct->GetScaledWidth(true);
+
+ ///
+ /// Gets the scaled height.
+ ///
+ public readonly float ScaledHeight => this.IsNull ? 0f : this.Struct->GetScaledHeight(true);
+
+ ///
+ /// Gets the position.
+ ///
+ public readonly Vector2 Position => new(this.X, this.Y);
+
+ ///
+ /// Gets the size.
+ ///
+ public readonly Vector2 Size => new(this.Width, this.Height);
+
+ ///
+ /// Gets the scaled size.
+ ///
+ public readonly Vector2 ScaledSize => new(this.ScaledWidth, this.ScaledHeight);
+
+ ///
+ /// Gets the number of entries.
+ ///
+ public readonly int AtkValuesCount => this.Struct->AtkValuesCount;
+
+ ///
+ /// Gets an enumerable collection of of the addons current AtkValues.
+ ///
+ ///
+ /// An of corresponding to the addons AtkValues.
+ ///
+ public IEnumerable AtkValues
+ {
+ get
+ {
+ for (var i = 0; i < this.AtkValuesCount; i++)
+ {
+ AtkValuePtr ptr;
+ unsafe
+ {
+ ptr = new AtkValuePtr((nint)this.Struct->AtkValuesSpan.GetPointer(i));
+ }
+
+ yield return ptr;
+ }
+ }
+ }
+
+ ///
+ /// Gets the AtkUnitBase*.
+ ///
+ /// Internal use only.
+ internal readonly AtkUnitBase* Struct => (AtkUnitBase*)this.Address;
+
+ public static implicit operator nint(AtkUnitBasePtr wrapper) => wrapper.Address;
+
+ public static implicit operator AtkUnitBasePtr(nint address) => new(address);
+
+ public static implicit operator AtkUnitBasePtr(void* ptr) => new((nint)ptr);
+
+ public static bool operator ==(AtkUnitBasePtr left, AtkUnitBasePtr right) => left.Address == right.Address;
+
+ public static bool operator !=(AtkUnitBasePtr left, AtkUnitBasePtr right) => left.Address != right.Address;
+
+ ///
+ /// Focuses the AtkUnitBase.
+ ///
+ public readonly void Focus()
+ {
+ if (!this.IsNull)
+ this.Struct->Focus();
+ }
+
+ /// Determines whether the specified AtkUnitBasePtr is equal to the current AtkUnitBasePtr.
+ /// The AtkUnitBasePtr to compare with the current AtkUnitBasePtr.
+ /// true if the specified AtkUnitBasePtr is equal to the current AtkUnitBasePtr; otherwise, false.
+ public readonly bool Equals(AtkUnitBasePtr other) => this.Address == other.Address;
+
+ ///
+ public override readonly bool Equals(object obj) => obj is AtkUnitBasePtr wrapper && this.Equals(wrapper);
+
+ ///
+ public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode();
+}
diff --git a/Dalamud/Game/NativeWrapper/AtkValuePtr.cs b/Dalamud/Game/NativeWrapper/AtkValuePtr.cs
new file mode 100644
index 000000000..a47483a66
--- /dev/null
+++ b/Dalamud/Game/NativeWrapper/AtkValuePtr.cs
@@ -0,0 +1,113 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+using Dalamud.Utility;
+
+using FFXIVClientStructs.FFXIV.Component.GUI;
+
+namespace Dalamud.Game.NativeWrapper;
+
+///
+/// A readonly wrapper for AtkValue.
+///
+/// The address to the AtkValue.
+[StructLayout(LayoutKind.Explicit, Size = 0x08)]
+public readonly unsafe struct AtkValuePtr(nint address) : IEquatable
+{
+ ///
+ /// The address to the AtkValue.
+ ///
+ [FieldOffset(0x00)]
+ public readonly nint Address = address;
+
+ ///
+ /// Gets a value indicating whether the underlying pointer is a nullptr.
+ ///
+ public readonly bool IsNull => this.Address == 0;
+
+ ///
+ /// Gets the value type.
+ ///
+ public readonly AtkValueType ValueType => (AtkValueType)this.Struct->Type;
+
+ ///
+ /// Gets the AtkValue*.
+ ///
+ /// Internal use only.
+ internal readonly AtkValue* Struct => (AtkValue*)this.Address;
+
+ public static implicit operator nint(AtkValuePtr wrapper) => wrapper.Address;
+
+ public static implicit operator AtkValuePtr(nint address) => new(address);
+
+ public static implicit operator AtkValuePtr(void* ptr) => new((nint)ptr);
+
+ public static bool operator ==(AtkValuePtr left, AtkValuePtr right) => left.Address == right.Address;
+
+ public static bool operator !=(AtkValuePtr left, AtkValuePtr right) => left.Address != right.Address;
+
+ ///
+ /// Gets the value of the underlying as a boxed object, based on its .
+ ///
+ ///
+ /// The boxed value represented by this , or null if the value is null or undefined.
+ ///
+ ///
+ /// Thrown if the value type is not currently handled by this implementation.
+ ///
+ public unsafe object? GetValue()
+ {
+ if (this.Struct == null)
+ return null;
+
+ return this.ValueType switch
+ {
+ AtkValueType.Undefined or AtkValueType.Null => null,
+ AtkValueType.Bool => this.Struct->Bool,
+ AtkValueType.Int => this.Struct->Int,
+ AtkValueType.Int64 => this.Struct->Int64,
+ AtkValueType.UInt => this.Struct->UInt,
+ AtkValueType.UInt64 => this.Struct->UInt64,
+ AtkValueType.Float => this.Struct->Float,
+ AtkValueType.String or AtkValueType.String8 or AtkValueType.ManagedString => this.Struct->String.HasValue ? this.Struct->String.AsReadOnlySeString() : default,
+ AtkValueType.WideString => this.Struct->WideString == null ? string.Empty : new string(this.Struct->WideString),
+ AtkValueType.Pointer => (nint)this.Struct->Pointer,
+ _ => throw new NotImplementedException($"AtkValueType {this.ValueType} is currently not supported"),
+ };
+ }
+
+ ///
+ /// Attempts to retrieve the value as a strongly-typed object if the underlying type matches.
+ ///
+ /// The expected value type to extract.
+ ///
+ /// When this method returns true, contains the extracted value of type .
+ /// Otherwise, contains the default value of type .
+ ///
+ ///
+ /// true if the value was successfully extracted and matched ; otherwise, false.
+ ///
+ public unsafe bool TryGet([NotNullWhen(true)] out T? result) where T : struct
+ {
+ object? value = this.GetValue();
+ if (value is T typed)
+ {
+ result = typed;
+ return true;
+ }
+
+ result = default;
+ return false;
+ }
+
+ /// Determines whether the specified AtkValuePtr is equal to the current AtkValuePtr.
+ /// The AtkValuePtr to compare with the current AtkValuePtr.
+ /// true if the specified AtkValuePtr is equal to the current AtkValuePtr; otherwise, false.
+ public readonly bool Equals(AtkValuePtr other) => this.Address == other.Address || this.Struct->EqualTo(other.Struct);
+
+ ///
+ public override readonly bool Equals(object obj) => obj is AtkValuePtr wrapper && this.Equals(wrapper);
+
+ ///
+ public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode();
+}
diff --git a/Dalamud/Game/NativeWrapper/AtkValueType.cs b/Dalamud/Game/NativeWrapper/AtkValueType.cs
new file mode 100644
index 000000000..ef169e102
--- /dev/null
+++ b/Dalamud/Game/NativeWrapper/AtkValueType.cs
@@ -0,0 +1,87 @@
+namespace Dalamud.Game.NativeWrapper;
+
+///
+/// Represents the data type of the AtkValue.
+///
+public enum AtkValueType
+{
+ ///
+ /// The value is undefined or invalid.
+ ///
+ Undefined = 0,
+
+ ///
+ /// The value is null.
+ ///
+ Null = 0x1,
+
+ ///
+ /// The value is a boolean.
+ ///
+ Bool = 0x2,
+
+ ///
+ /// The value is a 32-bit signed integer.
+ ///
+ Int = 0x3,
+
+ ///
+ /// The value is a 64-bit signed integer.
+ ///
+ Int64 = 0x4,
+
+ ///
+ /// The value is a 32-bit unsigned integer.
+ ///
+ UInt = 0x5,
+
+ ///
+ /// The value is a 64-bit unsigned integer.
+ ///
+ UInt64 = 0x6,
+
+ ///
+ /// The value is a 32-bit floating-point number.
+ ///
+ Float = 0x7,
+
+ ///
+ /// The value points to a null-terminated 8-bit character string (ASCII or UTF-8).
+ ///
+ String = 0x8,
+
+ ///
+ /// The value points to a null-terminated 16-bit character string (UTF-16 / wide string).
+ ///
+ WideString = 0x9,
+
+ ///
+ /// The value points to a constant null-terminated 8-bit character string (const char*).
+ ///
+ String8 = 0xA,
+
+ ///
+ /// The value is a vector.
+ ///
+ Vector = 0xB,
+
+ ///
+ /// The value is a pointer.
+ ///
+ Pointer = 0xC,
+
+ ///
+ /// The value is pointing to an array of AtkValue entries.
+ ///
+ AtkValues = 0xD,
+
+ ///
+ /// The value is a managed string. See .
+ ///
+ ManagedString = 0x28,
+
+ ///
+ /// The value is a managed vector. See .
+ ///
+ ManagedVector = 0x2B,
+}
diff --git a/Dalamud/Game/NativeWrapper/UIModulePtr.cs b/Dalamud/Game/NativeWrapper/UIModulePtr.cs
new file mode 100644
index 000000000..f6b841610
--- /dev/null
+++ b/Dalamud/Game/NativeWrapper/UIModulePtr.cs
@@ -0,0 +1,51 @@
+using System.Runtime.InteropServices;
+
+using FFXIVClientStructs.FFXIV.Client.UI;
+
+namespace Dalamud.Game.NativeWrapper;
+
+///
+/// A readonly wrapper for UIModule.
+///
+/// The address to the UIModule.
+[StructLayout(LayoutKind.Explicit, Size = 0x08)]
+public readonly unsafe struct UIModulePtr(nint address) : IEquatable
+{
+ ///
+ /// The address to the UIModule.
+ ///
+ [FieldOffset(0x00)]
+ public readonly nint Address = address;
+
+ ///
+ /// Gets a value indicating whether the underlying pointer is a nullptr.
+ ///
+ public readonly bool IsNull => this.Address == 0;
+
+ ///
+ /// Gets the UIModule*.
+ ///
+ /// Internal use only.
+ internal readonly UIModule* Struct => (UIModule*)this.Address;
+
+ public static implicit operator nint(UIModulePtr wrapper) => wrapper.Address;
+
+ public static implicit operator UIModulePtr(nint address) => new(address);
+
+ public static implicit operator UIModulePtr(void* ptr) => new((nint)ptr);
+
+ public static bool operator ==(UIModulePtr left, UIModulePtr right) => left.Address == right.Address;
+
+ public static bool operator !=(UIModulePtr left, UIModulePtr right) => left.Address != right.Address;
+
+ /// Determines whether the specified UIModulePtr is equal to the current UIModulePtr.
+ /// The UIModulePtr to compare with the current UIModulePtr.
+ /// true if the specified UIModulePtr is equal to the current UIModulePtr; otherwise, false.
+ public readonly bool Equals(UIModulePtr other) => this.Address == other.Address;
+
+ ///
+ public override readonly bool Equals(object obj) => obj is UIModulePtr wrapper && this.Equals(wrapper);
+
+ ///
+ public override readonly int GetHashCode() => ((nuint)this.Address).GetHashCode();
+}
diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs
index 5c21add0f..be464ef34 100644
--- a/Dalamud/Game/Network/GameNetwork.cs
+++ b/Dalamud/Game/Network/GameNetwork.cs
@@ -17,7 +17,7 @@ namespace Dalamud.Game.Network;
/// This class handles interacting with game network events.
///
[ServiceManager.EarlyLoadedService]
-internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetwork
+internal sealed unsafe class GameNetwork : IInternalDisposableService
{
private readonly GameNetworkAddressResolver address;
private readonly Hook processZonePacketDownHook;
@@ -51,11 +51,23 @@ internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetw
this.processZonePacketUpHook.Enable();
}
+ ///
+ /// The delegate type of a network message event.
+ ///
+ /// The pointer to the raw data.
+ /// The operation ID code.
+ /// The source actor ID.
+ /// The taret actor ID.
+ /// The direction of the packed.
+ public delegate void OnNetworkMessageDelegate(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction);
+
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
- ///
- public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
+ ///
+ /// Event that is called when a network message is sent/received.
+ ///
+ public event OnNetworkMessageDelegate? NetworkMessage;
///
void IInternalDisposableService.DisposeService()
@@ -136,39 +148,3 @@ internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetw
return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4);
}
}
-
-///
-/// Plugin-scoped version of a AddonLifecycle service.
-///
-[PluginInterface]
-[ServiceManager.ScopedService]
-#pragma warning disable SA1015
-[ResolveVia]
-#pragma warning restore SA1015
-internal class GameNetworkPluginScoped : IInternalDisposableService, IGameNetwork
-{
- [ServiceManager.ServiceDependency]
- private readonly GameNetwork gameNetworkService = Service.Get();
-
- ///
- /// Initializes a new instance of the class.
- ///
- internal GameNetworkPluginScoped()
- {
- this.gameNetworkService.NetworkMessage += this.NetworkMessageForward;
- }
-
- ///
- public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
-
- ///
- void IInternalDisposableService.DisposeService()
- {
- this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward;
-
- this.NetworkMessage = null;
- }
-
- private void NetworkMessageForward(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
- => this.NetworkMessage?.Invoke(dataPtr, opCode, sourceActorId, targetActorId, direction);
-}
diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs
index 7d6304655..9b85d0ff3 100644
--- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs
+++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs
@@ -275,7 +275,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
if (uploaderId == 0)
{
var playerState = PlayerState.Instance();
- if (playerState->IsLoaded == 1)
+ if (playerState->IsLoaded)
{
uploaderId = playerState->ContentId;
}
diff --git a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs
index 18c48b67d..9cd46f798 100644
--- a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs
+++ b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs
@@ -16,6 +16,6 @@ internal class NetworkHandlersAddressResolver : BaseAddressResolver
{
this.CustomTalkEventResponsePacketHandler =
scanner.ScanText(
- "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); // unnamed in CS
+ "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 44 0F B6 54 24 ?? 44 0F B6 CF 44 88 54 24 ?? 44 0F B7 C6 8B D5"); // unnamed in CS
}
}
diff --git a/Dalamud/Game/Network/Internal/WinSockHandlers.cs b/Dalamud/Game/Network/Internal/WinSockHandlers.cs
index 384858cfe..4b002021a 100644
--- a/Dalamud/Game/Network/Internal/WinSockHandlers.cs
+++ b/Dalamud/Game/Network/Internal/WinSockHandlers.cs
@@ -55,4 +55,37 @@ internal sealed class WinSockHandlers : IInternalDisposableService
return socket;
}
+
+ ///
+ /// Native ws2_32 functions.
+ ///
+ private static class NativeFunctions
+ {
+ ///
+ /// See https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt.
+ /// The setsockopt function sets a socket option.
+ ///
+ ///
+ /// A descriptor that identifies a socket.
+ ///
+ ///
+ /// The level at which the option is defined (for example, SOL_SOCKET).
+ ///
+ ///
+ /// The socket option for which the value is to be set (for example, SO_BROADCAST). The optname parameter must be a
+ /// socket option defined within the specified level, or behavior is undefined.
+ ///
+ ///
+ /// A pointer to the buffer in which the value for the requested option is specified.
+ ///
+ ///
+ /// The size, in bytes, of the buffer pointed to by the optval parameter.
+ ///
+ ///
+ /// If no error occurs, setsockopt returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error
+ /// code can be retrieved by calling WSAGetLastError.
+ ///
+ [DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi, EntryPoint = "setsockopt")]
+ public static extern int SetSockOpt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen);
+ }
}
diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs
index 5aaf17f12..dc1e54e77 100644
--- a/Dalamud/Game/SigScanner.cs
+++ b/Dalamud/Game/SigScanner.cs
@@ -21,6 +21,8 @@ namespace Dalamud.Game;
///
public class SigScanner : IDisposable, ISigScanner
{
+ private static byte[]? fileBytes;
+
private readonly FileInfo? cacheFile;
private nint moduleCopyPtr;
@@ -51,12 +53,16 @@ public class SigScanner : IDisposable, ISigScanner
this.Is32BitProcess = !Environment.Is64BitProcess;
this.IsCopy = doCopy;
+ if (this.IsCopy)
+ {
+ this.SetupCopiedSegments();
+
+ fileBytes ??= File.ReadAllBytes(module.FileName);
+ }
+
// Limit the search space to .text section.
this.SetupSearchSpace(module);
- if (this.IsCopy)
- this.SetupCopiedSegments();
-
Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}");
Log.Verbose($"Module size: 0x{this.TextSectionSize:X}");
@@ -494,6 +500,18 @@ public class SigScanner : IDisposable, ISigScanner
case 0x747865742E: // .text
this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8);
+
+ if (this.IsCopy)
+ {
+ var pointerToRawData = Marshal.ReadInt32(sectionCursor, 20);
+
+ Marshal.Copy(
+ fileBytes.AsSpan(pointerToRawData, this.TextSectionSize).ToArray(),
+ 0,
+ this.moduleCopyPtr + (nint)this.TextSectionOffset,
+ this.TextSectionSize);
+ }
+
break;
case 0x617461642E: // .data
this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
diff --git a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
index f851e7686..523086f48 100644
--- a/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
+++ b/Dalamud/Game/Text/Evaluator/Internal/SheetRedirectResolver.cs
@@ -3,7 +3,6 @@ using Dalamud.Utility;
using Lumina.Extensions;
-using ItemKind = Dalamud.Game.Text.SeStringHandling.Payloads.ItemPayload.ItemKind;
using LSheets = Lumina.Excel.Sheets;
namespace Dalamud.Game.Text.Evaluator.Internal;
@@ -222,8 +221,8 @@ internal class SheetRedirectResolver : IServiceType
sheetName = nameof(LSheets.AkatsukiNoteString);
colIndex = 0;
- if (this.dataManager.Excel.GetSubrowSheet().TryGetRow(rowId, out var row))
- rowId = (uint)row[0].Unknown2;
+ if (this.dataManager.Excel.GetSubrowSheet().TryGetSubrow(rowId, 0, out var row))
+ rowId = row.ListName.RowId;
break;
}
}
diff --git a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
index 57040701c..9e2466df4 100644
--- a/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
+++ b/Dalamud/Game/Text/Evaluator/SeStringEvaluator.cs
@@ -40,8 +40,6 @@ using StatusSheet = Lumina.Excel.Sheets.Status;
namespace Dalamud.Game.Text.Evaluator;
-#pragma warning disable SeStringEvaluator
-
///
/// Evaluator for SeStrings.
///
@@ -317,6 +315,9 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
case MacroCode.Sheet:
return this.TryResolveSheet(in context, payload);
+ case MacroCode.SheetSub:
+ return this.TryResolveSheetSub(in context, payload);
+
case MacroCode.String:
return this.TryResolveString(in context, payload);
@@ -759,6 +760,65 @@ internal class SeStringEvaluator : IServiceType, ISeStringEvaluator
return true;
}
+ private bool TryResolveSheetSub(in SeStringContext context, in ReadOnlySePayloadSpan payload)
+ {
+ var enu = payload.GetEnumerator();
+
+ if (!enu.MoveNext() || !enu.Current.TryGetString(out var eSheetNameStr))
+ return false;
+
+ if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eRowIdValue))
+ return false;
+
+ if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eSubrowIdValue))
+ return false;
+
+ if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var eColIndexValue))
+ return false;
+
+ var secondaryRowId = this.GetSubrowSheetIntValue(context.Language, eSheetNameStr.ExtractText(), eRowIdValue, (ushort)eSubrowIdValue, eColIndexValue);
+ if (secondaryRowId == -1)
+ return false;
+
+ if (!enu.MoveNext() || !enu.Current.TryGetString(out var eSecondarySheetNameStr))
+ return false;
+
+ if (!enu.MoveNext() || !this.TryResolveUInt(in context, enu.Current, out var secondaryColIndex))
+ return false;
+
+ var text = this.FormatSheetValue(context.Language, eSecondarySheetNameStr.ExtractText(), (uint)secondaryRowId, secondaryColIndex, 0);
+ if (text.IsEmpty)
+ return false;
+
+ this.CreateSheetLink(context, eSecondarySheetNameStr.ExtractText(), text, eRowIdValue, eSubrowIdValue);
+
+ return true;
+ }
+
+ private int GetSubrowSheetIntValue(ClientLanguage language, string sheetName, uint rowId, ushort subrowId, uint colIndex)
+ {
+ if (!this.dataManager.Excel.SheetNames.Contains(sheetName))
+ return -1;
+
+ if (!this.dataManager.GetSubrowExcelSheet(language, sheetName)
+ .TryGetSubrow(rowId, subrowId, out var row))
+ return -1;
+
+ if (colIndex >= row.Columns.Count)
+ return -1;
+
+ var column = row.Columns[(int)colIndex];
+ return column.Type switch
+ {
+ ExcelColumnDataType.Int8 => row.ReadInt8(column.Offset),
+ ExcelColumnDataType.UInt8 => row.ReadUInt8(column.Offset),
+ ExcelColumnDataType.Int16 => row.ReadInt16(column.Offset),
+ ExcelColumnDataType.UInt16 => row.ReadUInt16(column.Offset),
+ ExcelColumnDataType.Int32 => row.ReadInt32(column.Offset),
+ _ => -1,
+ };
+ }
+
private ReadOnlySeString FormatSheetValue(ClientLanguage language, string sheetName, uint rowId, uint colIndex, uint colParam)
{
if (!this.dataManager.Excel.SheetNames.Contains(sheetName))
diff --git a/Dalamud/Game/Text/Evaluator/SeStringParameter.cs b/Dalamud/Game/Text/Evaluator/SeStringParameter.cs
index c1f238f56..7201179ea 100644
--- a/Dalamud/Game/Text/Evaluator/SeStringParameter.cs
+++ b/Dalamud/Game/Text/Evaluator/SeStringParameter.cs
@@ -71,6 +71,7 @@ public readonly struct SeStringParameter
public static implicit operator SeStringParameter(ReadOnlySeStringSpan value) => new(new ReadOnlySeString(value));
+ [Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
public static implicit operator SeStringParameter(LSeString value) => new(new ReadOnlySeString(value.RawData));
public static implicit operator SeStringParameter(DSeString value) => new(new ReadOnlySeString(value.Encode()));
diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs
index 25cdf7f9f..0c1f75a1d 100644
--- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs
+++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs
@@ -72,33 +72,6 @@ public class ItemPayload : Payload
{
}
- ///
- /// Kinds of items that can be fetched from this payload.
- ///
- [Api13ToDo("Move this out of ItemPayload. It's used in other classes too.")]
- public enum ItemKind : uint
- {
- ///
- /// Normal items.
- ///
- Normal,
-
- ///
- /// Collectible Items.
- ///
- Collectible = 500_000,
-
- ///
- /// High-Quality items.
- ///
- Hq = 1_000_000,
-
- ///
- /// Event/Key items.
- ///
- EventItem = 2_000_000,
- }
-
///
public override PayloadType Type => PayloadType.Item;
diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs
index b7618305a..8805c2177 100644
--- a/Dalamud/Game/Text/SeStringHandling/SeString.cs
+++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs
@@ -118,6 +118,7 @@ public class SeString
///
/// string to convert.
/// Equivalent SeString.
+ [Obsolete("Switch to using ReadOnlySeString instead of Lumina's SeString.", true)]
public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString();
///
@@ -181,7 +182,7 @@ public class SeString
/// An optional name override to display, instead of the actual item name.
/// An SeString containing all the payloads necessary to display an item link in the chat log.
public static SeString CreateItemLink(uint itemId, bool isHq, string? displayNameOverride = null) =>
- CreateItemLink(itemId, isHq ? ItemPayload.ItemKind.Hq : ItemPayload.ItemKind.Normal, displayNameOverride);
+ CreateItemLink(itemId, isHq ? ItemKind.Hq : ItemKind.Normal, displayNameOverride);
///
/// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log.
@@ -190,7 +191,7 @@ public class SeString
/// The kind of item to link.
/// An optional name override to display, instead of the actual item name.
/// An SeString containing all the payloads necessary to display an item link in the chat log.
- public static SeString CreateItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? displayNameOverride = null)
+ public static SeString CreateItemLink(uint itemId, ItemKind kind = ItemKind.Normal, string? displayNameOverride = null)
{
var clientState = Service.Get();
var seStringEvaluator = Service.Get();
diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs b/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs
index d5080e6e8..ae673e516 100644
--- a/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs
+++ b/Dalamud/Game/Text/SeStringHandling/SeStringBuilder.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Dalamud.Game.Text.SeStringHandling.Payloads;
+using Dalamud.Utility;
namespace Dalamud.Game.Text.SeStringHandling;
@@ -126,7 +127,7 @@ public class SeStringBuilder
/// Kind of item to encode.
/// Override for the item's name.
/// The current builder.
- public SeStringBuilder AddItemLink(uint itemId, ItemPayload.ItemKind kind = ItemPayload.ItemKind.Normal, string? itemNameOverride = null) =>
+ public SeStringBuilder AddItemLink(uint itemId, ItemKind kind = ItemKind.Normal, string? itemNameOverride = null) =>
this.Append(SeString.CreateItemLink(itemId, kind, itemNameOverride));
///
diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs
index 972a2730d..faf4658a5 100644
--- a/Dalamud/Hooking/Hook.cs
+++ b/Dalamud/Hooking/Hook.cs
@@ -201,11 +201,11 @@ public abstract class Hook : IDalamudHook where T : Delegate
if (EnvironmentConfiguration.DalamudForceMinHook)
useMinHook = true;
- var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName);
- if (moduleHandle == IntPtr.Zero)
+ using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
+ if (moduleHandle.IsInvalid)
throw new Exception($"Could not get a handle to module {moduleName}");
- var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName);
+ var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
if (procAddress == IntPtr.Zero)
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
diff --git a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs
index 5f27d9a37..8a53e664a 100644
--- a/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs
+++ b/Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs
@@ -1,10 +1,11 @@
using System.Collections.Generic;
-using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
-using Dalamud.Memory;
using JetBrains.Annotations;
+using Windows.Win32.System.Memory;
+
+using Win32Exception = System.ComponentModel.Win32Exception;
namespace Dalamud.Hooking.Internal;
@@ -12,7 +13,7 @@ namespace Dalamud.Hooking.Internal;
/// Manages a hook with MinHook.
///
/// Delegate type to represents a function prototype. This must be the same prototype as original function do.
-internal class FunctionPointerVariableHook : Hook
+internal unsafe class FunctionPointerVariableHook : Hook
where T : Delegate
{
private readonly nint pfnDetour;
@@ -55,11 +56,11 @@ internal class FunctionPointerVariableHook : Hook
// Note: WINE seemingly tries to clean up all heap allocations on process exit.
// We want our allocation to be kept there forever, until no running thread remains.
// Therefore we're using VirtualAlloc instead of HeapCreate/HeapAlloc.
- var pfnThunkBytes = (byte*)NativeFunctions.VirtualAlloc(
- 0,
+ var pfnThunkBytes = (byte*)Windows.Win32.PInvoke.VirtualAlloc(
+ null,
12,
- NativeFunctions.AllocationType.Reserve | NativeFunctions.AllocationType.Commit,
- MemoryProtection.ExecuteReadWrite);
+ VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE | VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT,
+ PAGE_PROTECTION_FLAGS.PAGE_EXECUTE_READWRITE);
if (pfnThunkBytes == null)
{
throw new OutOfMemoryException("Failed to allocate memory for import hooks.");
@@ -78,10 +79,10 @@ internal class FunctionPointerVariableHook : Hook
this.ppfnThunkJumpTarget = this.pfnThunk + 2;
- if (!NativeFunctions.VirtualProtect(
- this.Address,
+ if (!Windows.Win32.PInvoke.VirtualProtect(
+ this.Address.ToPointer(),
(UIntPtr)Marshal.SizeOf(),
- MemoryProtection.ExecuteReadWrite,
+ PAGE_PROTECTION_FLAGS.PAGE_EXECUTE_READWRITE,
out var oldProtect))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
@@ -93,7 +94,7 @@ internal class FunctionPointerVariableHook : Hook
Marshal.WriteIntPtr(this.Address, this.pfnThunk);
// This really should not fail, but then even if it does, whatever.
- NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf(), oldProtect, out _);
+ Windows.Win32.PInvoke.VirtualProtect(this.Address.ToPointer(), (UIntPtr)Marshal.SizeOf(), oldProtect, out _);
// Add afterwards, so the hookIdent starts at 0.
indexList.Add(this);
diff --git a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs
index e2f68eab2..e2f9d9970 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs
@@ -1,10 +1,9 @@
using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
-using ImGuiNET;
-
namespace Dalamud.Interface.Components;
///
diff --git a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs
index ab2ed4724..a8c3c03a3 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs
@@ -1,9 +1,8 @@
using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
-using ImGuiNET;
-
namespace Dalamud.Interface.Components;
///
diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs
index 3392136d1..57a4bd150 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs
@@ -1,9 +1,7 @@
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
-
using FFXIVClientStructs.FFXIV.Common.Math;
-using ImGuiNET;
-
namespace Dalamud.Interface.Components;
///
@@ -45,7 +43,7 @@ public static partial class ImGuiComponents
{
using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f))
{
- ImGui.TextUnformatted(helpText);
+ ImGui.Text(helpText);
}
}
}
diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs
index 3e61e16bb..03befd11f 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs
@@ -1,10 +1,9 @@
using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
-using ImGuiNET;
-
namespace Dalamud.Interface.Components;
///
diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs
index 99050473f..1dc454c9a 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.IconButtonSelect.cs
@@ -2,10 +2,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
-using ImGuiNET;
-
namespace Dalamud.Interface.Components;
///
diff --git a/Dalamud/Interface/Components/ImGuiComponents.Test.cs b/Dalamud/Interface/Components/ImGuiComponents.Test.cs
index ddc083cd8..4937b57c0 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.Test.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.Test.cs
@@ -1,4 +1,4 @@
-using ImGuiNET;
+using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Components;
@@ -12,6 +12,6 @@ public static partial class ImGuiComponents
///
public static void Test()
{
- ImGui.Text("You are viewing the test component. The test was a success.");
+ ImGui.Text("You are viewing the test component. The test was a success."u8);
}
}
diff --git a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs
index 43b54fc93..84d38799c 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs
@@ -1,7 +1,6 @@
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
-using ImGuiNET;
-
namespace Dalamud.Interface.Components;
///
@@ -30,7 +29,7 @@ public static partial class ImGuiComponents
{
using (ImRaii.Tooltip())
{
- ImGui.TextUnformatted(hint);
+ ImGui.Text(hint);
}
}
}
diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs
index 6d6e0f6c3..b114eafd7 100644
--- a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs
+++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs
@@ -1,6 +1,6 @@
using System.Numerics;
-using ImGuiNET;
+using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.Components;
diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs
index c9f0f9b80..4375ddea9 100644
--- a/Dalamud/Interface/DragDrop/DragDropManager.cs
+++ b/Dalamud/Interface/DragDrop/DragDropManager.cs
@@ -1,11 +1,10 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Internal;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
-
-using ImGuiNET;
using Serilog;
namespace Dalamud.Interface.DragDrop;
@@ -32,7 +31,7 @@ internal partial class DragDropManager : IInternalDisposableService, IDragDropMa
Service.GetAsync()
.ContinueWith(t =>
{
- this.windowHandlePtr = t.Result.Manager.WindowHandlePtr;
+ this.windowHandlePtr = t.Result.Manager.GameWindowHandle;
this.Enable();
});
}
@@ -103,7 +102,7 @@ internal partial class DragDropManager : IInternalDisposableService, IDragDropMa
}
///
- public void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder)
+ public unsafe void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder)
{
if (!this.IsDragging && !this.IsDropping())
{
@@ -115,7 +114,7 @@ internal partial class DragDropManager : IInternalDisposableService, IDragDropMa
return;
}
- ImGui.SetDragDropPayload(label, nint.Zero, 0);
+ ImGui.SetDragDropPayload(label, null, 0);
if (this.CheckTooltipFrame(out var frame) && tooltipBuilder(this))
{
this.lastTooltipFrame = frame;
@@ -136,7 +135,7 @@ internal partial class DragDropManager : IInternalDisposableService, IDragDropMa
unsafe
{
- if (ImGui.AcceptDragDropPayload(label, ImGuiDragDropFlags.AcceptBeforeDelivery).NativePtr != null && this.IsDropping())
+ if (ImGui.AcceptDragDropPayload(label, ImGuiDragDropFlags.AcceptBeforeDelivery).Handle != null && this.IsDropping())
{
this.lastDropFrame = -2;
files = this.Files;
diff --git a/Dalamud/Interface/DragDrop/DragDropTarget.cs b/Dalamud/Interface/DragDrop/DragDropTarget.cs
index 8115e7353..c6b66e7e8 100644
--- a/Dalamud/Interface/DragDrop/DragDropTarget.cs
+++ b/Dalamud/Interface/DragDrop/DragDropTarget.cs
@@ -4,8 +4,8 @@ using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
+using Dalamud.Bindings.ImGui;
using Dalamud.Utility;
-using ImGuiNET;
using Serilog;
namespace Dalamud.Interface.DragDrop;
diff --git a/Dalamud/Interface/FontIdentifier/DalamudAssetFontAndFamilyId.cs b/Dalamud/Interface/FontIdentifier/DalamudAssetFontAndFamilyId.cs
index a6d40e4b7..c531dced5 100644
--- a/Dalamud/Interface/FontIdentifier/DalamudAssetFontAndFamilyId.cs
+++ b/Dalamud/Interface/FontIdentifier/DalamudAssetFontAndFamilyId.cs
@@ -1,12 +1,9 @@
using System.Collections.Generic;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Storage.Assets;
-
-using ImGuiNET;
-
using Newtonsoft.Json;
-
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.FontIdentifier;
diff --git a/Dalamud/Interface/FontIdentifier/DalamudDefaultFontAndFamilyId.cs b/Dalamud/Interface/FontIdentifier/DalamudDefaultFontAndFamilyId.cs
index 7c6a69622..c45cf256b 100644
--- a/Dalamud/Interface/FontIdentifier/DalamudDefaultFontAndFamilyId.cs
+++ b/Dalamud/Interface/FontIdentifier/DalamudDefaultFontAndFamilyId.cs
@@ -1,11 +1,8 @@
using System.Collections.Generic;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ManagedFontAtlas;
-
-using ImGuiNET;
-
using Newtonsoft.Json;
-
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.FontIdentifier;
diff --git a/Dalamud/Interface/FontIdentifier/GameFontAndFamilyId.cs b/Dalamud/Interface/FontIdentifier/GameFontAndFamilyId.cs
index dd4ba0d66..e294c8813 100644
--- a/Dalamud/Interface/FontIdentifier/GameFontAndFamilyId.cs
+++ b/Dalamud/Interface/FontIdentifier/GameFontAndFamilyId.cs
@@ -1,12 +1,9 @@
using System.Collections.Generic;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.ManagedFontAtlas;
-
-using ImGuiNET;
-
using Newtonsoft.Json;
-
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.FontIdentifier;
diff --git a/Dalamud/Interface/FontIdentifier/IFontId.cs b/Dalamud/Interface/FontIdentifier/IFontId.cs
index 4c611edf8..7b457a95b 100644
--- a/Dalamud/Interface/FontIdentifier/IFontId.cs
+++ b/Dalamud/Interface/FontIdentifier/IFontId.cs
@@ -1,7 +1,6 @@
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ManagedFontAtlas;
-using ImGuiNET;
-
namespace Dalamud.Interface.FontIdentifier;
///
diff --git a/Dalamud/Interface/FontIdentifier/IFontSpec.cs b/Dalamud/Interface/FontIdentifier/IFontSpec.cs
index 4d0719d4e..c597ed4dd 100644
--- a/Dalamud/Interface/FontIdentifier/IFontSpec.cs
+++ b/Dalamud/Interface/FontIdentifier/IFontSpec.cs
@@ -1,7 +1,6 @@
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ManagedFontAtlas;
-using ImGuiNET;
-
namespace Dalamud.Interface.FontIdentifier;
///
diff --git a/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs b/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs
index 946215b85..070b1c1e1 100644
--- a/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs
+++ b/Dalamud/Interface/FontIdentifier/SingleFontSpec.cs
@@ -3,11 +3,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Text;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.Utility;
-
-using ImGuiNET;
-
using Newtonsoft.Json;
namespace Dalamud.Interface.FontIdentifier;
diff --git a/Dalamud/Interface/FontIdentifier/SystemFontId.cs b/Dalamud/Interface/FontIdentifier/SystemFontId.cs
index 0a350fc3a..e11759a88 100644
--- a/Dalamud/Interface/FontIdentifier/SystemFontId.cs
+++ b/Dalamud/Interface/FontIdentifier/SystemFontId.cs
@@ -2,13 +2,10 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Utility;
-
-using ImGuiNET;
-
using Newtonsoft.Json;
-
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
@@ -38,7 +35,7 @@ public sealed class SystemFontId : IFontId
this.EnglishName = name;
else if (this.LocaleNames.TryGetValue("en", out name))
this.EnglishName = name;
- else
+ else
this.EnglishName = this.LocaleNames.Values.First();
}
diff --git a/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs b/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs
index 6b602d911..5b0fe100b 100644
--- a/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs
+++ b/Dalamud/Interface/GameFonts/GameFontLayoutPlan.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Numerics;
-using ImGuiNET;
+using Dalamud.Bindings.ImGui;
namespace Dalamud.Interface.GameFonts;
diff --git a/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiBuildUiDelegate.cs b/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiBuildUiDelegate.cs
new file mode 100644
index 000000000..6ebab55c6
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiBuildUiDelegate.cs
@@ -0,0 +1,4 @@
+namespace Dalamud.Interface.ImGuiBackend.Delegates;
+
+/// Delegate to be called when ImGui should be used to layout now.
+public delegate void ImGuiBuildUiDelegate();
diff --git a/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiNewInputFrameDelegate.cs b/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiNewInputFrameDelegate.cs
new file mode 100644
index 000000000..7397d8d7f
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiNewInputFrameDelegate.cs
@@ -0,0 +1,4 @@
+namespace Dalamud.Interface.ImGuiBackend.Delegates;
+
+/// Delegate to be called on new input frame.
+public delegate void ImGuiNewInputFrameDelegate();
diff --git a/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiNewRenderFrameDelegate.cs b/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiNewRenderFrameDelegate.cs
new file mode 100644
index 000000000..4a4b38b71
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Delegates/ImGuiNewRenderFrameDelegate.cs
@@ -0,0 +1,4 @@
+namespace Dalamud.Interface.ImGuiBackend.Delegates;
+
+/// Delegate to be called on new render frame.
+public delegate void ImGuiNewRenderFrameDelegate();
diff --git a/Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs b/Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs
new file mode 100644
index 000000000..ea609828d
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Dx11Win32Backend.cs
@@ -0,0 +1,253 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Dalamud.Bindings.ImGui;
+using Dalamud.Bindings.ImGuizmo;
+using Dalamud.Bindings.ImPlot;
+using Dalamud.Interface.ImGuiBackend.Delegates;
+using Dalamud.Interface.ImGuiBackend.Helpers;
+using Dalamud.Interface.ImGuiBackend.InputHandler;
+using Dalamud.Interface.ImGuiBackend.Renderers;
+using Dalamud.Utility;
+
+using Serilog;
+
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.ImGuiBackend;
+
+///
+/// Backend for ImGui, using and .
+///
+[SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed/using scopes")]
+internal sealed unsafe class Dx11Win32Backend : IWin32Backend
+{
+ private readonly Dx11Renderer imguiRenderer;
+ private readonly Win32InputHandler imguiInput;
+
+ private ComPtr swapChainPossiblyWrapped;
+ private ComPtr swapChain;
+ private ComPtr device;
+ private ComPtr deviceContext;
+
+ private int targetWidth;
+ private int targetHeight;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pointer to an instance of . The reference is copied.
+ public Dx11Win32Backend(IDXGISwapChain* swapChain)
+ {
+ try
+ {
+ this.swapChainPossiblyWrapped = new(swapChain);
+ this.swapChain = new(swapChain);
+ fixed (ComPtr* ppSwapChain = &this.swapChain)
+ ReShadePeeler.PeelSwapChain(ppSwapChain);
+
+ fixed (Guid* guid = &IID.IID_ID3D11Device)
+ fixed (ID3D11Device** pp = &this.device.GetPinnableReference())
+ this.swapChain.Get()->GetDevice(guid, (void**)pp).ThrowOnError();
+
+ fixed (ID3D11DeviceContext** pp = &this.deviceContext.GetPinnableReference())
+ this.device.Get()->GetImmediateContext(pp);
+
+ using var buffer = default(ComPtr);
+ fixed (Guid* guid = &IID.IID_ID3D11Resource)
+ this.swapChain.Get()->GetBuffer(0, guid, (void**)buffer.GetAddressOf()).ThrowOnError();
+
+ var desc = default(DXGI_SWAP_CHAIN_DESC);
+ this.swapChain.Get()->GetDesc(&desc).ThrowOnError();
+ this.targetWidth = (int)desc.BufferDesc.Width;
+ this.targetHeight = (int)desc.BufferDesc.Height;
+ this.WindowHandle = desc.OutputWindow;
+
+ var ctx = ImGui.CreateContext();
+ ImGuizmo.SetImGuiContext(ctx);
+ ImPlot.SetImGuiContext(ctx);
+ ImPlot.CreateContext();
+
+ ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable;
+
+ this.imguiRenderer = new(this.SwapChain, this.Device, this.DeviceContext);
+ this.imguiInput = new(this.WindowHandle);
+ }
+ catch
+ {
+ this.Dispose();
+ throw;
+ }
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~Dx11Win32Backend() => this.ReleaseUnmanagedResources();
+
+ ///
+ public event ImGuiBuildUiDelegate? BuildUi;
+
+ ///
+ public event ImGuiNewInputFrameDelegate? NewInputFrame;
+
+ ///
+ public event ImGuiNewRenderFrameDelegate? NewRenderFrame;
+
+ ///
+ public bool UpdateCursor
+ {
+ get => this.imguiInput.UpdateCursor;
+ set => this.imguiInput.UpdateCursor = value;
+ }
+
+ ///
+ public string? IniPath
+ {
+ get => this.imguiInput.IniPath;
+ set => this.imguiInput.IniPath = value;
+ }
+
+ ///
+ public IImGuiInputHandler InputHandler => this.imguiInput;
+
+ ///
+ public IImGuiRenderer Renderer => this.imguiRenderer;
+
+ ///
+ /// Gets the pointer to an instance of .
+ ///
+ public IDXGISwapChain* SwapChain => this.swapChain;
+
+ ///
+ /// Gets the pointer to an instance of .
+ ///
+ public ID3D11Device* Device => this.device;
+
+ ///
+ /// Gets the pointer to an instance of , in .
+ ///
+ public nint DeviceHandle => (nint)this.device.Get();
+
+ ///
+ /// Gets the pointer to an instance of .
+ ///
+ public ID3D11DeviceContext* DeviceContext => this.deviceContext;
+
+ ///
+ /// Gets the window handle.
+ ///
+ public HWND WindowHandle { get; }
+
+ ///
+ public void Dispose()
+ {
+ this.ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ public nint? ProcessWndProcW(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) =>
+ this.imguiInput.ProcessWndProcW(hWnd, msg, wParam, lParam);
+
+ ///
+ public void Render()
+ {
+ this.imguiRenderer.OnNewFrame();
+ this.NewRenderFrame?.Invoke();
+ this.imguiInput.NewFrame(this.targetWidth, this.targetHeight);
+ this.NewInputFrame?.Invoke();
+
+ ImGui.NewFrame();
+ ImGuizmo.BeginFrame();
+
+ this.BuildUi?.Invoke();
+
+ ImGui.Render();
+
+ this.imguiRenderer.RenderDrawData(ImGui.GetDrawData());
+
+ ImGui.UpdatePlatformWindows();
+ ImGui.RenderPlatformWindowsDefault();
+ }
+
+ ///
+ public void OnPreResize() => this.imguiRenderer.OnPreResize();
+
+ ///
+ public void OnPostResize(int newWidth, int newHeight)
+ {
+ this.imguiRenderer.OnPostResize(newWidth, newHeight);
+ this.targetWidth = newWidth;
+ this.targetHeight = newHeight;
+ }
+
+ ///
+ public void InvalidateFonts() => this.imguiRenderer.RebuildFontTexture();
+
+ ///
+ public bool IsImGuiCursor(nint cursorHandle) => this.imguiInput.IsImGuiCursor(cursorHandle);
+
+ ///
+ public bool IsAttachedToPresentationTarget(nint targetHandle) =>
+ AreIUnknownEqual(this.swapChain.Get(), (IUnknown*)targetHandle)
+ || AreIUnknownEqual(this.swapChainPossiblyWrapped.Get(), (IUnknown*)targetHandle);
+
+ ///
+ public bool IsMainViewportFullScreen()
+ {
+ BOOL fullscreen;
+ this.swapChain.Get()->GetFullscreenState(&fullscreen, null);
+ return fullscreen;
+ }
+
+ private static bool AreIUnknownEqual(T1* punk1, T2* punk2)
+ where T1 : unmanaged, IUnknown.Interface
+ where T2 : unmanaged, IUnknown.Interface
+ {
+ // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)
+ // For any given COM object (also known as a COM component), a specific query for the IUnknown interface on any
+ // of the object's interfaces must always return the same pointer value.
+
+ if (punk1 is null || punk2 is null)
+ return false;
+
+ fixed (Guid* iid = &IID.IID_IUnknown)
+ {
+ using var u1 = default(ComPtr);
+ if (punk1->QueryInterface(iid, (void**)u1.GetAddressOf()).FAILED)
+ return false;
+
+ using var u2 = default(ComPtr);
+ if (punk2->QueryInterface(iid, (void**)u2.GetAddressOf()).FAILED)
+ return false;
+
+ return u1.Get() == u2.Get();
+ }
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ if (this.device.IsEmpty())
+ return;
+
+ this.imguiRenderer?.Dispose();
+ this.imguiInput?.Dispose();
+
+ ImPlot.DestroyContext();
+ ImGui.DestroyContext();
+
+ this.swapChain.Dispose();
+ this.deviceContext.Dispose();
+ this.device.Dispose();
+ this.swapChainPossiblyWrapped.Dispose();
+ }
+}
diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs
new file mode 100644
index 000000000..df1087ce3
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/D3D11DeviceContextStateBackup.cs
@@ -0,0 +1,655 @@
+using System.Runtime.InteropServices;
+
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.ImGuiBackend.Helpers.D3D11;
+
+///
+/// Captures states of a .
+///
+internal unsafe struct D3D11DeviceContextStateBackup : IDisposable
+{
+ private InputAssemblerState inputAssemblerState;
+ private RasterizerState rasterizerState;
+ private OutputMergerState outputMergerState;
+ private VertexShaderState vertexShaderState;
+ private HullShaderState hullShaderState;
+ private DomainShaderState domainShaderState;
+ private GeometryShaderState geometryShaderState;
+ private PixelShaderState pixelShaderState;
+ private ComputeShaderState computeShaderState;
+
+ ///
+ /// Initializes a new instance of the struct,
+ /// by capturing all states of a .
+ ///
+ /// The feature level.
+ /// The device context.
+ public D3D11DeviceContextStateBackup(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
+ {
+ this.inputAssemblerState = InputAssemblerState.From(ctx);
+ this.rasterizerState = RasterizerState.From(ctx);
+ this.outputMergerState = OutputMergerState.From(featureLevel, ctx);
+ this.vertexShaderState = VertexShaderState.From(ctx);
+ this.hullShaderState = HullShaderState.From(ctx);
+ this.domainShaderState = DomainShaderState.From(ctx);
+ this.geometryShaderState = GeometryShaderState.From(ctx);
+ this.pixelShaderState = PixelShaderState.From(ctx);
+ this.computeShaderState = ComputeShaderState.From(featureLevel, ctx);
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.inputAssemblerState.Dispose();
+ this.rasterizerState.Dispose();
+ this.outputMergerState.Dispose();
+ this.vertexShaderState.Dispose();
+ this.hullShaderState.Dispose();
+ this.domainShaderState.Dispose();
+ this.geometryShaderState.Dispose();
+ this.pixelShaderState.Dispose();
+ this.computeShaderState.Dispose();
+ }
+
+ ///
+ /// Captures Input Assembler states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct InputAssemblerState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT;
+
+ private ComPtr context;
+ private ComPtr layout;
+ private ComPtr indexBuffer;
+ private DXGI_FORMAT indexFormat;
+ private uint indexOffset;
+ private D3D_PRIMITIVE_TOPOLOGY topology;
+ private fixed ulong buffers[BufferCount];
+ private fixed uint strides[BufferCount];
+ private fixed uint offsets[BufferCount];
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static InputAssemblerState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(InputAssemblerState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ ctx->IAGetInputLayout(state.layout.GetAddressOf());
+ ctx->IAGetPrimitiveTopology(&state.topology);
+ ctx->IAGetIndexBuffer(state.indexBuffer.GetAddressOf(), &state.indexFormat, &state.indexOffset);
+ ctx->IAGetVertexBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers, state.strides, state.offsets);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (InputAssemblerState* pThis = &this)
+ {
+ ctx->IASetInputLayout(pThis->layout);
+ ctx->IASetPrimitiveTopology(pThis->topology);
+ ctx->IASetIndexBuffer(pThis->indexBuffer, pThis->indexFormat, pThis->indexOffset);
+ ctx->IASetVertexBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers, pThis->strides, pThis->offsets);
+
+ pThis->context.Dispose();
+ pThis->layout.Dispose();
+ pThis->indexBuffer.Dispose();
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Rasterizer states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct RasterizerState : IDisposable
+ {
+ private const int Count = TerraFX.Interop.DirectX.D3D11.D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX;
+
+ private ComPtr context;
+ private ComPtr state;
+ private fixed byte viewports[24 * Count];
+ private fixed ulong scissorRects[16 * Count];
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static RasterizerState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(RasterizerState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ ctx->RSGetState(state.state.GetAddressOf());
+ uint n = Count;
+ ctx->RSGetViewports(&n, (D3D11_VIEWPORT*)state.viewports);
+ n = Count;
+ ctx->RSGetScissorRects(&n, (RECT*)state.scissorRects);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (RasterizerState* pThis = &this)
+ {
+ ctx->RSSetState(pThis->state);
+ ctx->RSSetViewports(Count, (D3D11_VIEWPORT*)pThis->viewports);
+ ctx->RSSetScissorRects(Count, (RECT*)pThis->scissorRects);
+
+ pThis->context.Dispose();
+ pThis->state.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Output Merger states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct OutputMergerState : IDisposable
+ {
+ private const int RtvCount = TerraFX.Interop.DirectX.D3D11.D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT;
+ private const int UavCountMax = TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT;
+
+ private ComPtr context;
+ private ComPtr blendState;
+ private fixed float blendFactor[4];
+ private uint sampleMask;
+ private uint stencilRef;
+ private ComPtr depthStencilState;
+ private fixed ulong rtvs[RtvCount]; // ID3D11RenderTargetView*[RtvCount]
+ private ComPtr dsv;
+ private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCount]
+ private int uavCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The feature level.
+ /// The device context.
+ /// The captured state.
+ public static OutputMergerState From(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
+ {
+ var state = default(OutputMergerState);
+ state.uavCount = featureLevel >= D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1
+ ? TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT
+ : TerraFX.Interop.DirectX.D3D11.D3D11_PS_CS_UAV_REGISTER_COUNT;
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ ctx->OMGetBlendState(state.blendState.GetAddressOf(), state.blendFactor, &state.sampleMask);
+ ctx->OMGetDepthStencilState(state.depthStencilState.GetAddressOf(), &state.stencilRef);
+ ctx->OMGetRenderTargetsAndUnorderedAccessViews(
+ RtvCount,
+ (ID3D11RenderTargetView**)state.rtvs,
+ state.dsv.GetAddressOf(),
+ 0,
+ (uint)state.uavCount,
+ (ID3D11UnorderedAccessView**)state.uavs);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (OutputMergerState* pThis = &this)
+ {
+ ctx->OMSetBlendState(pThis->blendState, pThis->blendFactor, pThis->sampleMask);
+ ctx->OMSetDepthStencilState(pThis->depthStencilState, pThis->stencilRef);
+ var rtvc = (uint)RtvCount;
+ while (rtvc > 0 && pThis->rtvs[rtvc - 1] == 0)
+ rtvc--;
+
+ var uavlb = rtvc;
+ while (uavlb < this.uavCount && pThis->uavs[uavlb] == 0)
+ uavlb++;
+
+ var uavc = (uint)this.uavCount;
+ while (uavc > uavlb && pThis->uavs[uavc - 1] == 0)
+ uavlb--;
+ uavc -= uavlb;
+
+ ctx->OMSetRenderTargetsAndUnorderedAccessViews(
+ rtvc,
+ (ID3D11RenderTargetView**)pThis->rtvs,
+ pThis->dsv,
+ uavc == 0 ? 0 : uavlb,
+ uavc,
+ uavc == 0 ? null : (ID3D11UnorderedAccessView**)pThis->uavs,
+ null);
+
+ this.context.Reset();
+ this.blendState.Reset();
+ this.depthStencilState.Reset();
+ this.dsv.Reset();
+ foreach (ref var b in new Span>(pThis->rtvs, RtvCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->uavs, this.uavCount))
+ b.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Vertex Shader states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct VertexShaderState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
+ private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+ private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+ private const int ClassInstanceCount = 256; // According to msdn
+
+ private ComPtr context;
+ private ComPtr shader;
+ private fixed ulong insts[ClassInstanceCount];
+ private fixed ulong buffers[BufferCount];
+ private fixed ulong samplers[SamplerCount];
+ private fixed ulong resources[ResourceCount];
+ private uint instCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static VertexShaderState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(VertexShaderState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ state.instCount = ClassInstanceCount;
+ ctx->VSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
+ ctx->VSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
+ ctx->VSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
+ ctx->VSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (VertexShaderState* pThis = &this)
+ {
+ ctx->VSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
+ ctx->VSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
+ ctx->VSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
+ ctx->VSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
+
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->samplers, SamplerCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->resources, ResourceCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount))
+ b.Dispose();
+ pThis->context.Dispose();
+ pThis->shader.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Hull Shader states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct HullShaderState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
+ private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+ private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+ private const int ClassInstanceCount = 256; // According to msdn
+
+ private ComPtr context;
+ private ComPtr shader;
+ private fixed ulong insts[ClassInstanceCount];
+ private fixed ulong buffers[BufferCount];
+ private fixed ulong samplers[SamplerCount];
+ private fixed ulong resources[ResourceCount];
+ private uint instCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static HullShaderState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(HullShaderState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ state.instCount = ClassInstanceCount;
+ ctx->HSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
+ ctx->HSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
+ ctx->HSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
+ ctx->HSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (HullShaderState* pThis = &this)
+ {
+ ctx->HSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
+ ctx->HSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
+ ctx->HSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
+ ctx->HSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
+
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->samplers, SamplerCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->resources, ResourceCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount))
+ b.Dispose();
+ pThis->context.Dispose();
+ pThis->shader.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Domain Shader states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DomainShaderState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
+ private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+ private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+ private const int ClassInstanceCount = 256; // According to msdn
+
+ private ComPtr context;
+ private ComPtr shader;
+ private fixed ulong insts[ClassInstanceCount];
+ private fixed ulong buffers[BufferCount];
+ private fixed ulong samplers[SamplerCount];
+ private fixed ulong resources[ResourceCount];
+ private uint instCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static DomainShaderState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(DomainShaderState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ state.instCount = ClassInstanceCount;
+ ctx->DSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
+ ctx->DSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
+ ctx->DSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
+ ctx->DSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (DomainShaderState* pThis = &this)
+ {
+ ctx->DSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
+ ctx->DSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
+ ctx->DSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
+ ctx->DSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
+
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->samplers, SamplerCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->resources, ResourceCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount))
+ b.Dispose();
+ pThis->context.Dispose();
+ pThis->shader.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Geometry Shader states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct GeometryShaderState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
+ private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+ private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+ private const int ClassInstanceCount = 256; // According to msdn
+
+ private ComPtr context;
+ private ComPtr shader;
+ private fixed ulong insts[ClassInstanceCount];
+ private fixed ulong buffers[BufferCount];
+ private fixed ulong samplers[SamplerCount];
+ private fixed ulong resources[ResourceCount];
+ private uint instCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static GeometryShaderState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(GeometryShaderState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ state.instCount = ClassInstanceCount;
+ ctx->GSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
+ ctx->GSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
+ ctx->GSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
+ ctx->GSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (GeometryShaderState* pThis = &this)
+ {
+ ctx->GSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
+ ctx->GSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
+ ctx->GSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
+ ctx->GSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
+
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->samplers, SamplerCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->resources, ResourceCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount))
+ b.Dispose();
+ pThis->context.Dispose();
+ pThis->shader.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Pixel Shader states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct PixelShaderState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
+ private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+ private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+ private const int ClassInstanceCount = 256; // According to msdn
+
+ private ComPtr context;
+ private ComPtr shader;
+ private fixed ulong insts[ClassInstanceCount];
+ private fixed ulong buffers[BufferCount];
+ private fixed ulong samplers[SamplerCount];
+ private fixed ulong resources[ResourceCount];
+ private uint instCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The device context.
+ /// The captured state.
+ public static PixelShaderState From(ID3D11DeviceContext* ctx)
+ {
+ var state = default(PixelShaderState);
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ state.instCount = ClassInstanceCount;
+ ctx->PSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
+ ctx->PSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
+ ctx->PSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
+ ctx->PSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (PixelShaderState* pThis = &this)
+ {
+ ctx->PSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
+ ctx->PSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
+ ctx->PSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
+ ctx->PSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
+
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->samplers, SamplerCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->resources, ResourceCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount))
+ b.Dispose();
+ pThis->context.Dispose();
+ pThis->shader.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Captures Compute Shader states of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ComputeShaderState : IDisposable
+ {
+ private const int BufferCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
+ private const int SamplerCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT;
+ private const int ResourceCount = TerraFX.Interop.DirectX.D3D11.D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT;
+ private const int InstanceCount = 256; // According to msdn
+ private const int UavCountMax = TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT;
+
+ private ComPtr context;
+ private ComPtr shader;
+ private fixed ulong insts[InstanceCount]; // ID3D11ClassInstance*[BufferCount]
+ private fixed ulong buffers[BufferCount]; // ID3D11Buffer*[BufferCount]
+ private fixed ulong samplers[SamplerCount]; // ID3D11SamplerState*[SamplerCount]
+ private fixed ulong resources[ResourceCount]; // ID3D11ShaderResourceView*[ResourceCount]
+ private fixed ulong uavs[UavCountMax]; // ID3D11UnorderedAccessView*[UavCountMax]
+ private uint instCount;
+ private int uavCount;
+
+ ///
+ /// Creates a new instance of from .
+ ///
+ /// The feature level.
+ /// The device context.
+ /// The captured state.
+ public static ComputeShaderState From(D3D_FEATURE_LEVEL featureLevel, ID3D11DeviceContext* ctx)
+ {
+ var state = default(ComputeShaderState);
+ state.uavCount = featureLevel >= D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1
+ ? TerraFX.Interop.DirectX.D3D11.D3D11_1_UAV_SLOT_COUNT
+ : TerraFX.Interop.DirectX.D3D11.D3D11_PS_CS_UAV_REGISTER_COUNT;
+ state.context.Attach(ctx);
+ ctx->AddRef();
+ state.instCount = InstanceCount;
+ ctx->CSGetShader(state.shader.GetAddressOf(), (ID3D11ClassInstance**)state.insts, &state.instCount);
+ ctx->CSGetConstantBuffers(0, BufferCount, (ID3D11Buffer**)state.buffers);
+ ctx->CSGetSamplers(0, SamplerCount, (ID3D11SamplerState**)state.samplers);
+ ctx->CSGetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)state.resources);
+ ctx->CSGetUnorderedAccessViews(0, (uint)state.uavCount, (ID3D11UnorderedAccessView**)state.uavs);
+ return state;
+ }
+
+ ///
+ public void Dispose()
+ {
+ var ctx = this.context.Get();
+ if (ctx is null)
+ return;
+
+ fixed (ComputeShaderState* pThis = &this)
+ {
+ ctx->CSSetShader(pThis->shader, (ID3D11ClassInstance**)pThis->insts, pThis->instCount);
+ ctx->CSSetConstantBuffers(0, BufferCount, (ID3D11Buffer**)pThis->buffers);
+ ctx->CSSetSamplers(0, SamplerCount, (ID3D11SamplerState**)pThis->samplers);
+ ctx->CSSetShaderResources(0, ResourceCount, (ID3D11ShaderResourceView**)pThis->resources);
+ ctx->CSSetUnorderedAccessViews(0, (uint)this.uavCount, (ID3D11UnorderedAccessView**)pThis->uavs, null);
+
+ foreach (ref var b in new Span>(pThis->buffers, BufferCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->samplers, SamplerCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->resources, ResourceCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->insts, (int)pThis->instCount))
+ b.Dispose();
+ foreach (ref var b in new Span>(pThis->uavs, this.uavCount))
+ b.Dispose();
+ pThis->context.Dispose();
+ pThis->shader.Dispose();
+ }
+ }
+ }
+}
diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs
new file mode 100644
index 000000000..4d81cb5fc
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Helpers/D3D11/Extensions.cs
@@ -0,0 +1,26 @@
+using System.Text;
+
+using Dalamud.Utility;
+
+using TerraFX.Interop.DirectX;
+
+namespace Dalamud.Interface.ImGuiBackend.Helpers.D3D11;
+
+/// Utility extension methods for D3D11 objects.
+internal static class Extensions
+{
+ /// Sets the name for debugging.
+ /// D3D11 object.
+ /// Debug name.
+ /// Object type.
+ public static unsafe void SetDebugName(ref this T child, string name)
+ where T : unmanaged, ID3D11DeviceChild.Interface
+ {
+ var len = Encoding.UTF8.GetByteCount(name);
+ var buf = stackalloc byte[len + 1];
+ Encoding.UTF8.GetBytes(name, new(buf, len + 1));
+ buf[len] = 0;
+ fixed (Guid* pId = &DirectX.WKPDID_D3DDebugObjectName)
+ child.SetPrivateData(pId, (uint)(len + 1), buf).ThrowOnError();
+ }
+}
diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs b/Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs
new file mode 100644
index 000000000..bbcaa618e
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Helpers/ImGuiViewportHelpers.cs
@@ -0,0 +1,165 @@
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+using Dalamud.Bindings.ImGui;
+
+using TerraFX.Interop.Windows;
+
+using static TerraFX.Interop.Windows.Windows;
+
+namespace Dalamud.Interface.ImGuiBackend.Helpers;
+
+///
+/// Helpers for using ImGui Viewports.
+///
+internal static class ImGuiViewportHelpers
+{
+ ///
+ /// Delegate to be called when a window should be created.
+ ///
+ /// An instance of .
+ public delegate void CreateWindowDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when a window should be destroyed.
+ ///
+ /// An instance of .
+ public delegate void DestroyWindowDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when a window should be resized.
+ ///
+ /// An instance of .
+ /// Size of the new window.
+ public delegate void SetWindowSizeDelegate(ImGuiViewportPtr viewport, Vector2 size);
+
+ ///
+ /// Delegate to be called when a window should be rendered.
+ ///
+ /// An instance of .
+ /// Custom user-provided argument from .
+ public delegate void RenderWindowDelegate(ImGuiViewportPtr viewport, nint v);
+
+ ///
+ /// Delegate to be called when buffers for the window should be swapped.
+ ///
+ /// An instance of .
+ /// Custom user-provided argument from .
+ public delegate void SwapBuffersDelegate(ImGuiViewportPtr viewport, nint v);
+
+ ///
+ /// Delegate to be called when the window should be showed.
+ ///
+ /// An instance of .
+ public delegate void ShowWindowDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when the window should be updated.
+ ///
+ /// An instance of .
+ public delegate void UpdateWindowDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when the window position is queried.
+ ///
+ /// The return value storage.
+ /// An instance of .
+ /// Same value with .
+ public unsafe delegate Vector2* GetWindowPosDelegate(Vector2* returnStorage, ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when the window should be moved.
+ ///
+ /// An instance of .
+ /// The new position.
+ public delegate void SetWindowPosDelegate(ImGuiViewportPtr viewport, Vector2 pos);
+
+ ///
+ /// Delegate to be called when the window size is queried.
+ ///
+ /// The return value storage.
+ /// An instance of .
+ /// Same value with .
+ public unsafe delegate Vector2* GetWindowSizeDelegate(Vector2* returnStorage, ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when the window should be given focus.
+ ///
+ /// An instance of .
+ public delegate void SetWindowFocusDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when the window is focused.
+ ///
+ /// An instance of .
+ /// Whether the window is focused.
+ public delegate bool GetWindowFocusDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when whether the window is minimized is queried.
+ ///
+ /// An instance of .
+ /// Whether the window is minimized.
+ public delegate bool GetWindowMinimizedDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when the window title should be changed.
+ ///
+ /// An instance of .
+ /// The new title.
+ public delegate void SetWindowTitleDelegate(ImGuiViewportPtr viewport, string title);
+
+ ///
+ /// Delegate to be called when the window alpha should be changed.
+ ///
+ /// An instance of .
+ /// The new alpha.
+ public delegate void SetWindowAlphaDelegate(ImGuiViewportPtr viewport, float alpha);
+
+ ///
+ /// Delegate to be called when the IME input position should be changed.
+ ///
+ /// An instance of .
+ /// The new position.
+ public delegate void SetImeInputPosDelegate(ImGuiViewportPtr viewport, Vector2 pos);
+
+ ///
+ /// Delegate to be called when the window's DPI scale value is queried.
+ ///
+ /// An instance of .
+ /// The DPI scale.
+ public delegate float GetWindowDpiScaleDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Delegate to be called when viewport is changed.
+ ///
+ /// An instance of .
+ public delegate void ChangedViewportDelegate(ImGuiViewportPtr viewport);
+
+ ///
+ /// Disables ImGui from disabling alpha for Viewport window backgrounds.
+ ///
+ public static unsafe void EnableViewportWindowBackgroundAlpha()
+ {
+ // TODO: patch imgui.cpp:6126, which disables background transparency for extra viewport windows
+ var offset = 0x00007FFB6ADA632C - 0x00007FFB6AD60000;
+ offset += Process.GetCurrentProcess().Modules.Cast().First(x => x.ModuleName == "cimgui.dll")
+ .BaseAddress;
+ var b = (byte*)offset;
+ uint old;
+ if (!VirtualProtect(b, 1, PAGE.PAGE_EXECUTE_READWRITE, &old))
+ {
+ throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
+ ?? throw new InvalidOperationException($"{nameof(VirtualProtect)} failed.");
+ }
+
+ *b = 0xEB;
+ if (!VirtualProtect(b, 1, old, &old))
+ {
+ throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
+ ?? throw new InvalidOperationException($"{nameof(VirtualProtect)} failed.");
+ }
+ }
+}
diff --git a/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs
new file mode 100644
index 000000000..824ba382a
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/Helpers/ReShadePeeler.cs
@@ -0,0 +1,230 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using TerraFX.Interop.DirectX;
+using TerraFX.Interop.Windows;
+
+using static TerraFX.Interop.Windows.Windows;
+
+namespace Dalamud.Interface.ImGuiBackend.Helpers;
+
+///
+/// Peels ReShade off stuff.
+///
+[SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed blocks")]
+internal static unsafe class ReShadePeeler
+{
+ ///
+ /// Peels if it is wrapped by ReShade.
+ ///
+ /// [inout] The COM pointer to an instance of .
+ /// A COM type that is or extends .
+ /// true if peeled.
+ public static bool PeelSwapChain(ComPtr* comptr)
+ where T : unmanaged, IDXGISwapChain.Interface =>
+ PeelIUnknown(comptr, sizeof(IDXGISwapChain.Vtbl));
+
+ private static bool PeelIUnknown(ComPtr* comptr, nint vtblSize)
+ where T : unmanaged, IUnknown.Interface
+ {
+ var changed = false;
+ while (comptr->Get() != null && IsReShadedComObject(comptr->Get()))
+ {
+ // Expectation: the pointer to the underlying object should come early after the overriden vtable.
+ for (nint i = 8; i <= 0x20; i += 8)
+ {
+ var ppObjectBehind = (nint)comptr->Get() + i;
+
+ // Is the thing directly pointed from the address an actual something in the memory?
+ if (!IsValidReadableMemoryAddress(ppObjectBehind, 8))
+ continue;
+
+ var pObjectBehind = *(nint*)ppObjectBehind;
+
+ // Is the address of vtable readable?
+ if (!IsValidReadableMemoryAddress(pObjectBehind, sizeof(nint)))
+ continue;
+ var pObjectBehindVtbl = *(nint*)pObjectBehind;
+
+ // Is the vtable itself readable?
+ if (!IsValidReadableMemoryAddress(pObjectBehindVtbl, vtblSize))
+ continue;
+
+ // Are individual functions in vtable executable?
+ var valid = true;
+ for (var j = 0; valid && j < vtblSize; j += sizeof(nint))
+ valid &= IsValidExecutableMemoryAddress(*(nint*)(pObjectBehindVtbl + j), 1);
+ if (!valid)
+ continue;
+
+ // Interpret the object as an IUnknown.
+ // Note that `using` is not used, and `Attach` is used. We do not alter the reference count yet.
+ var punk = default(ComPtr);
+ punk.Attach((IUnknown*)pObjectBehind);
+
+ // Is the IUnknown object also the type we want?
+ using var comptr2 = default(ComPtr);
+ if (punk.As(&comptr2).FAILED)
+ continue;
+
+ comptr2.Swap(comptr);
+ changed = true;
+ break;
+ }
+
+ if (!changed)
+ break;
+ }
+
+ return changed;
+ }
+
+ private static bool BelongsInReShadeDll(nint ptr)
+ {
+ foreach (ProcessModule processModule in Process.GetCurrentProcess().Modules)
+ {
+ if (ptr < processModule.BaseAddress)
+ continue;
+
+ var dosh = (IMAGE_DOS_HEADER*)processModule.BaseAddress;
+ var nth = (IMAGE_NT_HEADERS64*)(processModule.BaseAddress + dosh->e_lfanew);
+ if (ptr >= processModule.BaseAddress + nth->OptionalHeader.SizeOfImage)
+ continue;
+
+ fixed (byte* pfn0 = "CreateDXGIFactory"u8)
+ fixed (byte* pfn1 = "D2D1CreateDevice"u8)
+ fixed (byte* pfn2 = "D3D10CreateDevice"u8)
+ fixed (byte* pfn3 = "D3D11CreateDevice"u8)
+ fixed (byte* pfn4 = "D3D12CreateDevice"u8)
+ fixed (byte* pfn5 = "glBegin"u8)
+ fixed (byte* pfn6 = "vkCreateDevice"u8)
+ {
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn0) == 0)
+ continue;
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn1) == 0)
+ continue;
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn2) == 0)
+ continue;
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn3) == 0)
+ continue;
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn4) == 0)
+ continue;
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn5) == 0)
+ continue;
+ if (GetProcAddress((HMODULE)dosh, (sbyte*)pfn6) == 0)
+ continue;
+ }
+
+ var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName);
+
+ if (fileInfo.FileDescription == null)
+ continue;
+
+ if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade"))
+ continue;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsReShadedComObject(T* obj)
+ where T : unmanaged, IUnknown.Interface
+ {
+ if (!IsValidReadableMemoryAddress((nint)obj, sizeof(nint)))
+ return false;
+
+ try
+ {
+ var vtbl = (nint**)Marshal.ReadIntPtr((nint)obj);
+ if (!IsValidReadableMemoryAddress((nint)vtbl, sizeof(nint) * 3))
+ return false;
+
+ for (var i = 0; i < 3; i++)
+ {
+ var pfn = Marshal.ReadIntPtr((nint)(vtbl + i));
+ if (!IsValidExecutableMemoryAddress(pfn, 1))
+ return false;
+ if (!BelongsInReShadeDll(pfn))
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static bool IsValidReadableMemoryAddress(nint p, nint size)
+ {
+ while (size > 0)
+ {
+ if (!IsValidUserspaceMemoryAddress(p))
+ return false;
+
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0)
+ return false;
+
+ if (mbi is not
+ {
+ State: MEM.MEM_COMMIT,
+ Protect: PAGE.PAGE_READONLY or PAGE.PAGE_READWRITE or PAGE.PAGE_EXECUTE_READ
+ or PAGE.PAGE_EXECUTE_READWRITE,
+ })
+ return false;
+
+ var regionSize = (nint)((mbi.RegionSize + 0xFFFUL) & ~0x1000UL);
+ var checkedSize = ((nint)mbi.BaseAddress + regionSize) - p;
+ size -= checkedSize;
+ p += checkedSize;
+ }
+
+ return true;
+ }
+
+ private static bool IsValidExecutableMemoryAddress(nint p, nint size)
+ {
+ while (size > 0)
+ {
+ if (!IsValidUserspaceMemoryAddress(p))
+ return false;
+
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0)
+ return false;
+
+ if (mbi is not
+ {
+ State: MEM.MEM_COMMIT,
+ Protect: PAGE.PAGE_EXECUTE or PAGE.PAGE_EXECUTE_READ or PAGE.PAGE_EXECUTE_READWRITE
+ or PAGE.PAGE_EXECUTE_WRITECOPY,
+ })
+ return false;
+
+ var regionSize = (nint)((mbi.RegionSize + 0xFFFUL) & ~0x1000UL);
+ var checkedSize = ((nint)mbi.BaseAddress + regionSize) - p;
+ size -= checkedSize;
+ p += checkedSize;
+ }
+
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsValidUserspaceMemoryAddress(nint p)
+ {
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/virtual-address-spaces
+ // A 64-bit process on 64-bit Windows has a virtual address space within the 128-terabyte range
+ // 0x000'00000000 through 0x7FFF'FFFFFFFF.
+ return p >= 0x10000 && p <= unchecked((nint)0x7FFF_FFFFFFFFUL);
+ }
+}
diff --git a/Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs b/Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs
new file mode 100644
index 000000000..4a41191b5
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/IImGuiBackend.cs
@@ -0,0 +1,64 @@
+using Dalamud.Interface.ImGuiBackend.Delegates;
+using Dalamud.Interface.ImGuiBackend.InputHandler;
+using Dalamud.Interface.ImGuiBackend.Renderers;
+
+namespace Dalamud.Interface.ImGuiBackend;
+
+/// Backend for ImGui.
+internal interface IImGuiBackend : IDisposable
+{
+ /// User methods invoked every ImGui frame to construct custom UIs.
+ event ImGuiBuildUiDelegate? BuildUi;
+
+ /// User methods invoked every ImGui frame on handling inputs.
+ event ImGuiNewInputFrameDelegate? NewInputFrame;
+
+ /// User methods invoked every ImGui frame on handling renders.
+ event ImGuiNewRenderFrameDelegate? NewRenderFrame;
+
+ /// Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor.
+ ///
+ bool UpdateCursor { get; set; }
+
+ /// Gets or sets the path of ImGui configuration .ini file.
+ string? IniPath { get; set; }
+
+ /// Gets the device handle.
+ nint DeviceHandle { get; }
+
+ /// Gets the input handler.
+ IImGuiInputHandler InputHandler { get; }
+
+ /// Gets the renderer.
+ IImGuiRenderer Renderer { get; }
+
+ /// Performs a render cycle.
+ void Render();
+
+ /// Handles stuff before resizing happens.
+ void OnPreResize();
+
+ /// Handles stuff after resizing happens.
+ /// The new width.
+ /// The new height.
+ void OnPostResize(int newWidth, int newHeight);
+
+ /// Invalidates fonts immediately.
+ /// Call this while handling .
+ void InvalidateFonts();
+
+ /// Determines if is owned by this.
+ /// The cursor.
+ /// Whether it is the case.
+ bool IsImGuiCursor(nint cursorHandle);
+
+ /// Determines if this instance of is rendering to
+ /// .
+ /// The present target handle.
+ /// Whether it is the case.
+ bool IsAttachedToPresentationTarget(nint targetHandle);
+
+ /// Determines if the main viewport is full screen.
+ /// Whether it is the case.
+ bool IsMainViewportFullScreen();
+}
diff --git a/Dalamud/Interface/ImGuiBackend/IWin32Backend.cs b/Dalamud/Interface/ImGuiBackend/IWin32Backend.cs
new file mode 100644
index 000000000..0b158dfbc
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/IWin32Backend.cs
@@ -0,0 +1,15 @@
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.ImGuiBackend;
+
+/// with Win32 support.
+internal interface IWin32Backend : IImGuiBackend
+{
+ /// Processes window messages.
+ /// Handle of the window.
+ /// Type of window message.
+ /// wParam.
+ /// lParam.
+ /// Return value.
+ public nint? ProcessWndProcW(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam);
+}
diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs
new file mode 100644
index 000000000..e6da2281e
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/InputHandler/IImGuiInputHandler.cs
@@ -0,0 +1,22 @@
+namespace Dalamud.Interface.ImGuiBackend.InputHandler;
+
+/// A simple shared public interface that all ImGui input implementations follows.
+internal interface IImGuiInputHandler : IDisposable
+{
+ /// Gets or sets a value indicating whether the cursor should be overridden with the ImGui cursor.
+ ///
+ public bool UpdateCursor { get; set; }
+
+ /// Gets or sets the path of ImGui configuration .ini file.
+ string? IniPath { get; set; }
+
+ /// Determines if is owned by this.
+ /// The cursor.
+ /// Whether it is the case.
+ public bool IsImGuiCursor(nint cursorHandle);
+
+ /// Marks the beginning of a new frame.
+ /// The width of the new frame.
+ /// The height of the new frame.
+ void NewFrame(int width, int height);
+}
diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs
new file mode 100644
index 000000000..5710a5991
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs
@@ -0,0 +1,309 @@
+using System.Runtime.CompilerServices;
+
+using Dalamud.Bindings.ImGui;
+
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Interface.ImGuiBackend.InputHandler;
+
+///
+/// An implementation of , using Win32 APIs.
+///
+internal sealed partial class Win32InputHandler
+{
+ ///
+ /// Maps a to .
+ ///
+ /// The virtual key.
+ /// The corresponding .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ImGuiKey VirtualKeyToImGuiKey(int key) => key switch
+ {
+ VK.VK_TAB => ImGuiKey.Tab,
+ VK.VK_LEFT => ImGuiKey.LeftArrow,
+ VK.VK_RIGHT => ImGuiKey.RightArrow,
+ VK.VK_UP => ImGuiKey.UpArrow,
+ VK.VK_DOWN => ImGuiKey.DownArrow,
+ VK.VK_PRIOR => ImGuiKey.PageUp,
+ VK.VK_NEXT => ImGuiKey.PageDown,
+ VK.VK_HOME => ImGuiKey.Home,
+ VK.VK_END => ImGuiKey.End,
+ VK.VK_INSERT => ImGuiKey.Insert,
+ VK.VK_DELETE => ImGuiKey.Delete,
+ VK.VK_BACK => ImGuiKey.Backspace,
+ VK.VK_SPACE => ImGuiKey.Space,
+ VK.VK_RETURN => ImGuiKey.Enter,
+ VK.VK_ESCAPE => ImGuiKey.Escape,
+ VK.VK_OEM_7 => ImGuiKey.Apostrophe,
+ VK.VK_OEM_COMMA => ImGuiKey.Comma,
+ VK.VK_OEM_MINUS => ImGuiKey.Minus,
+ VK.VK_OEM_PERIOD => ImGuiKey.Period,
+ VK.VK_OEM_2 => ImGuiKey.Slash,
+ VK.VK_OEM_1 => ImGuiKey.Semicolon,
+ VK.VK_OEM_PLUS => ImGuiKey.Equal,
+ VK.VK_OEM_4 => ImGuiKey.LeftBracket,
+ VK.VK_OEM_5 => ImGuiKey.Backslash,
+ VK.VK_OEM_6 => ImGuiKey.RightBracket,
+ VK.VK_OEM_3 => ImGuiKey.GraveAccent,
+ VK.VK_CAPITAL => ImGuiKey.CapsLock,
+ VK.VK_SCROLL => ImGuiKey.ScrollLock,
+ VK.VK_NUMLOCK => ImGuiKey.NumLock,
+ VK.VK_SNAPSHOT => ImGuiKey.PrintScreen,
+ VK.VK_PAUSE => ImGuiKey.Pause,
+ VK.VK_NUMPAD0 => ImGuiKey.Keypad0,
+ VK.VK_NUMPAD1 => ImGuiKey.Keypad1,
+ VK.VK_NUMPAD2 => ImGuiKey.Keypad2,
+ VK.VK_NUMPAD3 => ImGuiKey.Keypad3,
+ VK.VK_NUMPAD4 => ImGuiKey.Keypad4,
+ VK.VK_NUMPAD5 => ImGuiKey.Keypad5,
+ VK.VK_NUMPAD6 => ImGuiKey.Keypad6,
+ VK.VK_NUMPAD7 => ImGuiKey.Keypad7,
+ VK.VK_NUMPAD8 => ImGuiKey.Keypad8,
+ VK.VK_NUMPAD9 => ImGuiKey.Keypad9,
+ VK.VK_DECIMAL => ImGuiKey.KeypadDecimal,
+ VK.VK_DIVIDE => ImGuiKey.KeypadDivide,
+ VK.VK_MULTIPLY => ImGuiKey.KeypadMultiply,
+ VK.VK_SUBTRACT => ImGuiKey.KeypadSubtract,
+ VK.VK_ADD => ImGuiKey.KeypadAdd,
+ VK.VK_RETURN + 256 => ImGuiKey.KeypadEnter,
+ VK.VK_LSHIFT => ImGuiKey.LeftShift,
+ VK.VK_LCONTROL => ImGuiKey.LeftCtrl,
+ VK.VK_LMENU => ImGuiKey.LeftAlt,
+ VK.VK_LWIN => ImGuiKey.LeftSuper,
+ VK.VK_RSHIFT => ImGuiKey.RightShift,
+ VK.VK_RCONTROL => ImGuiKey.RightCtrl,
+ VK.VK_RMENU => ImGuiKey.RightAlt,
+ VK.VK_RWIN => ImGuiKey.RightSuper,
+ VK.VK_APPS => ImGuiKey.Menu,
+ '0' => ImGuiKey.Key0,
+ '1' => ImGuiKey.Key1,
+ '2' => ImGuiKey.Key2,
+ '3' => ImGuiKey.Key3,
+ '4' => ImGuiKey.Key4,
+ '5' => ImGuiKey.Key5,
+ '6' => ImGuiKey.Key6,
+ '7' => ImGuiKey.Key7,
+ '8' => ImGuiKey.Key8,
+ '9' => ImGuiKey.Key9,
+ 'A' => ImGuiKey.A,
+ 'B' => ImGuiKey.B,
+ 'C' => ImGuiKey.C,
+ 'D' => ImGuiKey.D,
+ 'E' => ImGuiKey.E,
+ 'F' => ImGuiKey.F,
+ 'G' => ImGuiKey.G,
+ 'H' => ImGuiKey.H,
+ 'I' => ImGuiKey.I,
+ 'J' => ImGuiKey.J,
+ 'K' => ImGuiKey.K,
+ 'L' => ImGuiKey.L,
+ 'M' => ImGuiKey.M,
+ 'N' => ImGuiKey.N,
+ 'O' => ImGuiKey.O,
+ 'P' => ImGuiKey.P,
+ 'Q' => ImGuiKey.Q,
+ 'R' => ImGuiKey.R,
+ 'S' => ImGuiKey.S,
+ 'T' => ImGuiKey.T,
+ 'U' => ImGuiKey.U,
+ 'V' => ImGuiKey.V,
+ 'W' => ImGuiKey.W,
+ 'X' => ImGuiKey.X,
+ 'Y' => ImGuiKey.Y,
+ 'Z' => ImGuiKey.Z,
+ VK.VK_F1 => ImGuiKey.F1,
+ VK.VK_F2 => ImGuiKey.F2,
+ VK.VK_F3 => ImGuiKey.F3,
+ VK.VK_F4 => ImGuiKey.F4,
+ VK.VK_F5 => ImGuiKey.F5,
+ VK.VK_F6 => ImGuiKey.F6,
+ VK.VK_F7 => ImGuiKey.F7,
+ VK.VK_F8 => ImGuiKey.F8,
+ VK.VK_F9 => ImGuiKey.F9,
+ VK.VK_F10 => ImGuiKey.F10,
+ VK.VK_F11 => ImGuiKey.F11,
+ VK.VK_F12 => ImGuiKey.F12,
+ _ => ImGuiKey.None,
+ };
+
+ ///
+ /// Maps a to .
+ ///
+ /// The ImGui key.
+ /// The corresponding .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ImGuiKeyToVirtualKey(ImGuiKey key) => key switch
+ {
+ ImGuiKey.Tab => VK.VK_TAB,
+ ImGuiKey.LeftArrow => VK.VK_LEFT,
+ ImGuiKey.RightArrow => VK.VK_RIGHT,
+ ImGuiKey.UpArrow => VK.VK_UP,
+ ImGuiKey.DownArrow => VK.VK_DOWN,
+ ImGuiKey.PageUp => VK.VK_PRIOR,
+ ImGuiKey.PageDown => VK.VK_NEXT,
+ ImGuiKey.Home => VK.VK_HOME,
+ ImGuiKey.End => VK.VK_END,
+ ImGuiKey.Insert => VK.VK_INSERT,
+ ImGuiKey.Delete => VK.VK_DELETE,
+ ImGuiKey.Backspace => VK.VK_BACK,
+ ImGuiKey.Space => VK.VK_SPACE,
+ ImGuiKey.Enter => VK.VK_RETURN,
+ ImGuiKey.Escape => VK.VK_ESCAPE,
+ ImGuiKey.Apostrophe => VK.VK_OEM_7,
+ ImGuiKey.Comma => VK.VK_OEM_COMMA,
+ ImGuiKey.Minus => VK.VK_OEM_MINUS,
+ ImGuiKey.Period => VK.VK_OEM_PERIOD,
+ ImGuiKey.Slash => VK.VK_OEM_2,
+ ImGuiKey.Semicolon => VK.VK_OEM_1,
+ ImGuiKey.Equal => VK.VK_OEM_PLUS,
+ ImGuiKey.LeftBracket => VK.VK_OEM_4,
+ ImGuiKey.Backslash => VK.VK_OEM_5,
+ ImGuiKey.RightBracket => VK.VK_OEM_6,
+ ImGuiKey.GraveAccent => VK.VK_OEM_3,
+ ImGuiKey.CapsLock => VK.VK_CAPITAL,
+ ImGuiKey.ScrollLock => VK.VK_SCROLL,
+ ImGuiKey.NumLock => VK.VK_NUMLOCK,
+ ImGuiKey.PrintScreen => VK.VK_SNAPSHOT,
+ ImGuiKey.Pause => VK.VK_PAUSE,
+ ImGuiKey.Keypad0 => VK.VK_NUMPAD0,
+ ImGuiKey.Keypad1 => VK.VK_NUMPAD1,
+ ImGuiKey.Keypad2 => VK.VK_NUMPAD2,
+ ImGuiKey.Keypad3 => VK.VK_NUMPAD3,
+ ImGuiKey.Keypad4 => VK.VK_NUMPAD4,
+ ImGuiKey.Keypad5 => VK.VK_NUMPAD5,
+ ImGuiKey.Keypad6 => VK.VK_NUMPAD6,
+ ImGuiKey.Keypad7 => VK.VK_NUMPAD7,
+ ImGuiKey.Keypad8 => VK.VK_NUMPAD8,
+ ImGuiKey.Keypad9 => VK.VK_NUMPAD9,
+ ImGuiKey.KeypadDecimal => VK.VK_DECIMAL,
+ ImGuiKey.KeypadDivide => VK.VK_DIVIDE,
+ ImGuiKey.KeypadMultiply => VK.VK_MULTIPLY,
+ ImGuiKey.KeypadSubtract => VK.VK_SUBTRACT,
+ ImGuiKey.KeypadAdd => VK.VK_ADD,
+ ImGuiKey.KeypadEnter => VK.VK_RETURN + 256,
+ ImGuiKey.LeftShift => VK.VK_LSHIFT,
+ ImGuiKey.LeftCtrl => VK.VK_LCONTROL,
+ ImGuiKey.LeftAlt => VK.VK_LMENU,
+ ImGuiKey.LeftSuper => VK.VK_LWIN,
+ ImGuiKey.RightShift => VK.VK_RSHIFT,
+ ImGuiKey.RightCtrl => VK.VK_RCONTROL,
+ ImGuiKey.RightAlt => VK.VK_RMENU,
+ ImGuiKey.RightSuper => VK.VK_RWIN,
+ ImGuiKey.Menu => VK.VK_APPS,
+ ImGuiKey.Key0 => '0',
+ ImGuiKey.Key1 => '1',
+ ImGuiKey.Key2 => '2',
+ ImGuiKey.Key3 => '3',
+ ImGuiKey.Key4 => '4',
+ ImGuiKey.Key5 => '5',
+ ImGuiKey.Key6 => '6',
+ ImGuiKey.Key7 => '7',
+ ImGuiKey.Key8 => '8',
+ ImGuiKey.Key9 => '9',
+ ImGuiKey.A => 'A',
+ ImGuiKey.B => 'B',
+ ImGuiKey.C => 'C',
+ ImGuiKey.D => 'D',
+ ImGuiKey.E => 'E',
+ ImGuiKey.F => 'F',
+ ImGuiKey.G => 'G',
+ ImGuiKey.H => 'H',
+ ImGuiKey.I => 'I',
+ ImGuiKey.J => 'J',
+ ImGuiKey.K => 'K',
+ ImGuiKey.L => 'L',
+ ImGuiKey.M => 'M',
+ ImGuiKey.N => 'N',
+ ImGuiKey.O => 'O',
+ ImGuiKey.P => 'P',
+ ImGuiKey.Q => 'Q',
+ ImGuiKey.R => 'R',
+ ImGuiKey.S => 'S',
+ ImGuiKey.T => 'T',
+ ImGuiKey.U => 'U',
+ ImGuiKey.V => 'V',
+ ImGuiKey.W => 'W',
+ ImGuiKey.X => 'X',
+ ImGuiKey.Y => 'Y',
+ ImGuiKey.Z => 'Z',
+ ImGuiKey.F1 => VK.VK_F1,
+ ImGuiKey.F2 => VK.VK_F2,
+ ImGuiKey.F3 => VK.VK_F3,
+ ImGuiKey.F4 => VK.VK_F4,
+ ImGuiKey.F5 => VK.VK_F5,
+ ImGuiKey.F6 => VK.VK_F6,
+ ImGuiKey.F7 => VK.VK_F7,
+ ImGuiKey.F8 => VK.VK_F8,
+ ImGuiKey.F9 => VK.VK_F9,
+ ImGuiKey.F10 => VK.VK_F10,
+ ImGuiKey.F11 => VK.VK_F11,
+ ImGuiKey.F12 => VK.VK_F12,
+ _ => 0,
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsGamepadKey(ImGuiKey key) => (int)key is >= 617 and <= 640;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsModKey(ImGuiKey key) =>
+ key is ImGuiKey.LeftShift
+ or ImGuiKey.RightShift
+ or ImGuiKey.ModShift
+ or ImGuiKey.LeftCtrl
+ or ImGuiKey.ModCtrl
+ or ImGuiKey.LeftAlt
+ or ImGuiKey.RightAlt
+ or ImGuiKey.ModAlt;
+
+ private static void AddKeyEvent(ImGuiKey key, bool down, int nativeKeycode, int nativeScancode = -1)
+ {
+ var io = ImGui.GetIO();
+ io.AddKeyEvent(key, down);
+ io.SetKeyEventNativeData(key, nativeKeycode, nativeScancode);
+ }
+
+ private static void UpdateKeyModifiers()
+ {
+ var io = ImGui.GetIO();
+ io.AddKeyEvent(ImGuiKey.ModCtrl, IsVkDown(VK.VK_CONTROL));
+ io.AddKeyEvent(ImGuiKey.ModShift, IsVkDown(VK.VK_SHIFT));
+ io.AddKeyEvent(ImGuiKey.ModAlt, IsVkDown(VK.VK_MENU));
+ io.AddKeyEvent(ImGuiKey.ModSuper, IsVkDown(VK.VK_APPS));
+ }
+
+ private static void UpAllKeys()
+ {
+ var io = ImGui.GetIO();
+ for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++)
+ io.AddKeyEvent((ImGuiKey)i, false);
+ }
+
+ private static void UpAllMouseButton()
+ {
+ var io = ImGui.GetIO();
+ for (var i = 0; i < io.MouseDown.Length; i++)
+ io.MouseDown[i] = false;
+ }
+
+ private static bool IsVkDown(int key) => (TerraFX.Interop.Windows.Windows.GetKeyState(key) & 0x8000) != 0;
+
+ private static int GetButton(uint msg, WPARAM wParam) => msg switch
+ {
+ WM.WM_LBUTTONUP or WM.WM_LBUTTONDOWN or WM.WM_LBUTTONDBLCLK => 0,
+ WM.WM_RBUTTONUP or WM.WM_RBUTTONDOWN or WM.WM_RBUTTONDBLCLK => 1,
+ WM.WM_MBUTTONUP or WM.WM_MBUTTONDOWN or WM.WM_MBUTTONDBLCLK => 2,
+ WM.WM_XBUTTONUP or WM.WM_XBUTTONDOWN or WM.WM_XBUTTONDBLCLK =>
+ TerraFX.Interop.Windows.Windows.GET_XBUTTON_WPARAM(wParam) == TerraFX.Interop.Windows.Windows.XBUTTON1 ? 3 : 4,
+ _ => 0,
+ };
+
+ private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
+ {
+ style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW);
+ exStyle =
+ (int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW);
+ exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
+ if (flags.HasFlag(ImGuiViewportFlags.TopMost))
+ exStyle |= WS.WS_EX_TOPMOST;
+ }
+}
diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs
new file mode 100644
index 000000000..a76eb1703
--- /dev/null
+++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs
@@ -0,0 +1,1041 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+using Dalamud.Bindings.ImGui;
+
+using Serilog;
+
+using TerraFX.Interop.Windows;
+
+using static Dalamud.Interface.ImGuiBackend.Helpers.ImGuiViewportHelpers;
+
+using static TerraFX.Interop.Windows.Windows;
+
+using ERROR = TerraFX.Interop.Windows.ERROR;
+
+namespace Dalamud.Interface.ImGuiBackend.InputHandler;
+
+///
+/// An implementation of , using Win32 APIs.
+/// Largely a port of https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_win32.cpp,
+/// though some changes and wndproc hooking.
+///
+[SuppressMessage(
+ "StyleCop.CSharp.LayoutRules",
+ "SA1519:Braces should not be omitted from multi-line child statement",
+ Justification = "Multiple fixed/using scopes")]
+internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
+{
+ private readonly HWND hWnd;
+ private readonly HCURSOR[] cursors;
+
+ private readonly WndProcDelegate wndProcDelegate;
+ private readonly bool[] imguiMouseIsDown;
+ private readonly nint platformNamePtr;
+
+ private ViewportHandler viewportHandler;
+
+ private long lastTime;
+
+ private nint iniPathPtr;
+
+ private bool disposedValue;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The window handle.
+ public Win32InputHandler(HWND hWnd)
+ {
+ var io = ImGui.GetIO();
+ if (ImGui.GetIO().Handle->BackendPlatformName is not null)
+ throw new InvalidOperationException("ImGui backend platform seems to be have been already attached.");
+
+ this.hWnd = hWnd;
+
+ // hook wndproc
+ // have to hold onto the delegate to keep it in memory for unmanaged code
+ this.wndProcDelegate = this.WndProcDetour;
+
+ io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors |
+ ImGuiBackendFlags.HasSetMousePos |
+ ImGuiBackendFlags.RendererHasViewports |
+ ImGuiBackendFlags.PlatformHasViewports;
+
+ this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#");
+ io.Handle->BackendPlatformName = (byte*)this.platformNamePtr;
+
+ var mainViewport = ImGui.GetMainViewport();
+ mainViewport.PlatformHandle = mainViewport.PlatformHandleRaw = hWnd;
+ if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
+ this.viewportHandler = new(this);
+
+ this.imguiMouseIsDown = new bool[5];
+
+ this.cursors = new HCURSOR[9];
+ this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW);
+ this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM);
+ this.cursors[(int)ImGuiMouseCursor.ResizeAll] = LoadCursorW(default, IDC.IDC_SIZEALL);
+ this.cursors[(int)ImGuiMouseCursor.ResizeEw] = LoadCursorW(default, IDC.IDC_SIZEWE);
+ this.cursors[(int)ImGuiMouseCursor.ResizeNs] = LoadCursorW(default, IDC.IDC_SIZENS);
+ this.cursors[(int)ImGuiMouseCursor.ResizeNesw] = LoadCursorW(default, IDC.IDC_SIZENESW);
+ this.cursors[(int)ImGuiMouseCursor.ResizeNwse] = LoadCursorW(default, IDC.IDC_SIZENWSE);
+ this.cursors[(int)ImGuiMouseCursor.Hand] = LoadCursorW(default, IDC.IDC_HAND);
+ this.cursors[(int)ImGuiMouseCursor.NotAllowed] = LoadCursorW(default, IDC.IDC_NO);
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~Win32InputHandler() => this.ReleaseUnmanagedResources();
+
+ private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam);
+
+ private delegate BOOL MonitorEnumProcDelegate(HMONITOR monitor, HDC hdc, RECT* rect, LPARAM lparam);
+
+ ///
+ public bool UpdateCursor { get; set; } = true;
+
+ ///
+ public string? IniPath
+ {
+ get
+ {
+ var ptr = (byte*)this.iniPathPtr;
+ if (ptr is null)
+ return string.Empty;
+ var len = 0;
+ while (ptr![len] != 0)
+ len++;
+ return Encoding.UTF8.GetString(ptr, len);
+ }
+
+ set
+ {
+ if (this.iniPathPtr != 0)
+ Marshal.FreeHGlobal(this.iniPathPtr);
+ if (!string.IsNullOrEmpty(value))
+ {
+ var e = Encoding.UTF8.GetByteCount(value);
+ var newAlloc = Marshal.AllocHGlobal(e + 2);
+ try
+ {
+ var span = new Span((void*)newAlloc, e + 2);
+ span[^1] = span[^2] = 0;
+ Encoding.UTF8.GetBytes(value, span);
+ }
+ catch
+ {
+ Marshal.FreeHGlobal(newAlloc);
+ throw;
+ }
+
+ this.iniPathPtr = newAlloc;
+ }
+
+ ImGui.GetIO().Handle->IniFilename = (byte*)this.iniPathPtr;
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ public bool IsImGuiCursor(nint hCursor) => this.cursors.Contains((HCURSOR)hCursor);
+
+ ///
+ public void NewFrame(int targetWidth, int targetHeight)
+ {
+ var io = ImGui.GetIO();
+
+ io.DisplaySize.X = targetWidth;
+ io.DisplaySize.Y = targetHeight;
+ io.DisplayFramebufferScale.X = 1f;
+ io.DisplayFramebufferScale.Y = 1f;
+
+ var frequency = Stopwatch.Frequency;
+ var currentTime = Stopwatch.GetTimestamp();
+ io.DeltaTime = this.lastTime > 0 ? (float)((double)(currentTime - this.lastTime) / frequency) : 1f / 60;
+ this.lastTime = currentTime;
+
+ this.viewportHandler.UpdateMonitors();
+
+ this.UpdateMousePos();
+
+ this.ProcessKeyEventsWorkarounds();
+
+ // TODO: need to figure out some way to unify all this
+ // The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues
+ // The top case more or less works if we use ImGui's software cursor (and ideally hide the
+ // game's hardware cursor)
+ // It would be nice if hooking WM_SETCURSOR worked as it 'should' so that external hooking
+ // wasn't necessary
+
+ // this is what imgui's example does, but it doesn't seem to work for us
+ // this could be a timing issue.. or their logic could just be wrong for many applications
+ // var cursor = io.MouseDrawCursor ? ImGuiMouseCursor.None : ImGui.GetMouseCursor();
+ // if (_oldCursor != cursor)
+ // {
+ // _oldCursor = cursor;
+ // UpdateMouseCursor();
+ // }
+
+ // hacky attempt to make cursors work how I think they 'should'
+ if ((io.WantCaptureMouse || io.MouseDrawCursor) && this.UpdateCursor)
+ {
+ this.UpdateMouseCursor();
+ }
+
+ // Similar issue seen with overlapping mouse clicks
+ // eg, right click and hold on imgui window, drag off, left click and hold
+ // release right click, release left click -> right click was 'stuck' and imgui
+ // would become unresponsive
+ if (!io.WantCaptureMouse)
+ {
+ for (var i = 0; i < io.MouseDown.Length; i++)
+ {
+ io.MouseDown[i] = false;
+ }
+ }
+ }
+
+ ///
+ /// Processes window messages. Supports both WndProcA and WndProcW.
+ ///
+ /// Handle of the window.
+ /// Type of window message.
+ /// wParam.
+ /// lParam.
+ /// Return value, if not doing further processing.
+ public LRESULT? ProcessWndProcW(HWND hWndCurrent, uint msg, WPARAM wParam, LPARAM lParam)
+ {
+ if (ImGui.GetCurrentContext().IsNull)
+ return null;
+
+ var io = ImGui.GetIO();
+
+ switch (msg)
+ {
+ case WM.WM_LBUTTONDOWN:
+ case WM.WM_LBUTTONDBLCLK:
+ case WM.WM_RBUTTONDOWN:
+ case WM.WM_RBUTTONDBLCLK:
+ case WM.WM_MBUTTONDOWN:
+ case WM.WM_MBUTTONDBLCLK:
+ case WM.WM_XBUTTONDOWN:
+ case WM.WM_XBUTTONDBLCLK:
+ {
+ var button = GetButton(msg, wParam);
+ if (io.WantCaptureMouse)
+ {
+ if (!ImGui.IsAnyMouseDown() && GetCapture() == nint.Zero)
+ SetCapture(hWndCurrent);
+
+ io.MouseDown[button] = true;
+ this.imguiMouseIsDown[button] = true;
+ return default(LRESULT);
+ }
+
+ if (ImGui.IsAnyItemActive())
+ ImGui.ClearWindowFocus();
+
+ break;
+ }
+
+ case WM.WM_LBUTTONUP:
+ case WM.WM_RBUTTONUP:
+ case WM.WM_MBUTTONUP:
+ case WM.WM_XBUTTONUP:
+ {
+ var button = GetButton(msg, wParam);
+ if (io.WantCaptureMouse && this.imguiMouseIsDown[button])
+ {
+ if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
+ ReleaseCapture();
+
+ io.MouseDown[button] = false;
+ this.imguiMouseIsDown[button] = false;
+ return default(LRESULT);
+ }
+
+ break;
+ }
+
+ case WM.WM_MOUSEWHEEL:
+ if (io.WantCaptureMouse)
+ {
+ io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ return default(LRESULT);
+ }
+
+ break;
+ case WM.WM_MOUSEHWHEEL:
+ if (io.WantCaptureMouse)
+ {
+ io.MouseWheelH += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ return default(LRESULT);
+ }
+
+ break;
+ case WM.WM_KEYDOWN:
+ case WM.WM_SYSKEYDOWN:
+ case WM.WM_KEYUP:
+ case WM.WM_SYSKEYUP:
+ {
+ var isKeyDown = msg is WM.WM_KEYDOWN or WM.WM_SYSKEYDOWN;
+ if ((int)wParam >= 256)
+ break;
+
+ // Submit modifiers
+ UpdateKeyModifiers();
+
+ // Obtain virtual key code
+ // (keypad enter doesn't have its own... VK_RETURN with KF_EXTENDED flag means keypad enter, see IM_VK_KEYPAD_ENTER definition for details, it is mapped to ImGuiKey.KeyPadEnter.)
+ var vk = (int)wParam;
+ if (vk == VK.VK_RETURN && ((int)lParam & (256 << 16)) > 0)
+ vk = VK.VK_RETURN + 256;
+
+ // Submit key event
+ var key = VirtualKeyToImGuiKey(vk);
+ var scancode = ((int)lParam & 0xff0000) >> 16;
+ if (key != ImGuiKey.None && io.WantTextInput)
+ {
+ AddKeyEvent(key, isKeyDown, vk, scancode);
+ return nint.Zero;
+ }
+
+ switch (vk)
+ {
+ // Submit individual left/right modifier events
+ case VK.VK_SHIFT:
+ // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in OnProcessKeyEventsWorkarounds()
+ if (IsVkDown(VK.VK_LSHIFT) == isKeyDown)
+ AddKeyEvent(ImGuiKey.LeftShift, isKeyDown, VK.VK_LSHIFT, scancode);
+
+ if (IsVkDown(VK.VK_RSHIFT) == isKeyDown)
+ AddKeyEvent(ImGuiKey.RightShift, isKeyDown, VK.VK_RSHIFT, scancode);
+
+ break;
+
+ case VK.VK_CONTROL:
+ if (IsVkDown(VK.VK_LCONTROL) == isKeyDown)
+ AddKeyEvent(ImGuiKey.LeftCtrl, isKeyDown, VK.VK_LCONTROL, scancode);
+
+ if (IsVkDown(VK.VK_RCONTROL) == isKeyDown)
+ AddKeyEvent(ImGuiKey.RightCtrl, isKeyDown, VK.VK_RCONTROL, scancode);
+
+ break;
+
+ case VK.VK_MENU:
+ if (IsVkDown(VK.VK_LMENU) == isKeyDown)
+ AddKeyEvent(ImGuiKey.LeftAlt, isKeyDown, VK.VK_LMENU, scancode);
+
+ if (IsVkDown(VK.VK_RMENU) == isKeyDown)
+ AddKeyEvent(ImGuiKey.RightAlt, isKeyDown, VK.VK_RMENU, scancode);
+
+ break;
+ }
+
+ break;
+ }
+
+ case WM.WM_CHAR:
+ if (io.WantTextInput)
+ {
+ io.AddInputCharacter(new Rune((uint)wParam));
+ return nint.Zero;
+ }
+
+ break;
+
+ // this never seemed to work reasonably, but I'll leave it for now
+ case WM.WM_SETCURSOR:
+ if (io.WantCaptureMouse)
+ {
+ if (LOWORD(lParam) == HTCLIENT && this.UpdateMouseCursor())
+ {
+ // this message returns 1 to block further processing
+ // because consistency is no fun
+ return 1;
+ }
+ }
+
+ break;
+
+ case WM.WM_DISPLAYCHANGE:
+ this.viewportHandler.UpdateMonitors();
+ break;
+
+ case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd:
+ if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
+ ReleaseCapture();
+
+ ImGui.GetIO().WantCaptureMouse = false;
+ ImGui.ClearWindowFocus();
+ break;
+ }
+
+ return null;
+ }
+
+ private void UpdateMousePos()
+ {
+ var io = ImGui.GetIO();
+ var pt = default(POINT);
+
+ // Depending on if Viewports are enabled, we have to change how we process
+ // the cursor position. If viewports are enabled, we pass the absolute cursor
+ // position to ImGui. Otherwise, we use the old method of passing client-local
+ // mouse position to ImGui.
+ if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
+ {
+ if (io.WantSetMousePos)
+ {
+ SetCursorPos((int)io.MousePos.X, (int)io.MousePos.Y);
+ }
+
+ if (GetCursorPos(&pt))
+ {
+ io.MousePos.X = pt.x;
+ io.MousePos.Y = pt.y;
+ }
+ else
+ {
+ io.MousePos.X = float.MinValue;
+ io.MousePos.Y = float.MinValue;
+ }
+ }
+ else
+ {
+ if (io.WantSetMousePos)
+ {
+ pt.x = (int)io.MousePos.X;
+ pt.y = (int)io.MousePos.Y;
+ ClientToScreen(this.hWnd, &pt);
+ SetCursorPos(pt.x, pt.y);
+ }
+
+ if (GetCursorPos(&pt) && ScreenToClient(this.hWnd, &pt))
+ {
+ io.MousePos.X = pt.x;
+ io.MousePos.Y = pt.y;
+ }
+ else
+ {
+ io.MousePos.X = float.MinValue;
+ io.MousePos.Y = float.MinValue;
+ }
+ }
+ }
+
+ private bool UpdateMouseCursor()
+ {
+ var io = ImGui.GetIO();
+ if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.NoMouseCursorChange))
+ return false;
+
+ var cur = ImGui.GetMouseCursor();
+ if (cur == ImGuiMouseCursor.None || io.MouseDrawCursor)
+ SetCursor(default);
+ else
+ SetCursor(this.cursors[(int)cur]);
+
+ return true;
+ }
+
+ private void ProcessKeyEventsWorkarounds()
+ {
+ // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.
+ if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT))
+ AddKeyEvent(ImGuiKey.LeftShift, false, VK.VK_LSHIFT);
+ if (ImGui.IsKeyDown(ImGuiKey.RightShift) && !IsVkDown(VK.VK_RSHIFT))
+ AddKeyEvent(ImGuiKey.RightShift, false, VK.VK_RSHIFT);
+
+ // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW).
+ if (ImGui.IsKeyDown(ImGuiKey.LeftSuper) && !IsVkDown(VK.VK_LWIN))
+ AddKeyEvent(ImGuiKey.LeftSuper, false, VK.VK_LWIN);
+ if (ImGui.IsKeyDown(ImGuiKey.RightSuper) && !IsVkDown(VK.VK_RWIN))
+ AddKeyEvent(ImGuiKey.RightSuper, false, VK.VK_RWIN);
+
+ // From ImGui's FAQ:
+ // Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event
+ // that your application receive will typically have io.WantCaptureKeyboard == false. Depending on your
+ // application logic it may or not be inconvenient.
+ //
+ // With how the local wndproc works, this causes the key up event to be missed when exiting ImGui text entry
+ // (eg, from hitting enter or escape. There may be other ways as well)
+ // This then causes the key to appear 'stuck' down, which breaks subsequent attempts to use the input field.
+ // This is something of a brute force fix that basically makes key up events irrelevant
+ // Holding a key will send repeated key down events and (re)set these where appropriate, so this should be ok.
+ var io = ImGui.GetIO();
+ if (!io.WantTextInput)
+ {
+ // See: https://github.com/goatcorp/ImGuiScene/pull/13
+ // > GetForegroundWindow from winuser.h is a surprisingly expensive function.
+ var isForeground = GetForegroundWindow() == this.hWnd;
+ for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++)
+ {
+ // Skip raising modifier keys if the game is focused.
+ // This allows us to raise the keys when one is held and the window becomes unfocused,
+ // but if we do not skip them, they will only be held down every 4th frame or so.
+ if (isForeground && (IsGamepadKey((ImGuiKey)i) || IsModKey((ImGuiKey)i)))
+ continue;
+ io.AddKeyEvent((ImGuiKey)i, false);
+ }
+ }
+ }
+
+ ///
+ /// This WndProc is called for ImGuiScene windows. WndProc for main window will be called back from somewhere else.
+ ///
+ private LRESULT WndProcDetour(HWND hWndCurrent, uint msg, WPARAM wParam, LPARAM lParam)
+ {
+ // Attempt to process the result of this window message
+ // We will return the result here if we consider the message handled
+ var processResult = this.ProcessWndProcW(hWndCurrent, msg, wParam, lParam);
+
+ if (processResult != null) return processResult.Value;
+
+ // The message wasn't handled, but it's a platform window
+ // So we have to handle some messages ourselves
+ // BUT we might have disposed the context, so check that
+ if (ImGui.GetCurrentContext().IsNull)
+ return DefWindowProcW(hWndCurrent, msg, wParam, lParam);
+
+ var viewport = ImGui.FindViewportByPlatformHandle(hWndCurrent);
+ if (viewport.Handle == null)
+ return DefWindowProcW(hWndCurrent, msg, wParam, lParam);
+
+ switch (msg)
+ {
+ case WM.WM_CLOSE:
+ viewport.PlatformRequestClose = true;
+ return 0;
+ case WM.WM_MOVE:
+ viewport.PlatformRequestMove = true;
+ return 0;
+ case WM.WM_SIZE:
+ viewport.PlatformRequestResize = true;
+ return 0;
+ case WM.WM_MOUSEACTIVATE:
+ // We never want our platform windows to be active, or else Windows will think we
+ // want messages dispatched with its hWnd. We don't. The only way to activate a platform
+ // window is via clicking, it does not appear on the taskbar or alt-tab, so we just
+ // brute force behavior here.
+
+ // Make the game the foreground window. This prevents ImGui windows from becoming
+ // choppy when users have the "limit FPS" option enabled in-game
+ SetForegroundWindow(this.hWnd);
+
+ // Also set the window capture to the main window, as focus will not cause
+ // future messages to be dispatched to the main window unless it is receiving capture
+ SetCapture(this.hWnd);
+
+ // We still want to return MA_NOACTIVATE
+ // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mouseactivate
+ return MA.MA_NOACTIVATE;
+ case WM.WM_NCHITTEST:
+ // Let mouse pass-through the window. This will allow the backend to set io.MouseHoveredViewport properly (which is OPTIONAL).
+ // The ImGuiViewportFlags_NoInputs flag is set while dragging a viewport, as want to detect the window behind the one we are dragging.
+ // If you cannot easily access those viewport flags from your windowing/event code: you may manually synchronize its state e.g. in
+ // your main loop after calling UpdatePlatformWindows(). Iterate all viewports/platform windows and pass the flag to your windowing system.
+ if (viewport.Flags.HasFlag(ImGuiViewportFlags.NoInputs))
+ {
+ return HTTRANSPARENT;
+ }
+
+ break;
+ }
+
+ return DefWindowProcW(hWndCurrent, msg, wParam, lParam);
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ if (this.disposedValue)
+ return;
+
+ this.viewportHandler.Dispose();
+
+ this.cursors.AsSpan().Clear();
+
+ if (ImGui.GetIO().Handle->BackendPlatformName == (void*)this.platformNamePtr)
+ ImGui.GetIO().Handle->BackendPlatformName = null;
+ if (this.platformNamePtr != nint.Zero)
+ Marshal.FreeHGlobal(this.platformNamePtr);
+
+ if (this.iniPathPtr != nint.Zero)
+ {
+ ImGui.GetIO().Handle->IniFilename = null;
+ Marshal.FreeHGlobal(this.iniPathPtr);
+ this.iniPathPtr = nint.Zero;
+ }
+
+ this.disposedValue = true;
+ }
+
+ private struct ViewportHandler : IDisposable
+ {
+ [SuppressMessage("ReSharper", "CollectionNeverQueried.Local", Justification = "Keeping references alive")]
+ private readonly List