diff --git a/Dalamud/ClientLanguage.cs b/Dalamud/ClientLanguage.cs index ddd69576d..4e04d4a54 100644 --- a/Dalamud/ClientLanguage.cs +++ b/Dalamud/ClientLanguage.cs @@ -1,28 +1,27 @@ -namespace Dalamud +namespace Dalamud; + +/// +/// Enum describing the language the game loads in. +/// +public enum ClientLanguage { /// - /// Enum describing the language the game loads in. + /// Indicating a Japanese game client. /// - public enum ClientLanguage - { - /// - /// Indicating a Japanese game client. - /// - Japanese, + Japanese, - /// - /// Indicating an English game client. - /// - English, + /// + /// Indicating an English game client. + /// + English, - /// - /// Indicating a German game client. - /// - German, + /// + /// Indicating a German game client. + /// + German, - /// - /// Indicating a French game client. - /// - French, - } + /// + /// Indicating a French game client. + /// + French, } diff --git a/Dalamud/ClientLanguageExtensions.cs b/Dalamud/ClientLanguageExtensions.cs index dccefb93f..b7e021f61 100644 --- a/Dalamud/ClientLanguageExtensions.cs +++ b/Dalamud/ClientLanguageExtensions.cs @@ -1,27 +1,26 @@ using System; -namespace Dalamud +namespace Dalamud; + +/// +/// Extension methods for the class. +/// +public static class ClientLanguageExtensions { /// - /// Extension methods for the class. + /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant. /// - public static class ClientLanguageExtensions + /// Langauge to convert. + /// Converted langauge. + public static Lumina.Data.Language ToLumina(this ClientLanguage language) { - /// - /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant. - /// - /// Langauge to convert. - /// Converted langauge. - public static Lumina.Data.Language ToLumina(this ClientLanguage language) + return language switch { - return language switch - { - ClientLanguage.Japanese => Lumina.Data.Language.Japanese, - ClientLanguage.English => Lumina.Data.Language.English, - ClientLanguage.German => Lumina.Data.Language.German, - ClientLanguage.French => Lumina.Data.Language.French, - _ => throw new ArgumentOutOfRangeException(nameof(language)), - }; - } + ClientLanguage.Japanese => Lumina.Data.Language.Japanese, + ClientLanguage.English => Lumina.Data.Language.English, + ClientLanguage.German => Lumina.Data.Language.German, + ClientLanguage.French => Lumina.Data.Language.French, + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; } } diff --git a/Dalamud/Configuration/IPluginConfiguration.cs b/Dalamud/Configuration/IPluginConfiguration.cs index 884e38871..dcc93d8d7 100644 --- a/Dalamud/Configuration/IPluginConfiguration.cs +++ b/Dalamud/Configuration/IPluginConfiguration.cs @@ -1,13 +1,12 @@ -namespace Dalamud.Configuration +namespace Dalamud.Configuration; + +/// +/// Configuration to store settings for a dalamud plugin. +/// +public interface IPluginConfiguration { /// - /// Configuration to store settings for a dalamud plugin. + /// Gets or sets configuration version. /// - public interface IPluginConfiguration - { - /// - /// Gets or sets configuration version. - /// - int Version { get; set; } - } + int Version { get; set; } } diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index b4263679e..7e9718f2d 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -8,268 +8,267 @@ using Newtonsoft.Json; using Serilog; using Serilog.Events; -namespace Dalamud.Configuration.Internal +namespace Dalamud.Configuration.Internal; + +/// +/// Class containing Dalamud settings. +/// +[Serializable] +internal sealed class DalamudConfiguration { - /// - /// Class containing Dalamud settings. - /// - [Serializable] - internal sealed class DalamudConfiguration + private static readonly JsonSerializerSettings SerializerSettings = new() { - private static readonly JsonSerializerSettings SerializerSettings = new() + TypeNameHandling = TypeNameHandling.All, + TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, + Formatting = Formatting.Indented, + }; + + [JsonIgnore] + private string configPath; + + /// + /// Delegate for the event that occurs when the dalamud configuration is saved. + /// + /// The current dalamud configuration. + public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration); + + /// + /// Event that occurs when dalamud configuration is saved. + /// + public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved; + + /// + /// Gets or sets a list of muted works. + /// + public List BadWords { get; set; } + + /// + /// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found. + /// + public bool DutyFinderTaskbarFlash { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found. + /// + public bool DutyFinderChatMessage { get; set; } = true; + + /// + /// Gets or sets the language code to load Dalamud localization with. + /// + public string LanguageOverride { get; set; } = null; + + /// + /// Gets or sets the last loaded Dalamud version. + /// + public string LastVersion { get; set; } = null; + + /// + /// Gets or sets the last loaded Dalamud version. + /// + public string LastChangelogMajorMinor { get; set; } = null; + + /// + /// Gets or sets the chat type used by default for plugin messages. + /// + public XivChatType GeneralChatType { get; set; } = XivChatType.Debug; + + /// + /// Gets or sets a value indicating whether or not plugin testing builds should be shown. + /// + public bool DoPluginTest { get; set; } = false; + + /// + /// Gets or sets a value indicating whether or not Dalamud testing builds should be used. + /// + public bool DoDalamudTest { get; set; } = false; + + /// + /// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime. + /// + public bool DoDalamudRuntime { get; set; } = false; + + /// + /// Gets or sets a list of custom repos. + /// + public List ThirdRepoList { get; set; } = new(); + + /// + /// Gets or sets a list of hidden plugins. + /// + public List HiddenPluginInternalName { get; set; } = new(); + + /// + /// Gets or sets a list of seen plugins. + /// + public List SeenPluginInternalName { get; set; } = new(); + + /// + /// Gets or sets a list of additional settings for devPlugins. The key is the absolute path + /// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder. + /// However by specifiying this value manually, you can add arbitrary files outside the normal + /// file paths. + /// + public Dictionary DevPluginSettings { get; set; } = new(); + + /// + /// Gets or sets a list of additional locations that dev plugins should be loaded from. This can + /// be either a DLL or folder, but should be the absolute path, or a path relative to the currently + /// injected Dalamud instance. + /// + public List DevPluginLoadLocations { get; set; } = new(); + + /// + /// Gets or sets the global UI scale. + /// + public float GlobalUiScale { get; set; } = 1.0f; + + /// + /// Gets or sets a value indicating whether or not plugin UI should be hidden. + /// + public bool ToggleUiHide { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes. + /// + public bool ToggleUiHideDuringCutscenes { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose. + /// + public bool ToggleUiHideDuringGpose { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login. + /// + public bool PrintPluginsWelcomeMsg { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not plugins should be auto-updated. + /// + public bool AutoUpdatePlugins { get; set; } + + /// + /// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu. + /// + public bool DoButtonsSystemMenu { get; set; } = true; + + /// + /// Gets or sets the default Dalamud debug log level on startup. + /// + public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; + + /// + /// Gets or sets a value indicating whether or not the debug log should scroll automatically. + /// + public bool LogAutoScroll { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not the debug log should open at startup. + /// + public bool LogOpenAtStartup { get; set; } + + /// + /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup. + /// + public bool AssertsEnabledAtStartup { get; set; } + + /// + /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. + /// + public bool IsDocking { get; set; } + + /// + /// Gets or sets a value indicating whether viewports should always be disabled. + /// + public bool IsDisableViewport { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui. + /// + public bool IsGamepadNavigationEnabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not focus management is enabled. + /// + public bool IsFocusManagementEnabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup. + /// + public bool IsAntiAntiDebugEnabled { get; set; } = false; + + /// + /// Gets or sets the kind of beta to download when is set to true. + /// + public string DalamudBetaKind { get; set; } + + /// + /// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded. + /// + public bool LoadAllApiLevels { get; set; } + + /// + /// Gets or sets a value indicating whether or not banned plugins should be loaded. + /// + public bool LoadBannedPlugins { get; set; } + + /// + /// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started. + /// It is reset immediately when read. + /// + public bool PluginSafeMode { get; set; } + + /// + /// Gets or sets a list of saved styles. + /// + [JsonProperty("SavedStyles")] + public List? SavedStylesOld { get; set; } + + /// + /// Gets or sets a list of saved styles. + /// + [JsonProperty("SavedStylesVersioned")] + public List? SavedStyles { get; set; } + + /// + /// Gets or sets the name of the currently chosen style. + /// + public string ChosenStyle { get; set; } = "Dalamud Standard"; + + /// + /// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled. + /// + public bool DisableRmtFiltering { get; set; } + + /// + /// Load a configuration from the provided path. + /// + /// The path to load the configuration file from. + /// The deserialized configuration file. + public static DalamudConfiguration Load(string path) + { + DalamudConfiguration deserialized; + try { - TypeNameHandling = TypeNameHandling.All, - TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, - Formatting = Formatting.Indented, - }; - - [JsonIgnore] - private string configPath; - - /// - /// Delegate for the event that occurs when the dalamud configuration is saved. - /// - /// The current dalamud configuration. - public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration); - - /// - /// Event that occurs when dalamud configuration is saved. - /// - public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved; - - /// - /// Gets or sets a list of muted works. - /// - public List BadWords { get; set; } - - /// - /// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found. - /// - public bool DutyFinderTaskbarFlash { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found. - /// - public bool DutyFinderChatMessage { get; set; } = true; - - /// - /// Gets or sets the language code to load Dalamud localization with. - /// - public string LanguageOverride { get; set; } = null; - - /// - /// Gets or sets the last loaded Dalamud version. - /// - public string LastVersion { get; set; } = null; - - /// - /// Gets or sets the last loaded Dalamud version. - /// - public string LastChangelogMajorMinor { get; set; } = null; - - /// - /// Gets or sets the chat type used by default for plugin messages. - /// - public XivChatType GeneralChatType { get; set; } = XivChatType.Debug; - - /// - /// Gets or sets a value indicating whether or not plugin testing builds should be shown. - /// - public bool DoPluginTest { get; set; } = false; - - /// - /// Gets or sets a value indicating whether or not Dalamud testing builds should be used. - /// - public bool DoDalamudTest { get; set; } = false; - - /// - /// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime. - /// - public bool DoDalamudRuntime { get; set; } = false; - - /// - /// Gets or sets a list of custom repos. - /// - public List ThirdRepoList { get; set; } = new(); - - /// - /// Gets or sets a list of hidden plugins. - /// - public List HiddenPluginInternalName { get; set; } = new(); - - /// - /// Gets or sets a list of seen plugins. - /// - public List SeenPluginInternalName { get; set; } = new(); - - /// - /// Gets or sets a list of additional settings for devPlugins. The key is the absolute path - /// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder. - /// However by specifiying this value manually, you can add arbitrary files outside the normal - /// file paths. - /// - public Dictionary DevPluginSettings { get; set; } = new(); - - /// - /// Gets or sets a list of additional locations that dev plugins should be loaded from. This can - /// be either a DLL or folder, but should be the absolute path, or a path relative to the currently - /// injected Dalamud instance. - /// - public List DevPluginLoadLocations { get; set; } = new(); - - /// - /// Gets or sets the global UI scale. - /// - public float GlobalUiScale { get; set; } = 1.0f; - - /// - /// Gets or sets a value indicating whether or not plugin UI should be hidden. - /// - public bool ToggleUiHide { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes. - /// - public bool ToggleUiHideDuringCutscenes { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose. - /// - public bool ToggleUiHideDuringGpose { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login. - /// - public bool PrintPluginsWelcomeMsg { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not plugins should be auto-updated. - /// - public bool AutoUpdatePlugins { get; set; } - - /// - /// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu. - /// - public bool DoButtonsSystemMenu { get; set; } = true; - - /// - /// Gets or sets the default Dalamud debug log level on startup. - /// - public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; - - /// - /// Gets or sets a value indicating whether or not the debug log should scroll automatically. - /// - public bool LogAutoScroll { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not the debug log should open at startup. - /// - public bool LogOpenAtStartup { get; set; } - - /// - /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup. - /// - public bool AssertsEnabledAtStartup { get; set; } - - /// - /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. - /// - public bool IsDocking { get; set; } - - /// - /// Gets or sets a value indicating whether viewports should always be disabled. - /// - public bool IsDisableViewport { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui. - /// - public bool IsGamepadNavigationEnabled { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not focus management is enabled. - /// - public bool IsFocusManagementEnabled { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup. - /// - public bool IsAntiAntiDebugEnabled { get; set; } = false; - - /// - /// Gets or sets the kind of beta to download when is set to true. - /// - public string DalamudBetaKind { get; set; } - - /// - /// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded. - /// - public bool LoadAllApiLevels { get; set; } - - /// - /// Gets or sets a value indicating whether or not banned plugins should be loaded. - /// - public bool LoadBannedPlugins { get; set; } - - /// - /// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started. - /// It is reset immediately when read. - /// - public bool PluginSafeMode { get; set; } - - /// - /// Gets or sets a list of saved styles. - /// - [JsonProperty("SavedStyles")] - public List? SavedStylesOld { get; set; } - - /// - /// Gets or sets a list of saved styles. - /// - [JsonProperty("SavedStylesVersioned")] - public List? SavedStyles { get; set; } - - /// - /// Gets or sets the name of the currently chosen style. - /// - public string ChosenStyle { get; set; } = "Dalamud Standard"; - - /// - /// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled. - /// - public bool DisableRmtFiltering { get; set; } - - /// - /// Load a configuration from the provided path. - /// - /// The path to load the configuration file from. - /// The deserialized configuration file. - public static DalamudConfiguration Load(string path) + deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings); + } + catch (Exception ex) { - DalamudConfiguration deserialized; - try - { - deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings); - } - catch (Exception ex) - { - Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path); - deserialized = new DalamudConfiguration(); - } - - deserialized.configPath = path; - - return deserialized; + Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path); + deserialized = new DalamudConfiguration(); } - /// - /// Save the configuration at the path it was loaded from. - /// - public void Save() - { - File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings)); - this.DalamudConfigurationSaved?.Invoke(this); - } + deserialized.configPath = path; + + return deserialized; + } + + /// + /// Save the configuration at the path it was loaded from. + /// + public void Save() + { + File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings)); + this.DalamudConfigurationSaved?.Invoke(this); } } diff --git a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs index 995fb1a23..2bf34c4fa 100644 --- a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs +++ b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs @@ -1,24 +1,23 @@ -namespace Dalamud.Configuration +namespace Dalamud.Configuration.Internal; + +/// +/// Additional locations to load dev plugins from. +/// +internal sealed class DevPluginLocationSettings { /// - /// Additional locations to load dev plugins from. + /// Gets or sets the dev pluign path. /// - internal sealed class DevPluginLocationSettings - { - /// - /// Gets or sets the dev pluign path. - /// - public string Path { get; set; } + public string Path { get; set; } - /// - /// Gets or sets a value indicating whether the third party repo is enabled. - /// - public bool IsEnabled { get; set; } + /// + /// Gets or sets a value indicating whether the third party repo is enabled. + /// + public bool IsEnabled { get; set; } - /// - /// Clone this object. - /// - /// A shallow copy of this object. - public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings; - } + /// + /// Clone this object. + /// + /// A shallow copy of this object. + public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings; } diff --git a/Dalamud/Configuration/Internal/DevPluginSettings.cs b/Dalamud/Configuration/Internal/DevPluginSettings.cs index 17350cba0..939b03eca 100644 --- a/Dalamud/Configuration/Internal/DevPluginSettings.cs +++ b/Dalamud/Configuration/Internal/DevPluginSettings.cs @@ -1,18 +1,17 @@ -namespace Dalamud.Configuration.Internal +namespace Dalamud.Configuration.Internal; + +/// +/// Settings for DevPlugins. +/// +internal sealed class DevPluginSettings { /// - /// Settings for DevPlugins. + /// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up. /// - internal sealed class DevPluginSettings - { - /// - /// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up. - /// - public bool StartOnBoot { get; set; } = true; + public bool StartOnBoot { get; set; } = true; - /// - /// Gets or sets a value indicating whether this plugin should automatically reload on file change. - /// - public bool AutomaticReloading { get; set; } = false; - } + /// + /// Gets or sets a value indicating whether this plugin should automatically reload on file change. + /// + public bool AutomaticReloading { get; set; } = false; } diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs index 2cb89915c..42885e8e0 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -1,38 +1,37 @@ using System; -namespace Dalamud.Configuration.Internal +namespace Dalamud.Configuration.Internal; + +/// +/// Environmental configuration settings. +/// +internal class EnvironmentConfiguration { /// - /// Environmental configuration settings. + /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. /// - internal class EnvironmentConfiguration - { - /// - /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. - /// - public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); + public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); - /// - /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. - /// - public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS"); + /// + /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. + /// + public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS"); - /// - /// Gets a value indicating whether the DalamudForceReloaded setting has been enabled. - /// - public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED"); + /// + /// Gets a value indicating whether the DalamudForceReloaded setting has been enabled. + /// + public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED"); - /// - /// Gets a value indicating whether the DalamudForceMinHook setting has been enabled. - /// - public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK"); + /// + /// Gets a value indicating whether the DalamudForceMinHook setting has been enabled. + /// + public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK"); - /// - /// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing. - /// - public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER"); + /// + /// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing. + /// + public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER"); - private static bool GetEnvironmentVariable(string name) - => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false"); - } + private static bool GetEnvironmentVariable(string name) + => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false"); } diff --git a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs index cafb96a47..94bfc96e7 100644 --- a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs +++ b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs @@ -1,29 +1,28 @@ -namespace Dalamud.Configuration +namespace Dalamud.Configuration.Internal; + +/// +/// Third party repository for dalamud plugins. +/// +internal sealed class ThirdPartyRepoSettings { /// - /// Third party repository for dalamud plugins. + /// Gets or sets the third party repo url. /// - internal sealed class ThirdPartyRepoSettings - { - /// - /// Gets or sets the third party repo url. - /// - public string Url { get; set; } + public string Url { get; set; } - /// - /// Gets or sets a value indicating whether the third party repo is enabled. - /// - public bool IsEnabled { get; set; } + /// + /// Gets or sets a value indicating whether the third party repo is enabled. + /// + public bool IsEnabled { get; set; } - /// - /// Gets or sets a short name for the repo url. - /// - public string Name { get; set; } + /// + /// Gets or sets a short name for the repo url. + /// + public string Name { get; set; } - /// - /// Clone this object. - /// - /// A shallow copy of this object. - public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings; - } + /// + /// Clone this object. + /// + /// A shallow copy of this object. + public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings; } diff --git a/Dalamud/Configuration/PluginConfigurations.cs b/Dalamud/Configuration/PluginConfigurations.cs index faa01af99..349d51e57 100644 --- a/Dalamud/Configuration/PluginConfigurations.cs +++ b/Dalamud/Configuration/PluginConfigurations.cs @@ -2,129 +2,128 @@ using System.IO; using Newtonsoft.Json; -namespace Dalamud.Configuration +namespace Dalamud.Configuration; + +/// +/// Configuration to store settings for a dalamud plugin. +/// +public sealed class PluginConfigurations { + private readonly DirectoryInfo configDirectory; + /// - /// Configuration to store settings for a dalamud plugin. + /// Initializes a new instance of the class. /// - public sealed class PluginConfigurations + /// Directory for storage of plugin configuration files. + public PluginConfigurations(string storageFolder) { - private readonly DirectoryInfo configDirectory; + this.configDirectory = new DirectoryInfo(storageFolder); + this.configDirectory.Create(); + } - /// - /// Initializes a new instance of the class. - /// - /// Directory for storage of plugin configuration files. - public PluginConfigurations(string storageFolder) + /// + /// Save/Load plugin configuration. + /// NOTE: Save/Load are still using Type information for now, + /// despite LoadForType superseding Load and not requiring or using it. + /// It might be worth removing the Type info from Save, to strip it from all future saved configs, + /// and then Load() can probably be removed entirely. + /// + /// Plugin configuration. + /// Plugin name. + public void Save(IPluginConfiguration config, string pluginName) + { + File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings { - this.configDirectory = new DirectoryInfo(storageFolder); - this.configDirectory.Create(); - } + TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, + TypeNameHandling = TypeNameHandling.Objects, + })); + } - /// - /// Save/Load plugin configuration. - /// NOTE: Save/Load are still using Type information for now, - /// despite LoadForType superseding Load and not requiring or using it. - /// It might be worth removing the Type info from Save, to strip it from all future saved configs, - /// and then Load() can probably be removed entirely. - /// - /// Plugin configuration. - /// Plugin name. - public void Save(IPluginConfiguration config, string pluginName) - { - File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings + /// + /// Load plugin configuration. + /// + /// Plugin name. + /// Plugin configuration. + public IPluginConfiguration? Load(string pluginName) + { + var path = this.GetConfigFile(pluginName); + + if (!path.Exists) + return null; + + return JsonConvert.DeserializeObject( + File.ReadAllText(path.FullName), + new JsonSerializerSettings { TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, TypeNameHandling = TypeNameHandling.Objects, - })); - } - - /// - /// Load plugin configuration. - /// - /// Plugin name. - /// Plugin configuration. - public IPluginConfiguration? Load(string pluginName) - { - var path = this.GetConfigFile(pluginName); - - if (!path.Exists) - return null; - - return JsonConvert.DeserializeObject( - File.ReadAllText(path.FullName), - new JsonSerializerSettings - { - TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, - TypeNameHandling = TypeNameHandling.Objects, - }); - } - - /// - /// Delete the configuration file and folder for the specified plugin. - /// This will throw an if the plugin did not correctly close its handles. - /// - /// The name of the plugin. - public void Delete(string pluginName) - { - var directory = this.GetDirectoryPath(pluginName); - if (directory.Exists) - directory.Delete(true); - - var file = this.GetConfigFile(pluginName); - if (file.Exists) - file.Delete(); - } - - /// - /// Get plugin directory. - /// - /// Plugin name. - /// Plugin directory path. - public string GetDirectory(string pluginName) - { - try - { - var path = this.GetDirectoryPath(pluginName); - if (!path.Exists) - { - path.Create(); - } - - return path.FullName; - } - catch - { - return string.Empty; - } - } - - /// - /// Load Plugin configuration. Parameterized deserialization. - /// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig(). - /// Eventually there may be an additional pluginInterface method that can call this directly - /// without reflection - for now this is in support of the existing plugin api. - /// - /// Plugin Name. - /// Configuration Type. - /// Plugin Configuration. - public T LoadForType(string pluginName) where T : IPluginConfiguration - { - var path = this.GetConfigFile(pluginName); - - return !path.Exists ? default : JsonConvert.DeserializeObject(File.ReadAllText(path.FullName)); - - // intentionally no type handling - it will break when updating a plugin at runtime - // and turns out to be unnecessary when we fully qualify the object type - } - - /// - /// Get FileInfo to plugin config file. - /// - /// InternalName of the plugin. - /// FileInfo of the config file. - public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json")); - - private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName)); + }); } + + /// + /// Delete the configuration file and folder for the specified plugin. + /// This will throw an if the plugin did not correctly close its handles. + /// + /// The name of the plugin. + public void Delete(string pluginName) + { + var directory = this.GetDirectoryPath(pluginName); + if (directory.Exists) + directory.Delete(true); + + var file = this.GetConfigFile(pluginName); + if (file.Exists) + file.Delete(); + } + + /// + /// Get plugin directory. + /// + /// Plugin name. + /// Plugin directory path. + public string GetDirectory(string pluginName) + { + try + { + var path = this.GetDirectoryPath(pluginName); + if (!path.Exists) + { + path.Create(); + } + + return path.FullName; + } + catch + { + return string.Empty; + } + } + + /// + /// Load Plugin configuration. Parameterized deserialization. + /// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig(). + /// Eventually there may be an additional pluginInterface method that can call this directly + /// without reflection - for now this is in support of the existing plugin api. + /// + /// Plugin Name. + /// Configuration Type. + /// Plugin Configuration. + public T LoadForType(string pluginName) where T : IPluginConfiguration + { + var path = this.GetConfigFile(pluginName); + + return !path.Exists ? default : JsonConvert.DeserializeObject(File.ReadAllText(path.FullName)); + + // intentionally no type handling - it will break when updating a plugin at runtime + // and turns out to be unnecessary when we fully qualify the object type + } + + /// + /// Get FileInfo to plugin config file. + /// + /// InternalName of the plugin. + /// FileInfo of the config file. + public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json")); + + private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName)); } diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index f5460af2f..eaf325ebf 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -33,379 +33,378 @@ using Serilog.Events; [assembly: InternalsVisibleTo("Dalamud.Test")] -namespace Dalamud +namespace Dalamud; + +/// +/// The main Dalamud class containing all subsystems. +/// +internal sealed class Dalamud : IDisposable { + #region Internals + + private readonly ManualResetEvent unloadSignal; + private readonly ManualResetEvent finishUnloadSignal; + private MonoMod.RuntimeDetour.Hook processMonoHook; + private bool hasDisposedPlugins = false; + + #endregion + /// - /// The main Dalamud class containing all subsystems. + /// Initializes a new instance of the class. /// - internal sealed class Dalamud : IDisposable + /// DalamudStartInfo instance. + /// LoggingLevelSwitch to control Serilog level. + /// Signal signalling shutdown. + /// The Dalamud configuration. + public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration) { - #region Internals + this.ApplyProcessPatch(); - private readonly ManualResetEvent unloadSignal; - private readonly ManualResetEvent finishUnloadSignal; - private MonoMod.RuntimeDetour.Hook processMonoHook; - private bool hasDisposedPlugins = false; + Service.Set(this); + Service.Set(info); + Service.Set(configuration); - #endregion + this.LogLevelSwitch = loggingLevelSwitch; - /// - /// Initializes a new instance of the class. - /// - /// DalamudStartInfo instance. - /// LoggingLevelSwitch to control Serilog level. - /// Signal signalling shutdown. - /// The Dalamud configuration. - public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration) + this.unloadSignal = new ManualResetEvent(false); + this.unloadSignal.Reset(); + + this.finishUnloadSignal = finishSignal; + this.finishUnloadSignal.Reset(); + } + + /// + /// Gets LoggingLevelSwitch for Dalamud and Plugin logs. + /// + internal LoggingLevelSwitch LogLevelSwitch { get; private set; } + + /// + /// Gets location of stored assets. + /// + internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory); + + /// + /// Runs tier 1 of the Dalamud initialization process. + /// + public void LoadTier1() + { + try { - this.ApplyProcessPatch(); + SerilogEventSink.Instance.LogLine += SerilogOnLogLine; - Service.Set(this); - Service.Set(info); - Service.Set(configuration); + Service.Set(); - this.LogLevelSwitch = loggingLevelSwitch; + // Initialize the process information. + Service.Set(new SigScanner(true)); + Service.Set(); - this.unloadSignal = new ManualResetEvent(false); - this.unloadSignal.Reset(); + // Initialize FFXIVClientStructs function resolver + FFXIVClientStructs.Resolver.Initialize(); + Log.Information("[T1] FFXIVClientStructs initialized!"); - this.finishUnloadSignal = finishSignal; - this.finishUnloadSignal.Reset(); - } - - /// - /// Gets LoggingLevelSwitch for Dalamud and Plugin logs. - /// - internal LoggingLevelSwitch LogLevelSwitch { get; private set; } - - /// - /// Gets location of stored assets. - /// - internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory); - - /// - /// Runs tier 1 of the Dalamud initialization process. - /// - public void LoadTier1() - { - try - { - SerilogEventSink.Instance.LogLine += SerilogOnLogLine; - - Service.Set(); - - // Initialize the process information. - Service.Set(new SigScanner(true)); - Service.Set(); - - // Initialize FFXIVClientStructs function resolver - FFXIVClientStructs.Resolver.Initialize(); - Log.Information("[T1] FFXIVClientStructs initialized!"); - - // Initialize game subsystem - var framework = Service.Set(); - Log.Information("[T1] Framework OK!"); + // Initialize game subsystem + var framework = Service.Set(); + Log.Information("[T1] Framework OK!"); #if DEBUG - Service.Set(); - Log.Information("[T1] TaskTracker OK!"); + Service.Set(); + Log.Information("[T1] TaskTracker OK!"); #endif - Service.Set(); - Service.Set(); + Service.Set(); + Service.Set(); - framework.Enable(); + framework.Enable(); - Log.Information("[T1] Load complete!"); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 1 load failed."); - this.Unload(); - } + Log.Information("[T1] Load complete!"); } - - /// - /// Runs tier 2 of the Dalamud initialization process. - /// - /// Whether or not the load succeeded. - public bool LoadTier2() + catch (Exception ex) { - try - { - var configuration = Service.Get(); + Log.Error(ex, "Tier 1 load failed."); + this.Unload(); + } + } - var antiDebug = Service.Set(); - if (!antiDebug.IsEnabled) - { + /// + /// Runs tier 2 of the Dalamud initialization process. + /// + /// Whether or not the load succeeded. + public bool LoadTier2() + { + try + { + var configuration = Service.Get(); + + var antiDebug = Service.Set(); + if (!antiDebug.IsEnabled) + { #if DEBUG - antiDebug.Enable(); + antiDebug.Enable(); #else if (configuration.IsAntiAntiDebugEnabled) antiDebug.Enable(); #endif - } + } - Log.Information("[T2] AntiDebug OK!"); + Log.Information("[T2] AntiDebug OK!"); - Service.Set(); - Log.Information("[T2] WinSock OK!"); + Service.Set(); + Log.Information("[T2] WinSock OK!"); - Service.Set(); - Log.Information("[T2] NH OK!"); + Service.Set(); + Log.Information("[T2] NH OK!"); - try - { - Service.Set().Initialize(this.AssetDirectory.FullName); - } - catch (Exception e) - { - Log.Error(e, "Could not initialize DataManager."); - this.Unload(); - return false; - } + try + { + Service.Set().Initialize(this.AssetDirectory.FullName); + } + catch (Exception e) + { + Log.Error(e, "Could not initialize DataManager."); + this.Unload(); + return false; + } - Log.Information("[T2] Data OK!"); + Log.Information("[T2] Data OK!"); - var clientState = Service.Set(); - Log.Information("[T2] CS OK!"); + var clientState = Service.Set(); + Log.Information("[T2] CS OK!"); - var localization = Service.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_")); - if (!string.IsNullOrEmpty(configuration.LanguageOverride)) - { - localization.SetupWithLangCode(configuration.LanguageOverride); - } - else - { - localization.SetupWithUiCulture(); - } + var localization = Service.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_")); + if (!string.IsNullOrEmpty(configuration.LanguageOverride)) + { + localization.SetupWithLangCode(configuration.LanguageOverride); + } + else + { + localization.SetupWithUiCulture(); + } - Log.Information("[T2] LOC OK!"); + Log.Information("[T2] LOC OK!"); - // This is enabled in ImGuiScene setup - Service.Set(); - Log.Information("[T2] IME OK!"); + // This is enabled in ImGuiScene setup + Service.Set(); + Log.Information("[T2] IME OK!"); - Service.Set().Enable(); - Log.Information("[T2] IM OK!"); + Service.Set().Enable(); + Log.Information("[T2] IM OK!"); #pragma warning disable CS0618 // Type or member is obsolete - Service.Set(); + Service.Set(); #pragma warning restore CS0618 // Type or member is obsolete - Log.Information("[T2] SeString OK!"); + Log.Information("[T2] SeString OK!"); - // Initialize managers. Basically handlers for the logic - Service.Set(); + // Initialize managers. Basically handlers for the logic + Service.Set(); - Service.Set().SetupCommands(); + Service.Set().SetupCommands(); - Log.Information("[T2] CM OK!"); + Log.Information("[T2] CM OK!"); - Service.Set(); + Service.Set(); - Log.Information("[T2] CH OK!"); + Log.Information("[T2] CH OK!"); - clientState.Enable(); - Log.Information("[T2] CS ENABLE!"); + clientState.Enable(); + Log.Information("[T2] CS ENABLE!"); - Service.Set().Enable(); + Service.Set().Enable(); - Log.Information("[T2] Load complete!"); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 2 load failed."); - this.Unload(); - return false; - } - - return true; + Log.Information("[T2] Load complete!"); + } + catch (Exception ex) + { + Log.Error(ex, "Tier 2 load failed."); + this.Unload(); + return false; } - /// - /// Runs tier 3 of the Dalamud initialization process. - /// - /// Whether or not the load succeeded. - public bool LoadTier3() + return true; + } + + /// + /// Runs tier 3 of the Dalamud initialization process. + /// + /// Whether or not the load succeeded. + public bool LoadTier3() + { + try { + Log.Information("[T3] START!"); + + var pluginManager = Service.Set(); + Service.Set(); + try { - Log.Information("[T3] START!"); + _ = pluginManager.SetPluginReposFromConfigAsync(false); - var pluginManager = Service.Set(); - Service.Set(); + pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting; - try - { - _ = pluginManager.SetPluginReposFromConfigAsync(false); + Log.Information("[T3] PM OK!"); - pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting; + pluginManager.CleanupPlugins(); + Log.Information("[T3] PMC OK!"); - Log.Information("[T3] PM OK!"); - - pluginManager.CleanupPlugins(); - Log.Information("[T3] PMC OK!"); - - pluginManager.LoadAllPlugins(); - Log.Information("[T3] PML OK!"); - } - catch (Exception ex) - { - Log.Error(ex, "Plugin load failed."); - } - - Service.Set(); - Log.Information("[T3] DUI OK!"); - - Troubleshooting.LogTroubleshooting(); - - Log.Information("Dalamud is ready."); + pluginManager.LoadAllPlugins(); + Log.Information("[T3] PML OK!"); } catch (Exception ex) { - Log.Error(ex, "Tier 3 load failed."); - this.Unload(); - - return false; + Log.Error(ex, "Plugin load failed."); } - return true; + Service.Set(); + Log.Information("[T3] DUI OK!"); + + Troubleshooting.LogTroubleshooting(); + + Log.Information("Dalamud is ready."); + } + catch (Exception ex) + { + Log.Error(ex, "Tier 3 load failed."); + this.Unload(); + + return false; } - /// - /// Queue an unload of Dalamud when it gets the chance. - /// - public void Unload() + return true; + } + + /// + /// Queue an unload of Dalamud when it gets the chance. + /// + public void Unload() + { + Log.Information("Trigger unload"); + this.unloadSignal.Set(); + } + + /// + /// Wait for an unload request to start. + /// + public void WaitForUnload() + { + this.unloadSignal.WaitOne(); + } + + /// + /// Wait for a queued unload to be finalized. + /// + public void WaitForUnloadFinish() + { + this.finishUnloadSignal?.WaitOne(); + } + + /// + /// Dispose subsystems related to plugin handling. + /// + public void DisposePlugins() + { + this.hasDisposedPlugins = true; + + // this must be done before unloading interface manager, in order to do rebuild + // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game + // will not receive any windows messages + Service.GetNullable()?.Dispose(); + + // this must be done before unloading plugins, or it can cause a race condition + // due to rendering happening on another thread, where a plugin might receive + // a render call after it has been disposed, which can crash if it attempts to + // use any resources that it freed in its own Dispose method + Service.GetNullable()?.Dispose(); + + Service.GetNullable()?.Dispose(); + + Service.GetNullable()?.Dispose(); + } + + /// + /// Dispose Dalamud subsystems. + /// + public void Dispose() + { + try { - Log.Information("Trigger unload"); - this.unloadSignal.Set(); - } - - /// - /// Wait for an unload request to start. - /// - public void WaitForUnload() - { - this.unloadSignal.WaitOne(); - } - - /// - /// Wait for a queued unload to be finalized. - /// - public void WaitForUnloadFinish() - { - this.finishUnloadSignal?.WaitOne(); - } - - /// - /// Dispose subsystems related to plugin handling. - /// - public void DisposePlugins() - { - this.hasDisposedPlugins = true; - - // this must be done before unloading interface manager, in order to do rebuild - // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game - // will not receive any windows messages - Service.GetNullable()?.Dispose(); - - // this must be done before unloading plugins, or it can cause a race condition - // due to rendering happening on another thread, where a plugin might receive - // a render call after it has been disposed, which can crash if it attempts to - // use any resources that it freed in its own Dispose method - Service.GetNullable()?.Dispose(); - - Service.GetNullable()?.Dispose(); - - Service.GetNullable()?.Dispose(); - } - - /// - /// Dispose Dalamud subsystems. - /// - public void Dispose() - { - try + if (!this.hasDisposedPlugins) { - if (!this.hasDisposedPlugins) - { - this.DisposePlugins(); - Thread.Sleep(100); - } - - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - - this.unloadSignal?.Dispose(); - - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - - SerilogEventSink.Instance.LogLine -= SerilogOnLogLine; - - this.processMonoHook?.Dispose(); - - Log.Debug("Dalamud::Dispose() OK!"); - } - catch (Exception ex) - { - Log.Error(ex, "Dalamud::Dispose() failed."); - } - } - - /// - /// Replace the built-in exception handler with a debug one. - /// - internal void ReplaceExceptionHandler() - { - var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??"; - var releaseFilter = Service.Get().ScanText(releaseSig); - Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}"); - - var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter); - Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter); - } - - private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e) - { - if (e.Exception == null) - return; - - Troubleshooting.LogException(e.Exception, e.Line); - } - - /// - /// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it - /// uses pseudo-handles to access memory, to prevent permission errors. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// A pseudo-handle for the current process, or the result from the original method. - private static IntPtr ProcessHandlePatch(Func orig, Process self) - { - var result = orig(self); - - if (self.Id == Environment.ProcessId) - { - result = (IntPtr)0xFFFFFFFF; + this.DisposePlugins(); + Thread.Sleep(100); } - // Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}"); - return result; + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + + this.unloadSignal?.Dispose(); + + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + + SerilogEventSink.Instance.LogLine -= SerilogOnLogLine; + + this.processMonoHook?.Dispose(); + + Log.Debug("Dalamud::Dispose() OK!"); } - - private void ApplyProcessPatch() + catch (Exception ex) { - var targetType = typeof(Process); - - var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod(); - var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static); - this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch); + Log.Error(ex, "Dalamud::Dispose() failed."); } } + + /// + /// Replace the built-in exception handler with a debug one. + /// + internal void ReplaceExceptionHandler() + { + var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??"; + var releaseFilter = Service.Get().ScanText(releaseSig); + Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}"); + + var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter); + Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter); + } + + private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e) + { + if (e.Exception == null) + return; + + Troubleshooting.LogException(e.Exception, e.Line); + } + + /// + /// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it + /// uses pseudo-handles to access memory, to prevent permission errors. + /// It should never be called manually. + /// + /// A delegate that acts as the original method. + /// The equivalent of `this`. + /// A pseudo-handle for the current process, or the result from the original method. + private static IntPtr ProcessHandlePatch(Func orig, Process self) + { + var result = orig(self); + + if (self.Id == Environment.ProcessId) + { + result = (IntPtr)0xFFFFFFFF; + } + + // Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}"); + return result; + } + + private void ApplyProcessPatch() + { + var targetType = typeof(Process); + + var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod(); + var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static); + this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch); + } } diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs index 891a57dca..fbdcd3588 100644 --- a/Dalamud/DalamudStartInfo.cs +++ b/Dalamud/DalamudStartInfo.cs @@ -3,58 +3,57 @@ using System; using Dalamud.Game; using Newtonsoft.Json; -namespace Dalamud +namespace Dalamud; + +/// +/// Struct containing information needed to initialize Dalamud. +/// +[Serializable] +public record DalamudStartInfo { /// - /// Struct containing information needed to initialize Dalamud. + /// Gets or sets the working directory of the XIVLauncher installations. /// - [Serializable] - public record DalamudStartInfo - { - /// - /// Gets or sets the working directory of the XIVLauncher installations. - /// - public string WorkingDirectory { get; set; } + public string WorkingDirectory { get; set; } - /// - /// Gets the path to the configuration file. - /// - public string ConfigurationPath { get; init; } + /// + /// Gets the path to the configuration file. + /// + public string ConfigurationPath { get; init; } - /// - /// Gets the path to the directory for installed plugins. - /// - public string PluginDirectory { get; init; } + /// + /// Gets the path to the directory for installed plugins. + /// + public string PluginDirectory { get; init; } - /// - /// Gets the path to the directory for developer plugins. - /// - public string DefaultPluginDirectory { get; init; } + /// + /// Gets the path to the directory for developer plugins. + /// + public string DefaultPluginDirectory { get; init; } - /// - /// Gets the path to core Dalamud assets. - /// - public string AssetDirectory { get; init; } + /// + /// Gets the path to core Dalamud assets. + /// + public string AssetDirectory { get; init; } - /// - /// Gets the language of the game client. - /// - public ClientLanguage Language { get; init; } + /// + /// Gets the language of the game client. + /// + public ClientLanguage Language { get; init; } - /// - /// Gets the current game version code. - /// - [JsonConverter(typeof(GameVersionConverter))] - public GameVersion GameVersion { get; init; } + /// + /// Gets the current game version code. + /// + [JsonConverter(typeof(GameVersionConverter))] + public GameVersion GameVersion { get; init; } - /// - /// Gets a value indicating whether or not market board information should be uploaded by default. - /// - public bool OptOutMbCollection { get; init; } + /// + /// Gets a value indicating whether or not market board information should be uploaded by default. + /// + public bool OptOutMbCollection { get; init; } - /// - /// Gets a value that specifies how much to wait before a new Dalamud session. - /// - public int DelayInitializeMs { get; init; } = 0; - } + /// + /// Gets a value that specifies how much to wait before a new Dalamud session. + /// + public int DelayInitializeMs { get; init; } = 0; } diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index a78f0eec3..61f701055 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -18,330 +18,329 @@ using Lumina.Excel; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Data +namespace Dalamud.Data; + +/// +/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class DataManager : IDisposable { + private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; + + private Thread luminaResourceThread; + private CancellationTokenSource luminaCancellationTokenSource; + /// - /// This class provides data for Dalamud-internal features, but can also be used by plugins if needed. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class DataManager : IDisposable + internal DataManager() { - private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; + this.Language = Service.Get().Language; - private Thread luminaResourceThread; - private CancellationTokenSource luminaCancellationTokenSource; + // Set up default values so plugins do not null-reference when data is being loaded. + this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); + } - /// - /// Initializes a new instance of the class. - /// - internal DataManager() + /// + /// Gets the current game client language. + /// + public ClientLanguage Language { get; private set; } + + /// + /// Gets the OpCodes sent by the server to the client. + /// + public ReadOnlyDictionary ServerOpCodes { get; private set; } + + /// + /// Gets the OpCodes sent by the client to the server. + /// + [UsedImplicitly] + public ReadOnlyDictionary ClientOpCodes { get; private set; } + + /// + /// Gets a object which gives access to any excel/game data. + /// + public GameData GameData { get; private set; } + + /// + /// Gets an object which gives access to any of the game's sheet data. + /// + public ExcelModule Excel => this.GameData?.Excel; + + /// + /// Gets a value indicating whether Game Data is ready to be read. + /// + public bool IsDataReady { get; private set; } + + #region Lumina Wrappers + + /// + /// Get an with the given Excel sheet row type. + /// + /// The excel sheet type to get. + /// The , giving access to game rows. + public ExcelSheet? GetExcelSheet() where T : ExcelRow + { + return this.Excel.GetSheet(); + } + + /// + /// Get an with the given Excel sheet row type with a specified language. + /// + /// Language of the sheet to get. + /// The excel sheet type to get. + /// The , giving access to game rows. + public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow + { + return this.Excel.GetSheet(language.ToLumina()); + } + + /// + /// Get a with the given path. + /// + /// The path inside of the game files. + /// The of the file. + public FileResource? GetFile(string path) + { + return this.GetFile(path); + } + + /// + /// Get a with the given path, of the given type. + /// + /// The type of resource. + /// The path inside of the game files. + /// The of the file. + public T? GetFile(string path) where T : FileResource + { + var filePath = GameData.ParseFilePath(path); + if (filePath == null) + return default; + return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default; + } + + /// + /// Check if the file with the given path exists within the game's index files. + /// + /// The path inside of the game files. + /// True if the file exists. + public bool FileExists(string path) + { + return this.GameData.FileExists(path); + } + + /// + /// Get a containing the icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(uint iconId) + { + return this.GetIcon(this.Language, iconId); + } + + /// + /// Get a containing the icon with the given ID, of the given quality. + /// + /// A value indicating whether the icon should be HQ. + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(bool isHq, uint iconId) + { + var type = isHq ? "hq/" : string.Empty; + return this.GetIcon(type, iconId); + } + + /// + /// Get a containing the icon with the given ID, of the given language. + /// + /// The requested language. + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId) + { + var type = iconLanguage switch { - this.Language = Service.Get().Language; + ClientLanguage.Japanese => "ja/", + ClientLanguage.English => "en/", + ClientLanguage.German => "de/", + ClientLanguage.French => "fr/", + _ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"), + }; - // Set up default values so plugins do not null-reference when data is being loaded. - this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); - } + return this.GetIcon(type, iconId); + } - /// - /// Gets the current game client language. - /// - public ClientLanguage Language { get; private set; } + /// + /// Get a containing the icon with the given ID, of the given type. + /// + /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(string type, uint iconId) + { + type ??= string.Empty; + if (type.Length > 0 && !type.EndsWith("/")) + type += "/"; - /// - /// Gets the OpCodes sent by the server to the client. - /// - public ReadOnlyDictionary ServerOpCodes { get; private set; } + var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId); + var file = this.GetFile(filePath); - /// - /// Gets the OpCodes sent by the client to the server. - /// - [UsedImplicitly] - public ReadOnlyDictionary ClientOpCodes { get; private set; } - - /// - /// Gets a object which gives access to any excel/game data. - /// - public GameData GameData { get; private set; } - - /// - /// Gets an object which gives access to any of the game's sheet data. - /// - public ExcelModule Excel => this.GameData?.Excel; - - /// - /// Gets a value indicating whether Game Data is ready to be read. - /// - public bool IsDataReady { get; private set; } - - #region Lumina Wrappers - - /// - /// Get an with the given Excel sheet row type. - /// - /// The excel sheet type to get. - /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet() where T : ExcelRow - { - return this.Excel.GetSheet(); - } - - /// - /// Get an with the given Excel sheet row type with a specified language. - /// - /// Language of the sheet to get. - /// The excel sheet type to get. - /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow - { - return this.Excel.GetSheet(language.ToLumina()); - } - - /// - /// Get a with the given path. - /// - /// The path inside of the game files. - /// The of the file. - public FileResource? GetFile(string path) - { - return this.GetFile(path); - } - - /// - /// Get a with the given path, of the given type. - /// - /// The type of resource. - /// The path inside of the game files. - /// The of the file. - public T? GetFile(string path) where T : FileResource - { - var filePath = GameData.ParseFilePath(path); - if (filePath == null) - return default; - return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default; - } - - /// - /// Check if the file with the given path exists within the game's index files. - /// - /// The path inside of the game files. - /// True if the file exists. - public bool FileExists(string path) - { - return this.GameData.FileExists(path); - } - - /// - /// Get a containing the icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(uint iconId) - { - return this.GetIcon(this.Language, iconId); - } - - /// - /// Get a containing the icon with the given ID, of the given quality. - /// - /// A value indicating whether the icon should be HQ. - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(bool isHq, uint iconId) - { - var type = isHq ? "hq/" : string.Empty; - return this.GetIcon(type, iconId); - } - - /// - /// Get a containing the icon with the given ID, of the given language. - /// - /// The requested language. - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId) - { - var type = iconLanguage switch - { - ClientLanguage.Japanese => "ja/", - ClientLanguage.English => "en/", - ClientLanguage.German => "de/", - ClientLanguage.French => "fr/", - _ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"), - }; - - return this.GetIcon(type, iconId); - } - - /// - /// Get a containing the icon with the given ID, of the given type. - /// - /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(string type, uint iconId) - { - type ??= string.Empty; - if (type.Length > 0 && !type.EndsWith("/")) - type += "/"; - - var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId); - var file = this.GetFile(filePath); - - if (type == string.Empty || file != default) - return file; - - // Couldn't get specific type, try for generic version. - filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId); - file = this.GetFile(filePath); + if (type == string.Empty || file != default) return file; - } - /// - /// Get a containing the HQ icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TexFile? GetHqIcon(uint iconId) - => this.GetIcon(true, iconId); + // Couldn't get specific type, try for generic version. + filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId); + file = this.GetFile(filePath); + return file; + } - /// - /// Get the passed as a drawable ImGui TextureWrap. - /// - /// The Lumina . - /// A that can be used to draw the texture. - public TextureWrap? GetImGuiTexture(TexFile? tex) + /// + /// Get a containing the HQ icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TexFile? GetHqIcon(uint iconId) + => this.GetIcon(true, iconId); + + /// + /// Get the passed as a drawable ImGui TextureWrap. + /// + /// The Lumina . + /// A that can be used to draw the texture. + public TextureWrap? GetImGuiTexture(TexFile? tex) + { + return tex == null ? null : Service.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4); + } + + /// + /// Get the passed texture path as a drawable ImGui TextureWrap. + /// + /// The internal path to the texture. + /// A that can be used to draw the texture. + public TextureWrap? GetImGuiTexture(string path) + => this.GetImGuiTexture(this.GetFile(path)); + + /// + /// Get a containing the icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(uint iconId) + => this.GetImGuiTexture(this.GetIcon(iconId)); + + /// + /// Get a containing the icon with the given ID, of the given quality. + /// + /// A value indicating whether the icon should be HQ. + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId) + => this.GetImGuiTexture(this.GetIcon(isHq, iconId)); + + /// + /// Get a containing the icon with the given ID, of the given language. + /// + /// The requested language. + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId) + => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId)); + + /// + /// Get a containing the icon with the given ID, of the given type. + /// + /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(string type, uint iconId) + => this.GetImGuiTexture(this.GetIcon(type, iconId)); + + /// + /// Get a containing the HQ icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureHqIcon(uint iconId) + => this.GetImGuiTexture(this.GetHqIcon(iconId)); + + #endregion + + /// + /// Dispose this DataManager. + /// + public void Dispose() + { + this.luminaCancellationTokenSource.Cancel(); + } + + /// + /// Initialize this data manager. + /// + /// The directory to load data from. + internal void Initialize(string baseDir) + { + try { - return tex == null ? null : Service.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4); - } + Log.Verbose("Starting data load..."); - /// - /// Get the passed texture path as a drawable ImGui TextureWrap. - /// - /// The internal path to the texture. - /// A that can be used to draw the texture. - public TextureWrap? GetImGuiTexture(string path) - => this.GetImGuiTexture(this.GetFile(path)); + var zoneOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json"))); + this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); - /// - /// Get a containing the icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(uint iconId) - => this.GetImGuiTexture(this.GetIcon(iconId)); + Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); - /// - /// Get a containing the icon with the given ID, of the given quality. - /// - /// A value indicating whether the icon should be HQ. - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId) - => this.GetImGuiTexture(this.GetIcon(isHq, iconId)); + var clientOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json"))); + this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict); - /// - /// Get a containing the icon with the given ID, of the given language. - /// - /// The requested language. - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId) - => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId)); + Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); - /// - /// Get a containing the icon with the given ID, of the given type. - /// - /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(string type, uint iconId) - => this.GetImGuiTexture(this.GetIcon(type, iconId)); - - /// - /// Get a containing the HQ icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureHqIcon(uint iconId) - => this.GetImGuiTexture(this.GetHqIcon(iconId)); - - #endregion - - /// - /// Dispose this DataManager. - /// - public void Dispose() - { - this.luminaCancellationTokenSource.Cancel(); - } - - /// - /// Initialize this data manager. - /// - /// The directory to load data from. - internal void Initialize(string baseDir) - { - try + var luminaOptions = new LuminaOptions { - Log.Verbose("Starting data load..."); - - var zoneOpCodeDict = JsonConvert.DeserializeObject>( - File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json"))); - this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); - - Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); - - var clientOpCodeDict = JsonConvert.DeserializeObject>( - File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json"))); - this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict); - - Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); - - var luminaOptions = new LuminaOptions - { - CacheFileResources = true, + CacheFileResources = true, #if DEBUG - PanicOnSheetChecksumMismatch = true, + PanicOnSheetChecksumMismatch = true, #else PanicOnSheetChecksumMismatch = false, #endif - DefaultExcelLanguage = this.Language.ToLumina(), - }; + DefaultExcelLanguage = this.Language.ToLumina(), + }; - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) - { - this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions); - } - - Log.Information("Lumina is ready: {0}", this.GameData.DataPath); - - this.IsDataReady = true; - - this.luminaCancellationTokenSource = new(); - - var luminaCancellationToken = this.luminaCancellationTokenSource.Token; - this.luminaResourceThread = new(() => - { - while (!luminaCancellationToken.IsCancellationRequested) - { - if (this.GameData.FileHandleManager.HasPendingFileLoads) - { - this.GameData.ProcessFileHandleQueue(); - } - else - { - Thread.Sleep(5); - } - } - }); - this.luminaResourceThread.Start(); - } - catch (Exception ex) + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) { - Log.Error(ex, "Could not download data."); + this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions); } + + Log.Information("Lumina is ready: {0}", this.GameData.DataPath); + + this.IsDataReady = true; + + this.luminaCancellationTokenSource = new(); + + var luminaCancellationToken = this.luminaCancellationTokenSource.Token; + this.luminaResourceThread = new(() => + { + while (!luminaCancellationToken.IsCancellationRequested) + { + if (this.GameData.FileHandleManager.HasPendingFileLoads) + { + this.GameData.ProcessFileHandleQueue(); + } + else + { + Thread.Sleep(5); + } + } + }); + this.luminaResourceThread.Start(); + } + catch (Exception ex) + { + Log.Error(ex, "Could not download data."); } } } diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index e9df6be92..e630d3ee5 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -21,262 +21,261 @@ using Serilog.Events; using static Dalamud.NativeFunctions; -namespace Dalamud +namespace Dalamud; + +/// +/// The main entrypoint for the Dalamud system. +/// +public sealed class EntryPoint { /// - /// The main entrypoint for the Dalamud system. + /// A delegate used during initialization of the CLR from Dalamud.Boot. /// - public sealed class EntryPoint + /// Pointer to a serialized data. + public delegate void InitDelegate(IntPtr infoPtr); + + /// + /// Initialize Dalamud. + /// + /// Pointer to a serialized data. + public static void Initialize(IntPtr infoPtr) { - /// - /// A delegate used during initialization of the CLR from Dalamud.Boot. - /// - /// Pointer to a serialized data. - public delegate void InitDelegate(IntPtr infoPtr); + var infoStr = Marshal.PtrToStringUTF8(infoPtr); + var info = JsonConvert.DeserializeObject(infoStr); - /// - /// Initialize Dalamud. - /// - /// Pointer to a serialized data. - public static void Initialize(IntPtr infoPtr) + new Thread(() => RunThread(info)).Start(); + } + + /// + /// Initialize all Dalamud subsystems and start running on the main thread. + /// + /// The containing information needed to initialize Dalamud. + private static void RunThread(DalamudStartInfo info) + { + if (EnvironmentConfiguration.DalamudWaitForDebugger) { - var infoStr = Marshal.PtrToStringUTF8(infoPtr); - var info = JsonConvert.DeserializeObject(infoStr); - - new Thread(() => RunThread(info)).Start(); + while (!Debugger.IsAttached) + { + Thread.Sleep(100); + } } - /// - /// Initialize all Dalamud subsystems and start running on the main thread. - /// - /// The containing information needed to initialize Dalamud. - private static void RunThread(DalamudStartInfo info) - { - if (EnvironmentConfiguration.DalamudWaitForDebugger) - { - while (!Debugger.IsAttached) - { - Thread.Sleep(100); - } - } + // Setup logger + var levelSwitch = InitLogging(info.WorkingDirectory); - // Setup logger - var levelSwitch = InitLogging(info.WorkingDirectory); + // Load configuration first to get some early persistent state, like log level + var configuration = DalamudConfiguration.Load(info.ConfigurationPath); - // Load configuration first to get some early persistent state, like log level - var configuration = DalamudConfiguration.Load(info.ConfigurationPath); - - // Set the appropriate logging level from the configuration + // Set the appropriate logging level from the configuration #if !DEBUG levelSwitch.MinimumLevel = configuration.LogLevel; #endif - // Log any unhandled exception. - AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; - TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + // Log any unhandled exception. + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; - var finishSignal = new ManualResetEvent(false); + var finishSignal = new ManualResetEvent(false); - try - { - if (info.DelayInitializeMs > 0) - { - Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs)); - Thread.Sleep(info.DelayInitializeMs); - } - - Log.Information(new string('-', 80)); - Log.Information("Initializing a session.."); - - // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; - - if (!Util.IsLinux()) - InitSymbolHandler(info); - - var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration); - Log.Information("Starting a session.."); - - // Run session - dalamud.LoadTier1(); - dalamud.WaitForUnload(); - - dalamud.Dispose(); - } - catch (Exception ex) - { - Log.Fatal(ex, "Unhandled exception on main thread."); - } - finally - { - TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; - AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; - - Log.Information("Session has ended."); - Log.CloseAndFlush(); - - finishSignal.Set(); - } - } - - private static void InitSymbolHandler(DalamudStartInfo info) + try { - try + if (info.DelayInitializeMs > 0) { - if (string.IsNullOrEmpty(info.AssetDirectory)) - return; - - 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()); - - if (!SymInitialize(GetCurrentProcess(), searchPath, true)) - throw new Win32Exception(); - } - catch (Exception ex) - { - Log.Error(ex, "SymbolHandler Initialize Failed."); + Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs)); + Thread.Sleep(info.DelayInitializeMs); } + + Log.Information(new string('-', 80)); + Log.Information("Initializing a session.."); + + // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; + + if (!Util.IsLinux()) + InitSymbolHandler(info); + + var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration); + Log.Information("Starting a session.."); + + // Run session + dalamud.LoadTier1(); + dalamud.WaitForUnload(); + + dalamud.Dispose(); } - - private static LoggingLevelSwitch InitLogging(string baseDirectory) + catch (Exception ex) { + Log.Fatal(ex, "Unhandled exception on main thread."); + } + finally + { + TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; + AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; + + Log.Information("Session has ended."); + Log.CloseAndFlush(); + + finishSignal.Set(); + } + } + + private static void InitSymbolHandler(DalamudStartInfo info) + { + try + { + if (string.IsNullOrEmpty(info.AssetDirectory)) + return; + + 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()); + + if (!SymInitialize(GetCurrentProcess(), searchPath, true)) + throw new Win32Exception(); + } + catch (Exception ex) + { + Log.Error(ex, "SymbolHandler Initialize Failed."); + } + } + + private static LoggingLevelSwitch InitLogging(string baseDirectory) + { #if DEBUG - var logPath = Path.Combine(baseDirectory, "dalamud.log"); - var oldPath = Path.Combine(baseDirectory, "dalamud.log.old"); + var logPath = Path.Combine(baseDirectory, "dalamud.log"); + var oldPath = Path.Combine(baseDirectory, "dalamud.log.old"); #else var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log"); var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old"); #endif - CullLogFile(logPath, oldPath, 1 * 1024 * 1024); - CullLogFile(oldPath, null, 10 * 1024 * 1024); + CullLogFile(logPath, oldPath, 1 * 1024 * 1024); + CullLogFile(oldPath, null, 10 * 1024 * 1024); - var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose); - Log.Logger = new LoggerConfiguration() - .WriteTo.Async(a => a.File(logPath)) - .WriteTo.Sink(SerilogEventSink.Instance) - .MinimumLevel.ControlledBy(levelSwitch) - .CreateLogger(); + var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose); + Log.Logger = new LoggerConfiguration() + .WriteTo.Async(a => a.File(logPath)) + .WriteTo.Sink(SerilogEventSink.Instance) + .MinimumLevel.ControlledBy(levelSwitch) + .CreateLogger(); - return levelSwitch; - } + return levelSwitch; + } - private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize) + private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize) + { + try { - try + var bufferSize = 4096; + + var logFile = new FileInfo(logPath); + + if (!logFile.Exists) + logFile.Create(); + + if (logFile.Length <= cullingFileSize) + return; + + var amountToCull = logFile.Length - cullingFileSize; + + if (amountToCull < bufferSize) + return; + + if (oldPath != null) { - var bufferSize = 4096; + var oldFile = new FileInfo(oldPath); - var logFile = new FileInfo(logPath); + if (!oldFile.Exists) + oldFile.Create().Close(); - if (!logFile.Exists) - logFile.Create(); + using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite)); - if (logFile.Length <= cullingFileSize) - return; - - var amountToCull = logFile.Length - cullingFileSize; - - if (amountToCull < bufferSize) - return; - - if (oldPath != null) + var read = -1; + var total = 0; + var buffer = new byte[bufferSize]; + while (read != 0 && total < amountToCull) { - var oldFile = new FileInfo(oldPath); - - if (!oldFile.Exists) - oldFile.Create().Close(); - - using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite)); - - var read = -1; - var total = 0; - var buffer = new byte[bufferSize]; - while (read != 0 && total < amountToCull) - { - read = reader.Read(buffer, 0, buffer.Length); - writer.Write(buffer, 0, read); - total += read; - } - } - - { - using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite)); - - reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin); - - var read = -1; - var total = 0; - var buffer = new byte[bufferSize]; - while (read != 0) - { - read = reader.Read(buffer, 0, buffer.Length); - writer.Write(buffer, 0, read); - total += read; - } - - writer.BaseStream.SetLength(total); + read = reader.Read(buffer, 0, buffer.Length); + writer.Write(buffer, 0, read); + total += read; } } - catch (Exception ex) - { - Log.Error(ex, "Log cull failed"); - /* - var caption = "XIVLauncher Error"; - var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}"; - _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok); - */ + { + using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite)); + + reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin); + + var read = -1; + var total = 0; + var buffer = new byte[bufferSize]; + while (read != 0) + { + read = reader.Read(buffer, 0, buffer.Length); + writer.Write(buffer, 0, read); + total += read; + } + + writer.BaseStream.SetLength(total); } } - - private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) + catch (Exception ex) { - switch (args.ExceptionObject) - { - case Exception ex: - Log.Fatal(ex, "Unhandled exception on AppDomain"); - Troubleshooting.LogException(ex, "DalamudUnhandled"); + Log.Error(ex, "Log cull failed"); - var info = "Further information could not be obtained"; - if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null) - { - info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}"; - } - - const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal; - var result = MessageBoxW( - Process.GetCurrentProcess().MainWindowHandle, - $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?", - "Dalamud", - flags); - - if (result == (int)User32.MessageBoxResult.IDYES) - { - Log.Information("User chose to disable plugins on next launch..."); - var config = Service.Get(); - config.PluginSafeMode = true; - config.Save(); - } - - Environment.Exit(-1); - - break; - default: - Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); - break; - } - } - - private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) - { - if (!args.Observed) - Log.Error(args.Exception, "Unobserved exception in Task."); + /* + var caption = "XIVLauncher Error"; + var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}"; + _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok); + */ } } + + private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) + { + switch (args.ExceptionObject) + { + case Exception ex: + Log.Fatal(ex, "Unhandled exception on AppDomain"); + Troubleshooting.LogException(ex, "DalamudUnhandled"); + + var info = "Further information could not be obtained"; + if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null) + { + info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}"; + } + + const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal; + var result = MessageBoxW( + Process.GetCurrentProcess().MainWindowHandle, + $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?", + "Dalamud", + flags); + + if (result == (int)User32.MessageBoxResult.IDYES) + { + Log.Information("User chose to disable plugins on next launch..."); + var config = Service.Get(); + config.PluginSafeMode = true; + config.Save(); + } + + Environment.Exit(-1); + + break; + default: + Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); + break; + } + } + + private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) + { + if (!args.Observed) + Log.Error(args.Exception, "Unobserved exception in Task."); + } } diff --git a/Dalamud/Game/BaseAddressResolver.cs b/Dalamud/Game/BaseAddressResolver.cs index 4528b138e..ab1502376 100644 --- a/Dalamud/Game/BaseAddressResolver.cs +++ b/Dalamud/Game/BaseAddressResolver.cs @@ -3,112 +3,111 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// Base memory address resolver. +/// +public abstract class BaseAddressResolver { /// - /// Base memory address resolver. + /// Gets a list of memory addresses that were found, to list in /xldata. /// - public abstract class BaseAddressResolver + public static Dictionary> DebugScannedValues { get; } = new(); + + /// + /// Gets or sets a value indicating whether the resolver has successfully run or . + /// + protected bool IsResolved { get; set; } + + /// + /// Setup the resolver, calling the appopriate method based on the process architecture. + /// + public void Setup() { - /// - /// Gets a list of memory addresses that were found, to list in /xldata. - /// - public static Dictionary> DebugScannedValues { get; } = new(); + var scanner = Service.Get(); + this.Setup(scanner); + } - /// - /// Gets or sets a value indicating whether the resolver has successfully run or . - /// - protected bool IsResolved { get; set; } + /// + /// Setup the resolver, calling the appopriate method based on the process architecture. + /// + /// The SigScanner instance. + public void Setup(SigScanner scanner) + { + // Because C# don't allow to call virtual function while in ctor + // we have to do this shit :\ - /// - /// Setup the resolver, calling the appopriate method based on the process architecture. - /// - public void Setup() + if (this.IsResolved) { - var scanner = Service.Get(); - this.Setup(scanner); + return; } - /// - /// Setup the resolver, calling the appopriate method based on the process architecture. - /// - /// The SigScanner instance. - public void Setup(SigScanner scanner) + if (scanner.Is32BitProcess) { - // Because C# don't allow to call virtual function while in ctor - // we have to do this shit :\ - - if (this.IsResolved) - { - return; - } - - if (scanner.Is32BitProcess) - { - this.Setup32Bit(scanner); - } - else - { - this.Setup64Bit(scanner); - } - - this.SetupInternal(scanner); - - var className = this.GetType().Name; - DebugScannedValues[className] = new List<(string, IntPtr)>(); - - foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) - { - DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this))); - } - - this.IsResolved = true; + this.Setup32Bit(scanner); + } + else + { + this.Setup64Bit(scanner); } - /// - /// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer. - /// - /// The delegate to marshal the function pointer to. - /// The address of the virtual table. - /// The offset from address to the vtable pointer. - /// The vfunc index. - /// A delegate function pointer that can be invoked. - public T GetVirtualFunction(IntPtr address, int vtableOffset, int count) where T : class + this.SetupInternal(scanner); + + var className = this.GetType().Name; + DebugScannedValues[className] = new List<(string, IntPtr)>(); + + foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) { - // Get vtable - var vtable = Marshal.ReadIntPtr(address, vtableOffset); - - // Get an address to the function - var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count); - - return Marshal.GetDelegateForFunctionPointer(functionAddress); + DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this))); } - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void Setup32Bit(SigScanner scanner) - { - throw new NotSupportedException("32 bit version is not supported."); - } + this.IsResolved = true; + } - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void Setup64Bit(SigScanner scanner) - { - throw new NotSupportedException("64 bit version is not supported."); - } + /// + /// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer. + /// + /// The delegate to marshal the function pointer to. + /// The address of the virtual table. + /// The offset from address to the vtable pointer. + /// The vfunc index. + /// A delegate function pointer that can be invoked. + public T GetVirtualFunction(IntPtr address, int vtableOffset, int count) where T : class + { + // Get vtable + var vtable = Marshal.ReadIntPtr(address, vtableOffset); - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void SetupInternal(SigScanner scanner) - { - // Do nothing - } + // Get an address to the function + var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count); + + return Marshal.GetDelegateForFunctionPointer(functionAddress); + } + + /// + /// Setup the resolver by finding any necessary memory addresses. + /// + /// The SigScanner instance. + protected virtual void Setup32Bit(SigScanner scanner) + { + throw new NotSupportedException("32 bit version is not supported."); + } + + /// + /// Setup the resolver by finding any necessary memory addresses. + /// + /// The SigScanner instance. + protected virtual void Setup64Bit(SigScanner scanner) + { + throw new NotSupportedException("64 bit version is not supported."); + } + + /// + /// Setup the resolver by finding any necessary memory addresses. + /// + /// The SigScanner instance. + protected virtual void SetupInternal(SigScanner scanner) + { + // Do nothing } } diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 5465fef97..0da9f6dcf 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -21,298 +21,298 @@ using Dalamud.Plugin.Internal; using Dalamud.Utility; using Serilog; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// Chat events and public helper functions. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public class ChatHandlers { - /// - /// Chat events and public helper functions. - /// - [PluginInterface] - [InterfaceVersion("1.0")] - public class ChatHandlers + // private static readonly Dictionary UnicodeToDiscordEmojiDict = new() + // { + // { "", "<:ffxive071:585847382210642069>" }, + // { "", "<:ffxive083:585848592699490329>" }, + // }; + + // private readonly Dictionary handledChatTypeColors = new() + // { + // { XivChatType.CrossParty, Color.DodgerBlue }, + // { XivChatType.Party, Color.DodgerBlue }, + // { XivChatType.FreeCompany, Color.DeepSkyBlue }, + // { XivChatType.CrossLinkShell1, Color.ForestGreen }, + // { XivChatType.CrossLinkShell2, Color.ForestGreen }, + // { XivChatType.CrossLinkShell3, Color.ForestGreen }, + // { XivChatType.CrossLinkShell4, Color.ForestGreen }, + // { XivChatType.CrossLinkShell5, Color.ForestGreen }, + // { XivChatType.CrossLinkShell6, Color.ForestGreen }, + // { XivChatType.CrossLinkShell7, Color.ForestGreen }, + // { XivChatType.CrossLinkShell8, Color.ForestGreen }, + // { XivChatType.Ls1, Color.ForestGreen }, + // { XivChatType.Ls2, Color.ForestGreen }, + // { XivChatType.Ls3, Color.ForestGreen }, + // { XivChatType.Ls4, Color.ForestGreen }, + // { XivChatType.Ls5, Color.ForestGreen }, + // { XivChatType.Ls6, Color.ForestGreen }, + // { XivChatType.Ls7, Color.ForestGreen }, + // { XivChatType.Ls8, Color.ForestGreen }, + // { XivChatType.TellIncoming, Color.HotPink }, + // { XivChatType.PvPTeam, Color.SandyBrown }, + // { XivChatType.Urgent, Color.DarkViolet }, + // { XivChatType.NoviceNetwork, Color.SaddleBrown }, + // { XivChatType.Echo, Color.Gray }, + // }; + + private readonly Regex rmtRegex = new( + @"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia", + RegexOptions.Compiled); + + private readonly Dictionary retainerSaleRegexes = new() { - // private static readonly Dictionary UnicodeToDiscordEmojiDict = new() - // { - // { "", "<:ffxive071:585847382210642069>" }, - // { "", "<:ffxive083:585848592699490329>" }, - // }; - - // private readonly Dictionary handledChatTypeColors = new() - // { - // { XivChatType.CrossParty, Color.DodgerBlue }, - // { XivChatType.Party, Color.DodgerBlue }, - // { XivChatType.FreeCompany, Color.DeepSkyBlue }, - // { XivChatType.CrossLinkShell1, Color.ForestGreen }, - // { XivChatType.CrossLinkShell2, Color.ForestGreen }, - // { XivChatType.CrossLinkShell3, Color.ForestGreen }, - // { XivChatType.CrossLinkShell4, Color.ForestGreen }, - // { XivChatType.CrossLinkShell5, Color.ForestGreen }, - // { XivChatType.CrossLinkShell6, Color.ForestGreen }, - // { XivChatType.CrossLinkShell7, Color.ForestGreen }, - // { XivChatType.CrossLinkShell8, Color.ForestGreen }, - // { XivChatType.Ls1, Color.ForestGreen }, - // { XivChatType.Ls2, Color.ForestGreen }, - // { XivChatType.Ls3, Color.ForestGreen }, - // { XivChatType.Ls4, Color.ForestGreen }, - // { XivChatType.Ls5, Color.ForestGreen }, - // { XivChatType.Ls6, Color.ForestGreen }, - // { XivChatType.Ls7, Color.ForestGreen }, - // { XivChatType.Ls8, Color.ForestGreen }, - // { XivChatType.TellIncoming, Color.HotPink }, - // { XivChatType.PvPTeam, Color.SandyBrown }, - // { XivChatType.Urgent, Color.DarkViolet }, - // { XivChatType.NoviceNetwork, Color.SaddleBrown }, - // { XivChatType.Echo, Color.Gray }, - // }; - - private readonly Regex rmtRegex = new( - @"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia", - RegexOptions.Compiled); - - private readonly Dictionary retainerSaleRegexes = new() { + ClientLanguage.Japanese, + new Regex[] { - ClientLanguage.Japanese, - new Regex[] - { new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - } - }, + } + }, + { + ClientLanguage.English, + new Regex[] { - ClientLanguage.English, - new Regex[] - { new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), - } - }, + } + }, + { + ClientLanguage.German, + new Regex[] { - ClientLanguage.German, - new Regex[] - { new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled), new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.French, - new Regex[] - { - new Regex(@"^Un servant a vendu (?.+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled), - } - }, - }; - - private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); - - private readonly DalamudLinkPayload openInstallerWindowLink; - - private bool hasSeenLoadingMsg; - private bool hasAutoUpdatedPlugins; - - /// - /// Initializes a new instance of the class. - /// - internal ChatHandlers() - { - var chatGui = Service.Get(); - - chatGui.CheckMessageHandled += this.OnCheckMessageHandled; - chatGui.ChatMessage += this.OnChatMessage; - - this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) => - { - Service.Get().OpenPluginInstaller(); - }); - } - - /// - /// Gets the last URL seen in chat. - /// - public string? LastLink { get; private set; } - - /// - /// Convert a TextPayload to SeString and wrap in italics payloads. - /// - /// Text to convert. - /// SeString payload of italicized text. - public static SeString MakeItalics(string text) - => MakeItalics(new TextPayload(text)); - - /// - /// Convert a TextPayload to SeString and wrap in italics payloads. - /// - /// Text to convert. - /// SeString payload of italicized text. - public static SeString MakeItalics(TextPayload text) - => new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff); - - private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) - { - var configuration = Service.Get(); - - var textVal = message.TextValue; - - if (!configuration.DisableRmtFiltering) - { - var matched = this.rmtRegex.IsMatch(textVal); - if (matched) - { - // This seems to be a RMT ad - let's not show it - Log.Debug("Handled RMT ad: " + message.TextValue); - isHandled = true; - return; - } } - - if (configuration.BadWords != null && - configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) + }, + { + ClientLanguage.French, + new Regex[] { - // This seems to be in the user block list - let's not show it - Log.Debug("Blocklist triggered"); + new Regex(@"^Un servant a vendu (?.+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled), + } + }, + }; + + private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); + + private readonly DalamudLinkPayload openInstallerWindowLink; + + private bool hasSeenLoadingMsg; + private bool hasAutoUpdatedPlugins; + + /// + /// Initializes a new instance of the class. + /// + internal ChatHandlers() + { + var chatGui = Service.Get(); + + chatGui.CheckMessageHandled += this.OnCheckMessageHandled; + chatGui.ChatMessage += this.OnChatMessage; + + this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) => + { + Service.Get().OpenPluginInstaller(); + }); + } + + /// + /// Gets the last URL seen in chat. + /// + public string? LastLink { get; private set; } + + /// + /// Convert a TextPayload to SeString and wrap in italics payloads. + /// + /// Text to convert. + /// SeString payload of italicized text. + public static SeString MakeItalics(string text) + => MakeItalics(new TextPayload(text)); + + /// + /// Convert a TextPayload to SeString and wrap in italics payloads. + /// + /// Text to convert. + /// SeString payload of italicized text. + public static SeString MakeItalics(TextPayload text) + => new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff); + + private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) + { + var configuration = Service.Get(); + + var textVal = message.TextValue; + + if (!configuration.DisableRmtFiltering) + { + var matched = this.rmtRegex.IsMatch(textVal); + if (matched) + { + // This seems to be a RMT ad - let's not show it + Log.Debug("Handled RMT ad: " + message.TextValue); isHandled = true; return; } } - private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) + if (configuration.BadWords != null && + configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) { - var startInfo = Service.Get(); - var clientState = Service.Get(); + // This seems to be in the user block list - let's not show it + Log.Debug("Blocklist triggered"); + isHandled = true; + return; + } + } - if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) - this.PrintWelcomeMessage(); + private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) + { + var startInfo = Service.Get(); + var clientState = Service.Get(); - // For injections while logged in - if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg) - this.PrintWelcomeMessage(); + if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) + this.PrintWelcomeMessage(); - if (!this.hasAutoUpdatedPlugins) - this.AutoUpdatePlugins(); + // For injections while logged in + if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg) + this.PrintWelcomeMessage(); + + if (!this.hasAutoUpdatedPlugins) + this.AutoUpdatePlugins(); #if !DEBUG && false if (!this.hasSeenLoadingMsg) return; #endif - if (type == XivChatType.RetainerSale) + if (type == XivChatType.RetainerSale) + { + foreach (var regex in this.retainerSaleRegexes[startInfo.Language]) { - foreach (var regex in this.retainerSaleRegexes[startInfo.Language]) + var matchInfo = regex.Match(message.TextValue); + + // we no longer really need to do/validate the item matching since we read the id from the byte array + // but we'd be checking the main match anyway + var itemInfo = matchInfo.Groups["item"]; + if (!itemInfo.Success) + continue; + + var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload; + if (itemLink == default) { - var matchInfo = regex.Match(message.TextValue); - - // we no longer really need to do/validate the item matching since we read the id from the byte array - // but we'd be checking the main match anyway - var itemInfo = matchInfo.Groups["item"]; - if (!itemInfo.Success) - continue; - - var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload; - if (itemLink == default) - { - Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode())); - break; - } - - Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); - - var valueInfo = matchInfo.Groups["value"]; - // not sure if using a culture here would work correctly, so just strip symbols instead - if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue)) - continue; - - // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ)); + Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode())); break; } + + Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); + + var valueInfo = matchInfo.Groups["value"]; + // not sure if using a culture here would work correctly, so just strip symbols instead + if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue)) + continue; + + // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ)); + break; } - - var messageCopy = message; - var senderCopy = sender; - - var linkMatch = this.urlRegex.Match(message.TextValue); - if (linkMatch.Value.Length > 0) - this.LastLink = linkMatch.Value; } - private void PrintWelcomeMessage() + var messageCopy = message; + var senderCopy = sender; + + var linkMatch = this.urlRegex.Match(message.TextValue); + if (linkMatch.Value.Length > 0) + this.LastLink = linkMatch.Value; + } + + private void PrintWelcomeMessage() + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + var dalamudInterface = Service.Get(); + + var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); + + chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion) + + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count)); + + if (configuration.PrintPluginsWelcomeMsg) { - var chatGui = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - var dalamudInterface = Service.Get(); - - var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - - chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion) - + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count)); - - if (configuration.PrintPluginsWelcomeMsg) + foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name)) { - foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name)) - { - chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion)); - } + chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion)); } - - if (string.IsNullOrEmpty(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion)) - { - chatGui.PrintChat(new XivChatEntry - { - Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."), - Type = XivChatType.Notice, - }); - - if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor))) - { - dalamudInterface.OpenChangelogWindow(); - configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor; - } - - configuration.LastVersion = assemblyVersion; - configuration.Save(); - } - - this.hasSeenLoadingMsg = true; } - private void AutoUpdatePlugins() + if (string.IsNullOrEmpty(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion)) { - var chatGui = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - var notifications = Service.Get(); - - if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0) + chatGui.PrintChat(new XivChatEntry { - // Plugins aren't ready yet. + Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."), + Type = XivChatType.Notice, + }); + + if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor))) + { + dalamudInterface.OpenChangelogWindow(); + configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor; + } + + configuration.LastVersion = assemblyVersion; + configuration.Save(); + } + + this.hasSeenLoadingMsg = true; + } + + private void AutoUpdatePlugins() + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + var notifications = Service.Get(); + + if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0) + { + // Plugins aren't ready yet. + return; + } + + this.hasAutoUpdatedPlugins = true; + + Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task => + { + if (task.IsFaulted) + { + Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates.")); return; } - this.hasAutoUpdatedPlugins = true; - - Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task => + var updatedPlugins = task.Result; + if (updatedPlugins != null && updatedPlugins.Any()) { - if (task.IsFaulted) + if (configuration.AutoUpdatePlugins) { - Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates.")); - return; + pluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:")); + notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info); } - - var updatedPlugins = task.Result; - if (updatedPlugins != null && updatedPlugins.Any()) + else { - if (configuration.AutoUpdatePlugins) - { - pluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:")); - notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info); - } - else - { - var data = Service.Get(); + var data = Service.Get(); - chatGui.PrintChat(new XivChatEntry - { - Message = new SeString(new List() - { + chatGui.PrintChat(new XivChatEntry + { + Message = new SeString(new List() + { new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")), new TextPayload(" ["), new UIForegroundPayload(500), @@ -321,12 +321,11 @@ namespace Dalamud.Game RawPayload.LinkTerminator, new UIForegroundPayload(0), new TextPayload("]"), - }), - Type = XivChatType.Urgent, - }); - } + }), + Type = XivChatType.Urgent, + }); } - }); - } + } + }); } } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index 21a31d868..9af104e80 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -7,180 +7,179 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Buddy +namespace Dalamud.Game.ClientState.Buddy; + +/// +/// This collection represents the buddies present in your squadron or trust party. +/// It does not include the local player. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed partial class BuddyList { + private const uint InvalidObjectID = 0xE0000000; + + private readonly ClientStateAddressResolver address; + /// - /// This collection represents the buddies present in your squadron or trust party. - /// It does not include the local player. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed partial class BuddyList + /// Client state address resolver. + internal BuddyList(ClientStateAddressResolver addressResolver) { - private const uint InvalidObjectID = 0xE0000000; + this.address = addressResolver; - private readonly ClientStateAddressResolver address; + Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); + } - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal BuddyList(ClientStateAddressResolver addressResolver) + /// + /// Gets the amount of battle buddies the local player has. + /// + public int Length + { + get { - this.address = addressResolver; - - Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); - } - - /// - /// Gets the amount of battle buddies the local player has. - /// - public int Length - { - get + var i = 0; + for (; i < 3; i++) { - var i = 0; - for (; i < 3; i++) - { - var addr = this.GetBattleBuddyMemberAddress(i); - var member = this.CreateBuddyMemberReference(addr); - if (member == null) - break; - } - - return i; + var addr = this.GetBattleBuddyMemberAddress(i); + var member = this.CreateBuddyMemberReference(addr); + if (member == null) + break; } - } - /// - /// Gets a value indicating whether the local player's companion is present. - /// - public bool CompanionBuddyPresent => this.CompanionBuddy != null; - - /// - /// Gets a value indicating whether the local player's pet is present. - /// - public bool PetBuddyPresent => this.PetBuddy != null; - - /// - /// Gets the active companion buddy. - /// - public BuddyMember? CompanionBuddy - { - get - { - var addr = this.GetCompanionBuddyMemberAddress(); - return this.CreateBuddyMemberReference(addr); - } - } - - /// - /// Gets the active pet buddy. - /// - public BuddyMember? PetBuddy - { - get - { - var addr = this.GetPetBuddyMemberAddress(); - return this.CreateBuddyMemberReference(addr); - } - } - - /// - /// Gets the address of the buddy list. - /// - internal IntPtr BuddyListAddress => this.address.BuddyList; - - private static int BuddyMemberSize { get; } = Marshal.SizeOf(); - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; - - /// - /// Gets a battle buddy at the specified spawn index. - /// - /// Spawn index. - /// A at the specified spawn index. - public BuddyMember? this[int index] - { - get - { - var address = this.GetBattleBuddyMemberAddress(index); - return this.CreateBuddyMemberReference(address); - } - } - - /// - /// Gets the address of the companion buddy. - /// - /// The memory address of the companion buddy. - public unsafe IntPtr GetCompanionBuddyMemberAddress() - { - return (IntPtr)(&this.BuddyListStruct->Companion); - } - - /// - /// Gets the address of the pet buddy. - /// - /// The memory address of the pet buddy. - public unsafe IntPtr GetPetBuddyMemberAddress() - { - return (IntPtr)(&this.BuddyListStruct->Pet); - } - - /// - /// Gets the address of the battle buddy at the specified index of the buddy list. - /// - /// The index of the battle buddy. - /// The memory address of the battle buddy. - public unsafe IntPtr GetBattleBuddyMemberAddress(int index) - { - if (index < 0 || index >= 3) - return IntPtr.Zero; - - return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize)); - } - - /// - /// Create a reference to a buddy. - /// - /// The address of the buddy in memory. - /// object containing the requested data. - public BuddyMember? CreateBuddyMemberReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - var buddy = new BuddyMember(address); - if (buddy.ObjectId == InvalidObjectID) - return null; - - return buddy; + return i; } } /// - /// This collection represents the buddies present in your squadron or trust party. + /// Gets a value indicating whether the local player's companion is present. /// - public sealed partial class BuddyList : IReadOnlyCollection + public bool CompanionBuddyPresent => this.CompanionBuddy != null; + + /// + /// Gets a value indicating whether the local player's pet is present. + /// + public bool PetBuddyPresent => this.PetBuddy != null; + + /// + /// Gets the active companion buddy. + /// + public BuddyMember? CompanionBuddy { - /// - int IReadOnlyCollection.Count => this.Length; - - /// - public IEnumerator GetEnumerator() + get { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } + var addr = this.GetCompanionBuddyMemberAddress(); + return this.CreateBuddyMemberReference(addr); } + } - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// + /// Gets the active pet buddy. + /// + public BuddyMember? PetBuddy + { + get + { + var addr = this.GetPetBuddyMemberAddress(); + return this.CreateBuddyMemberReference(addr); + } + } + + /// + /// Gets the address of the buddy list. + /// + internal IntPtr BuddyListAddress => this.address.BuddyList; + + private static int BuddyMemberSize { get; } = Marshal.SizeOf(); + + private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; + + /// + /// Gets a battle buddy at the specified spawn index. + /// + /// Spawn index. + /// A at the specified spawn index. + public BuddyMember? this[int index] + { + get + { + var address = this.GetBattleBuddyMemberAddress(index); + return this.CreateBuddyMemberReference(address); + } + } + + /// + /// Gets the address of the companion buddy. + /// + /// The memory address of the companion buddy. + public unsafe IntPtr GetCompanionBuddyMemberAddress() + { + return (IntPtr)(&this.BuddyListStruct->Companion); + } + + /// + /// Gets the address of the pet buddy. + /// + /// The memory address of the pet buddy. + public unsafe IntPtr GetPetBuddyMemberAddress() + { + return (IntPtr)(&this.BuddyListStruct->Pet); + } + + /// + /// Gets the address of the battle buddy at the specified index of the buddy list. + /// + /// The index of the battle buddy. + /// The memory address of the battle buddy. + public unsafe IntPtr GetBattleBuddyMemberAddress(int index) + { + if (index < 0 || index >= 3) + return IntPtr.Zero; + + return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize)); + } + + /// + /// Create a reference to a buddy. + /// + /// The address of the buddy in memory. + /// object containing the requested data. + public BuddyMember? CreateBuddyMemberReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + var buddy = new BuddyMember(address); + if (buddy.ObjectId == InvalidObjectID) + return null; + + return buddy; } } + +/// +/// This collection represents the buddies present in your squadron or trust party. +/// +public sealed partial class BuddyList : IReadOnlyCollection +{ + /// + int IReadOnlyCollection.Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < this.Length; i++) + { + yield return this[i]; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +} diff --git a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs index 0029d20db..4d16a69d1 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs @@ -4,70 +4,69 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Resolvers; -namespace Dalamud.Game.ClientState.Buddy +namespace Dalamud.Game.ClientState.Buddy; + +/// +/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. +/// +public unsafe class BuddyMember { /// - /// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. + /// Initializes a new instance of the class. /// - public unsafe class BuddyMember + /// Buddy address. + internal BuddyMember(IntPtr address) { - /// - /// Initializes a new instance of the class. - /// - /// Buddy address. - internal BuddyMember(IntPtr address) - { - this.Address = address; - } - - /// - /// Gets the address of the buddy in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the object ID of this buddy. - /// - public uint ObjectId => this.Struct->ObjectID; - - /// - /// Gets the actor associated with this buddy. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); - - /// - /// Gets the current health of this buddy. - /// - public uint CurrentHP => this.Struct->CurrentHealth; - - /// - /// Gets the maximum health of this buddy. - /// - public uint MaxHP => this.Struct->MaxHealth; - - /// - /// Gets the data ID of this buddy. - /// - public uint DataID => this.Struct->DataID; - - /// - /// Gets the Mount data related to this buddy. It should only be used with companion buddies. - /// - public ExcelResolver MountData => new(this.DataID); - - /// - /// Gets the Pet data related to this buddy. It should only be used with pet buddies. - /// - public ExcelResolver PetData => new(this.DataID); - - /// - /// Gets the Trust data related to this buddy. It should only be used with battle buddies. - /// - public ExcelResolver TrustData => new(this.DataID); - - private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; + this.Address = address; } + + /// + /// Gets the address of the buddy in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the object ID of this buddy. + /// + public uint ObjectId => this.Struct->ObjectID; + + /// + /// Gets the actor associated with this buddy. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); + + /// + /// Gets the current health of this buddy. + /// + public uint CurrentHP => this.Struct->CurrentHealth; + + /// + /// Gets the maximum health of this buddy. + /// + public uint MaxHP => this.Struct->MaxHealth; + + /// + /// Gets the data ID of this buddy. + /// + public uint DataID => this.Struct->DataID; + + /// + /// Gets the Mount data related to this buddy. It should only be used with companion buddies. + /// + public ExcelResolver MountData => new(this.DataID); + + /// + /// Gets the Pet data related to this buddy. It should only be used with pet buddies. + /// + public ExcelResolver PetData => new(this.DataID); + + /// + /// Gets the Trust data related to this buddy. It should only be used with battle buddies. + /// + public ExcelResolver TrustData => new(this.DataID); + + private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 52b63ed9e..6f7fdb6f3 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -16,165 +16,164 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState +namespace Dalamud.Game.ClientState; + +/// +/// This class represents the state of the game client at the time of access. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class ClientState : IDisposable { + private readonly ClientStateAddressResolver address; + private readonly Hook setupTerritoryTypeHook; + + private bool lastConditionNone = true; + /// - /// This class represents the state of the game client at the time of access. + /// Initializes a new instance of the class. + /// Set up client state access. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class ClientState : IDisposable + internal ClientState() { - private readonly ClientStateAddressResolver address; - private readonly Hook setupTerritoryTypeHook; + this.address = new ClientStateAddressResolver(); + this.address.Setup(); - private bool lastConditionNone = true; + Log.Verbose("===== C L I E N T S T A T E ====="); - /// - /// Initializes a new instance of the class. - /// Set up client state access. - /// - internal ClientState() + this.ClientLanguage = Service.Get().Language; + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); + + this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); + + var framework = Service.Get(); + framework.Update += this.FrameworkOnOnUpdateEvent; + + var networkHandlers = Service.Get(); + networkHandlers.CfPop += this.NetworkHandlersOnCfPop; + } + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); + + /// + /// Event that gets fired when the current Territory changes. + /// + public event EventHandler TerritoryChanged; + + /// + /// Event that fires when a character is logging in. + /// + public event EventHandler Login; + + /// + /// Event that fires when a character is logging out. + /// + public event EventHandler Logout; + + /// + /// Event that gets fired when a duty is ready. + /// + public event EventHandler CfPop; + + /// + /// Gets the language of the client. + /// + public ClientLanguage ClientLanguage { get; } + + /// + /// Gets the current Territory the player resides in. + /// + public ushort TerritoryType { get; private set; } + + /// + /// Gets the local player character, if one is present. + /// + public PlayerCharacter? LocalPlayer => Service.Get()[0] as PlayerCharacter; + + /// + /// Gets the content ID of the local character. + /// + public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); + + /// + /// Gets a value indicating whether a character is logged in. + /// + public bool IsLoggedIn { get; private set; } + + /// + /// Enable this module. + /// + public void Enable() + { + Service.Get().Enable(); + Service.Get().Enable(); + this.setupTerritoryTypeHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.setupTerritoryTypeHook.Dispose(); + Service.Get().Dispose(); + Service.Get().Dispose(); + Service.Get().Update -= this.FrameworkOnOnUpdateEvent; + Service.Get().CfPop -= this.NetworkHandlersOnCfPop; + } + + private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) + { + this.TerritoryType = terriType; + this.TerritoryChanged?.Invoke(this, terriType); + + Log.Debug("TerritoryType changed: {0}", terriType); + + return this.setupTerritoryTypeHook.Original(manager, terriType); + } + + private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e) + { + this.CfPop?.Invoke(this, e); + } + + private void FrameworkOnOnUpdateEvent(Framework framework) + { + var condition = Service.Get(); + if (condition.Any() && this.lastConditionNone == true) { - this.address = new ClientStateAddressResolver(); - this.address.Setup(); - - Log.Verbose("===== C L I E N T S T A T E ====="); - - this.ClientLanguage = Service.Get().Language; - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); - - this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); - - var framework = Service.Get(); - framework.Update += this.FrameworkOnOnUpdateEvent; - - var networkHandlers = Service.Get(); - networkHandlers.CfPop += this.NetworkHandlersOnCfPop; + Log.Debug("Is login"); + this.lastConditionNone = false; + this.IsLoggedIn = true; + this.Login?.Invoke(this, null); } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); - - /// - /// Event that gets fired when the current Territory changes. - /// - public event EventHandler TerritoryChanged; - - /// - /// Event that fires when a character is logging in. - /// - public event EventHandler Login; - - /// - /// Event that fires when a character is logging out. - /// - public event EventHandler Logout; - - /// - /// Event that gets fired when a duty is ready. - /// - public event EventHandler CfPop; - - /// - /// Gets the language of the client. - /// - public ClientLanguage ClientLanguage { get; } - - /// - /// Gets the current Territory the player resides in. - /// - public ushort TerritoryType { get; private set; } - - /// - /// Gets the local player character, if one is present. - /// - public PlayerCharacter? LocalPlayer => Service.Get()[0] as PlayerCharacter; - - /// - /// Gets the content ID of the local character. - /// - public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); - - /// - /// Gets a value indicating whether a character is logged in. - /// - public bool IsLoggedIn { get; private set; } - - /// - /// Enable this module. - /// - public void Enable() + if (!condition.Any() && this.lastConditionNone == false) { - Service.Get().Enable(); - Service.Get().Enable(); - this.setupTerritoryTypeHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.setupTerritoryTypeHook.Dispose(); - Service.Get().Dispose(); - Service.Get().Dispose(); - Service.Get().Update -= this.FrameworkOnOnUpdateEvent; - Service.Get().CfPop -= this.NetworkHandlersOnCfPop; - } - - private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) - { - this.TerritoryType = terriType; - this.TerritoryChanged?.Invoke(this, terriType); - - Log.Debug("TerritoryType changed: {0}", terriType); - - return this.setupTerritoryTypeHook.Original(manager, terriType); - } - - private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e) - { - this.CfPop?.Invoke(this, e); - } - - private void FrameworkOnOnUpdateEvent(Framework framework) - { - var condition = Service.Get(); - if (condition.Any() && this.lastConditionNone == true) - { - Log.Debug("Is login"); - this.lastConditionNone = false; - this.IsLoggedIn = true; - this.Login?.Invoke(this, null); - } - - if (!condition.Any() && this.lastConditionNone == false) - { - Log.Debug("Is logout"); - this.lastConditionNone = true; - this.IsLoggedIn = false; - this.Logout?.Invoke(this, null); - } + Log.Debug("Is logout"); + this.lastConditionNone = true; + this.IsLoggedIn = false; + this.Logout?.Invoke(this, null); } } } diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 11b77e2df..1be46668d 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -1,114 +1,113 @@ using System; -namespace Dalamud.Game.ClientState +namespace Dalamud.Game.ClientState; + +/// +/// Client state memory address resolver. +/// +public sealed class ClientStateAddressResolver : BaseAddressResolver { + // Static offsets + /// - /// Client state memory address resolver. + /// Gets the address of the actor table. /// - public sealed class ClientStateAddressResolver : BaseAddressResolver + public IntPtr ObjectTable { get; private set; } + + /// + /// Gets the address of the buddy list. + /// + public IntPtr BuddyList { get; private set; } + + /// + /// Gets the address of a pointer to the fate table. + /// + /// + /// This is a static address to a pointer, not the address of the table itself. + /// + public IntPtr FateTablePtr { get; private set; } + + /// + /// Gets the address of the Group Manager. + /// + public IntPtr GroupManager { get; private set; } + + /// + /// Gets the address of the local content id. + /// + public IntPtr LocalContentId { get; private set; } + + /// + /// Gets the address of job gauge data. + /// + public IntPtr JobGaugeData { get; private set; } + + /// + /// Gets the address of the keyboard state. + /// + public IntPtr KeyboardState { get; private set; } + + /// + /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state. + /// + public IntPtr KeyboardStateIndexArray { get; private set; } + + /// + /// Gets the address of the target manager. + /// + public IntPtr TargetManager { get; private set; } + + /// + /// Gets the address of the condition flag array. + /// + public IntPtr ConditionFlags { get; private set; } + + // Functions + + /// + /// Gets the address of the method which sets the territory type. + /// + public IntPtr SetupTerritoryType { get; private set; } + + /// + /// Gets the address of the method which polls the gamepads for data. + /// Called every frame, even when `Enable Gamepad` is off in the settings. + /// + public IntPtr GamepadPoll { get; private set; } + + /// + /// Scan for and setup any configured address pointers. + /// + /// The signature scanner to facilitate setup. + protected override void Setup64Bit(SigScanner sig) { - // Static offsets + // We don't need those anymore, but maybe someone else will - let's leave them here for good measure + // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148; + // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??"); - /// - /// Gets the address of the actor table. - /// - public IntPtr ObjectTable { get; private set; } + this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83"); - /// - /// Gets the address of the buddy list. - /// - public IntPtr BuddyList { get; private set; } + this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04"); - /// - /// Gets the address of a pointer to the fate table. - /// - /// - /// This is a static address to a pointer, not the address of the table itself. - /// - public IntPtr FateTablePtr { get; private set; } + this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??"); - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManager { get; private set; } + this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50"); - /// - /// Gets the address of the local content id. - /// - public IntPtr LocalContentId { get; private set; } + this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07"); + this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8; - /// - /// Gets the address of job gauge data. - /// - public IntPtr JobGaugeData { get; private set; } + this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??"); - /// - /// Gets the address of the keyboard state. - /// - public IntPtr KeyboardState { get; private set; } + // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. + // lea rcx, ds:1DB9F74h[rax*4] KeyboardState + // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray + this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; + this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; - /// - /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state. - /// - public IntPtr KeyboardStateIndexArray { get; private set; } + this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30"); - /// - /// Gets the address of the target manager. - /// - public IntPtr TargetManager { get; private set; } + this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB"); - /// - /// Gets the address of the condition flag array. - /// - public IntPtr ConditionFlags { get; private set; } - - // Functions - - /// - /// Gets the address of the method which sets the territory type. - /// - public IntPtr SetupTerritoryType { get; private set; } - - /// - /// Gets the address of the method which polls the gamepads for data. - /// Called every frame, even when `Enable Gamepad` is off in the settings. - /// - public IntPtr GamepadPoll { get; private set; } - - /// - /// Scan for and setup any configured address pointers. - /// - /// The signature scanner to facilitate setup. - protected override void Setup64Bit(SigScanner sig) - { - // We don't need those anymore, but maybe someone else will - let's leave them here for good measure - // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148; - // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??"); - - this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83"); - - this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04"); - - this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??"); - - this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50"); - - this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07"); - this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8; - - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??"); - - // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. - // lea rcx, ds:1DB9F74h[rax*4] KeyboardState - // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray - this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; - this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; - - this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30"); - - this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB"); - - this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); - } + this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); } } diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 597f4725b..22aadff7a 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -4,155 +4,154 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Conditions +namespace Dalamud.Game.ClientState.Conditions; + +/// +/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed partial class Condition { /// - /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. + /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed partial class Condition + public const int MaxConditionEntries = 100; + + private readonly bool[] cache = new bool[MaxConditionEntries]; + + /// + /// Initializes a new instance of the class. + /// + /// The ClientStateAddressResolver instance. + internal Condition(ClientStateAddressResolver resolver) { - /// - /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. - /// - public const int MaxConditionEntries = 100; + this.Address = resolver.ConditionFlags; + } - private readonly bool[] cache = new bool[MaxConditionEntries]; + /// + /// A delegate type used with the event. + /// + /// The changed condition. + /// The value the condition is set to. + public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value); - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - internal Condition(ClientStateAddressResolver resolver) + /// + /// Event that gets fired when a condition is set. + /// Should only get fired for actual changes, so the previous value will always be !value. + /// + public event ConditionChangeDelegate? ConditionChange; + + /// + /// Gets the condition array base pointer. + /// + public IntPtr Address { get; private set; } + + /// + /// Check the value of a specific condition/state flag. + /// + /// The condition flag to check. + public unsafe bool this[int flag] + { + get { - this.Address = resolver.ConditionFlags; + if (flag < 0 || flag >= MaxConditionEntries) + return false; + + return *(bool*)(this.Address + flag); + } + } + + /// + public unsafe bool this[ConditionFlag flag] + => this[(int)flag]; + + /// + /// Check if any condition flags are set. + /// + /// Whether any single flag is set. + public bool Any() + { + for (var i = 0; i < MaxConditionEntries; i++) + { + var cond = this[i]; + + if (cond) + return true; } - /// - /// A delegate type used with the event. - /// - /// The changed condition. - /// The value the condition is set to. - public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value); + return false; + } - /// - /// Event that gets fired when a condition is set. - /// Should only get fired for actual changes, so the previous value will always be !value. - /// - public event ConditionChangeDelegate? ConditionChange; + /// + /// Enables the hooks of the Condition class function. + /// + public void Enable() + { + // Initialization + for (var i = 0; i < MaxConditionEntries; i++) + this.cache[i] = this[i]; - /// - /// Gets the condition array base pointer. - /// - public IntPtr Address { get; private set; } + Service.Get().Update += this.FrameworkUpdate; + } - /// - /// Check the value of a specific condition/state flag. - /// - /// The condition flag to check. - public unsafe bool this[int flag] + private void FrameworkUpdate(Framework framework) + { + for (var i = 0; i < MaxConditionEntries; i++) { - get + var value = this[i]; + + if (value != this.cache[i]) { - if (flag < 0 || flag >= MaxConditionEntries) - return false; + this.cache[i] = value; - return *(bool*)(this.Address + flag); - } - } - - /// - public unsafe bool this[ConditionFlag flag] - => this[(int)flag]; - - /// - /// Check if any condition flags are set. - /// - /// Whether any single flag is set. - public bool Any() - { - for (var i = 0; i < MaxConditionEntries; i++) - { - var cond = this[i]; - - if (cond) - return true; - } - - return false; - } - - /// - /// Enables the hooks of the Condition class function. - /// - public void Enable() - { - // Initialization - for (var i = 0; i < MaxConditionEntries; i++) - this.cache[i] = this[i]; - - Service.Get().Update += this.FrameworkUpdate; - } - - private void FrameworkUpdate(Framework framework) - { - for (var i = 0; i < MaxConditionEntries; i++) - { - var value = this[i]; - - if (value != this.cache[i]) + try { - this.cache[i] = value; - - try - { - this.ConditionChange?.Invoke((ConditionFlag)i, value); - } - catch (Exception ex) - { - Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown."); - } + this.ConditionChange?.Invoke((ConditionFlag)i, value); + } + catch (Exception ex) + { + Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown."); } } } } +} + +/// +/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. +/// +public sealed partial class Condition : IDisposable +{ + private bool isDisposed; /// - /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. + /// Finalizes an instance of the class. /// - public sealed partial class Condition : IDisposable + ~Condition() { - private bool isDisposed; + this.Dispose(false); + } - /// - /// Finalizes an instance of the class. - /// - ~Condition() + /// + /// Disposes this instance, alongside its hooks. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + this.Dispose(true); + } + + private void Dispose(bool disposing) + { + if (this.isDisposed) + return; + + if (disposing) { - this.Dispose(false); + Service.Get().Update -= this.FrameworkUpdate; } - /// - /// Disposes this instance, alongside its hooks. - /// - public void Dispose() - { - GC.SuppressFinalize(this); - this.Dispose(true); - } - - private void Dispose(bool disposing) - { - if (this.isDisposed) - return; - - if (disposing) - { - Service.Get().Update -= this.FrameworkUpdate; - } - - this.isDisposed = true; - } + this.isDisposed = true; } } diff --git a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs index 75c295ed0..214f5f7a9 100644 --- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs +++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs @@ -1,453 +1,452 @@ -namespace Dalamud.Game.ClientState.Conditions +namespace Dalamud.Game.ClientState.Conditions; + +/// +/// Possible state flags (or conditions as they're called internally) that can be set on the local client. +/// +/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to +/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to. +/// +public enum ConditionFlag { /// - /// Possible state flags (or conditions as they're called internally) that can be set on the local client. - /// - /// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to - /// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to. + /// Unused. /// - public enum ConditionFlag - { - /// - /// Unused. - /// - None = 0, - - /// - /// Unable to execute command under normal conditions. - /// - NormalConditions = 1, - - /// - /// Unable to execute command while unconscious. - /// - Unconscious = 2, - - /// - /// Unable to execute command during an emote. - /// - Emoting = 3, - - /// - /// Unable to execute command while mounted. - /// - Mounted = 4, - - /// - /// Unable to execute command while crafting. - /// - Crafting = 5, - - /// - /// Unable to execute command while gathering. - /// - Gathering = 6, - - /// - /// Unable to execute command while melding materia. - /// - MeldingMateria = 7, - - /// - /// Unable to execute command while operating a siege machine. - /// - OperatingSiegeMachine = 8, - - /// - /// Unable to execute command while carrying an object. - /// - CarryingObject = 9, - - /// - /// Unable to execute command while mounted. - /// - Mounted2 = 10, - - /// - /// Unable to execute command while in that position. - /// - InThatPosition = 11, - - /// - /// Unable to execute command while chocobo racing. - /// - ChocoboRacing = 12, - - /// - /// Unable to execute command while playing a mini-game. - /// - PlayingMiniGame = 13, - - /// - /// Unable to execute command while playing Lord of Verminion. - /// - PlayingLordOfVerminion = 14, - - /// - /// Unable to execute command while participating in a custom match. - /// - ParticipatingInCustomMatch = 15, - - /// - /// Unable to execute command while performing. - /// - Performing = 16, - - // Unknown17 = 17, - // Unknown18 = 18, - // Unknown19 = 19, - // Unknown20 = 20, - // Unknown21 = 21, - // Unknown22 = 22, - // Unknown23 = 23, - // Unknown24 = 24, - - /// - /// Unable to execute command while occupied. - /// - Occupied = 25, - - /// - /// Unable to execute command during combat. - /// - InCombat = 26, - - /// - /// Unable to execute command while casting. - /// - Casting = 27, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction = 28, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction2 = 29, - - /// - /// Unable to execute command while occupied. - /// - Occupied30 = 30, - - /// - /// Unable to execute command while occupied. - /// - // todo: not sure if this is used for other event states/??? - OccupiedInEvent = 31, - - /// - /// Unable to execute command while occupied. - /// - OccupiedInQuestEvent = 32, - - /// - /// Unable to execute command while occupied. - /// - Occupied33 = 33, - - /// - /// Unable to execute command while bound by duty. - /// - BoundByDuty = 34, - - /// - /// Unable to execute command while occupied. - /// - OccupiedInCutSceneEvent = 35, - - /// - /// Unable to execute command while in a dueling area. - /// - InDuelingArea = 36, - - /// - /// Unable to execute command while a trade is open. - /// - TradeOpen = 37, - - /// - /// Unable to execute command while occupied. - /// - Occupied38 = 38, - - /// - /// Unable to execute command while occupied. - /// - Occupied39 = 39, - - /// - /// Unable to execute command while crafting. - /// - Crafting40 = 40, - - /// - /// Unable to execute command while preparing to craft. - /// - PreparingToCraft = 41, - - /// - /// Unable to execute command while gathering. - /// - Gathering42 = 42, - - /// - /// Unable to execute command while fishing. - /// - Fishing = 43, - - // Unknown44 = 44, - - /// - /// Unable to execute command while between areas. - /// - BetweenAreas = 45, - - /// - /// Unable to execute command while stealthed. - /// - Stealthed = 46, - - // Unknown47 = 47, - - /// - /// Unable to execute command while jumping. - /// - Jumping = 48, - - /// - /// Unable to execute command while auto-run is active. - /// - AutorunActive = 49, - - /// - /// Unable to execute command while occupied. - /// - // todo: used for other shits? - OccupiedSummoningBell = 50, - - /// - /// Unable to execute command while between areas. - /// - BetweenAreas51 = 51, - - /// - /// Unable to execute command due to system error. - /// - SystemError = 52, - - /// - /// Unable to execute command while logging out. - /// - LoggingOut = 53, - - /// - /// Unable to execute command at this location. - /// - ConditionLocation = 54, - - /// - /// Unable to execute command while waiting for duty. - /// - WaitingForDuty = 55, - - /// - /// Unable to execute command while bound by duty. - /// - BoundByDuty56 = 56, - - /// - /// Unable to execute command at this time. - /// - Unknown57 = 57, - - /// - /// Unable to execute command while watching a cutscene. - /// - WatchingCutscene = 58, - - /// - /// Unable to execute command while waiting for Duty Finder. - /// - WaitingForDutyFinder = 59, - - /// - /// Unable to execute command while creating a character. - /// - CreatingCharacter = 60, - - /// - /// Unable to execute command while jumping. - /// - Jumping61 = 61, - - /// - /// Unable to execute command while the PvP display is active. - /// - PvPDisplayActive = 62, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction63 = 63, - - /// - /// Unable to execute command while mounting. - /// - Mounting = 64, - - /// - /// Unable to execute command while carrying an item. - /// - CarryingItem = 65, - - /// - /// Unable to execute command while using the Party Finder. - /// - UsingPartyFinder = 66, - - /// - /// Unable to execute command while using housing functions. - /// - UsingHousingFunctions = 67, - - /// - /// Unable to execute command while transformed. - /// - Transformed = 68, - - /// - /// Unable to execute command while on the free trial. - /// - OnFreeTrial = 69, - - /// - /// Unable to execute command while being moved. - /// - BeingMoved = 70, - - /// - /// Unable to execute command while mounting. - /// - Mounting71 = 71, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction72 = 72, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction73 = 73, - - /// - /// Unable to execute command while registering for a race or match. - /// - RegisteringForRaceOrMatch = 74, - - /// - /// Unable to execute command while waiting for a race or match. - /// - WaitingForRaceOrMatch = 75, - - /// - /// Unable to execute command while waiting for a Triple Triad match. - /// - WaitingForTripleTriadMatch = 76, - - /// - /// Unable to execute command while in flight. - /// - InFlight = 77, - - /// - /// Unable to execute command while watching a cutscene. - /// - WatchingCutscene78 = 78, - - /// - /// Unable to execute command while delving into a deep dungeon. - /// - InDeepDungeon = 79, - - /// - /// Unable to execute command while swimming. - /// - Swimming = 80, - - /// - /// Unable to execute command while diving. - /// - Diving = 81, - - /// - /// Unable to execute command while registering for a Triple Triad match. - /// - RegisteringForTripleTriadMatch = 82, - - /// - /// Unable to execute command while waiting for a Triple Triad match. - /// - WaitingForTripleTriadMatch83 = 83, - - /// - /// Unable to execute command while participating in a cross-world party or alliance. - /// - ParticipatingInCrossWorldPartyOrAlliance = 84, - - // Unknown85 = 85, - - /// - /// Unable to execute command while playing duty record. - /// - DutyRecorderPlayback = 86, - - /// - /// Unable to execute command while casting. - /// - Casting87 = 87, - - /// - /// Unable to execute command in this state. - /// - InThisState88 = 88, - - /// - /// Unable to execute command in this state. - /// - InThisState89 = 89, - - /// - /// Unable to execute command while role-playing. - /// - RolePlaying = 90, - - /// - /// Unable to execute command while bound by duty. - /// - BoundToDuty97 = 91, - - /// - /// Unable to execute command while readying to visit another World. - /// - ReadyingVisitOtherWorld = 92, - - /// - /// Unable to execute command while waiting to visit another World. - /// - WaitingToVisitOtherWorld = 93, - - /// - /// Unable to execute command while using a parasol. - /// - UsingParasol = 94, - - /// - /// Unable to execute command while bound by duty. - /// - BoundByDuty95 = 95, - } + None = 0, + + /// + /// Unable to execute command under normal conditions. + /// + NormalConditions = 1, + + /// + /// Unable to execute command while unconscious. + /// + Unconscious = 2, + + /// + /// Unable to execute command during an emote. + /// + Emoting = 3, + + /// + /// Unable to execute command while mounted. + /// + Mounted = 4, + + /// + /// Unable to execute command while crafting. + /// + Crafting = 5, + + /// + /// Unable to execute command while gathering. + /// + Gathering = 6, + + /// + /// Unable to execute command while melding materia. + /// + MeldingMateria = 7, + + /// + /// Unable to execute command while operating a siege machine. + /// + OperatingSiegeMachine = 8, + + /// + /// Unable to execute command while carrying an object. + /// + CarryingObject = 9, + + /// + /// Unable to execute command while mounted. + /// + Mounted2 = 10, + + /// + /// Unable to execute command while in that position. + /// + InThatPosition = 11, + + /// + /// Unable to execute command while chocobo racing. + /// + ChocoboRacing = 12, + + /// + /// Unable to execute command while playing a mini-game. + /// + PlayingMiniGame = 13, + + /// + /// Unable to execute command while playing Lord of Verminion. + /// + PlayingLordOfVerminion = 14, + + /// + /// Unable to execute command while participating in a custom match. + /// + ParticipatingInCustomMatch = 15, + + /// + /// Unable to execute command while performing. + /// + Performing = 16, + + // Unknown17 = 17, + // Unknown18 = 18, + // Unknown19 = 19, + // Unknown20 = 20, + // Unknown21 = 21, + // Unknown22 = 22, + // Unknown23 = 23, + // Unknown24 = 24, + + /// + /// Unable to execute command while occupied. + /// + Occupied = 25, + + /// + /// Unable to execute command during combat. + /// + InCombat = 26, + + /// + /// Unable to execute command while casting. + /// + Casting = 27, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction = 28, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction2 = 29, + + /// + /// Unable to execute command while occupied. + /// + Occupied30 = 30, + + /// + /// Unable to execute command while occupied. + /// + // todo: not sure if this is used for other event states/??? + OccupiedInEvent = 31, + + /// + /// Unable to execute command while occupied. + /// + OccupiedInQuestEvent = 32, + + /// + /// Unable to execute command while occupied. + /// + Occupied33 = 33, + + /// + /// Unable to execute command while bound by duty. + /// + BoundByDuty = 34, + + /// + /// Unable to execute command while occupied. + /// + OccupiedInCutSceneEvent = 35, + + /// + /// Unable to execute command while in a dueling area. + /// + InDuelingArea = 36, + + /// + /// Unable to execute command while a trade is open. + /// + TradeOpen = 37, + + /// + /// Unable to execute command while occupied. + /// + Occupied38 = 38, + + /// + /// Unable to execute command while occupied. + /// + Occupied39 = 39, + + /// + /// Unable to execute command while crafting. + /// + Crafting40 = 40, + + /// + /// Unable to execute command while preparing to craft. + /// + PreparingToCraft = 41, + + /// + /// Unable to execute command while gathering. + /// + Gathering42 = 42, + + /// + /// Unable to execute command while fishing. + /// + Fishing = 43, + + // Unknown44 = 44, + + /// + /// Unable to execute command while between areas. + /// + BetweenAreas = 45, + + /// + /// Unable to execute command while stealthed. + /// + Stealthed = 46, + + // Unknown47 = 47, + + /// + /// Unable to execute command while jumping. + /// + Jumping = 48, + + /// + /// Unable to execute command while auto-run is active. + /// + AutorunActive = 49, + + /// + /// Unable to execute command while occupied. + /// + // todo: used for other shits? + OccupiedSummoningBell = 50, + + /// + /// Unable to execute command while between areas. + /// + BetweenAreas51 = 51, + + /// + /// Unable to execute command due to system error. + /// + SystemError = 52, + + /// + /// Unable to execute command while logging out. + /// + LoggingOut = 53, + + /// + /// Unable to execute command at this location. + /// + ConditionLocation = 54, + + /// + /// Unable to execute command while waiting for duty. + /// + WaitingForDuty = 55, + + /// + /// Unable to execute command while bound by duty. + /// + BoundByDuty56 = 56, + + /// + /// Unable to execute command at this time. + /// + Unknown57 = 57, + + /// + /// Unable to execute command while watching a cutscene. + /// + WatchingCutscene = 58, + + /// + /// Unable to execute command while waiting for Duty Finder. + /// + WaitingForDutyFinder = 59, + + /// + /// Unable to execute command while creating a character. + /// + CreatingCharacter = 60, + + /// + /// Unable to execute command while jumping. + /// + Jumping61 = 61, + + /// + /// Unable to execute command while the PvP display is active. + /// + PvPDisplayActive = 62, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction63 = 63, + + /// + /// Unable to execute command while mounting. + /// + Mounting = 64, + + /// + /// Unable to execute command while carrying an item. + /// + CarryingItem = 65, + + /// + /// Unable to execute command while using the Party Finder. + /// + UsingPartyFinder = 66, + + /// + /// Unable to execute command while using housing functions. + /// + UsingHousingFunctions = 67, + + /// + /// Unable to execute command while transformed. + /// + Transformed = 68, + + /// + /// Unable to execute command while on the free trial. + /// + OnFreeTrial = 69, + + /// + /// Unable to execute command while being moved. + /// + BeingMoved = 70, + + /// + /// Unable to execute command while mounting. + /// + Mounting71 = 71, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction72 = 72, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction73 = 73, + + /// + /// Unable to execute command while registering for a race or match. + /// + RegisteringForRaceOrMatch = 74, + + /// + /// Unable to execute command while waiting for a race or match. + /// + WaitingForRaceOrMatch = 75, + + /// + /// Unable to execute command while waiting for a Triple Triad match. + /// + WaitingForTripleTriadMatch = 76, + + /// + /// Unable to execute command while in flight. + /// + InFlight = 77, + + /// + /// Unable to execute command while watching a cutscene. + /// + WatchingCutscene78 = 78, + + /// + /// Unable to execute command while delving into a deep dungeon. + /// + InDeepDungeon = 79, + + /// + /// Unable to execute command while swimming. + /// + Swimming = 80, + + /// + /// Unable to execute command while diving. + /// + Diving = 81, + + /// + /// Unable to execute command while registering for a Triple Triad match. + /// + RegisteringForTripleTriadMatch = 82, + + /// + /// Unable to execute command while waiting for a Triple Triad match. + /// + WaitingForTripleTriadMatch83 = 83, + + /// + /// Unable to execute command while participating in a cross-world party or alliance. + /// + ParticipatingInCrossWorldPartyOrAlliance = 84, + + // Unknown85 = 85, + + /// + /// Unable to execute command while playing duty record. + /// + DutyRecorderPlayback = 86, + + /// + /// Unable to execute command while casting. + /// + Casting87 = 87, + + /// + /// Unable to execute command in this state. + /// + InThisState88 = 88, + + /// + /// Unable to execute command in this state. + /// + InThisState89 = 89, + + /// + /// Unable to execute command while role-playing. + /// + RolePlaying = 90, + + /// + /// Unable to execute command while bound by duty. + /// + BoundToDuty97 = 91, + + /// + /// Unable to execute command while readying to visit another World. + /// + ReadyingVisitOtherWorld = 92, + + /// + /// Unable to execute command while waiting to visit another World. + /// + WaitingToVisitOtherWorld = 93, + + /// + /// Unable to execute command while using a parasol. + /// + UsingParasol = 94, + + /// + /// Unable to execute command while bound by duty. + /// + BoundByDuty95 = 95, } diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 5f9a4cb95..560b32e60 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -6,131 +6,130 @@ using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -namespace Dalamud.Game.ClientState.Fates +namespace Dalamud.Game.ClientState.Fates; + +/// +/// This class represents an FFXIV Fate. +/// +public unsafe partial class Fate : IEquatable { /// - /// This class represents an FFXIV Fate. + /// Initializes a new instance of the class. /// - public unsafe partial class Fate : IEquatable + /// The address of this fate in memory. + internal Fate(IntPtr address) { - /// - /// Initializes a new instance of the class. - /// - /// The address of this fate in memory. - internal Fate(IntPtr address) - { - this.Address = address; - } - - /// - /// Gets the address of this Fate in memory. - /// - public IntPtr Address { get; } - - private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address; - - public static bool operator ==(Fate fate1, Fate fate2) - { - if (fate1 is null || fate2 is null) - return Equals(fate1, fate2); - - return fate1.Equals(fate2); - } - - public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2); - - /// - /// Gets a value indicating whether this Fate is still valid in memory. - /// - /// The fate to check. - /// True or false. - public static bool IsValid(Fate fate) - { - var clientState = Service.Get(); - - if (fate == null) - return false; - - if (clientState.LocalContentId == 0) - return false; - - return true; - } - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// True or false. - public bool IsValid() => IsValid(this); - - /// - bool IEquatable.Equals(Fate other) => this.FateId == other?.FateId; - - /// - public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as Fate); - - /// - public override int GetHashCode() => this.FateId.GetHashCode(); + this.Address = address; } /// - /// This class represents an FFXIV Fate. + /// Gets the address of this Fate in memory. /// - public unsafe partial class Fate + public IntPtr Address { get; } + + private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address; + + public static bool operator ==(Fate fate1, Fate fate2) { - /// - /// Gets the Fate ID of this . - /// - public ushort FateId => this.Struct->FateId; + if (fate1 is null || fate2 is null) + return Equals(fate1, fate2); - /// - /// Gets game data linked to this Fate. - /// - public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId); - - /// - /// Gets the time this started. - /// - public int StartTimeEpoch => this.Struct->StartTimeEpoch; - - /// - /// Gets how long this will run. - /// - public short Duration => this.Struct->Duration; - - /// - /// Gets the remaining time in seconds for this . - /// - public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); - - /// - /// Gets the displayname of this . - /// - public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); - - /// - /// Gets the state of this (Running, Ended, Failed, Preparation, WaitingForEnd). - /// - public FateState State => (FateState)this.Struct->State; - - /// - /// Gets the progress amount of this . - /// - public byte Progress => this.Struct->Progress; - - /// - /// Gets the level of this . - /// - public byte Level => this.Struct->Level; - - /// - /// Gets the position of this . - /// - public Vector3 Position => this.Struct->Location; - - /// - /// Gets the territory this is located in. - /// - public ExcelResolver TerritoryType => new(this.Struct->TerritoryID); + return fate1.Equals(fate2); } + + public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2); + + /// + /// Gets a value indicating whether this Fate is still valid in memory. + /// + /// The fate to check. + /// True or false. + public static bool IsValid(Fate fate) + { + var clientState = Service.Get(); + + if (fate == null) + return false; + + if (clientState.LocalContentId == 0) + return false; + + return true; + } + + /// + /// Gets a value indicating whether this actor is still valid in memory. + /// + /// True or false. + public bool IsValid() => IsValid(this); + + /// + bool IEquatable.Equals(Fate other) => this.FateId == other?.FateId; + + /// + public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as Fate); + + /// + public override int GetHashCode() => this.FateId.GetHashCode(); +} + +/// +/// This class represents an FFXIV Fate. +/// +public unsafe partial class Fate +{ + /// + /// Gets the Fate ID of this . + /// + public ushort FateId => this.Struct->FateId; + + /// + /// Gets game data linked to this Fate. + /// + public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId); + + /// + /// Gets the time this started. + /// + public int StartTimeEpoch => this.Struct->StartTimeEpoch; + + /// + /// Gets how long this will run. + /// + public short Duration => this.Struct->Duration; + + /// + /// Gets the remaining time in seconds for this . + /// + public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); + + /// + /// Gets the displayname of this . + /// + public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); + + /// + /// Gets the state of this (Running, Ended, Failed, Preparation, WaitingForEnd). + /// + public FateState State => (FateState)this.Struct->State; + + /// + /// Gets the progress amount of this . + /// + public byte Progress => this.Struct->Progress; + + /// + /// Gets the level of this . + /// + public byte Level => this.Struct->Level; + + /// + /// Gets the position of this . + /// + public Vector3 Position => this.Struct->Location; + + /// + /// Gets the territory this is located in. + /// + public ExcelResolver TerritoryType => new(this.Struct->TerritoryID); } diff --git a/Dalamud/Game/ClientState/Fates/FateState.cs b/Dalamud/Game/ClientState/Fates/FateState.cs index c7a789231..8f2ef85cc 100644 --- a/Dalamud/Game/ClientState/Fates/FateState.cs +++ b/Dalamud/Game/ClientState/Fates/FateState.cs @@ -1,33 +1,32 @@ -namespace Dalamud.Game.ClientState.Fates +namespace Dalamud.Game.ClientState.Fates; + +/// +/// This represents the state of a single Fate. +/// +public enum FateState : byte { /// - /// This represents the state of a single Fate. + /// The Fate is active. /// - public enum FateState : byte - { - /// - /// The Fate is active. - /// - Running = 0x02, + Running = 0x02, - /// - /// The Fate has ended. - /// - Ended = 0x04, + /// + /// The Fate has ended. + /// + Ended = 0x04, - /// - /// The player failed the Fate. - /// - Failed = 0x05, + /// + /// The player failed the Fate. + /// + Failed = 0x05, - /// - /// The Fate is preparing to run. - /// - Preparation = 0x07, + /// + /// The Fate is preparing to run. + /// + Preparation = 0x07, - /// - /// The Fate is preparing to end. - /// - WaitingForEnd = 0x08, - } + /// + /// The Fate is preparing to end. + /// + WaitingForEnd = 0x08, } diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index ff0361db9..96d8339fa 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -6,143 +6,142 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Fates +namespace Dalamud.Game.ClientState.Fates; + +/// +/// This collection represents the currently available Fate events. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed partial class FateTable { + private readonly ClientStateAddressResolver address; + /// - /// This collection represents the currently available Fate events. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed partial class FateTable + /// Client state address resolver. + internal FateTable(ClientStateAddressResolver addressResolver) { - private readonly ClientStateAddressResolver address; + this.address = addressResolver; - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal FateTable(ClientStateAddressResolver addressResolver) + Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); + } + + /// + /// Gets the address of the Fate table. + /// + public IntPtr Address => this.address.FateTablePtr; + + /// + /// Gets the amount of currently active Fates. + /// + public unsafe int Length + { + get { - this.address = addressResolver; - - Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); - } - - /// - /// Gets the address of the Fate table. - /// - public IntPtr Address => this.address.FateTablePtr; - - /// - /// Gets the amount of currently active Fates. - /// - public unsafe int Length - { - get - { - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) - return 0; - - // Sonar used this to check if the table was safe to read - var check = Struct->Unk80.ToInt64(); - if (check == 0) - return 0; - - var start = Struct->FirstFatePtr.ToInt64(); - var end = Struct->LastFatePtr.ToInt64(); - if (start == 0 || end == 0) - return 0; - - return (int)((end - start) / 8); - } - } - - /// - /// Gets the address of the Fate table. - /// - internal unsafe IntPtr FateTableAddress - { - get - { - if (this.address.FateTablePtr == IntPtr.Zero) - return IntPtr.Zero; - - return *(IntPtr*)this.address.FateTablePtr; - } - } - - private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; - - /// - /// Get an actor at the specified spawn index. - /// - /// Spawn index. - /// A at the specified spawn index. - public Fate? this[int index] - { - get - { - var address = this.GetFateAddress(index); - return this.CreateFateReference(address); - } - } - - /// - /// Gets the address of the Fate at the specified index of the fate table. - /// - /// The index of the Fate. - /// The memory address of the Fate. - public unsafe IntPtr GetFateAddress(int index) - { - if (index >= this.Length) - return IntPtr.Zero; - var fateTable = this.FateTableAddress; if (fateTable == IntPtr.Zero) - return IntPtr.Zero; + return 0; - var firstFate = this.Struct->FirstFatePtr; - return *(IntPtr*)(firstFate + (8 * index)); - } + // Sonar used this to check if the table was safe to read + var check = Struct->Unk80.ToInt64(); + if (check == 0) + return 0; - /// - /// Create a reference to a FFXIV actor. - /// - /// The offset of the actor in memory. - /// object containing requested data. - public Fate? CreateFateReference(IntPtr offset) - { - var clientState = Service.Get(); + var start = Struct->FirstFatePtr.ToInt64(); + var end = Struct->LastFatePtr.ToInt64(); + if (start == 0 || end == 0) + return 0; - if (clientState.LocalContentId == 0) - return null; - - if (offset == IntPtr.Zero) - return null; - - return new Fate(offset); + return (int)((end - start) / 8); } } /// - /// This collection represents the currently available Fate events. + /// Gets the address of the Fate table. /// - public sealed partial class FateTable : IReadOnlyCollection + internal unsafe IntPtr FateTableAddress { - /// - int IReadOnlyCollection.Count => this.Length; - - /// - public IEnumerator GetEnumerator() + get { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } - } + if (this.address.FateTablePtr == IntPtr.Zero) + return IntPtr.Zero; - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + return *(IntPtr*)this.address.FateTablePtr; + } + } + + private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; + + /// + /// Get an actor at the specified spawn index. + /// + /// Spawn index. + /// A at the specified spawn index. + public Fate? this[int index] + { + get + { + var address = this.GetFateAddress(index); + return this.CreateFateReference(address); + } + } + + /// + /// Gets the address of the Fate at the specified index of the fate table. + /// + /// The index of the Fate. + /// The memory address of the Fate. + public unsafe IntPtr GetFateAddress(int index) + { + if (index >= this.Length) + return IntPtr.Zero; + + var fateTable = this.FateTableAddress; + if (fateTable == IntPtr.Zero) + return IntPtr.Zero; + + var firstFate = this.Struct->FirstFatePtr; + return *(IntPtr*)(firstFate + (8 * index)); + } + + /// + /// Create a reference to a FFXIV actor. + /// + /// The offset of the actor in memory. + /// object containing requested data. + public Fate? CreateFateReference(IntPtr offset) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (offset == IntPtr.Zero) + return null; + + return new Fate(offset); } } + +/// +/// This collection represents the currently available Fate events. +/// +public sealed partial class FateTable : IReadOnlyCollection +{ + /// + int IReadOnlyCollection.Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < this.Length; i++) + { + yield return this[i]; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +} diff --git a/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs b/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs index 7813803c8..a73f72857 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs @@ -1,96 +1,95 @@ using System; -namespace Dalamud.Game.ClientState.GamePad +namespace Dalamud.Game.ClientState.GamePad; + +/// +/// Bitmask of the Button ushort used by the game. +/// +[Flags] +public enum GamepadButtons : ushort { /// - /// Bitmask of the Button ushort used by the game. + /// No buttons pressed. /// - [Flags] - public enum GamepadButtons : ushort - { - /// - /// No buttons pressed. - /// - None = 0, + None = 0, - /// - /// Digipad up. - /// - DpadUp = 0x0001, + /// + /// Digipad up. + /// + DpadUp = 0x0001, - /// - /// Digipad down. - /// - DpadDown = 0x0002, + /// + /// Digipad down. + /// + DpadDown = 0x0002, - /// - /// Digipad left. - /// - DpadLeft = 0x0004, + /// + /// Digipad left. + /// + DpadLeft = 0x0004, - /// - /// Digipad right. - /// - DpadRight = 0x0008, + /// + /// Digipad right. + /// + DpadRight = 0x0008, - /// - /// North action button. Triangle on PS, Y on Xbox. - /// - North = 0x0010, + /// + /// North action button. Triangle on PS, Y on Xbox. + /// + North = 0x0010, - /// - /// South action button. Cross on PS, A on Xbox. - /// - South = 0x0020, + /// + /// South action button. Cross on PS, A on Xbox. + /// + South = 0x0020, - /// - /// West action button. Square on PS, X on Xbos. - /// - West = 0x0040, + /// + /// West action button. Square on PS, X on Xbos. + /// + West = 0x0040, - /// - /// East action button. Circle on PS, B on Xbox. - /// - East = 0x0080, + /// + /// East action button. Circle on PS, B on Xbox. + /// + East = 0x0080, - /// - /// First button on left shoulder side. - /// - L1 = 0x0100, + /// + /// First button on left shoulder side. + /// + L1 = 0x0100, - /// - /// Second button on left shoulder side. Analog input lost in this bitmask. - /// - L2 = 0x0200, + /// + /// Second button on left shoulder side. Analog input lost in this bitmask. + /// + L2 = 0x0200, - /// - /// Press on left analogue stick. - /// - L3 = 0x0400, + /// + /// Press on left analogue stick. + /// + L3 = 0x0400, - /// - /// First button on right shoulder. - /// - R1 = 0x0800, + /// + /// First button on right shoulder. + /// + R1 = 0x0800, - /// - /// Second button on right shoulder. Analog input lost in this bitmask. - /// - R2 = 0x1000, + /// + /// Second button on right shoulder. Analog input lost in this bitmask. + /// + R2 = 0x1000, - /// - /// Press on right analogue stick. - /// - R3 = 0x2000, + /// + /// Press on right analogue stick. + /// + R3 = 0x2000, - /// - /// Button on the right inner side of the controller. Options on PS, Start on Xbox. - /// - Start = 0x8000, + /// + /// Button on the right inner side of the controller. Options on PS, Start on Xbox. + /// + Start = 0x8000, - /// - /// Button on the left inner side of the controller. ??? on PS, Back on Xbox. - /// - Select = 0x4000, - } + /// + /// Button on the left inner side of the controller. ??? on PS, Back on Xbox. + /// + Select = 0x4000, } diff --git a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs index d6d46a0cc..32439cd08 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs @@ -1,76 +1,75 @@ using System.Runtime.InteropServices; -namespace Dalamud.Game.ClientState.GamePad +namespace Dalamud.Game.ClientState.GamePad; + +/// +/// Struct which gets populated by polling the gamepads. +/// +/// Has an array of gamepads, among many other things (here not mapped). +/// All we really care about is the final data which the game uses to determine input. +/// +/// The size is definitely bigger than only the following fields but I do not know how big. +/// +[StructLayout(LayoutKind.Explicit)] +public struct GamepadInput { /// - /// Struct which gets populated by polling the gamepads. - /// - /// Has an array of gamepads, among many other things (here not mapped). - /// All we really care about is the final data which the game uses to determine input. - /// - /// The size is definitely bigger than only the following fields but I do not know how big. + /// Left analogue stick's horizontal value, -99 for left, 99 for right. /// - [StructLayout(LayoutKind.Explicit)] - public struct GamepadInput - { - /// - /// Left analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x88)] - public int LeftStickX; + [FieldOffset(0x88)] + public int LeftStickX; - /// - /// Left analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x8C)] - public int LeftStickY; + /// + /// Left analogue stick's vertical value, -99 for down, 99 for up. + /// + [FieldOffset(0x8C)] + public int LeftStickY; - /// - /// Right analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x90)] - public int RightStickX; + /// + /// Right analogue stick's horizontal value, -99 for left, 99 for right. + /// + [FieldOffset(0x90)] + public int RightStickX; - /// - /// Right analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x94)] - public int RightStickY; + /// + /// Right analogue stick's vertical value, -99 for down, 99 for up. + /// + [FieldOffset(0x94)] + public int RightStickY; - /// - /// Raw input, set the whole time while a button is held. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x98)] - public ushort ButtonsRaw; + /// + /// Raw input, set the whole time while a button is held. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0x98)] + public ushort ButtonsRaw; - /// - /// Button pressed, set once when the button is pressed. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x9C)] - public ushort ButtonsPressed; + /// + /// Button pressed, set once when the button is pressed. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0x9C)] + public ushort ButtonsPressed; - /// - /// Button released input, set once right after the button is not hold anymore. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0xA0)] - public ushort ButtonsReleased; + /// + /// Button released input, set once right after the button is not hold anymore. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0xA0)] + public ushort ButtonsReleased; - /// - /// Repeatedly emits the held button input in fixed intervals. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0xA4)] - public ushort ButtonsRepeat; - } + /// + /// Repeatedly emits the held button input in fixed intervals. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0xA4)] + public ushort ButtonsRepeat; } diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index 8759673b2..7a316d5af 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -4,258 +4,257 @@ using Dalamud.Hooking; using ImGuiNET; using Serilog; -namespace Dalamud.Game.ClientState.GamePad +namespace Dalamud.Game.ClientState.GamePad; + +/// +/// Exposes the game gamepad state to dalamud. +/// +/// Will block game's gamepad input if is set. +/// +public unsafe class GamepadState : IDisposable { + private readonly Hook gamepadPoll; + + private bool isDisposed; + + private int leftStickX; + private int leftStickY; + private int rightStickX; + private int rightStickY; + /// - /// Exposes the game gamepad state to dalamud. - /// - /// Will block game's gamepad input if is set. + /// Initializes a new instance of the class. /// - public unsafe class GamepadState : IDisposable + /// Resolver knowing the pointer to the GamepadPoll function. + public GamepadState(ClientStateAddressResolver resolver) { - private readonly Hook gamepadPoll; + Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); + this.gamepadPoll = new Hook(resolver.GamepadPoll, this.GamepadPollDetour); + } - private bool isDisposed; + /// + /// Finalizes an instance of the class. + /// + ~GamepadState() + { + this.Dispose(false); + } - private int leftStickX; - private int leftStickY; - private int rightStickX; - private int rightStickY; + private delegate int ControllerPoll(IntPtr controllerInput); - /// - /// Initializes a new instance of the class. - /// - /// Resolver knowing the pointer to the GamepadPoll function. - public GamepadState(ClientStateAddressResolver resolver) + /// + /// Gets the pointer to the current instance of the GamepadInput struct. + /// + public IntPtr GamepadInputAddress { get; private set; } + + /// + /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; + + /// + /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsPressed { get; private set; } + + /// + /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsRaw { get; private set; } + + /// + /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsReleased { get; private set; } + + /// + /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsRepeat { get; private set; } + + /// + /// Gets or sets a value indicating whether detour should block gamepad input for game. + /// + /// Ideally, we would use + /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0 + /// but this has a race condition during load with the detour which sets up ImGui + /// and throws if our detour gets called before the other. + /// + internal bool NavEnableGamepad { get; set; } + + /// + /// Gets whether has been pressed. + /// + /// Only true on first frame of the press. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. + /// + /// The button to check for. + /// 1 if pressed, 0 otherwise. + public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets whether is being pressed. + /// + /// True in intervals if button is held down. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. + /// + /// The button to check for. + /// 1 if still pressed during interval, 0 otherwise or in between intervals. + public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets whether has been released. + /// + /// Only true the frame after release. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. + /// + /// The button to check for. + /// 1 if released, 0 otherwise. + public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets the raw state of . + /// + /// Is set the entire time a button is pressed down. + /// + /// The button to check for. + /// 1 the whole time button is pressed, 0 otherwise. + public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; + + /// + /// Enables the hook of the GamepadPoll function. + /// + public void Enable() + { + this.gamepadPoll.Enable(); + } + + /// + /// Disposes this instance, alongside its hooks. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + private int GamepadPollDetour(IntPtr gamepadInput) + { + var original = this.gamepadPoll.Original(gamepadInput); + try { - Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); - this.gamepadPoll = new Hook(resolver.GamepadPoll, this.GamepadPollDetour); - } + this.GamepadInputAddress = gamepadInput; + var input = (GamepadInput*)gamepadInput; + this.leftStickX = input->LeftStickX; + this.leftStickY = input->LeftStickY; + this.rightStickX = input->RightStickX; + this.rightStickY = input->RightStickY; + this.ButtonsRaw = input->ButtonsRaw; + this.ButtonsPressed = input->ButtonsPressed; + this.ButtonsReleased = input->ButtonsReleased; + this.ButtonsRepeat = input->ButtonsRepeat; - /// - /// Finalizes an instance of the class. - /// - ~GamepadState() - { - this.Dispose(false); - } - - private delegate int ControllerPoll(IntPtr controllerInput); - - /// - /// Gets the pointer to the current instance of the GamepadInput struct. - /// - public IntPtr GamepadInputAddress { get; private set; } - - /// - /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; - - /// - /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; - - /// - /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; - - /// - /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; - - /// - /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsPressed { get; private set; } - - /// - /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsRaw { get; private set; } - - /// - /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsReleased { get; private set; } - - /// - /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsRepeat { get; private set; } - - /// - /// Gets or sets a value indicating whether detour should block gamepad input for game. - /// - /// Ideally, we would use - /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0 - /// but this has a race condition during load with the detour which sets up ImGui - /// and throws if our detour gets called before the other. - /// - internal bool NavEnableGamepad { get; set; } - - /// - /// Gets whether has been pressed. - /// - /// Only true on first frame of the press. - /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. - /// - /// The button to check for. - /// 1 if pressed, 0 otherwise. - public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; - - /// - /// Gets whether is being pressed. - /// - /// True in intervals if button is held down. - /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. - /// - /// The button to check for. - /// 1 if still pressed during interval, 0 otherwise or in between intervals. - public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; - - /// - /// Gets whether has been released. - /// - /// Only true the frame after release. - /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. - /// - /// The button to check for. - /// 1 if released, 0 otherwise. - public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; - - /// - /// Gets the raw state of . - /// - /// Is set the entire time a button is pressed down. - /// - /// The button to check for. - /// 1 the whole time button is pressed, 0 otherwise. - public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; - - /// - /// Enables the hook of the GamepadPoll function. - /// - public void Enable() - { - this.gamepadPoll.Enable(); - } - - /// - /// Disposes this instance, alongside its hooks. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - private int GamepadPollDetour(IntPtr gamepadInput) - { - var original = this.gamepadPoll.Original(gamepadInput); - try + if (this.NavEnableGamepad) { - this.GamepadInputAddress = gamepadInput; - var input = (GamepadInput*)gamepadInput; - this.leftStickX = input->LeftStickX; - this.leftStickY = input->LeftStickY; - this.rightStickX = input->RightStickX; - this.rightStickY = input->RightStickY; - this.ButtonsRaw = input->ButtonsRaw; - this.ButtonsPressed = input->ButtonsPressed; - this.ButtonsReleased = input->ButtonsReleased; - this.ButtonsRepeat = input->ButtonsRepeat; + input->LeftStickX = 0; + input->LeftStickY = 0; + input->RightStickX = 0; + input->RightStickY = 0; - if (this.NavEnableGamepad) - { - input->LeftStickX = 0; - input->LeftStickY = 0; - input->RightStickX = 0; - input->RightStickY = 0; - - // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` - // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). - // It does block, however, all input to the game. - // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 - // and the digipad (in some situations, but thankfully not in menus) functional. - // We can either: - // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or - // (b) ignore it as so far it seems only a 'visual' error - // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, - // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them - // because of the other blocked input) - // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on - // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. - // This is debatable. - // ImGui itself does not care either way as it uses the Raw values and does its own state handling. - const ushort deletionMask = (ushort)(~GamepadButtons.L2 - & ~GamepadButtons.R2 - & ~GamepadButtons.DpadDown - & ~GamepadButtons.DpadLeft - & ~GamepadButtons.DpadUp - & ~GamepadButtons.DpadRight); - input->ButtonsRaw &= deletionMask; - input->ButtonsPressed = 0; - input->ButtonsReleased = 0; - input->ButtonsRepeat = 0; - return 0; - } - - // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the - // original, zero or do the work adjusting the bits. - return original; - } - catch (Exception e) - { - Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!"); - - // NOTE (Chiv) Explicitly deactivate on error - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; - return original; - } - } - - private void Dispose(bool disposing) - { - if (this.isDisposed) return; - if (disposing) - { - this.gamepadPoll?.Disable(); - this.gamepadPoll?.Dispose(); + // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` + // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). + // It does block, however, all input to the game. + // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 + // and the digipad (in some situations, but thankfully not in menus) functional. + // We can either: + // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or + // (b) ignore it as so far it seems only a 'visual' error + // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, + // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them + // because of the other blocked input) + // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on + // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. + // This is debatable. + // ImGui itself does not care either way as it uses the Raw values and does its own state handling. + const ushort deletionMask = (ushort)(~GamepadButtons.L2 + & ~GamepadButtons.R2 + & ~GamepadButtons.DpadDown + & ~GamepadButtons.DpadLeft + & ~GamepadButtons.DpadUp + & ~GamepadButtons.DpadRight); + input->ButtonsRaw &= deletionMask; + input->ButtonsPressed = 0; + input->ButtonsReleased = 0; + input->ButtonsRepeat = 0; + return 0; } - this.isDisposed = true; + // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the + // original, zero or do the work adjusting the bits. + return original; + } + catch (Exception e) + { + Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!"); + + // NOTE (Chiv) Explicitly deactivate on error + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; + return original; } } + + private void Dispose(bool disposing) + { + if (this.isDisposed) return; + if (disposing) + { + this.gamepadPoll?.Disable(); + this.gamepadPoll?.Dispose(); + } + + this.isDisposed = true; + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs b/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs index 0c32755fd..37bc167f2 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs @@ -1,23 +1,22 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// DRG Blood of the Dragon state types. +/// +public enum BOTDState : byte { /// - /// DRG Blood of the Dragon state types. + /// Inactive type. /// - public enum BOTDState : byte - { - /// - /// Inactive type. - /// - NONE = 0, + NONE = 0, - /// - /// Blood of the Dragon is active. - /// - BOTD = 1, + /// + /// Blood of the Dragon is active. + /// + BOTD = 1, - /// - /// Life of the Dragon is active. - /// - LOTD = 2, - } + /// + /// Life of the Dragon is active. + /// + LOTD = 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs index 02e064b12..1b367a93e 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs @@ -1,53 +1,52 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// AST Arcanum (card) types. +/// +public enum CardType : byte { /// - /// AST Arcanum (card) types. + /// No card. /// - public enum CardType : byte - { - /// - /// No card. - /// - NONE = 0, + NONE = 0, - /// - /// The Balance card. - /// - BALANCE = 1, + /// + /// The Balance card. + /// + BALANCE = 1, - /// - /// The Bole card. - /// - BOLE = 2, + /// + /// The Bole card. + /// + BOLE = 2, - /// - /// The Arrow card. - /// - ARROW = 3, + /// + /// The Arrow card. + /// + ARROW = 3, - /// - /// The Spear card. - /// - SPEAR = 4, + /// + /// The Spear card. + /// + SPEAR = 4, - /// - /// The Ewer card. - /// - EWER = 5, + /// + /// The Ewer card. + /// + EWER = 5, - /// - /// The Spire card. - /// - SPIRE = 6, + /// + /// The Spire card. + /// + SPIRE = 6, - /// - /// The Lord of Crowns card. - /// - LORD = 0x70, + /// + /// The Lord of Crowns card. + /// + LORD = 0x70, - /// - /// The Lady of Crowns card. - /// - LADY = 0x80, - } + /// + /// The Lady of Crowns card. + /// + LADY = 0x80, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs index 9083aad8a..b674d11b8 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs @@ -1,18 +1,17 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// SCH Dismissed fairy types. +/// +public enum DismissedFairy : byte { /// - /// SCH Dismissed fairy types. + /// Dismissed fairy is Eos. /// - public enum DismissedFairy : byte - { - /// - /// Dismissed fairy is Eos. - /// - EOS = 6, + EOS = 6, - /// - /// Dismissed fairy is Selene. - /// - SELENE = 7, - } + /// + /// Dismissed fairy is Selene. + /// + SELENE = 7, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs index 71449645f..31ee6dac1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs @@ -1,23 +1,22 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// NIN Mudra types. +/// +public enum Mudras : byte { /// - /// NIN Mudra types. + /// Ten mudra. /// - public enum Mudras : byte - { - /// - /// Ten mudra. - /// - TEN = 1, + TEN = 1, - /// - /// Chi mudra. - /// - CHI = 2, + /// + /// Chi mudra. + /// + CHI = 2, - /// - /// Jin mudra. - /// - JIN = 3, - } + /// + /// Jin mudra. + /// + JIN = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs index 343cbfd8e..768810b56 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// SMN summoned pet glam types. +/// +public enum PetGlam : byte { /// - /// SMN summoned pet glam types. + /// No pet glam. /// - public enum PetGlam : byte - { - /// - /// No pet glam. - /// - NONE = 0, + NONE = 0, - /// - /// Emerald carbuncle pet glam. - /// - EMERALD = 1, + /// + /// Emerald carbuncle pet glam. + /// + EMERALD = 1, - /// - /// Topaz carbuncle pet glam. - /// - TOPAZ = 2, + /// + /// Topaz carbuncle pet glam. + /// + TOPAZ = 2, - /// - /// Ruby carbuncle pet glam. - /// - RUBY = 3, - } + /// + /// Ruby carbuncle pet glam. + /// + RUBY = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs index 04440dcdc..eacef91fc 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// AST Divination seal types. +/// +public enum SealType : byte { /// - /// AST Divination seal types. + /// No seal. /// - public enum SealType : byte - { - /// - /// No seal. - /// - NONE = 0, + NONE = 0, - /// - /// Sun seal. - /// - SUN = 1, + /// + /// Sun seal. + /// + SUN = 1, - /// - /// Moon seal. - /// - MOON = 2, + /// + /// Moon seal. + /// + MOON = 2, - /// - /// Celestial seal. - /// - CELESTIAL = 3, - } + /// + /// Celestial seal. + /// + CELESTIAL = 3, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs index de9e01fc4..bdd98b750 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs @@ -1,31 +1,30 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// Samurai Sen types. +/// +[Flags] +public enum Sen : byte { /// - /// Samurai Sen types. + /// No Sen. /// - [Flags] - public enum Sen : byte - { - /// - /// No Sen. - /// - NONE = 0, + NONE = 0, - /// - /// Setsu Sen type. - /// - SETSU = 1 << 0, + /// + /// Setsu Sen type. + /// + SETSU = 1 << 0, - /// - /// Getsu Sen type. - /// - GETSU = 1 << 1, + /// + /// Getsu Sen type. + /// + GETSU = 1 << 1, - /// - /// Ka Sen type. - /// - KA = 1 << 2, - } + /// + /// Ka Sen type. + /// + KA = 1 << 2, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs index 25ecf66cc..ce144741f 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// BRD Song types. +/// +public enum Song : byte { /// - /// BRD Song types. + /// No song is active type. /// - public enum Song : byte - { - /// - /// No song is active type. - /// - NONE = 0, + NONE = 0, - /// - /// Mage's Ballad type. - /// - MAGE = 5, + /// + /// Mage's Ballad type. + /// + MAGE = 5, - /// - /// Army's Paeon type. - /// - ARMY = 10, + /// + /// Army's Paeon type. + /// + ARMY = 10, - /// - /// The Wanderer's Minuet type. - /// - WANDERER = 15, - } + /// + /// The Wanderer's Minuet type. + /// + WANDERER = 15, } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs index d0b1e933f..e72521043 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums +namespace Dalamud.Game.ClientState.JobGauge.Enums; + +/// +/// SMN summoned pet types. +/// +public enum SummonPet : byte { /// - /// SMN summoned pet types. + /// No pet. /// - public enum SummonPet : byte - { - /// - /// No pet. - /// - NONE = 0, + NONE = 0, - /// - /// The summoned pet Ifrit. - /// - IFRIT = 3, + /// + /// The summoned pet Ifrit. + /// + IFRIT = 3, - /// - /// The summoned pet Titan. - /// - TITAN = 4, + /// + /// The summoned pet Titan. + /// + TITAN = 4, - /// - /// The summoned pet Garuda. - /// - GARUDA = 5, - } + /// + /// The summoned pet Garuda. + /// + GARUDA = 5, } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index 958f78b1b..9fbbd51ed 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -7,48 +7,47 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.JobGauge +namespace Dalamud.Game.ClientState.JobGauge; + +/// +/// This class converts in-memory Job gauge data to structs. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public class JobGauges { + private Dictionary cache = new(); + /// - /// This class converts in-memory Job gauge data to structs. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public class JobGauges + /// Address resolver with the JobGauge memory location(s). + public JobGauges(ClientStateAddressResolver addressResolver) { - private Dictionary cache = new(); + this.Address = addressResolver.JobGaugeData; - /// - /// Initializes a new instance of the class. - /// - /// Address resolver with the JobGauge memory location(s). - public JobGauges(ClientStateAddressResolver addressResolver) + Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); + } + + /// + /// Gets the address of the JobGauge data. + /// + public IntPtr Address { get; } + + /// + /// Get the JobGauge for a given job. + /// + /// A JobGauge struct from ClientState.Structs.JobGauge. + /// A JobGauge. + public T Get() where T : JobGaugeBase + { + // This is cached to mitigate the effects of using activator for instantiation. + // Since the gauge itself reads from live memory, there isn't much downside to doing this. + if (!this.cache.TryGetValue(typeof(T), out var gauge)) { - this.Address = addressResolver.JobGaugeData; - - Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); + gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null); } - /// - /// Gets the address of the JobGauge data. - /// - public IntPtr Address { get; } - - /// - /// Get the JobGauge for a given job. - /// - /// A JobGauge struct from ClientState.Structs.JobGauge. - /// A JobGauge. - public T Get() where T : JobGaugeBase - { - // This is cached to mitigate the effects of using activator for instantiation. - // Since the gauge itself reads from live memory, there isn't much downside to doing this. - if (!this.cache.TryGetValue(typeof(T), out var gauge)) - { - gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null); - } - - return (T)gauge; - } + return (T)gauge; } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs index 45175344f..b1fd996b8 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs @@ -2,39 +2,38 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory AST job gauge. +/// +public unsafe class ASTGauge : JobGaugeBase { /// - /// In-memory AST job gauge. + /// Initializes a new instance of the class. /// - public unsafe class ASTGauge : JobGaugeBase + /// Address of the job gauge. + internal ASTGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal ASTGauge(IntPtr address) - : base(address) - { - } + } - /// - /// Gets the currently drawn . - /// - /// Currently drawn . - public CardType DrawnCard => (CardType)this.Struct->Card; + /// + /// Gets the currently drawn . + /// + /// Currently drawn . + public CardType DrawnCard => (CardType)this.Struct->Card; - /// - /// Check if a is currently active on the divination gauge. - /// - /// The to check for. - /// If the given Seal is currently divined. - public unsafe bool ContainsSeal(SealType seal) - { - if (this.Struct->Seals[0] == (byte)seal) return true; - if (this.Struct->Seals[1] == (byte)seal) return true; - if (this.Struct->Seals[2] == (byte)seal) return true; - return false; - } + /// + /// Check if a is currently active on the divination gauge. + /// + /// The to check for. + /// If the given Seal is currently divined. + public unsafe bool ContainsSeal(SealType seal) + { + if (this.Struct->Seals[0] == (byte)seal) return true; + if (this.Struct->Seals[1] == (byte)seal) return true; + if (this.Struct->Seals[2] == (byte)seal) return true; + return false; } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs index 0e5516b7a..fef699e2b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs @@ -1,67 +1,66 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory BLM job gauge. +/// +public unsafe class BLMGauge : JobGaugeBase { /// - /// In-memory BLM job gauge. + /// Initializes a new instance of the class. /// - public unsafe class BLMGauge : JobGaugeBase + /// Address of the job gauge. + internal BLMGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal BLMGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the time remaining for the Enochian time in milliseconds. - /// - public short EnochianTimer => this.Struct->EnochianTimer; - - /// - /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds. - /// - public short ElementTimeRemaining => this.Struct->ElementTimeRemaining; - - /// - /// Gets the number of Polyglot stacks remaining. - /// - public byte PolyglotStacks => this.Struct->PolyglotStacks; - - /// - /// Gets the number of Umbral Hearts remaining. - /// - public byte UmbralHearts => this.Struct->UmbralHearts; - - /// - /// Gets the amount of Umbral Ice stacks. - /// - public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0); - - /// - /// Gets the amount of Astral Fire stacks. - /// - public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0); - - /// - /// Gets a value indicating whether if the player is in Umbral Ice. - /// - /// true or false. - public bool InUmbralIce => this.Struct->ElementStance < 0; - - /// - /// Gets a value indicating whether if the player is in Astral fire. - /// - /// true or false. - public bool InAstralFire => this.Struct->ElementStance > 0; - - /// - /// Gets a value indicating whether if Enochian is active. - /// - /// true or false. - public bool IsEnochianActive => this.Struct->Enochian != 0; } + + /// + /// Gets the time remaining for the Enochian time in milliseconds. + /// + public short EnochianTimer => this.Struct->EnochianTimer; + + /// + /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds. + /// + public short ElementTimeRemaining => this.Struct->ElementTimeRemaining; + + /// + /// Gets the number of Polyglot stacks remaining. + /// + public byte PolyglotStacks => this.Struct->PolyglotStacks; + + /// + /// Gets the number of Umbral Hearts remaining. + /// + public byte UmbralHearts => this.Struct->UmbralHearts; + + /// + /// Gets the amount of Umbral Ice stacks. + /// + public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0); + + /// + /// Gets the amount of Astral Fire stacks. + /// + public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0); + + /// + /// Gets a value indicating whether if the player is in Umbral Ice. + /// + /// true or false. + public bool InUmbralIce => this.Struct->ElementStance < 0; + + /// + /// Gets a value indicating whether if the player is in Astral fire. + /// + /// true or false. + public bool InAstralFire => this.Struct->ElementStance > 0; + + /// + /// Gets a value indicating whether if Enochian is active. + /// + /// true or false. + public bool IsEnochianActive => this.Struct->Enochian != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs index ed814d055..d361bfd4d 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs @@ -2,40 +2,39 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory BRD job gauge. +/// +public unsafe class BRDGauge : JobGaugeBase { /// - /// In-memory BRD job gauge. + /// Initializes a new instance of the class. /// - public unsafe class BRDGauge : JobGaugeBase + /// Address of the job gauge. + internal BRDGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal BRDGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the current song timer in milliseconds. - /// - public short SongTimer => this.Struct->SongTimer; - - /// - /// Gets the amount of Repertoire accumulated. - /// - public byte Repertoire => this.Struct->Repertoire; - - /// - /// Gets the amount of Soul Voice accumulated. - /// - public byte SoulVoice => this.Struct->SoulVoice; - - /// - /// Gets the type of song that is active. - /// - public Song Song => (Song)this.Struct->Song; } + + /// + /// Gets the current song timer in milliseconds. + /// + public short SongTimer => this.Struct->SongTimer; + + /// + /// Gets the amount of Repertoire accumulated. + /// + public byte Repertoire => this.Struct->Repertoire; + + /// + /// Gets the amount of Soul Voice accumulated. + /// + public byte SoulVoice => this.Struct->SoulVoice; + + /// + /// Gets the type of song that is active. + /// + public Song Song => (Song)this.Struct->Song; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs index 7ae2b7bca..455c5bca0 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs @@ -1,60 +1,59 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory DNC job gauge. +/// +public unsafe class DNCGauge : JobGaugeBase { /// - /// In-memory DNC job gauge. + /// Initializes a new instance of the class. /// - public unsafe class DNCGauge : JobGaugeBase + /// Address of the job gauge. + internal DNCGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal DNCGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the number of feathers available. - /// - public byte Feathers => this.Struct->Feathers; - - /// - /// Gets the amount of Espirit available. - /// - public byte Esprit => this.Struct->Esprit; - - /// - /// Gets the number of steps completed for the current dance. - /// - public byte CompletedSteps => this.Struct->StepIndex; - - /// - /// Gets all the steps in the current dance. - /// - public unsafe uint[] Steps - { - get - { - var arr = new uint[4]; - for (var i = 0; i < 4; i++) - arr[i] = this.Struct->DanceSteps[i] + 15999u - 1; - return arr; - } - } - - /// - /// Gets the next step in the current dance. - /// - /// The next dance step action ID. - public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1; - - /// - /// Gets a value indicating whether the player is dancing or not. - /// - /// true or false. - public bool IsDancing => this.Struct->DanceSteps[0] != 0; } + + /// + /// Gets the number of feathers available. + /// + public byte Feathers => this.Struct->Feathers; + + /// + /// Gets the amount of Espirit available. + /// + public byte Esprit => this.Struct->Esprit; + + /// + /// Gets the number of steps completed for the current dance. + /// + public byte CompletedSteps => this.Struct->StepIndex; + + /// + /// Gets all the steps in the current dance. + /// + public unsafe uint[] Steps + { + get + { + var arr = new uint[4]; + for (var i = 0; i < 4; i++) + arr[i] = this.Struct->DanceSteps[i] + 15999u - 1; + return arr; + } + } + + /// + /// Gets the next step in the current dance. + /// + /// The next dance step action ID. + public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1; + + /// + /// Gets a value indicating whether the player is dancing or not. + /// + /// true or false. + public bool IsDancing => this.Struct->DanceSteps[0] != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs index dd89b069a..20bd0c488 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs @@ -2,35 +2,34 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory DRG job gauge. +/// +public unsafe class DRGGauge : JobGaugeBase { /// - /// In-memory DRG job gauge. + /// Initializes a new instance of the class. /// - public unsafe class DRGGauge : JobGaugeBase + /// Address of the job gauge. + internal DRGGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal DRGGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the time remaining for Blood of the Dragon in milliseconds. - /// - public short BOTDTimer => this.Struct->BotdTimer; - - /// - /// Gets the current state of Blood of the Dragon. - /// - public BOTDState BOTDState => (BOTDState)this.Struct->BotdState; - - /// - /// Gets the count of eyes opened during Blood of the Dragon. - /// - public byte EyeCount => this.Struct->EyeCount; } + + /// + /// Gets the time remaining for Blood of the Dragon in milliseconds. + /// + public short BOTDTimer => this.Struct->BotdTimer; + + /// + /// Gets the current state of Blood of the Dragon. + /// + public BOTDState BOTDState => (BOTDState)this.Struct->BotdState; + + /// + /// Gets the count of eyes opened during Blood of the Dragon. + /// + public byte EyeCount => this.Struct->EyeCount; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs index d903c0f68..25a245ac6 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs @@ -1,40 +1,39 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory DRK job gauge. +/// +public unsafe class DRKGauge : JobGaugeBase { /// - /// In-memory DRK job gauge. + /// Initializes a new instance of the class. /// - public unsafe class DRKGauge : JobGaugeBase + /// Address of the job gauge. + internal DRKGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal DRKGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the amount of blood accumulated. - /// - public byte Blood => this.Struct->Blood; - - /// - /// Gets the Darkside time remaining in milliseconds. - /// - public ushort DarksideTimeRemaining => this.Struct->DarksideTimer; - - /// - /// Gets the Shadow time remaining in milliseconds. - /// - public ushort ShadowTimeRemaining => this.Struct->ShadowTimer; - - /// - /// Gets a value indicating whether the player has Dark Arts or not. - /// - /// true or false. - public bool HasDarkArts => this.Struct->DarkArtsState > 0; } + + /// + /// Gets the amount of blood accumulated. + /// + public byte Blood => this.Struct->Blood; + + /// + /// Gets the Darkside time remaining in milliseconds. + /// + public ushort DarksideTimeRemaining => this.Struct->DarksideTimer; + + /// + /// Gets the Shadow time remaining in milliseconds. + /// + public ushort ShadowTimeRemaining => this.Struct->ShadowTimer; + + /// + /// Gets a value indicating whether the player has Dark Arts or not. + /// + /// true or false. + public bool HasDarkArts => this.Struct->DarkArtsState > 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs index dc934d8f4..4acc9a712 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs @@ -1,34 +1,33 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory GNB job gauge. +/// +public unsafe class GNBGauge : JobGaugeBase { /// - /// In-memory GNB job gauge. + /// Initializes a new instance of the class. /// - public unsafe class GNBGauge : JobGaugeBase + /// Address of the job gauge. + internal GNBGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal GNBGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the amount of ammo available. - /// - public byte Ammo => this.Struct->Ammo; - - /// - /// Gets the max combo time of the Gnashing Fang combo. - /// - public short MaxTimerDuration => this.Struct->MaxTimerDuration; - - /// - /// Gets the current step of the Gnashing Fang combo. - /// - public byte AmmoComboStep => this.Struct->AmmoComboStep; } + + /// + /// Gets the amount of ammo available. + /// + public byte Ammo => this.Struct->Ammo; + + /// + /// Gets the max combo time of the Gnashing Fang combo. + /// + public short MaxTimerDuration => this.Struct->MaxTimerDuration; + + /// + /// Gets the current step of the Gnashing Fang combo. + /// + public byte AmmoComboStep => this.Struct->AmmoComboStep; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs index 0160a9a87..09280cf7c 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs @@ -1,24 +1,23 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// Base job gauge class. +/// +public abstract unsafe class JobGaugeBase { /// - /// Base job gauge class. + /// Initializes a new instance of the class. /// - public abstract unsafe class JobGaugeBase + /// Address of the job gauge. + internal JobGaugeBase(IntPtr address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal JobGaugeBase(IntPtr address) - { - this.Address = address; - } - - /// - /// Gets the address of this job gauge in memory. - /// - public IntPtr Address { get; } + this.Address = address; } + + /// + /// Gets the address of this job gauge in memory. + /// + public IntPtr Address { get; } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs index 0dc494d8b..dbe107d2c 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs @@ -1,25 +1,24 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// Base job gauge class. +/// +/// The underlying FFXIVClientStructs type. +public unsafe class JobGaugeBase : JobGaugeBase where T : unmanaged { /// - /// Base job gauge class. + /// Initializes a new instance of the class. /// - /// The underlying FFXIVClientStructs type. - public unsafe class JobGaugeBase : JobGaugeBase where T : unmanaged + /// Address of the job gauge. + internal JobGaugeBase(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal JobGaugeBase(IntPtr address) - : base(address) - { - } - - /// - /// Gets an unsafe struct pointer of this job gauge. - /// - private protected T* Struct => (T*)this.Address; } + + /// + /// Gets an unsafe struct pointer of this job gauge. + /// + private protected T* Struct => (T*)this.Address; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs index 1971137a5..76e3b00c4 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs @@ -1,56 +1,55 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory MCH job gauge. +/// +public unsafe class MCHGauge : JobGaugeBase { /// - /// In-memory MCH job gauge. + /// Initializes a new instance of the class. /// - public unsafe class MCHGauge : JobGaugeBase + /// Address of the job gauge. + internal MCHGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal MCHGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the time time remaining for Overheat in milliseconds. - /// - public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining; - - /// - /// Gets the time remaining for the Rook or Queen in milliseconds. - /// - public short SummonTimeRemaining => this.Struct->SummonTimeRemaining; - - /// - /// Gets the current Heat level. - /// - public byte Heat => this.Struct->Heat; - - /// - /// Gets the current Battery level. - /// - public byte Battery => this.Struct->Battery; - - /// - /// Gets the battery level of the last summon (robot). - /// - public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower; - - /// - /// Gets a value indicating whether the player is currently Overheated. - /// - /// true or false. - public bool IsOverheated => (this.Struct->TimerActive & 1) != 0; - - /// - /// Gets a value indicating whether the player has an active Robot. - /// - /// true or false. - public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0; } + + /// + /// Gets the time time remaining for Overheat in milliseconds. + /// + public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining; + + /// + /// Gets the time remaining for the Rook or Queen in milliseconds. + /// + public short SummonTimeRemaining => this.Struct->SummonTimeRemaining; + + /// + /// Gets the current Heat level. + /// + public byte Heat => this.Struct->Heat; + + /// + /// Gets the current Battery level. + /// + public byte Battery => this.Struct->Battery; + + /// + /// Gets the battery level of the last summon (robot). + /// + public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower; + + /// + /// Gets a value indicating whether the player is currently Overheated. + /// + /// true or false. + public bool IsOverheated => (this.Struct->TimerActive & 1) != 0; + + /// + /// Gets a value indicating whether the player has an active Robot. + /// + /// true or false. + public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs index 8de421541..1a84afb74 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs @@ -1,24 +1,23 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory MNK job gauge. +/// +public unsafe class MNKGauge : JobGaugeBase { /// - /// In-memory MNK job gauge. + /// Initializes a new instance of the class. /// - public unsafe class MNKGauge : JobGaugeBase + /// Address of the job gauge. + internal MNKGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal MNKGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the number of Chakra available. - /// - public byte Chakra => this.Struct->Chakra; } + + /// + /// Gets the number of Chakra available. + /// + public byte Chakra => this.Struct->Chakra; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs index 40bf64d4a..4e8dc7b65 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs @@ -1,34 +1,33 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory NIN job gauge. +/// +public unsafe class NINGauge : JobGaugeBase { /// - /// In-memory NIN job gauge. + /// Initializes a new instance of the class. /// - public unsafe class NINGauge : JobGaugeBase + /// The address of the gauge. + internal NINGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// The address of the gauge. - internal NINGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the time left on Huton in milliseconds. - /// - public int HutonTimer => this.Struct->HutonTimer; - - /// - /// Gets the amount of Ninki available. - /// - public byte Ninki => this.Struct->Ninki; - - /// - /// Gets the number of times Huton has been cast manually. - /// - public byte HutonManualCasts => this.Struct->HutonManualCasts; } + + /// + /// Gets the time left on Huton in milliseconds. + /// + public int HutonTimer => this.Struct->HutonTimer; + + /// + /// Gets the amount of Ninki available. + /// + public byte Ninki => this.Struct->Ninki; + + /// + /// Gets the number of times Huton has been cast manually. + /// + public byte HutonManualCasts => this.Struct->HutonManualCasts; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs index d610515e1..8998200c2 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs @@ -1,24 +1,23 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory PLD job gauge. +/// +public unsafe class PLDGauge : JobGaugeBase { /// - /// In-memory PLD job gauge. + /// Initializes a new instance of the class. /// - public unsafe class PLDGauge : JobGaugeBase + /// Address of the job gauge. + internal PLDGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal PLDGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the current level of the Oath gauge. - /// - public byte OathGauge => this.Struct->OathGauge; } + + /// + /// Gets the current level of the Oath gauge. + /// + public byte OathGauge => this.Struct->OathGauge; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs index 63c8b6a20..43171a3d7 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs @@ -1,29 +1,28 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory RDM job gauge. +/// +public unsafe class RDMGauge : JobGaugeBase { /// - /// In-memory RDM job gauge. + /// Initializes a new instance of the class. /// - public unsafe class RDMGauge : JobGaugeBase + /// Address of the job gauge. + internal RDMGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal RDMGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the level of the White gauge. - /// - public byte WhiteMana => this.Struct->WhiteMana; - - /// - /// Gets the level of the Black gauge. - /// - public byte BlackMana => this.Struct->BlackMana; } + + /// + /// Gets the level of the White gauge. + /// + public byte WhiteMana => this.Struct->WhiteMana; + + /// + /// Gets the level of the Black gauge. + /// + public byte BlackMana => this.Struct->BlackMana; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs index 64559e4e3..a006086f4 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs @@ -2,53 +2,52 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory SAM job gauge. +/// +public unsafe class SAMGauge : JobGaugeBase { /// - /// In-memory SAM job gauge. + /// Initializes a new instance of the class. /// - public unsafe class SAMGauge : JobGaugeBase + /// Address of the job gauge. + internal SAMGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal SAMGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the current amount of Kenki available. - /// - public byte Kenki => this.Struct->Kenki; - - /// - /// Gets the amount of Meditation stacks. - /// - public byte MeditationStacks => this.Struct->MeditationStacks; - - /// - /// Gets the active Sen. - /// - public Sen Sen => (Sen)this.Struct->SenFlags; - - /// - /// Gets a value indicating whether the Setsu Sen is active. - /// - /// true or false. - public bool HasSetsu => (this.Sen & Sen.SETSU) != 0; - - /// - /// Gets a value indicating whether the Getsu Sen is active. - /// - /// true or false. - public bool HasGetsu => (this.Sen & Sen.GETSU) != 0; - - /// - /// Gets a value indicating whether the Ka Sen is active. - /// - /// true or false. - public bool HasKa => (this.Sen & Sen.KA) != 0; } + + /// + /// Gets the current amount of Kenki available. + /// + public byte Kenki => this.Struct->Kenki; + + /// + /// Gets the amount of Meditation stacks. + /// + public byte MeditationStacks => this.Struct->MeditationStacks; + + /// + /// Gets the active Sen. + /// + public Sen Sen => (Sen)this.Struct->SenFlags; + + /// + /// Gets a value indicating whether the Setsu Sen is active. + /// + /// true or false. + public bool HasSetsu => (this.Sen & Sen.SETSU) != 0; + + /// + /// Gets a value indicating whether the Getsu Sen is active. + /// + /// true or false. + public bool HasGetsu => (this.Sen & Sen.GETSU) != 0; + + /// + /// Gets a value indicating whether the Ka Sen is active. + /// + /// true or false. + public bool HasKa => (this.Sen & Sen.KA) != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs index f72e7047b..d3f5669fa 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs @@ -2,40 +2,39 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory SCH job gauge. +/// +public unsafe class SCHGauge : JobGaugeBase { /// - /// In-memory SCH job gauge. + /// Initializes a new instance of the class. /// - public unsafe class SCHGauge : JobGaugeBase + /// Address of the job gauge. + internal SCHGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal SCHGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the amount of Aetherflow stacks available. - /// - public byte Aetherflow => this.Struct->Aetherflow; - - /// - /// Gets the current level of the Fairy Gauge. - /// - public byte FairyGauge => this.Struct->FairyGauge; - - /// - /// Gets the Seraph time remaiSCHg in milliseconds. - /// - public short SeraphTimer => this.Struct->SeraphTimer; - - /// - /// Gets the last dismissed fairy. - /// - public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy; } + + /// + /// Gets the amount of Aetherflow stacks available. + /// + public byte Aetherflow => this.Struct->Aetherflow; + + /// + /// Gets the current level of the Fairy Gauge. + /// + public byte FairyGauge => this.Struct->FairyGauge; + + /// + /// Gets the Seraph time remaiSCHg in milliseconds. + /// + public short SeraphTimer => this.Struct->SeraphTimer; + + /// + /// Gets the last dismissed fairy. + /// + public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index 5074f0bc9..f0f8b33dc 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -2,59 +2,58 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory SMN job gauge. +/// +public unsafe class SMNGauge : JobGaugeBase { /// - /// In-memory SMN job gauge. + /// Initializes a new instance of the class. /// - public unsafe class SMNGauge : JobGaugeBase + /// Address of the job gauge. + internal SMNGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal SMNGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the time remaining for the current summon. - /// - public short TimerRemaining => this.Struct->TimerRemaining; - - /// - /// Gets the summon that will return after the current summon expires. - /// - public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; - - /// - /// Gets the summon glam for the . - /// - public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; - - /// - /// Gets the current aether flags. - /// Use the summon accessors instead. - /// - public byte AetherFlags => this.Struct->AetherFlags; - - /// - /// Gets a value indicating whether if Phoenix is ready to be summoned. - /// - /// true or false. - public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0; - - /// - /// Gets a value indicating whether Bahamut is ready to be summoned. - /// - /// true or false. - public bool IsBahamutReady => (this.AetherFlags & 8) > 0; - - /// - /// Gets a value indicating whether there are any Aetherflow stacks available. - /// - /// true or false. - public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0; } + + /// + /// Gets the time remaining for the current summon. + /// + public short TimerRemaining => this.Struct->TimerRemaining; + + /// + /// Gets the summon that will return after the current summon expires. + /// + public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; + + /// + /// Gets the summon glam for the . + /// + public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; + + /// + /// Gets the current aether flags. + /// Use the summon accessors instead. + /// + public byte AetherFlags => this.Struct->AetherFlags; + + /// + /// Gets a value indicating whether if Phoenix is ready to be summoned. + /// + /// true or false. + public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0; + + /// + /// Gets a value indicating whether Bahamut is ready to be summoned. + /// + /// true or false. + public bool IsBahamutReady => (this.AetherFlags & 8) > 0; + + /// + /// Gets a value indicating whether there are any Aetherflow stacks available. + /// + /// true or false. + public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs index 484ac83a8..2a50e9d24 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs @@ -1,24 +1,23 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory WAR job gauge. +/// +public unsafe class WARGauge : JobGaugeBase { /// - /// In-memory WAR job gauge. + /// Initializes a new instance of the class. /// - public unsafe class WARGauge : JobGaugeBase + /// Address of the job gauge. + internal WARGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal WARGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the amount of wrath in the Beast gauge. - /// - public byte BeastGauge => this.Struct->BeastGauge; } + + /// + /// Gets the amount of wrath in the Beast gauge. + /// + public byte BeastGauge => this.Struct->BeastGauge; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs index f3933a5aa..afe19f59d 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs @@ -1,34 +1,33 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types +namespace Dalamud.Game.ClientState.JobGauge.Types; + +/// +/// In-memory WHM job gauge. +/// +public unsafe class WHMGauge : JobGaugeBase { /// - /// In-memory WHM job gauge. + /// Initializes a new instance of the class. /// - public unsafe class WHMGauge : JobGaugeBase + /// Address of the job gauge. + internal WHMGauge(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the job gauge. - internal WHMGauge(IntPtr address) - : base(address) - { - } - - /// - /// Gets the time to next lily in milliseconds. - /// - public short LilyTimer => this.Struct->LilyTimer; - - /// - /// Gets the number of Lilies. - /// - public byte Lily => this.Struct->Lily; - - /// - /// Gets the number of times the blood lily has been nourished. - /// - public byte BloodLily => this.Struct->BloodLily; } + + /// + /// Gets the time to next lily in milliseconds. + /// + public short LilyTimer => this.Struct->LilyTimer; + + /// + /// Gets the number of Lilies. + /// + public byte Lily => this.Struct->Lily; + + /// + /// Gets the number of times the blood lily has been nourished. + /// + public byte BloodLily => this.Struct->BloodLily; } diff --git a/Dalamud/Game/ClientState/Keys/KeyState.cs b/Dalamud/Game/ClientState/Keys/KeyState.cs index 0476eee89..eb99397f6 100644 --- a/Dalamud/Game/ClientState/Keys/KeyState.cs +++ b/Dalamud/Game/ClientState/Keys/KeyState.cs @@ -6,155 +6,154 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Keys +namespace Dalamud.Game.ClientState.Keys; + +/// +/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode. +/// +/// +/// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting +/// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without +/// setting the statue manually. +/// index & 0 = key pressed. +/// index & 1 = key down (ephemeral). +/// index & 2 = key up (ephemeral). +/// index & 3 = short key press (ephemeral). +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public class KeyState { + // The array is accessed in a way that this limit doesn't appear to exist + // but there is other state data past this point, and keys beyond here aren't + // generally valid for most things anyway + private const int MaxKeyCode = 0xF0; + private readonly IntPtr bufferBase; + private readonly IntPtr indexBase; + private VirtualKey[] validVirtualKeyCache = null; + /// - /// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode. + /// Initializes a new instance of the class. /// - /// - /// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting - /// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without - /// setting the statue manually. - /// index & 0 = key pressed. - /// index & 1 = key down (ephemeral). - /// index & 2 = key up (ephemeral). - /// index & 3 = short key press (ephemeral). - /// - [PluginInterface] - [InterfaceVersion("1.0")] - public class KeyState + /// The ClientStateAddressResolver instance. + public KeyState(ClientStateAddressResolver addressResolver) { - // The array is accessed in a way that this limit doesn't appear to exist - // but there is other state data past this point, and keys beyond here aren't - // generally valid for most things anyway - private const int MaxKeyCode = 0xF0; - private readonly IntPtr bufferBase; - private readonly IntPtr indexBase; - private VirtualKey[] validVirtualKeyCache = null; + var moduleBaseAddress = Service.Get().Module.BaseAddress; - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - public KeyState(ClientStateAddressResolver addressResolver) + this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); + this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); + + Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}"); + } + + /// + /// Get or set the key-pressed state for a given vkCode. + /// + /// The virtual key to change. + /// Whether the specified key is currently pressed. + /// If the vkCode is not valid. Refer to or . + /// If the set value is non-zero. + public unsafe bool this[int vkCode] + { + get => this.GetRawValue(vkCode) != 0; + set => this.SetRawValue(vkCode, value ? 1 : 0); + } + + /// + public bool this[VirtualKey vkCode] + { + get => this[(int)vkCode]; + set => this[(int)vkCode] = value; + } + + /// + /// Gets the value in the index array. + /// + /// The virtual key to change. + /// The raw value stored in the index array. + /// If the vkCode is not valid. Refer to or . + public int GetRawValue(int vkCode) + => this.GetRefValue(vkCode); + + /// + public int GetRawValue(VirtualKey vkCode) + => this.GetRawValue((int)vkCode); + + /// + /// Sets the value in the index array. + /// + /// The virtual key to change. + /// The raw value to set in the index array. + /// If the vkCode is not valid. Refer to or . + /// If the set value is non-zero. + public void SetRawValue(int vkCode, int value) + { + if (value != 0) + throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team."); + + this.GetRefValue(vkCode) = value; + } + + /// + public void SetRawValue(VirtualKey vkCode, int value) + => this.SetRawValue((int)vkCode, value); + + /// + /// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game. + /// + /// Virtual key code. + /// If the code is valid. + public bool IsVirtualKeyValid(int vkCode) + => this.ConvertVirtualKey(vkCode) != 0; + + /// + public bool IsVirtualKeyValid(VirtualKey vkCode) + => this.IsVirtualKeyValid((int)vkCode); + + /// + /// Gets an array of virtual keys the game considers valid input. + /// + /// An array of valid virtual keys. + public VirtualKey[] GetValidVirtualKeys() + => this.validVirtualKeyCache ??= Enum.GetValues().Where(vk => this.IsVirtualKeyValid(vk)).ToArray(); + + /// + /// Clears the pressed state for all keys. + /// + public void ClearAll() + { + foreach (var vk in this.GetValidVirtualKeys()) { - var moduleBaseAddress = Service.Get().Module.BaseAddress; - - this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); - this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); - - Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}"); - } - - /// - /// Get or set the key-pressed state for a given vkCode. - /// - /// The virtual key to change. - /// Whether the specified key is currently pressed. - /// If the vkCode is not valid. Refer to or . - /// If the set value is non-zero. - public unsafe bool this[int vkCode] - { - get => this.GetRawValue(vkCode) != 0; - set => this.SetRawValue(vkCode, value ? 1 : 0); - } - - /// - public bool this[VirtualKey vkCode] - { - get => this[(int)vkCode]; - set => this[(int)vkCode] = value; - } - - /// - /// Gets the value in the index array. - /// - /// The virtual key to change. - /// The raw value stored in the index array. - /// If the vkCode is not valid. Refer to or . - public int GetRawValue(int vkCode) - => this.GetRefValue(vkCode); - - /// - public int GetRawValue(VirtualKey vkCode) - => this.GetRawValue((int)vkCode); - - /// - /// Sets the value in the index array. - /// - /// The virtual key to change. - /// The raw value to set in the index array. - /// If the vkCode is not valid. Refer to or . - /// If the set value is non-zero. - public void SetRawValue(int vkCode, int value) - { - if (value != 0) - throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team."); - - this.GetRefValue(vkCode) = value; - } - - /// - public void SetRawValue(VirtualKey vkCode, int value) - => this.SetRawValue((int)vkCode, value); - - /// - /// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game. - /// - /// Virtual key code. - /// If the code is valid. - public bool IsVirtualKeyValid(int vkCode) - => this.ConvertVirtualKey(vkCode) != 0; - - /// - public bool IsVirtualKeyValid(VirtualKey vkCode) - => this.IsVirtualKeyValid((int)vkCode); - - /// - /// Gets an array of virtual keys the game considers valid input. - /// - /// An array of valid virtual keys. - public VirtualKey[] GetValidVirtualKeys() - => this.validVirtualKeyCache ??= Enum.GetValues().Where(vk => this.IsVirtualKeyValid(vk)).ToArray(); - - /// - /// Clears the pressed state for all keys. - /// - public void ClearAll() - { - foreach (var vk in this.GetValidVirtualKeys()) - { - this[vk] = false; - } - } - - /// - /// Converts a virtual key into the equivalent value that the game uses. - /// Valid values are non-zero. - /// - /// Virtual key. - /// Converted value. - private unsafe byte ConvertVirtualKey(int vkCode) - { - if (vkCode <= 0 || vkCode >= MaxKeyCode) - return 0; - - return *(byte*)(this.indexBase + vkCode); - } - - /// - /// Gets the raw value from the key state array. - /// - /// Virtual key code. - /// A reference to the indexed array. - private unsafe ref int GetRefValue(int vkCode) - { - vkCode = this.ConvertVirtualKey(vkCode); - - if (vkCode == 0) - throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help."); - - return ref *(int*)(this.bufferBase + (4 * vkCode)); + this[vk] = false; } } + + /// + /// Converts a virtual key into the equivalent value that the game uses. + /// Valid values are non-zero. + /// + /// Virtual key. + /// Converted value. + private unsafe byte ConvertVirtualKey(int vkCode) + { + if (vkCode <= 0 || vkCode >= MaxKeyCode) + return 0; + + return *(byte*)(this.indexBase + vkCode); + } + + /// + /// Gets the raw value from the key state array. + /// + /// Virtual key code. + /// A reference to the indexed array. + private unsafe ref int GetRefValue(int vkCode) + { + vkCode = this.ConvertVirtualKey(vkCode); + + if (vkCode == 0) + throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help."); + + return ref *(int*)(this.bufferBase + (4 * vkCode)); + } } diff --git a/Dalamud/Game/ClientState/Keys/VirtualKey.cs b/Dalamud/Game/ClientState/Keys/VirtualKey.cs index ab9436822..998c3307c 100644 --- a/Dalamud/Game/ClientState/Keys/VirtualKey.cs +++ b/Dalamud/Game/ClientState/Keys/VirtualKey.cs @@ -1,1044 +1,1043 @@ -namespace Dalamud.Game.ClientState.Keys +namespace Dalamud.Game.ClientState.Keys; + +/// +/// Virtual-key codes. +/// +/// +/// Defined in winuser.h from Windows SDK v6.1. +/// +public enum VirtualKey : ushort { /// - /// Virtual-key codes. + /// This is an addendum to use on functions in which you have to pass a zero value to represent no key code. + /// + NO_KEY = 0, + + /// + /// Left mouse button. + /// + LBUTTON = 1, + + /// + /// Right mouse button. + /// + RBUTTON = 2, + + /// + /// Control-break processing. + /// + CANCEL = 3, + + /// + /// Middle mouse button (three-button mouse). /// /// - /// Defined in winuser.h from Windows SDK v6.1. + /// NOT contiguous with L and R buttons. /// - public enum VirtualKey : ushort - { - /// - /// This is an addendum to use on functions in which you have to pass a zero value to represent no key code. - /// - NO_KEY = 0, - - /// - /// Left mouse button. - /// - LBUTTON = 1, - - /// - /// Right mouse button. - /// - RBUTTON = 2, - - /// - /// Control-break processing. - /// - CANCEL = 3, - - /// - /// Middle mouse button (three-button mouse). - /// - /// - /// NOT contiguous with L and R buttons. - /// - MBUTTON = 4, - - /// - /// X1 mouse button. - /// - /// - /// NOT contiguous with L and R buttons. - /// - XBUTTON1 = 5, - - /// - /// X2 mouse button. - /// - /// - /// NOT contiguous with L and R buttons. - /// - XBUTTON2 = 6, - - /// - /// BACKSPACE key. - /// - BACK = 8, - - /// - /// TAB key. - /// - TAB = 9, - - /// - /// CLEAR key. - /// - CLEAR = 12, - - /// - /// RETURN key. - /// - RETURN = 13, - - /// - /// SHIFT key. - /// - SHIFT = 16, - - /// - /// CONTROL key. - /// - CONTROL = 17, - - /// - /// ALT key. - /// - MENU = 18, - - /// - /// PAUSE key. - /// - PAUSE = 19, - - /// - /// CAPS LOCK key. - /// - CAPITAL = 20, - - /// - /// IME Kana mode. - /// - KANA = 21, - - /// - /// IME Hanguel mode (maintained for compatibility; use User32.VirtualKey.HANGUL). - /// - HANGEUL = KANA, - - /// - /// IME Hangul mode. - /// - HANGUL = KANA, - - /// - /// IME Junja mode. - /// - JUNJA = 23, - - /// - /// IME final mode. - /// - FINAL = 24, - - /// - /// IME Hanja mode. - /// - HANJA = 25, - - /// - /// IME Kanji mode. - /// - KANJI = HANJA, - - /// - /// ESC key. - /// - ESCAPE = 27, - - /// - /// IME convert. - /// - CONVERT = 28, - - /// - /// IME nonconvert. - /// - NONCONVERT = 29, - - /// - /// IME accept. - /// - ACCEPT = 30, - - /// - /// IME mode change request. - /// - MODECHANGE = 31, - - /// - /// SPACEBAR. - /// - SPACE = 32, - - /// - /// PAGE UP key. - /// - PRIOR = 33, - - /// - /// PAGE DOWN key. - /// - NEXT = 34, - - /// - /// END key. - /// - END = 35, - - /// - /// HOME key. - /// - HOME = 36, - - /// - /// LEFT ARROW key. - /// - LEFT = 37, - - /// - /// UP ARROW key. - /// - UP = 38, - - /// - /// RIGHT ARROW key. - /// - RIGHT = 39, - - /// - /// DOWN ARROW key. - /// - DOWN = 40, - - /// - /// SELECT key. - /// - SELECT = 41, - - /// - /// PRINT key. - /// - PRINT = 42, - - /// - /// EXECUTE key. - /// - EXECUTE = 43, - - /// - /// PRINT SCREEN key. - /// - SNAPSHOT = 44, - - /// - /// INS key. - /// - INSERT = 45, - - /// - /// DEL key. - /// - DELETE = 46, - - /// - /// HELP key. - /// - HELP = 47, - - /// - /// 0 key. - /// - KEY_0 = 48, - - /// - /// 1 key. - /// - KEY_1 = 49, - - /// - /// 2 key. - /// - KEY_2 = 50, - - /// - /// 3 key. - /// - KEY_3 = 51, - - /// - /// 4 key. - /// - KEY_4 = 52, - - /// - /// 5 key. - /// - KEY_5 = 53, - - /// - /// 6 key. - /// - KEY_6 = 54, - - /// - /// 7 key. - /// - KEY_7 = 55, - - /// - /// 8 key. - /// - KEY_8 = 56, - - /// - /// 9 key. - /// - KEY_9 = 57, - - /// - /// A key. - /// - A = 65, - - /// - /// B key. - /// - B = 66, - - /// - /// C key. - /// - C = 67, - - /// - /// D key. - /// - D = 68, - - /// - /// E key. - /// - E = 69, - - /// - /// F key. - /// - F = 70, - - /// - /// G key. - /// - G = 71, - - /// - /// H key. - /// - H = 72, - - /// - /// I key. - /// - I = 73, - - /// - /// J key. - /// - J = 74, - - /// - /// K key. - /// - K = 75, - - /// - /// L key. - /// - L = 76, - - /// - /// M key. - /// - M = 77, - - /// - /// N key. - /// - N = 78, - - /// - /// O key. - /// - O = 79, - - /// - /// P key. - /// - P = 80, - - /// - /// Q key. - /// - Q = 81, - - /// - /// R key. - /// - R = 82, - - /// - /// S key. - /// - S = 83, - - /// - /// T key. - /// - T = 84, - - /// - /// U key. - /// - U = 85, - - /// - /// V key. - /// - V = 86, - - /// - /// W key. - /// - W = 87, - - /// - /// X key. - /// - X = 88, - - /// - /// Y key. - /// - Y = 89, - - /// - /// Z key. - /// - Z = 90, - - /// - /// Left Windows key (Natural keyboard). - /// - LWIN = 91, - - /// - /// Right Windows key (Natural keyboard). - /// - RWIN = 92, - - /// - /// Applications key (Natural keyboard). - /// - APPS = 93, - - /// - /// Computer Sleep key. - /// - SLEEP = 95, - - /// - /// Numeric keypad 0 key. - /// - NUMPAD0 = 96, - - /// - /// Numeric keypad 1 key. - /// - NUMPAD1 = 97, - - /// - /// Numeric keypad 2 key. - /// - NUMPAD2 = 98, - - /// - /// Numeric keypad 3 key. - /// - NUMPAD3 = 99, - - /// - /// Numeric keypad 4 key. - /// - NUMPAD4 = 100, - - /// - /// Numeric keypad 5 key. - /// - NUMPAD5 = 101, - - /// - /// Numeric keypad 6 key. - /// - NUMPAD6 = 102, - - /// - /// Numeric keypad 7 key. - /// - NUMPAD7 = 103, - - /// - /// Numeric keypad 8 key. - /// - NUMPAD8 = 104, - - /// - /// Numeric keypad 9 key. - /// - NUMPAD9 = 105, - - /// - /// Multiply key. - /// - MULTIPLY = 106, - - /// - /// Add key. - /// - ADD = 107, - - /// - /// Separator key. - /// - SEPARATOR = 108, - - /// - /// Subtract key. - /// - SUBTRACT = 109, - - /// - /// Decimal key. - /// - DECIMAL = 110, - - /// - /// Divide key. - /// - DIVIDE = 111, - - /// - /// F1 Key. - /// - F1 = 112, - - /// - /// F2 Key. - /// - F2 = 113, - - /// - /// F3 Key. - /// - F3 = 114, - - /// - /// F4 Key. - /// - F4 = 115, - - /// - /// F5 Key. - /// - F5 = 116, - - /// - /// F6 Key. - /// - F6 = 117, - - /// - /// F7 Key. - /// - F7 = 118, - - /// - /// F8 Key. - /// - F8 = 119, - - /// - /// F9 Key. - /// - F9 = 120, - - /// - /// F10 Key. - /// - F10 = 121, - - /// - /// F11 Key. - /// - F11 = 122, - - /// - /// F12 Key. - /// - F12 = 123, - - /// - /// F13 Key. - /// - F13 = 124, - - /// - /// F14 Key. - /// - F14 = 125, - - /// - /// F15 Key. - /// - F15 = 126, - - /// - /// F16 Key. - /// - F16 = 127, - - /// - /// F17 Key. - /// - F17 = 128, - - /// - /// F18 Key. - /// - F18 = 129, - - /// - /// F19 Key. - /// - F19 = 130, - - /// - /// F20 Key. - /// - F20 = 131, - - /// - /// F21 Key. - /// - F21 = 132, - - /// - /// F22 Key. - /// - F22 = 133, - - /// - /// F23 Key. - /// - F23 = 134, - - /// - /// F24 Key. - /// - F24 = 135, - - /// - /// NUM LOCK key. - /// - NUMLOCK = 144, - - /// - /// SCROLL LOCK key. - /// - SCROLL = 145, - - /// - /// '=' key on numpad (NEC PC-9800 kbd definitions). - /// - OEM_NEC_EQUAL = 146, - - /// - /// 'Dictionary' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_JISHO = OEM_NEC_EQUAL, - - /// - /// 'Unregister word' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_MASSHOU = 147, - - /// - /// 'Register word' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_TOUROKU = 148, - - /// - /// 'Left OYAYUBI' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_LOYA = 149, - - /// - /// 'Right OYAYUBI' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_ROYA = 150, - - /// - /// Left SHIFT key. - /// - /// - /// Used only as parameters to User32.GetAsyncKeyState and User32.GetKeyState. No other API or message will distinguish left and right keys in this way. - /// - LSHIFT = 160, - - /// - /// Right SHIFT key. - /// - RSHIFT = 161, - - /// - /// Left CONTROL key. - /// - LCONTROL = 162, - - /// - /// Right CONTROL key. - /// - RCONTROL = 163, - - /// - /// Left MENU key. - /// - LMENU = 164, - - /// - /// Right MENU key. - /// - RMENU = 165, - - /// - /// Browser Back key. - /// - BROWSER_BACK = 166, - - /// - /// Browser Forward key. - /// - BROWSER_FORWARD = 167, - - /// - /// Browser Refresh key. - /// - BROWSER_REFRESH = 168, - - /// - /// Browser Stop key. - /// - BROWSER_STOP = 169, - - /// - /// Browser Search key. - /// - BROWSER_SEARCH = 170, - - /// - /// Browser Favorites key. - /// - BROWSER_FAVORITES = 171, - - /// - /// Browser Start and Home key. - /// - BROWSER_HOME = 172, - - /// - /// Volume Mute key. - /// - VOLUME_MUTE = 173, - - /// - /// Volume Down key. - /// - VOLUME_DOWN = 174, - - /// - /// Volume Up key. - /// - VOLUME_UP = 175, - - /// - /// Next Track key. - /// - MEDIA_NEXT_TRACK = 176, - - /// - /// Previous Track key. - /// - MEDIA_PREV_TRACK = 177, - - /// - /// Stop Media key. - /// - MEDIA_STOP = 178, - - /// - /// Play/Pause Media key. - /// - MEDIA_PLAY_PAUSE = 179, - - /// - /// Start Mail key. - /// - LAUNCH_MAIL = 180, - - /// - /// Select Media key. - /// - LAUNCH_MEDIA_SELECT = 181, - - /// - /// Start Application 1 key. - /// - LAUNCH_APP1 = 182, - - /// - /// Start Application 2 key. - /// - LAUNCH_APP2 = 183, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the ';:' key. - /// - OEM_1 = 186, - - /// - /// For any country/region, the '+' key. - /// - OEM_PLUS = 187, - - /// - /// For any country/region, the ',' key. - /// - OEM_COMMA = 188, - - /// - /// For any country/region, the '-' key. - /// - OEM_MINUS = 189, - - /// - /// For any country/region, the '.' key. - /// - OEM_PERIOD = 190, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '/?' key. - /// - OEM_2 = 191, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '`~' key. - /// - OEM_3 = 192, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '[{' key. - /// - OEM_4 = 219, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '\|' key. - /// - OEM_5 = 220, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the ']}' key. - /// - OEM_6 = 221, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the 'single-quote/double-quote' (''"') key. - /// - OEM_7 = 222, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - OEM_8 = 223, - - /// - /// OEM specific. - /// - /// - /// 'AX' key on Japanese AX kbd. - /// - OEM_AX = 225, - - /// - /// Either the angle bracket ("<>") key or the backslash ("\|") key on the RT 102-key keyboard. - /// - OEM_102 = 226, - - /// - /// OEM specific. - /// - /// - /// Help key on ICO. - /// - ICO_HELP = 227, - - /// - /// OEM specific. - /// - /// - /// 00 key on ICO. - /// - ICO_00 = 228, - - /// - /// IME PROCESS key. - /// - PROCESSKEY = 229, - - /// - /// OEM specific. - /// - /// - /// Clear key on ICO. - /// - ICO_CLEAR = 230, - - /// - /// Used to pass Unicode characters as if they were keystrokes. The PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods.. - /// - /// - /// For more information, see Remark in User32.KEYBDINPUT, User32.SendInput, User32.WindowMessage.WM_KEYDOWN, and User32.WindowMessage.WM_KEYUP. - /// - PACKET = 231, - - /// - /// Nokia/Ericsson definition. - /// - OEM_RESET = 233, - - /// - /// Nokia/Ericsson definition. - /// - OEM_JUMP = 234, - - /// - /// Nokia/Ericsson definition. - /// - OEM_PA1 = 235, - - /// - /// Nokia/Ericsson definition. - /// - OEM_PA2 = 236, - - /// - /// Nokia/Ericsson definition. - /// - OEM_PA3 = 237, - - /// - /// Nokia/Ericsson definition. - /// - OEM_WSCTRL = 238, - - /// - /// Nokia/Ericsson definition. - /// - OEM_CUSEL = 239, - - /// - /// Nokia/Ericsson definition. - /// - OEM_ATTN = 240, - - /// - /// Nokia/Ericsson definition. - /// - OEM_FINISH = 241, - - /// - /// Nokia/Ericsson definition. - /// - OEM_COPY = 242, - - /// - /// Nokia/Ericsson definition. - /// - OEM_AUTO = 243, - - /// - /// Nokia/Ericsson definition. - /// - OEM_ENLW = 244, - - /// - /// Nokia/Ericsson definition. - /// - OEM_BACKTAB = 245, - - /// - /// Attn key. - /// - ATTN = 246, - - /// - /// CrSel key. - /// - CRSEL = 247, - - /// - /// ExSel key. - /// - EXSEL = 248, - - /// - /// Erase EOF key. - /// - EREOF = 249, - - /// - /// Play key. - /// - PLAY = 250, - - /// - /// Zoom key. - /// - ZOOM = 251, - - /// - /// Reserved constant by Windows headers definition. - /// - NONAME = 252, - - /// - /// PA1 key. - /// - PA1 = 253, - - /// - /// Clear key. - /// - OEM_CLEAR = 254, - } + MBUTTON = 4, + + /// + /// X1 mouse button. + /// + /// + /// NOT contiguous with L and R buttons. + /// + XBUTTON1 = 5, + + /// + /// X2 mouse button. + /// + /// + /// NOT contiguous with L and R buttons. + /// + XBUTTON2 = 6, + + /// + /// BACKSPACE key. + /// + BACK = 8, + + /// + /// TAB key. + /// + TAB = 9, + + /// + /// CLEAR key. + /// + CLEAR = 12, + + /// + /// RETURN key. + /// + RETURN = 13, + + /// + /// SHIFT key. + /// + SHIFT = 16, + + /// + /// CONTROL key. + /// + CONTROL = 17, + + /// + /// ALT key. + /// + MENU = 18, + + /// + /// PAUSE key. + /// + PAUSE = 19, + + /// + /// CAPS LOCK key. + /// + CAPITAL = 20, + + /// + /// IME Kana mode. + /// + KANA = 21, + + /// + /// IME Hanguel mode (maintained for compatibility; use User32.VirtualKey.HANGUL). + /// + HANGEUL = KANA, + + /// + /// IME Hangul mode. + /// + HANGUL = KANA, + + /// + /// IME Junja mode. + /// + JUNJA = 23, + + /// + /// IME final mode. + /// + FINAL = 24, + + /// + /// IME Hanja mode. + /// + HANJA = 25, + + /// + /// IME Kanji mode. + /// + KANJI = HANJA, + + /// + /// ESC key. + /// + ESCAPE = 27, + + /// + /// IME convert. + /// + CONVERT = 28, + + /// + /// IME nonconvert. + /// + NONCONVERT = 29, + + /// + /// IME accept. + /// + ACCEPT = 30, + + /// + /// IME mode change request. + /// + MODECHANGE = 31, + + /// + /// SPACEBAR. + /// + SPACE = 32, + + /// + /// PAGE UP key. + /// + PRIOR = 33, + + /// + /// PAGE DOWN key. + /// + NEXT = 34, + + /// + /// END key. + /// + END = 35, + + /// + /// HOME key. + /// + HOME = 36, + + /// + /// LEFT ARROW key. + /// + LEFT = 37, + + /// + /// UP ARROW key. + /// + UP = 38, + + /// + /// RIGHT ARROW key. + /// + RIGHT = 39, + + /// + /// DOWN ARROW key. + /// + DOWN = 40, + + /// + /// SELECT key. + /// + SELECT = 41, + + /// + /// PRINT key. + /// + PRINT = 42, + + /// + /// EXECUTE key. + /// + EXECUTE = 43, + + /// + /// PRINT SCREEN key. + /// + SNAPSHOT = 44, + + /// + /// INS key. + /// + INSERT = 45, + + /// + /// DEL key. + /// + DELETE = 46, + + /// + /// HELP key. + /// + HELP = 47, + + /// + /// 0 key. + /// + KEY_0 = 48, + + /// + /// 1 key. + /// + KEY_1 = 49, + + /// + /// 2 key. + /// + KEY_2 = 50, + + /// + /// 3 key. + /// + KEY_3 = 51, + + /// + /// 4 key. + /// + KEY_4 = 52, + + /// + /// 5 key. + /// + KEY_5 = 53, + + /// + /// 6 key. + /// + KEY_6 = 54, + + /// + /// 7 key. + /// + KEY_7 = 55, + + /// + /// 8 key. + /// + KEY_8 = 56, + + /// + /// 9 key. + /// + KEY_9 = 57, + + /// + /// A key. + /// + A = 65, + + /// + /// B key. + /// + B = 66, + + /// + /// C key. + /// + C = 67, + + /// + /// D key. + /// + D = 68, + + /// + /// E key. + /// + E = 69, + + /// + /// F key. + /// + F = 70, + + /// + /// G key. + /// + G = 71, + + /// + /// H key. + /// + H = 72, + + /// + /// I key. + /// + I = 73, + + /// + /// J key. + /// + J = 74, + + /// + /// K key. + /// + K = 75, + + /// + /// L key. + /// + L = 76, + + /// + /// M key. + /// + M = 77, + + /// + /// N key. + /// + N = 78, + + /// + /// O key. + /// + O = 79, + + /// + /// P key. + /// + P = 80, + + /// + /// Q key. + /// + Q = 81, + + /// + /// R key. + /// + R = 82, + + /// + /// S key. + /// + S = 83, + + /// + /// T key. + /// + T = 84, + + /// + /// U key. + /// + U = 85, + + /// + /// V key. + /// + V = 86, + + /// + /// W key. + /// + W = 87, + + /// + /// X key. + /// + X = 88, + + /// + /// Y key. + /// + Y = 89, + + /// + /// Z key. + /// + Z = 90, + + /// + /// Left Windows key (Natural keyboard). + /// + LWIN = 91, + + /// + /// Right Windows key (Natural keyboard). + /// + RWIN = 92, + + /// + /// Applications key (Natural keyboard). + /// + APPS = 93, + + /// + /// Computer Sleep key. + /// + SLEEP = 95, + + /// + /// Numeric keypad 0 key. + /// + NUMPAD0 = 96, + + /// + /// Numeric keypad 1 key. + /// + NUMPAD1 = 97, + + /// + /// Numeric keypad 2 key. + /// + NUMPAD2 = 98, + + /// + /// Numeric keypad 3 key. + /// + NUMPAD3 = 99, + + /// + /// Numeric keypad 4 key. + /// + NUMPAD4 = 100, + + /// + /// Numeric keypad 5 key. + /// + NUMPAD5 = 101, + + /// + /// Numeric keypad 6 key. + /// + NUMPAD6 = 102, + + /// + /// Numeric keypad 7 key. + /// + NUMPAD7 = 103, + + /// + /// Numeric keypad 8 key. + /// + NUMPAD8 = 104, + + /// + /// Numeric keypad 9 key. + /// + NUMPAD9 = 105, + + /// + /// Multiply key. + /// + MULTIPLY = 106, + + /// + /// Add key. + /// + ADD = 107, + + /// + /// Separator key. + /// + SEPARATOR = 108, + + /// + /// Subtract key. + /// + SUBTRACT = 109, + + /// + /// Decimal key. + /// + DECIMAL = 110, + + /// + /// Divide key. + /// + DIVIDE = 111, + + /// + /// F1 Key. + /// + F1 = 112, + + /// + /// F2 Key. + /// + F2 = 113, + + /// + /// F3 Key. + /// + F3 = 114, + + /// + /// F4 Key. + /// + F4 = 115, + + /// + /// F5 Key. + /// + F5 = 116, + + /// + /// F6 Key. + /// + F6 = 117, + + /// + /// F7 Key. + /// + F7 = 118, + + /// + /// F8 Key. + /// + F8 = 119, + + /// + /// F9 Key. + /// + F9 = 120, + + /// + /// F10 Key. + /// + F10 = 121, + + /// + /// F11 Key. + /// + F11 = 122, + + /// + /// F12 Key. + /// + F12 = 123, + + /// + /// F13 Key. + /// + F13 = 124, + + /// + /// F14 Key. + /// + F14 = 125, + + /// + /// F15 Key. + /// + F15 = 126, + + /// + /// F16 Key. + /// + F16 = 127, + + /// + /// F17 Key. + /// + F17 = 128, + + /// + /// F18 Key. + /// + F18 = 129, + + /// + /// F19 Key. + /// + F19 = 130, + + /// + /// F20 Key. + /// + F20 = 131, + + /// + /// F21 Key. + /// + F21 = 132, + + /// + /// F22 Key. + /// + F22 = 133, + + /// + /// F23 Key. + /// + F23 = 134, + + /// + /// F24 Key. + /// + F24 = 135, + + /// + /// NUM LOCK key. + /// + NUMLOCK = 144, + + /// + /// SCROLL LOCK key. + /// + SCROLL = 145, + + /// + /// '=' key on numpad (NEC PC-9800 kbd definitions). + /// + OEM_NEC_EQUAL = 146, + + /// + /// 'Dictionary' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_JISHO = OEM_NEC_EQUAL, + + /// + /// 'Unregister word' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_MASSHOU = 147, + + /// + /// 'Register word' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_TOUROKU = 148, + + /// + /// 'Left OYAYUBI' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_LOYA = 149, + + /// + /// 'Right OYAYUBI' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_ROYA = 150, + + /// + /// Left SHIFT key. + /// + /// + /// Used only as parameters to User32.GetAsyncKeyState and User32.GetKeyState. No other API or message will distinguish left and right keys in this way. + /// + LSHIFT = 160, + + /// + /// Right SHIFT key. + /// + RSHIFT = 161, + + /// + /// Left CONTROL key. + /// + LCONTROL = 162, + + /// + /// Right CONTROL key. + /// + RCONTROL = 163, + + /// + /// Left MENU key. + /// + LMENU = 164, + + /// + /// Right MENU key. + /// + RMENU = 165, + + /// + /// Browser Back key. + /// + BROWSER_BACK = 166, + + /// + /// Browser Forward key. + /// + BROWSER_FORWARD = 167, + + /// + /// Browser Refresh key. + /// + BROWSER_REFRESH = 168, + + /// + /// Browser Stop key. + /// + BROWSER_STOP = 169, + + /// + /// Browser Search key. + /// + BROWSER_SEARCH = 170, + + /// + /// Browser Favorites key. + /// + BROWSER_FAVORITES = 171, + + /// + /// Browser Start and Home key. + /// + BROWSER_HOME = 172, + + /// + /// Volume Mute key. + /// + VOLUME_MUTE = 173, + + /// + /// Volume Down key. + /// + VOLUME_DOWN = 174, + + /// + /// Volume Up key. + /// + VOLUME_UP = 175, + + /// + /// Next Track key. + /// + MEDIA_NEXT_TRACK = 176, + + /// + /// Previous Track key. + /// + MEDIA_PREV_TRACK = 177, + + /// + /// Stop Media key. + /// + MEDIA_STOP = 178, + + /// + /// Play/Pause Media key. + /// + MEDIA_PLAY_PAUSE = 179, + + /// + /// Start Mail key. + /// + LAUNCH_MAIL = 180, + + /// + /// Select Media key. + /// + LAUNCH_MEDIA_SELECT = 181, + + /// + /// Start Application 1 key. + /// + LAUNCH_APP1 = 182, + + /// + /// Start Application 2 key. + /// + LAUNCH_APP2 = 183, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the ';:' key. + /// + OEM_1 = 186, + + /// + /// For any country/region, the '+' key. + /// + OEM_PLUS = 187, + + /// + /// For any country/region, the ',' key. + /// + OEM_COMMA = 188, + + /// + /// For any country/region, the '-' key. + /// + OEM_MINUS = 189, + + /// + /// For any country/region, the '.' key. + /// + OEM_PERIOD = 190, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '/?' key. + /// + OEM_2 = 191, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '`~' key. + /// + OEM_3 = 192, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '[{' key. + /// + OEM_4 = 219, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '\|' key. + /// + OEM_5 = 220, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the ']}' key. + /// + OEM_6 = 221, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the 'single-quote/double-quote' (''"') key. + /// + OEM_7 = 222, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + OEM_8 = 223, + + /// + /// OEM specific. + /// + /// + /// 'AX' key on Japanese AX kbd. + /// + OEM_AX = 225, + + /// + /// Either the angle bracket ("<>") key or the backslash ("\|") key on the RT 102-key keyboard. + /// + OEM_102 = 226, + + /// + /// OEM specific. + /// + /// + /// Help key on ICO. + /// + ICO_HELP = 227, + + /// + /// OEM specific. + /// + /// + /// 00 key on ICO. + /// + ICO_00 = 228, + + /// + /// IME PROCESS key. + /// + PROCESSKEY = 229, + + /// + /// OEM specific. + /// + /// + /// Clear key on ICO. + /// + ICO_CLEAR = 230, + + /// + /// Used to pass Unicode characters as if they were keystrokes. The PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods.. + /// + /// + /// For more information, see Remark in User32.KEYBDINPUT, User32.SendInput, User32.WindowMessage.WM_KEYDOWN, and User32.WindowMessage.WM_KEYUP. + /// + PACKET = 231, + + /// + /// Nokia/Ericsson definition. + /// + OEM_RESET = 233, + + /// + /// Nokia/Ericsson definition. + /// + OEM_JUMP = 234, + + /// + /// Nokia/Ericsson definition. + /// + OEM_PA1 = 235, + + /// + /// Nokia/Ericsson definition. + /// + OEM_PA2 = 236, + + /// + /// Nokia/Ericsson definition. + /// + OEM_PA3 = 237, + + /// + /// Nokia/Ericsson definition. + /// + OEM_WSCTRL = 238, + + /// + /// Nokia/Ericsson definition. + /// + OEM_CUSEL = 239, + + /// + /// Nokia/Ericsson definition. + /// + OEM_ATTN = 240, + + /// + /// Nokia/Ericsson definition. + /// + OEM_FINISH = 241, + + /// + /// Nokia/Ericsson definition. + /// + OEM_COPY = 242, + + /// + /// Nokia/Ericsson definition. + /// + OEM_AUTO = 243, + + /// + /// Nokia/Ericsson definition. + /// + OEM_ENLW = 244, + + /// + /// Nokia/Ericsson definition. + /// + OEM_BACKTAB = 245, + + /// + /// Attn key. + /// + ATTN = 246, + + /// + /// CrSel key. + /// + CRSEL = 247, + + /// + /// ExSel key. + /// + EXSEL = 248, + + /// + /// Erase EOF key. + /// + EREOF = 249, + + /// + /// Play key. + /// + PLAY = 250, + + /// + /// Zoom key. + /// + ZOOM = 251, + + /// + /// Reserved constant by Windows headers definition. + /// + NONAME = 252, + + /// + /// PA1 key. + /// + PA1 = 253, + + /// + /// Clear key. + /// + OEM_CLEAR = 254, } diff --git a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs index 489891c45..dd6057d36 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Game.ClientState.Objects.Enums +namespace Dalamud.Game.ClientState.Objects.Enums; + +/// +/// An Enum describing possible BattleNpc kinds. +/// +public enum BattleNpcSubKind : byte { /// - /// An Enum describing possible BattleNpc kinds. + /// Invalid BattleNpc. /// - public enum BattleNpcSubKind : byte - { - /// - /// Invalid BattleNpc. - /// - None = 0, + None = 0, - /// - /// BattleNpc representing a Pet. - /// - Pet = 2, + /// + /// BattleNpc representing a Pet. + /// + Pet = 2, - /// - /// BattleNpc representing a Chocobo. - /// - Chocobo = 3, + /// + /// BattleNpc representing a Chocobo. + /// + Chocobo = 3, - /// - /// BattleNpc representing a standard enemy. - /// - Enemy = 5, - } + /// + /// BattleNpc representing a standard enemy. + /// + Enemy = 5, } diff --git a/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs b/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs index b1827c0c5..299583fd3 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs @@ -1,139 +1,138 @@ -namespace Dalamud.Game.ClientState.Objects.Enums +namespace Dalamud.Game.ClientState.Objects.Enums; + +/// +/// This enum describes the indices of the Customize array. +/// +// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire) +public enum CustomizeIndex { /// - /// This enum describes the indices of the Customize array. + /// The race of the character. /// - // TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire) - public enum CustomizeIndex - { - /// - /// The race of the character. - /// - Race = 0x00, + Race = 0x00, - /// - /// The gender of the character. - /// - Gender = 0x01, + /// + /// The gender of the character. + /// + Gender = 0x01, - /// - /// The tribe of the character. - /// - Tribe = 0x04, + /// + /// The tribe of the character. + /// + Tribe = 0x04, - /// - /// The height of the character. - /// - Height = 0x03, + /// + /// The height of the character. + /// + Height = 0x03, - /// - /// The model type of the character. - /// - ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features + /// + /// The model type of the character. + /// + ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features - /// - /// The face type of the character. - /// - FaceType = 0x05, + /// + /// The face type of the character. + /// + FaceType = 0x05, - /// - /// The hair of the character. - /// - HairStyle = 0x06, + /// + /// The hair of the character. + /// + HairStyle = 0x06, - /// - /// Whether or not the character has hair highlights. - /// - HasHighlights = 0x07, // negative to enable, positive to disable + /// + /// Whether or not the character has hair highlights. + /// + HasHighlights = 0x07, // negative to enable, positive to disable - /// - /// The skin color of the character. - /// - SkinColor = 0x08, + /// + /// The skin color of the character. + /// + SkinColor = 0x08, - /// - /// The eye color of the character. - /// - EyeColor = 0x09, // color of character's right eye + /// + /// The eye color of the character. + /// + EyeColor = 0x09, // color of character's right eye - /// - /// The hair color of the character. - /// - HairColor = 0x0A, // main color + /// + /// The hair color of the character. + /// + HairColor = 0x0A, // main color - /// - /// The highlights hair color of the character. - /// - HairColor2 = 0x0B, // highlights color + /// + /// The highlights hair color of the character. + /// + HairColor2 = 0x0B, // highlights color - /// - /// The face features of the character. - /// - FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small + /// + /// The face features of the character. + /// + FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small - /// - /// The color of the face features of the character. - /// - FaceFeaturesColor = 0x0D, + /// + /// The color of the face features of the character. + /// + FaceFeaturesColor = 0x0D, - /// - /// The eyebrows of the character. - /// - Eyebrows = 0x0E, + /// + /// The eyebrows of the character. + /// + Eyebrows = 0x0E, - /// - /// The 2nd eye color of the character. - /// - EyeColor2 = 0x0F, // color of character's left eye + /// + /// The 2nd eye color of the character. + /// + EyeColor2 = 0x0F, // color of character's left eye - /// - /// The eye shape of the character. - /// - EyeShape = 0x10, + /// + /// The eye shape of the character. + /// + EyeShape = 0x10, - /// - /// The nose shape of the character. - /// - NoseShape = 0x11, + /// + /// The nose shape of the character. + /// + NoseShape = 0x11, - /// - /// The jaw shape of the character. - /// - JawShape = 0x12, + /// + /// The jaw shape of the character. + /// + JawShape = 0x12, - /// - /// The lip style of the character. - /// - LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour) + /// + /// The lip style of the character. + /// + LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour) - /// - /// The lip color of the character. - /// - LipColor = 0x14, + /// + /// The lip color of the character. + /// + LipColor = 0x14, - /// - /// The race feature size of the character. - /// - RaceFeatureSize = 0x15, + /// + /// The race feature size of the character. + /// + RaceFeatureSize = 0x15, - /// - /// The race feature type of the character. - /// - RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client + /// + /// The race feature type of the character. + /// + RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client - /// - /// The bust size of the character. - /// - BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference + /// + /// The bust size of the character. + /// + BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference - /// - /// The face paint of the character. - /// - Facepaint = 0x18, + /// + /// The face paint of the character. + /// + Facepaint = 0x18, - /// - /// The face paint color of the character. - /// - FacepaintColor = 0x19, - } + /// + /// The face paint color of the character. + /// + FacepaintColor = 0x19, } diff --git a/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs b/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs index 038bce143..6bfb80863 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs @@ -1,83 +1,82 @@ -namespace Dalamud.Game.ClientState.Objects.Enums +namespace Dalamud.Game.ClientState.Objects.Enums; + +/// +/// Enum describing possible entity kinds. +/// +public enum ObjectKind : byte { /// - /// Enum describing possible entity kinds. + /// Invalid character. /// - public enum ObjectKind : byte - { - /// - /// Invalid character. - /// - None = 0x00, + None = 0x00, - /// - /// Objects representing player characters. - /// - Player = 0x01, + /// + /// Objects representing player characters. + /// + Player = 0x01, - /// - /// Objects representing battle NPCs. - /// - BattleNpc = 0x02, + /// + /// Objects representing battle NPCs. + /// + BattleNpc = 0x02, - /// - /// Objects representing event NPCs. - /// - EventNpc = 0x03, + /// + /// Objects representing event NPCs. + /// + EventNpc = 0x03, - /// - /// Objects representing treasures. - /// - Treasure = 0x04, + /// + /// Objects representing treasures. + /// + Treasure = 0x04, - /// - /// Objects representing aetherytes. - /// - Aetheryte = 0x05, + /// + /// Objects representing aetherytes. + /// + Aetheryte = 0x05, - /// - /// Objects representing gathering points. - /// - GatheringPoint = 0x06, + /// + /// Objects representing gathering points. + /// + GatheringPoint = 0x06, - /// - /// Objects representing event objects. - /// - EventObj = 0x07, + /// + /// Objects representing event objects. + /// + EventObj = 0x07, - /// - /// Objects representing mounts. - /// - MountType = 0x08, + /// + /// Objects representing mounts. + /// + MountType = 0x08, - /// - /// Objects representing minions. - /// - Companion = 0x09, // Minion + /// + /// Objects representing minions. + /// + Companion = 0x09, // Minion - /// - /// Objects representing retainers. - /// - Retainer = 0x0A, + /// + /// Objects representing retainers. + /// + Retainer = 0x0A, - /// - /// Objects representing area objects. - /// - Area = 0x0B, + /// + /// Objects representing area objects. + /// + Area = 0x0B, - /// - /// Objects representing housing objects. - /// - Housing = 0x0C, + /// + /// Objects representing housing objects. + /// + Housing = 0x0C, - /// - /// Objects representing cutscene objects. - /// - Cutscene = 0x0D, + /// + /// Objects representing cutscene objects. + /// + Cutscene = 0x0D, - /// - /// Objects representing card stand objects. - /// - CardStand = 0x0E, - } + /// + /// Objects representing card stand objects. + /// + CardStand = 0x0E, } diff --git a/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs b/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs index 43587c66b..2fed2a655 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs @@ -1,56 +1,55 @@ using System; -namespace Dalamud.Game.ClientState.Objects.Enums +namespace Dalamud.Game.ClientState.Objects.Enums; + +/// +/// Enum describing possible status flags. +/// +[Flags] +public enum StatusFlags : byte { /// - /// Enum describing possible status flags. + /// No status flags set. /// - [Flags] - public enum StatusFlags : byte - { - /// - /// No status flags set. - /// - None = 0, + None = 0, - /// - /// Hostile character. - /// - Hostile = 1, + /// + /// Hostile character. + /// + Hostile = 1, - /// - /// Character in combat. - /// - InCombat = 2, + /// + /// Character in combat. + /// + InCombat = 2, - /// - /// Character weapon is out. - /// - WeaponOut = 4, + /// + /// Character weapon is out. + /// + WeaponOut = 4, - /// - /// Character offhand is out. - /// - OffhandOut = 8, + /// + /// Character offhand is out. + /// + OffhandOut = 8, - /// - /// Character is a party member. - /// - PartyMember = 16, + /// + /// Character is a party member. + /// + PartyMember = 16, - /// - /// Character is a alliance member. - /// - AllianceMember = 32, + /// + /// Character is a alliance member. + /// + AllianceMember = 32, - /// - /// Character is in friend list. - /// - Friend = 64, + /// + /// Character is in friend list. + /// + Friend = 64, - /// - /// Character is casting. - /// - IsCasting = 128, - } + /// + /// Character is casting. + /// + IsCasting = 128, } diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index df045be1c..83e1c77f5 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -9,140 +9,139 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Objects +namespace Dalamud.Game.ClientState.Objects; + +/// +/// This collection represents the currently spawned FFXIV game objects. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed partial class ObjectTable { + private const int ObjectTableLength = 424; + + private readonly ClientStateAddressResolver address; + /// - /// This collection represents the currently spawned FFXIV game objects. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed partial class ObjectTable + /// Client state address resolver. + internal ObjectTable(ClientStateAddressResolver addressResolver) { - private const int ObjectTableLength = 424; + this.address = addressResolver; - private readonly ClientStateAddressResolver address; + Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); + } - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal ObjectTable(ClientStateAddressResolver addressResolver) + /// + /// Gets the address of the object table. + /// + public IntPtr Address => this.address.ObjectTable; + + /// + /// Gets the length of the object table. + /// + public int Length => ObjectTableLength; + + /// + /// Get an object at the specified spawn index. + /// + /// Spawn index. + /// An at the specified spawn index. + public GameObject? this[int index] + { + get { - this.address = addressResolver; - - Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); - } - - /// - /// Gets the address of the object table. - /// - public IntPtr Address => this.address.ObjectTable; - - /// - /// Gets the length of the object table. - /// - public int Length => ObjectTableLength; - - /// - /// Get an object at the specified spawn index. - /// - /// Spawn index. - /// An at the specified spawn index. - public GameObject? this[int index] - { - get - { - var address = this.GetObjectAddress(index); - return this.CreateObjectReference(address); - } - } - - /// - /// Search for a game object by their Object ID. - /// - /// Object ID to find. - /// A game object or null. - public GameObject? SearchById(uint objectId) - { - if (objectId is GameObject.InvalidGameObjectId or 0) - return null; - - foreach (var obj in this) - { - if (obj == null) - continue; - - if (obj.ObjectId == objectId) - return obj; - } - - return null; - } - - /// - /// Gets the address of the game object at the specified index of the object table. - /// - /// The index of the object. - /// The memory address of the object. - public unsafe IntPtr GetObjectAddress(int index) - { - if (index < 0 || index >= ObjectTableLength) - return IntPtr.Zero; - - return *(IntPtr*)(this.address.ObjectTable + (8 * index)); - } - - /// - /// Create a reference to an FFXIV game object. - /// - /// The address of the object in memory. - /// object or inheritor containing the requested data. - public unsafe GameObject? CreateObjectReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address; - var objKind = (ObjectKind)obj->ObjectKind; - return objKind switch - { - ObjectKind.Player => new PlayerCharacter(address), - ObjectKind.BattleNpc => new BattleNpc(address), - ObjectKind.EventObj => new EventObj(address), - ObjectKind.Companion => new Npc(address), - _ => new GameObject(address), - }; + var address = this.GetObjectAddress(index); + return this.CreateObjectReference(address); } } /// - /// This collection represents the currently spawned FFXIV game objects. + /// Search for a game object by their Object ID. /// - public sealed partial class ObjectTable : IReadOnlyCollection + /// Object ID to find. + /// A game object or null. + public GameObject? SearchById(uint objectId) { - /// - int IReadOnlyCollection.Count => this.Length; + if (objectId is GameObject.InvalidGameObjectId or 0) + return null; - /// - public IEnumerator GetEnumerator() + foreach (var obj in this) { - for (var i = 0; i < ObjectTableLength; i++) - { - var obj = this[i]; + if (obj == null) + continue; - if (obj == null) - continue; - - yield return obj; - } + if (obj.ObjectId == objectId) + return obj; } - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + return null; + } + + /// + /// Gets the address of the game object at the specified index of the object table. + /// + /// The index of the object. + /// The memory address of the object. + public unsafe IntPtr GetObjectAddress(int index) + { + if (index < 0 || index >= ObjectTableLength) + return IntPtr.Zero; + + return *(IntPtr*)(this.address.ObjectTable + (8 * index)); + } + + /// + /// Create a reference to an FFXIV game object. + /// + /// The address of the object in memory. + /// object or inheritor containing the requested data. + public unsafe GameObject? CreateObjectReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address; + var objKind = (ObjectKind)obj->ObjectKind; + return objKind switch + { + ObjectKind.Player => new PlayerCharacter(address), + ObjectKind.BattleNpc => new BattleNpc(address), + ObjectKind.EventObj => new EventObj(address), + ObjectKind.Companion => new Npc(address), + _ => new GameObject(address), + }; } } + +/// +/// This collection represents the currently spawned FFXIV game objects. +/// +public sealed partial class ObjectTable : IReadOnlyCollection +{ + /// + int IReadOnlyCollection.Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < ObjectTableLength; i++) + { + var obj = this[i]; + + if (obj == null) + continue; + + yield return obj; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +} diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs b/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs index 42bf16f15..cbdc44a4f 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs @@ -2,29 +2,28 @@ using System; using Dalamud.Game.ClientState.Objects.Enums; -namespace Dalamud.Game.ClientState.Objects.Types +namespace Dalamud.Game.ClientState.Objects.Types; + +/// +/// This class represents a battle NPC. +/// +public unsafe class BattleNpc : BattleChara { /// - /// This class represents a battle NPC. + /// Initializes a new instance of the class. + /// Set up a new BattleNpc with the provided memory representation. /// - public unsafe class BattleNpc : BattleChara + /// The address of this actor in memory. + internal BattleNpc(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// Set up a new BattleNpc with the provided memory representation. - /// - /// The address of this actor in memory. - internal BattleNpc(IntPtr address) - : base(address) - { - } - - /// - /// Gets the BattleNpc of this BattleNpc. - /// - public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind; - - /// - public override uint TargetObjectId => this.Struct->Character.TargetObjectID; } + + /// + /// Gets the BattleNpc of this BattleNpc. + /// + public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind; + + /// + public override uint TargetObjectId => this.Struct->Character.TargetObjectID; } diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs b/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs index 61710f32b..9d977ec95 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs @@ -2,21 +2,20 @@ using System; using Dalamud.Game.ClientState.Objects.Types; -namespace Dalamud.Game.ClientState.Objects.SubKinds +namespace Dalamud.Game.ClientState.Objects.SubKinds; + +/// +/// This class represents an EventObj. +/// +public unsafe class EventObj : GameObject { /// - /// This class represents an EventObj. + /// Initializes a new instance of the class. + /// Set up a new EventObj with the provided memory representation. /// - public unsafe class EventObj : GameObject + /// The address of this event object in memory. + internal EventObj(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// Set up a new EventObj with the provided memory representation. - /// - /// The address of this event object in memory. - internal EventObj(IntPtr address) - : base(address) - { - } } } diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs b/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs index 3d9ff17b9..802e647ff 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs @@ -2,21 +2,20 @@ using System; using Dalamud.Game.ClientState.Objects.Types; -namespace Dalamud.Game.ClientState.Objects.SubKinds +namespace Dalamud.Game.ClientState.Objects.SubKinds; + +/// +/// This class represents a NPC. +/// +public unsafe class Npc : Character { /// - /// This class represents a NPC. + /// Initializes a new instance of the class. + /// Set up a new NPC with the provided memory representation. /// - public unsafe class Npc : Character + /// The address of this actor in memory. + internal Npc(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// Set up a new NPC with the provided memory representation. - /// - /// The address of this actor in memory. - internal Npc(IntPtr address) - : base(address) - { - } } } diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs index 121dae753..e998ae5e4 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs @@ -3,36 +3,35 @@ using System; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Resolvers; -namespace Dalamud.Game.ClientState.Objects.SubKinds +namespace Dalamud.Game.ClientState.Objects.SubKinds; + +/// +/// This class represents a player character. +/// +public unsafe class PlayerCharacter : BattleChara { /// - /// This class represents a player character. + /// Initializes a new instance of the class. + /// This represents a player character. /// - public unsafe class PlayerCharacter : BattleChara + /// The address of this actor in memory. + internal PlayerCharacter(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// This represents a player character. - /// - /// The address of this actor in memory. - internal PlayerCharacter(IntPtr address) - : base(address) - { - } - - /// - /// Gets the current world of the character. - /// - public ExcelResolver CurrentWorld => new(this.Struct->Character.CurrentWorld); - - /// - /// Gets the home world of the character. - /// - public ExcelResolver HomeWorld => new(this.Struct->Character.HomeWorld); - - /// - /// Gets the target actor ID of the PlayerCharacter. - /// - public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID; } + + /// + /// Gets the current world of the character. + /// + public ExcelResolver CurrentWorld => new(this.Struct->Character.CurrentWorld); + + /// + /// Gets the home world of the character. + /// + public ExcelResolver HomeWorld => new(this.Struct->Character.HomeWorld); + + /// + /// Gets the target actor ID of the PlayerCharacter. + /// + public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID; } diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index d9372674c..ea0eff382 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -4,161 +4,160 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; -namespace Dalamud.Game.ClientState.Objects +namespace Dalamud.Game.ClientState.Objects; + +/// +/// Get and set various kinds of targets for the player. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed unsafe class TargetManager { + private readonly ClientStateAddressResolver address; + /// - /// Get and set various kinds of targets for the player. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed unsafe class TargetManager + /// The ClientStateAddressResolver instance. + internal TargetManager(ClientStateAddressResolver addressResolver) { - private readonly ClientStateAddressResolver address; - - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - internal TargetManager(ClientStateAddressResolver addressResolver) - { - this.address = addressResolver; - } - - /// - /// Gets the address of the target manager. - /// - public IntPtr Address => this.address.TargetManager; - - /// - /// Gets or sets the current target. - /// - public GameObject? Target - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->Target); - set => this.SetTarget(value); - } - - /// - /// Gets or sets the mouseover target. - /// - public GameObject? MouseOverTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget); - set => this.SetMouseOverTarget(value); - } - - /// - /// Gets or sets the focus target. - /// - public GameObject? FocusTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->FocusTarget); - set => this.SetFocusTarget(value); - } - - /// - /// Gets or sets the previous target. - /// - public GameObject? PreviousTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget); - set => this.SetPreviousTarget(value); - } - - /// - /// Gets or sets the soft target. - /// - public GameObject? SoftTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->SoftTarget); - set => this.SetSoftTarget(value); - } - - private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address; - - /// - /// Sets the current target. - /// - /// Actor to target. - public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the mouseover target. - /// - /// Actor to target. - public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the focus target. - /// - /// Actor to target. - public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the previous target. - /// - /// Actor to target. - public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the soft target. - /// - /// Actor to target. - public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the current target. - /// - /// Actor (address) to target. - public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the mouseover target. - /// - /// Actor (address) to target. - public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the focus target. - /// - /// Actor (address) to target. - public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the previous target. - /// - /// Actor (address) to target. - public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the soft target. - /// - /// Actor (address) to target. - public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Clears the current target. - /// - public void ClearTarget() => this.SetTarget(IntPtr.Zero); - - /// - /// Clears the mouseover target. - /// - public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero); - - /// - /// Clears the focus target. - /// - public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero); - - /// - /// Clears the previous target. - /// - public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero); - - /// - /// Clears the soft target. - /// - public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero); + this.address = addressResolver; } + + /// + /// Gets the address of the target manager. + /// + public IntPtr Address => this.address.TargetManager; + + /// + /// Gets or sets the current target. + /// + public GameObject? Target + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->Target); + set => this.SetTarget(value); + } + + /// + /// Gets or sets the mouseover target. + /// + public GameObject? MouseOverTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget); + set => this.SetMouseOverTarget(value); + } + + /// + /// Gets or sets the focus target. + /// + public GameObject? FocusTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->FocusTarget); + set => this.SetFocusTarget(value); + } + + /// + /// Gets or sets the previous target. + /// + public GameObject? PreviousTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget); + set => this.SetPreviousTarget(value); + } + + /// + /// Gets or sets the soft target. + /// + public GameObject? SoftTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->SoftTarget); + set => this.SetSoftTarget(value); + } + + private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address; + + /// + /// Sets the current target. + /// + /// Actor to target. + public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the mouseover target. + /// + /// Actor to target. + public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the focus target. + /// + /// Actor to target. + public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the previous target. + /// + /// Actor to target. + public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the soft target. + /// + /// Actor to target. + public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the current target. + /// + /// Actor (address) to target. + public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the mouseover target. + /// + /// Actor (address) to target. + public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the focus target. + /// + /// Actor (address) to target. + public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the previous target. + /// + /// Actor (address) to target. + public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the soft target. + /// + /// Actor (address) to target. + public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Clears the current target. + /// + public void ClearTarget() => this.SetTarget(IntPtr.Zero); + + /// + /// Clears the mouseover target. + /// + public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero); + + /// + /// Clears the focus target. + /// + public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero); + + /// + /// Clears the previous target. + /// + public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero); + + /// + /// Clears the soft target. + /// + public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero); } diff --git a/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs b/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs index 633b5eb38..8db830491 100644 --- a/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs +++ b/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs @@ -2,66 +2,65 @@ using System; using Dalamud.Game.ClientState.Statuses; -namespace Dalamud.Game.ClientState.Objects.Types +namespace Dalamud.Game.ClientState.Objects.Types; + +/// +/// This class represents the battle characters. +/// +public unsafe class BattleChara : Character { /// - /// This class represents the battle characters. + /// Initializes a new instance of the class. + /// This represents a battle character. /// - public unsafe class BattleChara : Character + /// The address of this character in memory. + internal BattleChara(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// This represents a battle character. - /// - /// The address of this character in memory. - internal BattleChara(IntPtr address) - : base(address) - { - } - - /// - /// Gets the current status effects. - /// - public StatusList StatusList => new(&this.Struct->StatusManager); - - /// - /// Gets a value indicating whether the chara is currently casting. - /// - public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0; - - /// - /// Gets a value indicating whether the cast is interruptible. - /// - public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0; - - /// - /// Gets the spell action type of the spell being cast by the actor. - /// - public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType; - - /// - /// Gets the spell action ID of the spell being cast by the actor. - /// - public uint CastActionId => this.Struct->SpellCastInfo.ActionID; - - /// - /// Gets the object ID of the target currently being cast at by the chara. - /// - public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID; - - /// - /// Gets the current casting time of the spell being cast by the chara. - /// - public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime; - - /// - /// Gets the total casting time of the spell being cast by the chara. - /// - public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime; - - /// - /// Gets the underlying structure. - /// - private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address; } + + /// + /// Gets the current status effects. + /// + public StatusList StatusList => new(&this.Struct->StatusManager); + + /// + /// Gets a value indicating whether the chara is currently casting. + /// + public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0; + + /// + /// Gets a value indicating whether the cast is interruptible. + /// + public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0; + + /// + /// Gets the spell action type of the spell being cast by the actor. + /// + public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType; + + /// + /// Gets the spell action ID of the spell being cast by the actor. + /// + public uint CastActionId => this.Struct->SpellCastInfo.ActionID; + + /// + /// Gets the object ID of the target currently being cast at by the chara. + /// + public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID; + + /// + /// Gets the current casting time of the spell being cast by the chara. + /// + public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime; + + /// + /// Gets the total casting time of the spell being cast by the chara. + /// + public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime; + + /// + /// Gets the underlying structure. + /// + private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address; } diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index ff412d8e5..77d2c3e57 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -5,102 +5,101 @@ using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -namespace Dalamud.Game.ClientState.Objects.Types +namespace Dalamud.Game.ClientState.Objects.Types; + +/// +/// This class represents the base for non-static entities. +/// +public unsafe class Character : GameObject { /// - /// This class represents the base for non-static entities. + /// Initializes a new instance of the class. + /// This represents a non-static entity. /// - public unsafe class Character : GameObject + /// The address of this character in memory. + internal Character(IntPtr address) + : base(address) { - /// - /// Initializes a new instance of the class. - /// This represents a non-static entity. - /// - /// The address of this character in memory. - internal Character(IntPtr address) - : base(address) - { - } - - /// - /// Gets the current HP of this Chara. - /// - public uint CurrentHp => this.Struct->Health; - - /// - /// Gets the maximum HP of this Chara. - /// - public uint MaxHp => this.Struct->MaxHealth; - - /// - /// Gets the current MP of this Chara. - /// - public uint CurrentMp => this.Struct->Mana; - - /// - /// Gets the maximum MP of this Chara. - /// - public uint MaxMp => this.Struct->MaxMana; - - /// - /// Gets the current GP of this Chara. - /// - public uint CurrentGp => this.Struct->GatheringPoints; - - /// - /// Gets the maximum GP of this Chara. - /// - public uint MaxGp => this.Struct->MaxGatheringPoints; - - /// - /// Gets the current CP of this Chara. - /// - public uint CurrentCp => this.Struct->CraftingPoints; - - /// - /// Gets the maximum CP of this Chara. - /// - public uint MaxCp => this.Struct->MaxCraftingPoints; - - /// - /// Gets the ClassJob of this Chara. - /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); - - /// - /// Gets the level of this Chara. - /// - public byte Level => this.Struct->Level; - - /// - /// Gets a byte array describing the visual appearance of this Chara. - /// Indexed by . - /// - public byte[] Customize => MemoryHelper.Read((IntPtr)this.Struct->CustomizeData, 28); - - /// - /// Gets the Free Company tag of this chara. - /// - public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6); - - /// - /// Gets the target object ID of the character. - /// - public override uint TargetObjectId => this.Struct->TargetObjectID; - - /// - /// Gets the name ID of the character. - /// - public uint NameId => this.Struct->NameID; - - /// - /// Gets the status flags. - /// - public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags; - - /// - /// Gets the underlying structure. - /// - private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address; } + + /// + /// Gets the current HP of this Chara. + /// + public uint CurrentHp => this.Struct->Health; + + /// + /// Gets the maximum HP of this Chara. + /// + public uint MaxHp => this.Struct->MaxHealth; + + /// + /// Gets the current MP of this Chara. + /// + public uint CurrentMp => this.Struct->Mana; + + /// + /// Gets the maximum MP of this Chara. + /// + public uint MaxMp => this.Struct->MaxMana; + + /// + /// Gets the current GP of this Chara. + /// + public uint CurrentGp => this.Struct->GatheringPoints; + + /// + /// Gets the maximum GP of this Chara. + /// + public uint MaxGp => this.Struct->MaxGatheringPoints; + + /// + /// Gets the current CP of this Chara. + /// + public uint CurrentCp => this.Struct->CraftingPoints; + + /// + /// Gets the maximum CP of this Chara. + /// + public uint MaxCp => this.Struct->MaxCraftingPoints; + + /// + /// Gets the ClassJob of this Chara. + /// + public ExcelResolver ClassJob => new(this.Struct->ClassJob); + + /// + /// Gets the level of this Chara. + /// + public byte Level => this.Struct->Level; + + /// + /// Gets a byte array describing the visual appearance of this Chara. + /// Indexed by . + /// + public byte[] Customize => MemoryHelper.Read((IntPtr)this.Struct->CustomizeData, 28); + + /// + /// Gets the Free Company tag of this chara. + /// + public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6); + + /// + /// Gets the target object ID of the character. + /// + public override uint TargetObjectId => this.Struct->TargetObjectID; + + /// + /// Gets the name ID of the character. + /// + public uint NameId => this.Struct->NameID; + + /// + /// Gets the status flags. + /// + public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags; + + /// + /// Gets the underlying structure. + /// + private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address; } diff --git a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs index 6cd84e92b..199522b9a 100644 --- a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs +++ b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs @@ -5,171 +5,170 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -namespace Dalamud.Game.ClientState.Objects.Types +namespace Dalamud.Game.ClientState.Objects.Types; + +/// +/// This class represents a GameObject in FFXIV. +/// +public unsafe partial class GameObject : IEquatable { /// - /// This class represents a GameObject in FFXIV. + /// IDs of non-networked GameObjects. /// - public unsafe partial class GameObject : IEquatable + public const uint InvalidGameObjectId = 0xE0000000; + + /// + /// Initializes a new instance of the class. + /// + /// The address of this game object in memory. + internal GameObject(IntPtr address) { - /// - /// IDs of non-networked GameObjects. - /// - public const uint InvalidGameObjectId = 0xE0000000; - - /// - /// Initializes a new instance of the class. - /// - /// The address of this game object in memory. - internal GameObject(IntPtr address) - { - this.Address = address; - } - - /// - /// Gets the address of the game object in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the Dalamud instance. - /// - private protected Dalamud Dalamud { get; } - - /// - /// This allows you to if (obj) {...} to check for validity. - /// - /// The actor to check. - /// True or false. - public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject); - - public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2) - { - // Using == results in a stack overflow. - if (gameObject1 is null || gameObject2 is null) - return Equals(gameObject1, gameObject2); - - return gameObject1.Equals(gameObject2); - } - - public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2); - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// The actor to check. - /// True or false. - public static bool IsValid(GameObject? actor) - { - var clientState = Service.Get(); - - if (actor is null) - return false; - - if (clientState.LocalContentId == 0) - return false; - - return true; - } - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// True or false. - public bool IsValid() => IsValid(this); - - /// - bool IEquatable.Equals(GameObject other) => this.ObjectId == other?.ObjectId; - - /// - public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as GameObject); - - /// - public override int GetHashCode() => this.ObjectId.GetHashCode(); + this.Address = address; } /// - /// This class represents a basic actor (GameObject) in FFXIV. + /// Gets the address of the game object in memory. /// - public unsafe partial class GameObject + public IntPtr Address { get; } + + /// + /// Gets the Dalamud instance. + /// + private protected Dalamud Dalamud { get; } + + /// + /// This allows you to if (obj) {...} to check for validity. + /// + /// The actor to check. + /// True or false. + public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject); + + public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2) { - /// - /// Gets the name of this . - /// - public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64); + // Using == results in a stack overflow. + if (gameObject1 is null || gameObject2 is null) + return Equals(gameObject1, gameObject2); - /// - /// Gets the object ID of this . - /// - public uint ObjectId => this.Struct->ObjectID; - - /// - /// Gets the data ID for linking to other respective game data. - /// - public uint DataId => this.Struct->DataID; - - /// - /// Gets the ID of this GameObject's owner. - /// - public uint OwnerId => this.Struct->OwnerID; - - /// - /// Gets the entity kind of this . - /// See the ObjectKind enum for possible values. - /// - public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind; - - /// - /// Gets the sub kind of this Actor. - /// - public byte SubKind => this.Struct->SubKind; - - /// - /// Gets the X distance from the local player in yalms. - /// - public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX; - - /// - /// Gets the Y distance from the local player in yalms. - /// - public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ; - - /// - /// Gets the position of this . - /// - public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z); - - /// - /// Gets the rotation of this . - /// This ranges from -pi to pi radians. - /// - public float Rotation => this.Struct->Rotation; - - /// - /// Gets the hitbox radius of this . - /// - public float HitboxRadius => this.Struct->HitboxRadius; - - /// - /// Gets the current target of the game object. - /// - public virtual uint TargetObjectId => 0; - - /// - /// Gets the target object of the game object. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - // TODO: Fix for non-networked GameObjects - public virtual GameObject? TargetObject => Service.Get().SearchById(this.TargetObjectId); - - /// - /// Gets the underlying structure. - /// - private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address; - - /// - public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}"; + return gameObject1.Equals(gameObject2); } + + public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2); + + /// + /// Gets a value indicating whether this actor is still valid in memory. + /// + /// The actor to check. + /// True or false. + public static bool IsValid(GameObject? actor) + { + var clientState = Service.Get(); + + if (actor is null) + return false; + + if (clientState.LocalContentId == 0) + return false; + + return true; + } + + /// + /// Gets a value indicating whether this actor is still valid in memory. + /// + /// True or false. + public bool IsValid() => IsValid(this); + + /// + bool IEquatable.Equals(GameObject other) => this.ObjectId == other?.ObjectId; + + /// + public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as GameObject); + + /// + public override int GetHashCode() => this.ObjectId.GetHashCode(); +} + +/// +/// This class represents a basic actor (GameObject) in FFXIV. +/// +public unsafe partial class GameObject +{ + /// + /// Gets the name of this . + /// + public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64); + + /// + /// Gets the object ID of this . + /// + public uint ObjectId => this.Struct->ObjectID; + + /// + /// Gets the data ID for linking to other respective game data. + /// + public uint DataId => this.Struct->DataID; + + /// + /// Gets the ID of this GameObject's owner. + /// + public uint OwnerId => this.Struct->OwnerID; + + /// + /// Gets the entity kind of this . + /// See the ObjectKind enum for possible values. + /// + public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind; + + /// + /// Gets the sub kind of this Actor. + /// + public byte SubKind => this.Struct->SubKind; + + /// + /// Gets the X distance from the local player in yalms. + /// + public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX; + + /// + /// Gets the Y distance from the local player in yalms. + /// + public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ; + + /// + /// Gets the position of this . + /// + public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z); + + /// + /// Gets the rotation of this . + /// This ranges from -pi to pi radians. + /// + public float Rotation => this.Struct->Rotation; + + /// + /// Gets the hitbox radius of this . + /// + public float HitboxRadius => this.Struct->HitboxRadius; + + /// + /// Gets the current target of the game object. + /// + public virtual uint TargetObjectId => 0; + + /// + /// Gets the target object of the game object. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + // TODO: Fix for non-networked GameObjects + public virtual GameObject? TargetObject => Service.Get().SearchById(this.TargetObjectId); + + /// + /// Gets the underlying structure. + /// + private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address; + + /// + public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}"; } diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index bd303401f..2b9de5dd8 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -7,178 +7,177 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Party +namespace Dalamud.Game.ClientState.Party; + +/// +/// This collection represents the actors present in your party or alliance. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed unsafe partial class PartyList { + private const int GroupLength = 8; + private const int AllianceLength = 20; + + private readonly ClientStateAddressResolver address; + /// - /// This collection represents the actors present in your party or alliance. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed unsafe partial class PartyList + /// Client state address resolver. + internal PartyList(ClientStateAddressResolver addressResolver) { - private const int GroupLength = 8; - private const int AllianceLength = 20; + this.address = addressResolver; - private readonly ClientStateAddressResolver address; - - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal PartyList(ClientStateAddressResolver addressResolver) - { - this.address = addressResolver; - - Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); - } - - /// - /// Gets the amount of party members the local player has. - /// - public int Length => this.GroupManagerStruct->MemberCount; - - /// - /// Gets the index of the party leader. - /// - public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex; - - /// - /// Gets a value indicating whether this group is an alliance. - /// - public bool IsAlliance => this.GroupManagerStruct->IsAlliance; - - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManagerAddress => this.address.GroupManager; - - /// - /// Gets the address of the party list within the group manager. - /// - public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers; - - /// - /// Gets the address of the alliance member list within the group manager. - /// - public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers; - - private static int PartyMemberSize { get; } = Marshal.SizeOf(); - - private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; - - /// - /// Get a party member at the specified spawn index. - /// - /// Spawn index. - /// A at the specified spawn index. - public PartyMember? this[int index] - { - get - { - // Normally using Length results in a recursion crash, however we know the party size via ptr. - if (index < 0 || index >= this.Length) - return null; - - if (this.Length > GroupLength) - { - var addr = this.GetAllianceMemberAddress(index); - return this.CreateAllianceMemberReference(addr); - } - else - { - var addr = this.GetPartyMemberAddress(index); - return this.CreatePartyMemberReference(addr); - } - } - } - - /// - /// Gets the address of the party member at the specified index of the party list. - /// - /// The index of the party member. - /// The memory address of the party member. - public IntPtr GetPartyMemberAddress(int index) - { - if (index < 0 || index >= GroupLength) - return IntPtr.Zero; - - return this.GroupListAddress + (index * PartyMemberSize); - } - - /// - /// Create a reference to an FFXIV party member. - /// - /// The address of the party member in memory. - /// The party member object containing the requested data. - public PartyMember? CreatePartyMemberReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - return new PartyMember(address); - } - - /// - /// Gets the address of the alliance member at the specified index of the alliance list. - /// - /// The index of the alliance member. - /// The memory address of the alliance member. - public IntPtr GetAllianceMemberAddress(int index) - { - if (index < 0 || index >= AllianceLength) - return IntPtr.Zero; - - return this.AllianceListAddress + (index * PartyMemberSize); - } - - /// - /// Create a reference to an FFXIV alliance member. - /// - /// The address of the alliance member in memory. - /// The party member object containing the requested data. - public PartyMember? CreateAllianceMemberReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - return new PartyMember(address); - } + Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); } /// - /// This collection represents the party members present in your party or alliance. + /// Gets the amount of party members the local player has. /// - public sealed partial class PartyList : IReadOnlyCollection - { - /// - int IReadOnlyCollection.Count => this.Length; + public int Length => this.GroupManagerStruct->MemberCount; - /// - public IEnumerator GetEnumerator() + /// + /// Gets the index of the party leader. + /// + public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex; + + /// + /// Gets a value indicating whether this group is an alliance. + /// + public bool IsAlliance => this.GroupManagerStruct->IsAlliance; + + /// + /// Gets the address of the Group Manager. + /// + public IntPtr GroupManagerAddress => this.address.GroupManager; + + /// + /// Gets the address of the party list within the group manager. + /// + public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers; + + /// + /// Gets the address of the alliance member list within the group manager. + /// + public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers; + + private static int PartyMemberSize { get; } = Marshal.SizeOf(); + + private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; + + /// + /// Get a party member at the specified spawn index. + /// + /// Spawn index. + /// A at the specified spawn index. + public PartyMember? this[int index] + { + get { // Normally using Length results in a recursion crash, however we know the party size via ptr. - for (var i = 0; i < this.Length; i++) + if (index < 0 || index >= this.Length) + return null; + + if (this.Length > GroupLength) { - var member = this[i]; - - if (member == null) - break; - - yield return member; + var addr = this.GetAllianceMemberAddress(index); + return this.CreateAllianceMemberReference(addr); + } + else + { + var addr = this.GetPartyMemberAddress(index); + return this.CreatePartyMemberReference(addr); } } + } - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// + /// Gets the address of the party member at the specified index of the party list. + /// + /// The index of the party member. + /// The memory address of the party member. + public IntPtr GetPartyMemberAddress(int index) + { + if (index < 0 || index >= GroupLength) + return IntPtr.Zero; + + return this.GroupListAddress + (index * PartyMemberSize); + } + + /// + /// Create a reference to an FFXIV party member. + /// + /// The address of the party member in memory. + /// The party member object containing the requested data. + public PartyMember? CreatePartyMemberReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + return new PartyMember(address); + } + + /// + /// Gets the address of the alliance member at the specified index of the alliance list. + /// + /// The index of the alliance member. + /// The memory address of the alliance member. + public IntPtr GetAllianceMemberAddress(int index) + { + if (index < 0 || index >= AllianceLength) + return IntPtr.Zero; + + return this.AllianceListAddress + (index * PartyMemberSize); + } + + /// + /// Create a reference to an FFXIV alliance member. + /// + /// The address of the alliance member in memory. + /// The party member object containing the requested data. + public PartyMember? CreateAllianceMemberReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + return new PartyMember(address); } } + +/// +/// This collection represents the party members present in your party or alliance. +/// +public sealed partial class PartyList : IReadOnlyCollection +{ + /// + int IReadOnlyCollection.Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + // Normally using Length results in a recursion crash, however we know the party size via ptr. + for (var i = 0; i < this.Length; i++) + { + var member = this[i]; + + if (member == null) + break; + + yield return member; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +} diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index 9d70592f7..889e8479a 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -9,105 +9,104 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; using JetBrains.Annotations; -namespace Dalamud.Game.ClientState.Party +namespace Dalamud.Game.ClientState.Party; + +/// +/// This class represents a party member in the group manager. +/// +public unsafe class PartyMember { /// - /// This class represents a party member in the group manager. + /// Initializes a new instance of the class. /// - public unsafe class PartyMember + /// Address of the party member. + internal PartyMember(IntPtr address) { - /// - /// Initializes a new instance of the class. - /// - /// Address of the party member. - internal PartyMember(IntPtr address) - { - this.Address = address; - } - - /// - /// Gets the address of this party member in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets a list of buffs or debuffs applied to this party member. - /// - public StatusList Statuses => new(&this.Struct->StatusManager); - - /// - /// Gets the position of the party member. - /// - public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z); - - /// - /// Gets the content ID of the party member. - /// - public long ContentId => this.Struct->ContentID; - - /// - /// Gets the actor ID of this party member. - /// - public uint ObjectId => this.Struct->ObjectID; - - /// - /// Gets the actor associated with this buddy. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); - - /// - /// Gets the current HP of this party member. - /// - public uint CurrentHP => this.Struct->CurrentHP; - - /// - /// Gets the maximum HP of this party member. - /// - public uint MaxHP => this.Struct->MaxHP; - - /// - /// Gets the current MP of this party member. - /// - public ushort CurrentMP => this.Struct->CurrentMP; - - /// - /// Gets the maximum MP of this party member. - /// - public ushort MaxMP => this.Struct->MaxMP; - - /// - /// Gets the territory this party member is located in. - /// - public ExcelResolver Territory => new(this.Struct->TerritoryType); - - /// - /// Gets the World this party member resides in. - /// - public ExcelResolver World => new(this.Struct->HomeWorld); - - /// - /// Gets the displayname of this party member. - /// - public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40); - - /// - /// Gets the sex of this party member. - /// - public byte Sex => this.Struct->Sex; - - /// - /// Gets the classjob of this party member. - /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); - - /// - /// Gets the level of this party member. - /// - public byte Level => this.Struct->Level; - - private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address; + this.Address = address; } + + /// + /// Gets the address of this party member in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets a list of buffs or debuffs applied to this party member. + /// + public StatusList Statuses => new(&this.Struct->StatusManager); + + /// + /// Gets the position of the party member. + /// + public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z); + + /// + /// Gets the content ID of the party member. + /// + public long ContentId => this.Struct->ContentID; + + /// + /// Gets the actor ID of this party member. + /// + public uint ObjectId => this.Struct->ObjectID; + + /// + /// Gets the actor associated with this buddy. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); + + /// + /// Gets the current HP of this party member. + /// + public uint CurrentHP => this.Struct->CurrentHP; + + /// + /// Gets the maximum HP of this party member. + /// + public uint MaxHP => this.Struct->MaxHP; + + /// + /// Gets the current MP of this party member. + /// + public ushort CurrentMP => this.Struct->CurrentMP; + + /// + /// Gets the maximum MP of this party member. + /// + public ushort MaxMP => this.Struct->MaxMP; + + /// + /// Gets the territory this party member is located in. + /// + public ExcelResolver Territory => new(this.Struct->TerritoryType); + + /// + /// Gets the World this party member resides in. + /// + public ExcelResolver World => new(this.Struct->HomeWorld); + + /// + /// Gets the displayname of this party member. + /// + public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40); + + /// + /// Gets the sex of this party member. + /// + public byte Sex => this.Struct->Sex; + + /// + /// Gets the classjob of this party member. + /// + public ExcelResolver ClassJob => new(this.Struct->ClassJob); + + /// + /// Gets the level of this party member. + /// + public byte Level => this.Struct->Level; + + private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address; } diff --git a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs index 5a5af1080..5cc36a652 100644 --- a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs +++ b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs @@ -1,31 +1,30 @@ using Dalamud.Data; using Lumina.Excel; -namespace Dalamud.Game.ClientState.Resolvers +namespace Dalamud.Game.ClientState.Resolvers; + +/// +/// This object resolves a rowID within an Excel sheet. +/// +/// The type of Lumina sheet to resolve. +public class ExcelResolver where T : ExcelRow { /// - /// This object resolves a rowID within an Excel sheet. + /// Initializes a new instance of the class. /// - /// The type of Lumina sheet to resolve. - public class ExcelResolver where T : ExcelRow + /// The ID of the classJob. + internal ExcelResolver(uint id) { - /// - /// Initializes a new instance of the class. - /// - /// The ID of the classJob. - internal ExcelResolver(uint id) - { - this.Id = id; - } - - /// - /// Gets the ID to be resolved. - /// - public uint Id { get; } - - /// - /// Gets GameData linked to this excel row. - /// - public T GameData => Service.Get().GetExcelSheet().GetRow(this.Id); + this.Id = id; } + + /// + /// Gets the ID to be resolved. + /// + public uint Id { get; } + + /// + /// Gets GameData linked to this excel row. + /// + public T GameData => Service.Get().GetExcelSheet().GetRow(this.Id); } diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index 7dd88f8eb..4373186a4 100644 --- a/Dalamud/Game/ClientState/Statuses/Status.cs +++ b/Dalamud/Game/ClientState/Statuses/Status.cs @@ -4,65 +4,64 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Resolvers; -namespace Dalamud.Game.ClientState.Statuses +namespace Dalamud.Game.ClientState.Statuses; + +/// +/// This class represents a status effect an actor is afflicted by. +/// +public unsafe class Status { /// - /// This class represents a status effect an actor is afflicted by. + /// Initializes a new instance of the class. /// - public unsafe class Status + /// Status address. + internal Status(IntPtr address) { - /// - /// Initializes a new instance of the class. - /// - /// Status address. - internal Status(IntPtr address) - { - this.Address = address; - } - - /// - /// Gets the address of the status in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the status ID of this status. - /// - public uint StatusId => this.Struct->StatusID; - - /// - /// Gets the GameData associated with this status. - /// - public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver(this.Struct->StatusID).GameData; - - /// - /// Gets the parameter value of the status. - /// - public byte Param => this.Struct->Param; - - /// - /// Gets the stack count of this status. - /// - public byte StackCount => this.Struct->StackCount; - - /// - /// Gets the time remaining of this status. - /// - public float RemainingTime => this.Struct->RemainingTime; - - /// - /// Gets the source ID of this status. - /// - public uint SourceID => this.Struct->SourceID; - - /// - /// Gets the source actor associated with this status. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - public GameObject? SourceObject => Service.Get().SearchById(this.SourceID); - - private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; + this.Address = address; } + + /// + /// Gets the address of the status in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the status ID of this status. + /// + public uint StatusId => this.Struct->StatusID; + + /// + /// Gets the GameData associated with this status. + /// + public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver(this.Struct->StatusID).GameData; + + /// + /// Gets the parameter value of the status. + /// + public byte Param => this.Struct->Param; + + /// + /// Gets the stack count of this status. + /// + public byte StackCount => this.Struct->StackCount; + + /// + /// Gets the time remaining of this status. + /// + public float RemainingTime => this.Struct->RemainingTime; + + /// + /// Gets the source ID of this status. + /// + public uint SourceID => this.Struct->SourceID; + + /// + /// Gets the source actor associated with this status. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + public GameObject? SourceObject => Service.Get().SearchById(this.SourceID); + + private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; } diff --git a/Dalamud/Game/ClientState/Statuses/StatusList.cs b/Dalamud/Game/ClientState/Statuses/StatusList.cs index 591988e35..bcff50360 100644 --- a/Dalamud/Game/ClientState/Statuses/StatusList.cs +++ b/Dalamud/Game/ClientState/Statuses/StatusList.cs @@ -3,159 +3,158 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace Dalamud.Game.ClientState.Statuses +namespace Dalamud.Game.ClientState.Statuses; + +/// +/// This collection represents the status effects an actor is afflicted by. +/// +public sealed unsafe partial class StatusList { + private const int StatusListLength = 30; + /// - /// This collection represents the status effects an actor is afflicted by. + /// Initializes a new instance of the class. /// - public sealed unsafe partial class StatusList + /// Address of the status list. + internal StatusList(IntPtr address) { - private const int StatusListLength = 30; + this.Address = address; + } - /// - /// Initializes a new instance of the class. - /// - /// Address of the status list. - internal StatusList(IntPtr address) + /// + /// Initializes a new instance of the class. + /// + /// Pointer to the status list. + internal unsafe StatusList(void* pointer) + : this((IntPtr)pointer) + { + } + + /// + /// Gets the address of the status list in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the amount of status effect slots the actor has. + /// + public int Length => StatusListLength; + + private static int StatusSize { get; } = Marshal.SizeOf(); + + private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address; + + /// + /// Get a status effect at the specified index. + /// + /// Status Index. + /// The status at the specified index. + public Status? this[int index] + { + get { - this.Address = address; - } - - /// - /// Initializes a new instance of the class. - /// - /// Pointer to the status list. - internal unsafe StatusList(void* pointer) - : this((IntPtr)pointer) - { - } - - /// - /// Gets the address of the status list in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the amount of status effect slots the actor has. - /// - public int Length => StatusListLength; - - private static int StatusSize { get; } = Marshal.SizeOf(); - - private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address; - - /// - /// Get a status effect at the specified index. - /// - /// Status Index. - /// The status at the specified index. - public Status? this[int index] - { - get - { - if (index < 0 || index > StatusListLength) - return null; - - var addr = this.GetStatusAddress(index); - return CreateStatusReference(addr); - } - } - - /// - /// Create a reference to an FFXIV actor status list. - /// - /// The address of the status list in memory. - /// The status object containing the requested data. - public static StatusList? CreateStatusListReference(IntPtr address) - { - // The use case for CreateStatusListReference and CreateStatusReference to be static is so - // fake status lists can be generated. Since they aren't exposed as services, it's either - // here or somewhere else. - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) + if (index < 0 || index > StatusListLength) return null; - if (address == IntPtr.Zero) - return null; - - return new StatusList(address); - } - - /// - /// Create a reference to an FFXIV actor status. - /// - /// The address of the status effect in memory. - /// The status object containing the requested data. - public static Status? CreateStatusReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - return new Status(address); - } - - /// - /// Gets the address of the party member at the specified index of the party list. - /// - /// The index of the party member. - /// The memory address of the party member. - public IntPtr GetStatusAddress(int index) - { - if (index < 0 || index >= StatusListLength) - return IntPtr.Zero; - - return (IntPtr)(this.Struct->Status + (index * StatusSize)); + var addr = this.GetStatusAddress(index); + return CreateStatusReference(addr); } } /// - /// This collection represents the status effects an actor is afflicted by. + /// Create a reference to an FFXIV actor status list. /// - public sealed partial class StatusList : IReadOnlyCollection, ICollection + /// The address of the status list in memory. + /// The status object containing the requested data. + public static StatusList? CreateStatusListReference(IntPtr address) { - /// - int IReadOnlyCollection.Count => this.Length; + // The use case for CreateStatusListReference and CreateStatusReference to be static is so + // fake status lists can be generated. Since they aren't exposed as services, it's either + // here or somewhere else. + var clientState = Service.Get(); - /// - int ICollection.Count => this.Length; + if (clientState.LocalContentId == 0) + return null; - /// - bool ICollection.IsSynchronized => false; + if (address == IntPtr.Zero) + return null; - /// - object ICollection.SyncRoot => this; + return new StatusList(address); + } - /// - public IEnumerator GetEnumerator() + /// + /// Create a reference to an FFXIV actor status. + /// + /// The address of the status effect in memory. + /// The status object containing the requested data. + public static Status? CreateStatusReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + return new Status(address); + } + + /// + /// Gets the address of the party member at the specified index of the party list. + /// + /// The index of the party member. + /// The memory address of the party member. + public IntPtr GetStatusAddress(int index) + { + if (index < 0 || index >= StatusListLength) + return IntPtr.Zero; + + return (IntPtr)(this.Struct->Status + (index * StatusSize)); + } +} + +/// +/// This collection represents the status effects an actor is afflicted by. +/// +public sealed partial class StatusList : IReadOnlyCollection, ICollection +{ + /// + int IReadOnlyCollection.Count => this.Length; + + /// + int ICollection.Count => this.Length; + + /// + bool ICollection.IsSynchronized => false; + + /// + object ICollection.SyncRoot => this; + + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < StatusListLength; i++) { - for (var i = 0; i < StatusListLength; i++) - { - var status = this[i]; + var status = this[i]; - if (status == null || status.StatusId == 0) - continue; + if (status == null || status.StatusId == 0) + continue; - yield return status; - } + yield return status; } + } - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - /// - void ICollection.CopyTo(Array array, int index) + /// + void ICollection.CopyTo(Array array, int index) + { + for (var i = 0; i < this.Length; i++) { - for (var i = 0; i < this.Length; i++) - { - array.SetValue(this[i], index); - index++; - } + array.SetValue(this[i], index); + index++; } } } diff --git a/Dalamud/Game/ClientState/Structs/StatusEffect.cs b/Dalamud/Game/ClientState/Structs/StatusEffect.cs index 6d4c2fd77..2a60a7d3b 100644 --- a/Dalamud/Game/ClientState/Structs/StatusEffect.cs +++ b/Dalamud/Game/ClientState/Structs/StatusEffect.cs @@ -1,36 +1,35 @@ using System.Runtime.InteropServices; -namespace Dalamud.Game.ClientState.Structs +namespace Dalamud.Game.ClientState.Structs; + +/// +/// Native memory representation of a FFXIV status effect. +/// +[StructLayout(LayoutKind.Sequential)] +public struct StatusEffect { /// - /// Native memory representation of a FFXIV status effect. + /// The effect ID. /// - [StructLayout(LayoutKind.Sequential)] - public struct StatusEffect - { - /// - /// The effect ID. - /// - public short EffectId; + public short EffectId; - /// - /// How many stacks are present. - /// - public byte StackCount; + /// + /// How many stacks are present. + /// + public byte StackCount; - /// - /// Additional parameters. - /// - public byte Param; + /// + /// Additional parameters. + /// + public byte Param; - /// - /// The duration remaining. - /// - public float Duration; + /// + /// The duration remaining. + /// + public float Duration; - /// - /// The ID of the actor that caused this effect. - /// - public int OwnerId; - } + /// + /// The ID of the actor that caused this effect. + /// + public int OwnerId; } diff --git a/Dalamud/Game/Command/CommandInfo.cs b/Dalamud/Game/Command/CommandInfo.cs index 0eb97177c..9b559599a 100644 --- a/Dalamud/Game/Command/CommandInfo.cs +++ b/Dalamud/Game/Command/CommandInfo.cs @@ -1,48 +1,47 @@ using System.Reflection; -namespace Dalamud.Game.Command +namespace Dalamud.Game.Command; + +/// +/// This class describes a registered command. +/// +public sealed class CommandInfo { /// - /// This class describes a registered command. + /// Initializes a new instance of the class. + /// Create a new CommandInfo with the provided handler. /// - public sealed class CommandInfo + /// The method to call when the command is run. + public CommandInfo(HandlerDelegate handler) { - /// - /// Initializes a new instance of the class. - /// Create a new CommandInfo with the provided handler. - /// - /// The method to call when the command is run. - public CommandInfo(HandlerDelegate handler) - { - this.Handler = handler; - this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name; - } - - /// - /// The function to be executed when the command is dispatched. - /// - /// The command itself. - /// The arguments supplied to the command, ready for parsing. - public delegate void HandlerDelegate(string command, string arguments); - - /// - /// Gets a which will be called when the command is dispatched. - /// - public HandlerDelegate Handler { get; } - - /// - /// Gets or sets the help message for this command. - /// - public string HelpMessage { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether if this command should be shown in the help output. - /// - public bool ShowInHelp { get; set; } = true; - - /// - /// Gets or sets the name of the assembly responsible for this command. - /// - internal string LoaderAssemblyName { get; set; } = string.Empty; + this.Handler = handler; + this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name; } + + /// + /// The function to be executed when the command is dispatched. + /// + /// The command itself. + /// The arguments supplied to the command, ready for parsing. + public delegate void HandlerDelegate(string command, string arguments); + + /// + /// Gets a which will be called when the command is dispatched. + /// + public HandlerDelegate Handler { get; } + + /// + /// Gets or sets the help message for this command. + /// + public string HelpMessage { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether if this command should be shown in the help output. + /// + public bool ShowInHelp { get; set; } = true; + + /// + /// Gets or sets the name of the assembly responsible for this command. + /// + internal string LoaderAssemblyName { get; set; } = string.Empty; } diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index b1038f294..ba88f446d 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -10,167 +10,166 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Command +namespace Dalamud.Game.Command; + +/// +/// This class manages registered in-game slash commands. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class CommandManager { + private readonly Dictionary commandMap = new(); + private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); + private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled); + private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); + private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled); + private readonly Regex commandRegexCn = new(@"^“(?.+)”(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); + private readonly Regex currentLangCommandRegex; + /// - /// This class manages registered in-game slash commands. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class CommandManager + internal CommandManager() { - private readonly Dictionary commandMap = new(); - private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); - private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled); - private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); - private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled); - private readonly Regex commandRegexCn = new(@"^“(?.+)”(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); - private readonly Regex currentLangCommandRegex; + var startInfo = Service.Get(); - /// - /// Initializes a new instance of the class. - /// - internal CommandManager() + this.currentLangCommandRegex = startInfo.Language switch { - var startInfo = Service.Get(); + ClientLanguage.Japanese => this.commandRegexJp, + ClientLanguage.English => this.commandRegexEn, + ClientLanguage.German => this.commandRegexDe, + ClientLanguage.French => this.commandRegexFr, + _ => this.currentLangCommandRegex, + }; - this.currentLangCommandRegex = startInfo.Language switch - { - ClientLanguage.Japanese => this.commandRegexJp, - ClientLanguage.English => this.commandRegexEn, - ClientLanguage.German => this.commandRegexDe, - ClientLanguage.French => this.commandRegexFr, - _ => this.currentLangCommandRegex, - }; + Service.Get().CheckMessageHandled += this.OnCheckMessageHandled; + } - Service.Get().CheckMessageHandled += this.OnCheckMessageHandled; - } + /// + /// Gets a read-only list of all registered commands. + /// + public ReadOnlyDictionary Commands => new(this.commandMap); - /// - /// Gets a read-only list of all registered commands. - /// - public ReadOnlyDictionary Commands => new(this.commandMap); + /// + /// Process a command in full. + /// + /// The full command string. + /// True if the command was found and dispatched. + public bool ProcessCommand(string content) + { + string command; + string argument; - /// - /// Process a command in full. - /// - /// The full command string. - /// True if the command was found and dispatched. - public bool ProcessCommand(string content) + var separatorPosition = content.IndexOf(' '); + if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) { - string command; - string argument; - - var separatorPosition = content.IndexOf(' '); - if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) + // If no space was found or ends with the space. Process them as a no argument + if (separatorPosition + 1 >= content.Length) { - // If no space was found or ends with the space. Process them as a no argument - if (separatorPosition + 1 >= content.Length) - { - // Remove the trailing space - command = content.Substring(0, separatorPosition); - } - else - { - command = content; - } - - argument = string.Empty; + // Remove the trailing space + command = content.Substring(0, separatorPosition); } else { - // e.g.) - // /testcommand arg1 - // => Total of 17 chars - // => command: 0-12 (12 chars) - // => argument: 13-17 (4 chars) - // => content.IndexOf(' ') == 12 - command = content.Substring(0, separatorPosition); - - var argStart = separatorPosition + 1; - argument = content[argStart..]; + command = content; } - if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found. - return false; + argument = string.Empty; + } + else + { + // e.g.) + // /testcommand arg1 + // => Total of 17 chars + // => command: 0-12 (12 chars) + // => argument: 13-17 (4 chars) + // => content.IndexOf(' ') == 12 + command = content.Substring(0, separatorPosition); - this.DispatchCommand(command, argument, handler); + var argStart = separatorPosition + 1; + argument = content[argStart..]; + } + + if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found. + return false; + + this.DispatchCommand(command, argument, handler); + return true; + } + + /// + /// Dispatch the handling of a command. + /// + /// The command to dispatch. + /// The provided arguments. + /// A object describing this command. + public void DispatchCommand(string command, string argument, CommandInfo info) + { + try + { + info.Handler(command, argument); + } + catch (Exception ex) + { + Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); + } + } + + /// + /// Add a command handler, which you can use to add your own custom commands to the in-game chat. + /// + /// The command to register. + /// A object describing the command. + /// If adding was successful. + public bool AddHandler(string command, CommandInfo info) + { + if (info == null) + throw new ArgumentNullException(nameof(info), "Command handler is null."); + + try + { + this.commandMap.Add(command, info); return true; } - - /// - /// Dispatch the handling of a command. - /// - /// The command to dispatch. - /// The provided arguments. - /// A object describing this command. - public void DispatchCommand(string command, string argument, CommandInfo info) + catch (ArgumentException) { - try - { - info.Handler(command, argument); - } - catch (Exception ex) - { - Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); - } + Log.Error("Command {CommandName} is already registered.", command); + return false; } + } - /// - /// Add a command handler, which you can use to add your own custom commands to the in-game chat. - /// - /// The command to register. - /// A object describing the command. - /// If adding was successful. - public bool AddHandler(string command, CommandInfo info) + /// + /// Remove a command from the command handlers. + /// + /// The command to remove. + /// If the removal was successful. + public bool RemoveHandler(string command) + { + return this.commandMap.Remove(command); + } + + private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) + { + if (type == XivChatType.ErrorMessage && senderId == 0) { - if (info == null) - throw new ArgumentNullException(nameof(info), "Command handler is null."); - - try + var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"]; + if (cmdMatch.Success) { - this.commandMap.Add(command, info); - return true; + // Yes, it's a chat command. + var command = cmdMatch.Value; + if (this.ProcessCommand(command)) isHandled = true; } - catch (ArgumentException) + else { - Log.Error("Command {CommandName} is already registered.", command); - return false; - } - } - - /// - /// Remove a command from the command handlers. - /// - /// The command to remove. - /// If the removal was successful. - public bool RemoveHandler(string command) - { - return this.commandMap.Remove(command); - } - - private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) - { - if (type == XivChatType.ErrorMessage && senderId == 0) - { - var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"]; + // Always match for china, since they patch in language files without changing the ClientLanguage. + cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"]; if (cmdMatch.Success) { - // Yes, it's a chat command. + // Yes, it's a Chinese fallback chat command. var command = cmdMatch.Value; if (this.ProcessCommand(command)) isHandled = true; } - else - { - // Always match for china, since they patch in language files without changing the ClientLanguage. - cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"]; - if (cmdMatch.Success) - { - // Yes, it's a Chinese fallback chat command. - var command = cmdMatch.Value; - if (this.ProcessCommand(command)) isHandled = true; - } - } } } } diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 38a978e1f..1e49a348e 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -16,301 +16,300 @@ using Dalamud.IoC.Internal; using Dalamud.Utility; using Serilog; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// This class represents the Framework of the native game client and grants access to various subsystems. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class Framework : IDisposable { + private static Stopwatch statsStopwatch = new(); + private Stopwatch updateStopwatch = new(); + + private bool tier2Initialized = false; + private bool tier3Initialized = false; + private bool tierInitError = false; + + private Hook updateHook; + private Hook destroyHook; + private Hook realDestroyHook; + /// - /// This class represents the Framework of the native game client and grants access to various subsystems. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class Framework : IDisposable + internal Framework() { - private static Stopwatch statsStopwatch = new(); - private Stopwatch updateStopwatch = new(); + this.Address = new FrameworkAddressResolver(); + this.Address.Setup(); - private bool tier2Initialized = false; - private bool tier3Initialized = false; - private bool tierInitError = false; - - private Hook updateHook; - private Hook destroyHook; - private Hook realDestroyHook; - - /// - /// Initializes a new instance of the class. - /// - internal Framework() + Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}"); + if (this.Address.BaseAddress == IntPtr.Zero) { - this.Address = new FrameworkAddressResolver(); - this.Address.Setup(); - - Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}"); - if (this.Address.BaseAddress == IntPtr.Zero) - { - throw new InvalidOperationException("Framework is not initalized yet."); - } - - // Hook virtual functions - this.HookVTable(); + throw new InvalidOperationException("Framework is not initalized yet."); } - /// - /// A delegate type used with the event. - /// - /// The Framework instance. - public delegate void OnUpdateDelegate(Framework framework); + // Hook virtual functions + this.HookVTable(); + } - /// - /// A delegate type used during the native Framework::destroy. - /// - /// The native Framework address. - /// A value indicating if the call was successful. - public delegate bool OnRealDestroyDelegate(IntPtr framework); + /// + /// A delegate type used with the event. + /// + /// The Framework instance. + public delegate void OnUpdateDelegate(Framework framework); - /// - /// A delegate type used during the native Framework::free. - /// - /// The native Framework address. - public delegate IntPtr OnDestroyDelegate(); + /// + /// A delegate type used during the native Framework::destroy. + /// + /// The native Framework address. + /// A value indicating if the call was successful. + public delegate bool OnRealDestroyDelegate(IntPtr framework); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate bool OnUpdateDetour(IntPtr framework); + /// + /// A delegate type used during the native Framework::free. + /// + /// The native Framework address. + public delegate IntPtr OnDestroyDelegate(); - private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate bool OnUpdateDetour(IntPtr framework); - /// - /// Event that gets fired every time the game framework updates. - /// - public event OnUpdateDelegate Update; + private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate - /// - /// Gets or sets a value indicating whether the collection of stats is enabled. - /// - public static bool StatsEnabled { get; set; } + /// + /// Event that gets fired every time the game framework updates. + /// + public event OnUpdateDelegate Update; - /// - /// Gets the stats history mapping. - /// - public static Dictionary> StatsHistory { get; } = new(); + /// + /// Gets or sets a value indicating whether the collection of stats is enabled. + /// + public static bool StatsEnabled { get; set; } - /// - /// Gets a raw pointer to the instance of Client::Framework. - /// - public FrameworkAddressResolver Address { get; } + /// + /// Gets the stats history mapping. + /// + public static Dictionary> StatsHistory { get; } = new(); - /// - /// Gets the last time that the Framework Update event was triggered. - /// - public DateTime LastUpdate { get; private set; } = DateTime.MinValue; + /// + /// Gets a raw pointer to the instance of Client::Framework. + /// + public FrameworkAddressResolver Address { get; } - /// - /// Gets the last time in UTC that the Framework Update event was triggered. - /// - public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue; + /// + /// Gets the last time that the Framework Update event was triggered. + /// + public DateTime LastUpdate { get; private set; } = DateTime.MinValue; - /// - /// Gets the delta between the last Framework Update and the currently executing one. - /// - public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero; + /// + /// Gets the last time in UTC that the Framework Update event was triggered. + /// + public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue; - /// - /// Gets or sets a value indicating whether to dispatch update events. - /// - internal bool DispatchUpdateEvents { get; set; } = true; + /// + /// Gets the delta between the last Framework Update and the currently executing one. + /// + public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero; - /// - /// Enable this module. - /// - public void Enable() + /// + /// Gets or sets a value indicating whether to dispatch update events. + /// + internal bool DispatchUpdateEvents { get; set; } = true; + + /// + /// Enable this module. + /// + public void Enable() + { + Service.Set(); + Service.Get().Enable(); + Service.Get().Enable(); + + this.updateHook.Enable(); + this.destroyHook.Enable(); + this.realDestroyHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + + this.updateHook?.Disable(); + this.destroyHook?.Disable(); + this.realDestroyHook?.Disable(); + Thread.Sleep(500); + + this.updateHook?.Dispose(); + this.destroyHook?.Dispose(); + this.realDestroyHook?.Dispose(); + + this.updateStopwatch.Reset(); + statsStopwatch.Reset(); + } + + private void HookVTable() + { + var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress); + // Virtual function layout: + // .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor + // .rdata:00000001411F1FE8 dq offset Xiv__Framework__init + // .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy + // .rdata:00000001411F1FF8 dq offset Xiv__Framework__free + // .rdata:00000001411F2000 dq offset Xiv__Framework__update + + var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4); + this.updateHook = new Hook(pUpdate, this.HandleFrameworkUpdate); + + var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3); + this.destroyHook = new Hook(pDestroy, this.HandleFrameworkDestroy); + + var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2); + this.realDestroyHook = new Hook(pRealDestroy, this.HandleRealDestroy); + } + + private bool HandleFrameworkUpdate(IntPtr framework) + { + // If any of the tier loads failed, just go to the original code. + if (this.tierInitError) + goto original; + + var dalamud = Service.Get(); + + // If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously + if (!this.tier2Initialized) { - Service.Set(); - Service.Get().Enable(); - Service.Get().Enable(); - - this.updateHook.Enable(); - this.destroyHook.Enable(); - this.realDestroyHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - - this.updateHook?.Disable(); - this.destroyHook?.Disable(); - this.realDestroyHook?.Disable(); - Thread.Sleep(500); - - this.updateHook?.Dispose(); - this.destroyHook?.Dispose(); - this.realDestroyHook?.Dispose(); - - this.updateStopwatch.Reset(); - statsStopwatch.Reset(); - } - - private void HookVTable() - { - var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress); - // Virtual function layout: - // .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor - // .rdata:00000001411F1FE8 dq offset Xiv__Framework__init - // .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy - // .rdata:00000001411F1FF8 dq offset Xiv__Framework__free - // .rdata:00000001411F2000 dq offset Xiv__Framework__update - - var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4); - this.updateHook = new Hook(pUpdate, this.HandleFrameworkUpdate); - - var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3); - this.destroyHook = new Hook(pDestroy, this.HandleFrameworkDestroy); - - var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2); - this.realDestroyHook = new Hook(pRealDestroy, this.HandleRealDestroy); - } - - private bool HandleFrameworkUpdate(IntPtr framework) - { - // If any of the tier loads failed, just go to the original code. - if (this.tierInitError) - goto original; - - var dalamud = Service.Get(); - - // If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously + this.tier2Initialized = dalamud.LoadTier2(); if (!this.tier2Initialized) - { - this.tier2Initialized = dalamud.LoadTier2(); - if (!this.tier2Initialized) - this.tierInitError = true; + this.tierInitError = true; - goto original; - } + goto original; + } - // Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui - if (!this.tier3Initialized && Service.GetNullable()?.IsReady == true) - { - this.tier3Initialized = dalamud.LoadTier3(); - if (!this.tier3Initialized) - this.tierInitError = true; + // Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui + if (!this.tier3Initialized && Service.GetNullable()?.IsReady == true) + { + this.tier3Initialized = dalamud.LoadTier3(); + if (!this.tier3Initialized) + this.tierInitError = true; - goto original; - } + goto original; + } + + try + { + Service.Get().UpdateQueue(); + Service.Get().UpdateQueue(); + Service.Get().UpdateQueue(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception while handling Framework::Update hook."); + } + + if (this.DispatchUpdateEvents) + { + this.updateStopwatch.Stop(); + this.UpdateDelta = TimeSpan.FromMilliseconds(this.updateStopwatch.ElapsedMilliseconds); + this.updateStopwatch.Restart(); + + this.LastUpdate = DateTime.Now; + this.LastUpdateUTC = DateTime.UtcNow; try { - Service.Get().UpdateQueue(); - Service.Get().UpdateQueue(); - Service.Get().UpdateQueue(); + if (StatsEnabled && this.Update != null) + { + // Stat Tracking for Framework Updates + var invokeList = this.Update.GetInvocationList(); + var notUpdated = StatsHistory.Keys.ToList(); + + // Individually invoke OnUpdate handlers and time them. + foreach (var d in invokeList) + { + statsStopwatch.Restart(); + d.Method.Invoke(d.Target, new object[] { this }); + statsStopwatch.Stop(); + + var key = $"{d.Target}::{d.Method.Name}"; + if (notUpdated.Contains(key)) + notUpdated.Remove(key); + + if (!StatsHistory.ContainsKey(key)) + StatsHistory.Add(key, new List()); + + StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); + + if (StatsHistory[key].Count > 1000) + { + StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); + } + } + + // Cleanup handlers that are no longer being called + foreach (var key in notUpdated) + { + if (StatsHistory[key].Count > 0) + { + StatsHistory[key].RemoveAt(0); + } + else + { + StatsHistory.Remove(key); + } + } + } + else + { + this.Update?.Invoke(this); + } } catch (Exception ex) { - Log.Error(ex, "Exception while handling Framework::Update hook."); + Log.Error(ex, "Exception while dispatching Framework::Update event."); } - - if (this.DispatchUpdateEvents) - { - this.updateStopwatch.Stop(); - this.UpdateDelta = TimeSpan.FromMilliseconds(this.updateStopwatch.ElapsedMilliseconds); - this.updateStopwatch.Restart(); - - this.LastUpdate = DateTime.Now; - this.LastUpdateUTC = DateTime.UtcNow; - - try - { - if (StatsEnabled && this.Update != null) - { - // Stat Tracking for Framework Updates - var invokeList = this.Update.GetInvocationList(); - var notUpdated = StatsHistory.Keys.ToList(); - - // Individually invoke OnUpdate handlers and time them. - foreach (var d in invokeList) - { - statsStopwatch.Restart(); - d.Method.Invoke(d.Target, new object[] { this }); - statsStopwatch.Stop(); - - var key = $"{d.Target}::{d.Method.Name}"; - if (notUpdated.Contains(key)) - notUpdated.Remove(key); - - if (!StatsHistory.ContainsKey(key)) - StatsHistory.Add(key, new List()); - - StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); - - if (StatsHistory[key].Count > 1000) - { - StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); - } - } - - // Cleanup handlers that are no longer being called - foreach (var key in notUpdated) - { - if (StatsHistory[key].Count > 0) - { - StatsHistory[key].RemoveAt(0); - } - else - { - StatsHistory.Remove(key); - } - } - } - else - { - this.Update?.Invoke(this); - } - } - catch (Exception ex) - { - Log.Error(ex, "Exception while dispatching Framework::Update event."); - } - } - - original: - return this.updateHook.Original(framework); } - private bool HandleRealDestroy(IntPtr framework) + original: + return this.updateHook.Original(framework); + } + + private bool HandleRealDestroy(IntPtr framework) + { + if (this.DispatchUpdateEvents) { - if (this.DispatchUpdateEvents) - { - Log.Information("Framework::Destroy!"); - - var dalamud = Service.Get(); - dalamud.DisposePlugins(); - - Log.Information("Framework::Destroy OK!"); - } - - this.DispatchUpdateEvents = false; - - return this.realDestroyHook.Original(framework); - } - - private IntPtr HandleFrameworkDestroy() - { - Log.Information("Framework::Free!"); - - // Store the pointer to the original trampoline location - var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original); + Log.Information("Framework::Destroy!"); var dalamud = Service.Get(); - dalamud.Unload(); - dalamud.WaitForUnloadFinish(); + dalamud.DisposePlugins(); - Log.Information("Framework::Free OK!"); - - // Return the original trampoline location to cleanly exit - return originalPtr; + Log.Information("Framework::Destroy OK!"); } + + this.DispatchUpdateEvents = false; + + return this.realDestroyHook.Original(framework); + } + + private IntPtr HandleFrameworkDestroy() + { + Log.Information("Framework::Free!"); + + // Store the pointer to the original trampoline location + var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original); + + var dalamud = Service.Get(); + dalamud.Unload(); + dalamud.WaitForUnloadFinish(); + + Log.Information("Framework::Free OK!"); + + // Return the original trampoline location to cleanly exit + return originalPtr; } } diff --git a/Dalamud/Game/FrameworkAddressResolver.cs b/Dalamud/Game/FrameworkAddressResolver.cs index 7bcae5045..6dea4f579 100644 --- a/Dalamud/Game/FrameworkAddressResolver.cs +++ b/Dalamud/Game/FrameworkAddressResolver.cs @@ -1,57 +1,54 @@ using System; using System.Runtime.InteropServices; -using Dalamud.Game.Internal; +namespace Dalamud.Game; -namespace Dalamud.Game +/// +/// The address resolver for the class. +/// +public sealed class FrameworkAddressResolver : BaseAddressResolver { /// - /// The address resolver for the class. + /// Gets the base address native Framework class. /// - public sealed class FrameworkAddressResolver : BaseAddressResolver + public IntPtr BaseAddress { get; private set; } + + /// + /// Gets the address for the native GuiManager class. + /// + public IntPtr GuiManager { get; private set; } + + /// + /// Gets the address for the native ScriptManager class. + /// + public IntPtr ScriptManager { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the base address native Framework class. - /// - public IntPtr BaseAddress { get; private set; } + this.SetupFramework(sig); - /// - /// Gets the address for the native GuiManager class. - /// - public IntPtr GuiManager { get; private set; } + // Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h] + // Xiv__Framework__GetGuiManager+F 000 retn + this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08); - /// - /// Gets the address for the native ScriptManager class. - /// - public IntPtr ScriptManager { get; private set; } + // Called from Framework::Init + this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here + } - /// - protected override void Setup64Bit(SigScanner sig) - { - this.SetupFramework(sig); + private void SetupFramework(SigScanner scanner) + { + // Dissasembly of part of the .dtor + // 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0 + // 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130 + // 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38] + // 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0 + // 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80 + var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D"); + var fwOffset = Marshal.ReadInt32(fwDtor + 3); + var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset); - // Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h] - // Xiv__Framework__GetGuiManager+F 000 retn - this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08); - - // Called from Framework::Init - this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here - } - - private void SetupFramework(SigScanner scanner) - { - // Dissasembly of part of the .dtor - // 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0 - // 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130 - // 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38] - // 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0 - // 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80 - var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D"); - var fwOffset = Marshal.ReadInt32(fwDtor + 3); - var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset); - - // Framework does not change once initialized in startup so don't bother to deref again and again. - this.BaseAddress = Marshal.ReadIntPtr(pFramework); - } + // Framework does not change once initialized in startup so don't bother to deref again and again. + this.BaseAddress = Marshal.ReadIntPtr(pFramework); } } diff --git a/Dalamud/Game/GameVersion.cs b/Dalamud/Game/GameVersion.cs index a93e0bff2..53b55fd67 100644 --- a/Dalamud/Game/GameVersion.cs +++ b/Dalamud/Game/GameVersion.cs @@ -5,405 +5,404 @@ using System.Text; using Newtonsoft.Json; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// A GameVersion object contains give hierarchical numeric components: year, month, +/// day, major and minor. All components may be unspecified, which is represented +/// internally as a -1. By definition, an unspecified component matches anything +/// (both unspecified and specified), and an unspecified component is "less than" any +/// specified component. It will also equal the string "any" if all components are +/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game +/// installation directory. +/// +[Serializable] +public sealed class GameVersion : ICloneable, IComparable, IComparable, IEquatable { + private static readonly GameVersion AnyVersion = new(); + /// - /// A GameVersion object contains give hierarchical numeric components: year, month, - /// day, major and minor. All components may be unspecified, which is represented - /// internally as a -1. By definition, an unspecified component matches anything - /// (both unspecified and specified), and an unspecified component is "less than" any - /// specified component. It will also equal the string "any" if all components are - /// unspecified. The value can be retrieved from the ffxivgame.ver file in your game - /// installation directory. + /// Initializes a new instance of the class. /// - [Serializable] - public sealed class GameVersion : ICloneable, IComparable, IComparable, IEquatable + /// Version string to parse. + [JsonConstructor] + public GameVersion(string version) { - private static readonly GameVersion AnyVersion = new(); + var ver = Parse(version); + this.Year = ver.Year; + this.Month = ver.Month; + this.Day = ver.Day; + this.Major = ver.Major; + this.Minor = ver.Minor; + } - /// - /// Initializes a new instance of the class. - /// - /// Version string to parse. - [JsonConstructor] - public GameVersion(string version) + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + /// The day. + /// The major version. + /// The minor version. + public GameVersion(int year, int month, int day, int major, int minor) + { + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); + + if ((this.Day = day) < 0) + throw new ArgumentOutOfRangeException(nameof(day)); + + if ((this.Major = major) < 0) + throw new ArgumentOutOfRangeException(nameof(major)); + + if ((this.Minor = minor) < 0) + throw new ArgumentOutOfRangeException(nameof(minor)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + /// The day. + /// The major version. + public GameVersion(int year, int month, int day, int major) + { + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); + + if ((this.Day = day) < 0) + throw new ArgumentOutOfRangeException(nameof(day)); + + if ((this.Major = major) < 0) + throw new ArgumentOutOfRangeException(nameof(major)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + /// The day. + public GameVersion(int year, int month, int day) + { + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); + + if ((this.Day = day) < 0) + throw new ArgumentOutOfRangeException(nameof(day)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + public GameVersion(int year, int month) + { + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The year. + public GameVersion(int year) + { + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + } + + /// + /// Initializes a new instance of the class. + /// + public GameVersion() + { + } + + /// + /// Gets the default "any" game version. + /// + public static GameVersion Any => AnyVersion; + + /// + /// Gets the year component. + /// + public int Year { get; } = -1; + + /// + /// Gets the month component. + /// + public int Month { get; } = -1; + + /// + /// Gets the day component. + /// + public int Day { get; } = -1; + + /// + /// Gets the major version component. + /// + public int Major { get; } = -1; + + /// + /// Gets the minor version component. + /// + public int Minor { get; } = -1; + + public static implicit operator GameVersion(string ver) + { + return Parse(ver); + } + + public static bool operator ==(GameVersion v1, GameVersion v2) + { + if (v1 is null) { - var ver = Parse(version); - this.Year = ver.Year; - this.Month = ver.Month; - this.Day = ver.Day; - this.Major = ver.Major; - this.Minor = ver.Minor; + return v2 is null; } - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - /// The day. - /// The major version. - /// The minor version. - public GameVersion(int year, int month, int day, int major, int minor) + return v1.Equals(v2); + } + + public static bool operator !=(GameVersion v1, GameVersion v2) + { + return !(v1 == v2); + } + + public static bool operator <(GameVersion v1, GameVersion v2) + { + if (v1 is null) + throw new ArgumentNullException(nameof(v1)); + + return v1.CompareTo(v2) < 0; + } + + public static bool operator <=(GameVersion v1, GameVersion v2) + { + if (v1 is null) + throw new ArgumentNullException(nameof(v1)); + + return v1.CompareTo(v2) <= 0; + } + + public static bool operator >(GameVersion v1, GameVersion v2) + { + return v2 < v1; + } + + public static bool operator >=(GameVersion v1, GameVersion v2) + { + return v2 <= v1; + } + + public static GameVersion operator +(GameVersion v1, TimeSpan v2) + { + if (v1 == null) + throw new ArgumentNullException(nameof(v1)); + + if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) + return v1; + + var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2; + + return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); + } + + public static GameVersion operator -(GameVersion v1, TimeSpan v2) + { + if (v1 == null) + throw new ArgumentNullException(nameof(v1)); + + if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) + return v1; + + var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2; + + return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); + } + + /// + /// Parse a version string. YYYY.MM.DD.majr.minr or "any". + /// + /// Input to parse. + /// GameVersion object. + public static GameVersion Parse(string input) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (input.ToLower(CultureInfo.InvariantCulture) == "any") + return new GameVersion(); + + var parts = input.Split('.'); + var tplParts = parts.Select(p => { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); + var result = int.TryParse(p, out var value); + return (result, value); + }).ToArray(); - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); + if (tplParts.Any(t => !t.result)) + throw new FormatException("Bad formatting"); - if ((this.Day = day) < 0) - throw new ArgumentOutOfRangeException(nameof(day)); + var intParts = tplParts.Select(t => t.value).ToArray(); + var len = intParts.Length; - if ((this.Major = major) < 0) - throw new ArgumentOutOfRangeException(nameof(major)); + if (len == 1) + return new GameVersion(intParts[0]); + else if (len == 2) + return new GameVersion(intParts[0], intParts[1]); + else if (len == 3) + return new GameVersion(intParts[0], intParts[1], intParts[2]); + else if (len == 4) + return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]); + else if (len == 5) + return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]); + else + throw new ArgumentException("Too many parts"); + } - if ((this.Minor = minor) < 0) - throw new ArgumentOutOfRangeException(nameof(minor)); + /// + /// Try to parse a version string. YYYY.MM.DD.majr.minr or "any". + /// + /// Input to parse. + /// GameVersion object. + /// Success or failure. + public static bool TryParse(string input, out GameVersion result) + { + try + { + result = Parse(input); + return true; } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - /// The day. - /// The major version. - public GameVersion(int year, int month, int day, int major) + catch { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - - if ((this.Day = day) < 0) - throw new ArgumentOutOfRangeException(nameof(day)); - - if ((this.Major = major) < 0) - throw new ArgumentOutOfRangeException(nameof(major)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - /// The day. - public GameVersion(int year, int month, int day) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - - if ((this.Day = day) < 0) - throw new ArgumentOutOfRangeException(nameof(day)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - public GameVersion(int year, int month) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - public GameVersion(int year) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - } - - /// - /// Initializes a new instance of the class. - /// - public GameVersion() - { - } - - /// - /// Gets the default "any" game version. - /// - public static GameVersion Any => AnyVersion; - - /// - /// Gets the year component. - /// - public int Year { get; } = -1; - - /// - /// Gets the month component. - /// - public int Month { get; } = -1; - - /// - /// Gets the day component. - /// - public int Day { get; } = -1; - - /// - /// Gets the major version component. - /// - public int Major { get; } = -1; - - /// - /// Gets the minor version component. - /// - public int Minor { get; } = -1; - - public static implicit operator GameVersion(string ver) - { - return Parse(ver); - } - - public static bool operator ==(GameVersion v1, GameVersion v2) - { - if (v1 is null) - { - return v2 is null; - } - - return v1.Equals(v2); - } - - public static bool operator !=(GameVersion v1, GameVersion v2) - { - return !(v1 == v2); - } - - public static bool operator <(GameVersion v1, GameVersion v2) - { - if (v1 is null) - throw new ArgumentNullException(nameof(v1)); - - return v1.CompareTo(v2) < 0; - } - - public static bool operator <=(GameVersion v1, GameVersion v2) - { - if (v1 is null) - throw new ArgumentNullException(nameof(v1)); - - return v1.CompareTo(v2) <= 0; - } - - public static bool operator >(GameVersion v1, GameVersion v2) - { - return v2 < v1; - } - - public static bool operator >=(GameVersion v1, GameVersion v2) - { - return v2 <= v1; - } - - public static GameVersion operator +(GameVersion v1, TimeSpan v2) - { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - - if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) - return v1; - - var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2; - - return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); - } - - public static GameVersion operator -(GameVersion v1, TimeSpan v2) - { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - - if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) - return v1; - - var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2; - - return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); - } - - /// - /// Parse a version string. YYYY.MM.DD.majr.minr or "any". - /// - /// Input to parse. - /// GameVersion object. - public static GameVersion Parse(string input) - { - if (input == null) - throw new ArgumentNullException(nameof(input)); - - if (input.ToLower(CultureInfo.InvariantCulture) == "any") - return new GameVersion(); - - var parts = input.Split('.'); - var tplParts = parts.Select(p => - { - var result = int.TryParse(p, out var value); - return (result, value); - }).ToArray(); - - if (tplParts.Any(t => !t.result)) - throw new FormatException("Bad formatting"); - - var intParts = tplParts.Select(t => t.value).ToArray(); - var len = intParts.Length; - - if (len == 1) - return new GameVersion(intParts[0]); - else if (len == 2) - return new GameVersion(intParts[0], intParts[1]); - else if (len == 3) - return new GameVersion(intParts[0], intParts[1], intParts[2]); - else if (len == 4) - return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]); - else if (len == 5) - return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]); - else - throw new ArgumentException("Too many parts"); - } - - /// - /// Try to parse a version string. YYYY.MM.DD.majr.minr or "any". - /// - /// Input to parse. - /// GameVersion object. - /// Success or failure. - public static bool TryParse(string input, out GameVersion result) - { - try - { - result = Parse(input); - return true; - } - catch - { - result = null; - return false; - } - } - - /// - public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor); - - /// - public int CompareTo(object obj) - { - if (obj == null) - return 1; - - if (obj is GameVersion value) - { - return this.CompareTo(value); - } - else - { - throw new ArgumentException("Argument must be a GameVersion"); - } - } - - /// - public int CompareTo(GameVersion value) - { - if (value == null) - return 1; - - if (this == value) - return 0; - - if (this == AnyVersion) - return 1; - - if (value == AnyVersion) - return -1; - - if (this.Year != value.Year) - return this.Year > value.Year ? 1 : -1; - - if (this.Month != value.Month) - return this.Month > value.Month ? 1 : -1; - - if (this.Day != value.Day) - return this.Day > value.Day ? 1 : -1; - - if (this.Major != value.Major) - return this.Major > value.Major ? 1 : -1; - - if (this.Minor != value.Minor) - return this.Minor > value.Minor ? 1 : -1; - - return 0; - } - - /// - public override bool Equals(object obj) - { - if (obj is not GameVersion value) - return false; - - return this.Equals(value); - } - - /// - public bool Equals(GameVersion value) - { - if (value == null) - { - return false; - } - - return - (this.Year == value.Year) && - (this.Month == value.Month) && - (this.Day == value.Day) && - (this.Major == value.Major) && - (this.Minor == value.Minor); - } - - /// - public override int GetHashCode() - { - var accumulator = 0; - - // This might be horribly wrong, but it isn't used heavily. - accumulator |= this.Year.GetHashCode(); - accumulator |= this.Month.GetHashCode(); - accumulator |= this.Day.GetHashCode(); - accumulator |= this.Major.GetHashCode(); - accumulator |= this.Minor.GetHashCode(); - - return accumulator; - } - - /// - public override string ToString() - { - if (this.Year == -1 && - this.Month == -1 && - this.Day == -1 && - this.Major == -1 && - this.Minor == -1) - return "any"; - - return new StringBuilder() - .Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year)) - .Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month)) - .Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day)) - .Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major)) - .Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor)) - .ToString(); + result = null; + return false; } } + + /// + public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor); + + /// + public int CompareTo(object obj) + { + if (obj == null) + return 1; + + if (obj is GameVersion value) + { + return this.CompareTo(value); + } + else + { + throw new ArgumentException("Argument must be a GameVersion"); + } + } + + /// + public int CompareTo(GameVersion value) + { + if (value == null) + return 1; + + if (this == value) + return 0; + + if (this == AnyVersion) + return 1; + + if (value == AnyVersion) + return -1; + + if (this.Year != value.Year) + return this.Year > value.Year ? 1 : -1; + + if (this.Month != value.Month) + return this.Month > value.Month ? 1 : -1; + + if (this.Day != value.Day) + return this.Day > value.Day ? 1 : -1; + + if (this.Major != value.Major) + return this.Major > value.Major ? 1 : -1; + + if (this.Minor != value.Minor) + return this.Minor > value.Minor ? 1 : -1; + + return 0; + } + + /// + public override bool Equals(object obj) + { + if (obj is not GameVersion value) + return false; + + return this.Equals(value); + } + + /// + public bool Equals(GameVersion value) + { + if (value == null) + { + return false; + } + + return + (this.Year == value.Year) && + (this.Month == value.Month) && + (this.Day == value.Day) && + (this.Major == value.Major) && + (this.Minor == value.Minor); + } + + /// + public override int GetHashCode() + { + var accumulator = 0; + + // This might be horribly wrong, but it isn't used heavily. + accumulator |= this.Year.GetHashCode(); + accumulator |= this.Month.GetHashCode(); + accumulator |= this.Day.GetHashCode(); + accumulator |= this.Major.GetHashCode(); + accumulator |= this.Minor.GetHashCode(); + + return accumulator; + } + + /// + public override string ToString() + { + if (this.Year == -1 && + this.Month == -1 && + this.Day == -1 && + this.Major == -1 && + this.Minor == -1) + return "any"; + + return new StringBuilder() + .Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year)) + .Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month)) + .Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day)) + .Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major)) + .Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor)) + .ToString(); + } } diff --git a/Dalamud/Game/GameVersionConverter.cs b/Dalamud/Game/GameVersionConverter.cs index 9058e8be9..f307b6fb9 100644 --- a/Dalamud/Game/GameVersionConverter.cs +++ b/Dalamud/Game/GameVersionConverter.cs @@ -2,79 +2,78 @@ using System; using Newtonsoft.Json; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// Converts a to and from a string (e.g. "2010.01.01.1234.5678"). +/// +public sealed class GameVersionConverter : JsonConverter { /// - /// Converts a to and from a string (e.g. "2010.01.01.1234.5678"). + /// Writes the JSON representation of the object. /// - public sealed class GameVersionConverter : JsonConverter + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { - /// - /// Writes the JSON representation of the object. - /// - /// The to write to. - /// The value. - /// The calling serializer. - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + if (value == null) { - if (value == null) - { - writer.WriteNull(); - } - else if (value is GameVersion) - { - writer.WriteValue(value.ToString()); - } - else - { - throw new JsonSerializationException("Expected GameVersion object value"); - } + writer.WriteNull(); } - - /// - /// Reads the JSON representation of the object. - /// - /// The to read from. - /// Type of the object. - /// The existing property value of the JSON that is being converted. - /// The calling serializer. - /// The object value. - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + else if (value is GameVersion) { - if (reader.TokenType == JsonToken.Null) - { - return null; - } - else - { - if (reader.TokenType == JsonToken.String) - { - try - { - return new GameVersion((string)reader.Value!); - } - catch (Exception ex) - { - throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex); - } - } - else - { - throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}"); - } - } + writer.WriteValue(value.ToString()); } - - /// - /// Determines whether this instance can convert the specified object type. - /// - /// Type of the object. - /// - /// true if this instance can convert the specified object type; otherwise, false. - /// - public override bool CanConvert(Type objectType) + else { - return objectType == typeof(GameVersion); + throw new JsonSerializationException("Expected GameVersion object value"); } } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing property value of the JSON that is being converted. + /// The calling serializer. + /// The object value. + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + else + { + if (reader.TokenType == JsonToken.String) + { + try + { + return new GameVersion((string)reader.Value!); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex); + } + } + else + { + throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}"); + } + } + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(GameVersion); + } } diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 0aa7bb94f..bab3a83b4 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -13,471 +13,470 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Gui +namespace Dalamud.Game.Gui; + +/// +/// This class handles interacting with the native chat UI. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class ChatGui : IDisposable { + private readonly ChatGuiAddressResolver address; + + private readonly Queue chatQueue = new(); + private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); + + private readonly Hook printMessageHook; + private readonly Hook populateItemLinkHook; + private readonly Hook interactableLinkClickedHook; + + private IntPtr baseAddress = IntPtr.Zero; + /// - /// This class handles interacting with the native chat UI. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class ChatGui : IDisposable + /// The base address of the ChatManager. + internal ChatGui(IntPtr baseAddress) { - private readonly ChatGuiAddressResolver address; + this.address = new ChatGuiAddressResolver(baseAddress); + this.address.Setup(); - private readonly Queue chatQueue = new(); - private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); + Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}"); - private readonly Hook printMessageHook; - private readonly Hook populateItemLinkHook; - private readonly Hook interactableLinkClickedHook; + this.printMessageHook = new Hook(this.address.PrintMessage, this.HandlePrintMessageDetour); + this.populateItemLinkHook = new Hook(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); + this.interactableLinkClickedHook = new Hook(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); + } - private IntPtr baseAddress = IntPtr.Zero; + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + /// A value indicating whether the message was handled or should be propagated. + public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); - /// - /// Initializes a new instance of the class. - /// - /// The base address of the ChatManager. - internal ChatGui(IntPtr baseAddress) + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + /// A value indicating whether the message was handled or should be propagated. + public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); + + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); + + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr); + + /// + /// Event that will be fired when a chat message is sent to chat by the game. + /// + public event OnMessageDelegate ChatMessage; + + /// + /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true. + /// + public event OnCheckMessageHandledDelegate CheckMessageHandled; + + /// + /// Event that will be fired when a chat message is handled by Dalamud or a Plugin. + /// + public event OnMessageHandledDelegate ChatMessageHandled; + + /// + /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. + /// + public event OnMessageUnhandledDelegate ChatMessageUnhandled; + + /// + /// Gets the ID of the last linked item. + /// + public int LastLinkedItemId { get; private set; } + + /// + /// Gets the flags of the last linked item. + /// + public byte LastLinkedItemFlags { get; private set; } + + /// + /// Enables this module. + /// + public void Enable() + { + this.printMessageHook.Enable(); + this.populateItemLinkHook.Enable(); + this.interactableLinkClickedHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.printMessageHook.Dispose(); + this.populateItemLinkHook.Dispose(); + this.interactableLinkClickedHook.Dispose(); + } + + /// + /// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue, + /// later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void PrintChat(XivChatEntry chat) + { + this.chatQueue.Enqueue(chat); + } + + /// + /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, + /// later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void Print(string message) + { + var configuration = Service.Get(); + + // Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message); + this.PrintChat(new XivChatEntry { - this.address = new ChatGuiAddressResolver(baseAddress); - this.address.Setup(); + Message = message, + Type = configuration.GeneralChatType, + }); + } - Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}"); + /// + /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, + /// later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void Print(SeString message) + { + var configuration = Service.Get(); - this.printMessageHook = new Hook(this.address.PrintMessage, this.HandlePrintMessageDetour); - this.populateItemLinkHook = new Hook(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); - this.interactableLinkClickedHook = new Hook(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); - } - - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - /// A value indicating whether the message was handled or should be propagated. - public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); - - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - /// A value indicating whether the message was handled or should be propagated. - public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); - - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); - - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr); - - /// - /// Event that will be fired when a chat message is sent to chat by the game. - /// - public event OnMessageDelegate ChatMessage; - - /// - /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true. - /// - public event OnCheckMessageHandledDelegate CheckMessageHandled; - - /// - /// Event that will be fired when a chat message is handled by Dalamud or a Plugin. - /// - public event OnMessageHandledDelegate ChatMessageHandled; - - /// - /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. - /// - public event OnMessageUnhandledDelegate ChatMessageUnhandled; - - /// - /// Gets the ID of the last linked item. - /// - public int LastLinkedItemId { get; private set; } - - /// - /// Gets the flags of the last linked item. - /// - public byte LastLinkedItemFlags { get; private set; } - - /// - /// Enables this module. - /// - public void Enable() + // Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue); + this.PrintChat(new XivChatEntry { - this.printMessageHook.Enable(); - this.populateItemLinkHook.Enable(); - this.interactableLinkClickedHook.Enable(); - } + Message = message, + Type = configuration.GeneralChatType, + }); + } - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() + /// + /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to + /// the queue, later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void PrintError(string message) + { + // Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message); + this.PrintChat(new XivChatEntry { - this.printMessageHook.Dispose(); - this.populateItemLinkHook.Dispose(); - this.interactableLinkClickedHook.Dispose(); - } + Message = message, + Type = XivChatType.Urgent, + }); + } - /// - /// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue, - /// later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void PrintChat(XivChatEntry chat) + /// + /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to + /// the queue, later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void PrintError(SeString message) + { + // Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue); + this.PrintChat(new XivChatEntry { - this.chatQueue.Enqueue(chat); - } + Message = message, + Type = XivChatType.Urgent, + }); + } - /// - /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, - /// later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void Print(string message) + /// + /// Process a chat queue. + /// + public void UpdateQueue() + { + while (this.chatQueue.Count > 0) { - var configuration = Service.Get(); + var chat = this.chatQueue.Dequeue(); - // Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message); - this.PrintChat(new XivChatEntry + if (this.baseAddress == IntPtr.Zero) { - Message = message, - Type = configuration.GeneralChatType, - }); - } - - /// - /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, - /// later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void Print(SeString message) - { - var configuration = Service.Get(); - - // Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = configuration.GeneralChatType, - }); - } - - /// - /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to - /// the queue, later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void PrintError(string message) - { - // Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = XivChatType.Urgent, - }); - } - - /// - /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to - /// the queue, later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void PrintError(SeString message) - { - // Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = XivChatType.Urgent, - }); - } - - /// - /// Process a chat queue. - /// - public void UpdateQueue() - { - while (this.chatQueue.Count > 0) - { - var chat = this.chatQueue.Dequeue(); - - if (this.baseAddress == IntPtr.Zero) - { - continue; - } - - var senderRaw = (chat.Name ?? string.Empty).Encode(); - using var senderOwned = Service.Get().NewString(senderRaw); - - var messageRaw = (chat.Message ?? string.Empty).Encode(); - using var messageOwned = Service.Get().NewString(messageRaw); - - this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters); + continue; } - } - /// - /// Create a link handler. - /// - /// The name of the plugin handling the link. - /// The ID of the command to run. - /// The command action itself. - /// A payload for handling. - internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction) - { - var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId }; - this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction); - return payload; - } + var senderRaw = (chat.Name ?? string.Empty).Encode(); + using var senderOwned = Service.Get().NewString(senderRaw); - /// - /// Remove all handlers owned by a plugin. - /// - /// The name of the plugin handling the links. - internal void RemoveChatLinkHandler(string pluginName) + var messageRaw = (chat.Message ?? string.Empty).Encode(); + using var messageOwned = Service.Get().NewString(messageRaw); + + this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters); + } + } + + /// + /// Create a link handler. + /// + /// The name of the plugin handling the link. + /// The ID of the command to run. + /// The command action itself. + /// A payload for handling. + internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction) + { + var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId }; + this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction); + return payload; + } + + /// + /// Remove all handlers owned by a plugin. + /// + /// The name of the plugin handling the links. + internal void RemoveChatLinkHandler(string pluginName) + { + foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName)) { - foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName)) + this.dalamudLinkHandlers.Remove(handler); + } + } + + /// + /// Remove a registered link handler. + /// + /// The name of the plugin handling the link. + /// The ID of the command to be removed. + internal void RemoveChatLinkHandler(string pluginName, uint commandId) + { + if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) + { + this.dalamudLinkHandlers.Remove((pluginName, commandId)); + } + } + + private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2) + { + // Copyright (c) 2008-2013 Hafthor Stefansson + // Distributed under the MIT/X11 software license + // Ref: http://www.opensource.org/licenses/mit-license.php. + // https://stackoverflow.com/a/8808245 + + if (a1 == a2) return true; + if (a1 == null || a2 == null || a1.Length != a2.Length) + return false; + fixed (byte* p1 = a1, p2 = a2) + { + byte* x1 = p1, x2 = p2; + var l = a1.Length; + for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) { - this.dalamudLinkHandlers.Remove(handler); + if (*((long*)x1) != *((long*)x2)) + return false; } + + if ((l & 4) != 0) + { + if (*((int*)x1) != *((int*)x2)) + return false; + x1 += 4; + x2 += 4; + } + + if ((l & 2) != 0) + { + if (*((short*)x1) != *((short*)x2)) + return false; + x1 += 2; + x2 += 2; + } + + if ((l & 1) != 0) + { + if (*((byte*)x1) != *((byte*)x2)) + return false; + } + + return true; + } + } + + private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) + { + try + { + this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + + this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8); + this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14); + + // Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}"); + } + catch (Exception ex) + { + Log.Error(ex, "Exception onPopulateItemLink hook."); + this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + } + } + + private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter) + { + var retVal = IntPtr.Zero; + + try + { + var sender = StdString.ReadFromPointer(pSenderName); + var parsedSender = SeString.Parse(sender.RawData); + var originalSenderData = (byte[])sender.RawData.Clone(); + var oldEditedSender = parsedSender.Encode(); + var senderPtr = pSenderName; + OwnedStdString allocatedString = null; + + var message = StdString.ReadFromPointer(pMessage); + var parsedMessage = SeString.Parse(message.RawData); + var originalMessageData = (byte[])message.RawData.Clone(); + var oldEdited = parsedMessage.Encode(); + var messagePtr = pMessage; + OwnedStdString allocatedStringSender = null; + + // Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue); + + // Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}"); + + // Call events + var isHandled = false; + this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); + + if (!isHandled) + { + this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); + } + + var newEdited = parsedMessage.Encode(); + if (!FastByteArrayCompare(oldEdited, newEdited)) + { + Log.Verbose("SeString was edited, taking precedence over StdString edit."); + message.RawData = newEdited; + // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); + } + + if (!FastByteArrayCompare(originalMessageData, message.RawData)) + { + allocatedString = Service.Get().NewString(message.RawData); + Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); + messagePtr = allocatedString.Address; + } + + var newEditedSender = parsedSender.Encode(); + if (!FastByteArrayCompare(oldEditedSender, newEditedSender)) + { + Log.Verbose("SeString was edited, taking precedence over StdString edit."); + sender.RawData = newEditedSender; + // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); + } + + if (!FastByteArrayCompare(originalSenderData, sender.RawData)) + { + allocatedStringSender = Service.Get().NewString(sender.RawData); + Log.Debug( + $"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})"); + senderPtr = allocatedStringSender.Address; + } + + // Print the original chat if it's handled. + if (isHandled) + { + this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); + } + else + { + retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter); + this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); + } + + if (this.baseAddress == IntPtr.Zero) + this.baseAddress = manager; + + allocatedString?.Dispose(); + allocatedStringSender?.Dispose(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception on OnChatMessage hook."); + retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter); } - /// - /// Remove a registered link handler. - /// - /// The name of the plugin handling the link. - /// The ID of the command to be removed. - internal void RemoveChatLinkHandler(string pluginName, uint commandId) + return retVal; + } + + private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) + { + try { - if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) + var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1); + + if (interactableType != Payload.EmbeddedInfoType.DalamudLink) { - this.dalamudLinkHandlers.Remove((pluginName, commandId)); + this.interactableLinkClickedHook.Original(managerPtr, messagePtr); + return; } - } - private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2) - { - // Copyright (c) 2008-2013 Hafthor Stefansson - // Distributed under the MIT/X11 software license - // Ref: http://www.opensource.org/licenses/mit-license.php. - // https://stackoverflow.com/a/8808245 + Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); - if (a1 == a2) return true; - if (a1 == null || a2 == null || a1.Length != a2.Length) - return false; - fixed (byte* p1 = a1, p2 = a2) + var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10); + var messageSize = 0; + while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++; + var payloadBytes = new byte[messageSize]; + Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize); + var seStr = SeString.Parse(payloadBytes); + var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator); + var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads; + if (payloads.Count == 0) return; + var linkPayload = payloads[0]; + if (linkPayload is DalamudLinkPayload link) { - byte* x1 = p1, x2 = p2; - var l = a1.Length; - for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) + if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) { - if (*((long*)x1) != *((long*)x2)) - return false; - } - - if ((l & 4) != 0) - { - if (*((int*)x1) != *((int*)x2)) - return false; - x1 += 4; - x2 += 4; - } - - if ((l & 2) != 0) - { - if (*((short*)x1) != *((short*)x2)) - return false; - x1 += 2; - x2 += 2; - } - - if ((l & 1) != 0) - { - if (*((byte*)x1) != *((byte*)x2)) - return false; - } - - return true; - } - } - - private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) - { - try - { - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); - - this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8); - this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14); - - // Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}"); - } - catch (Exception ex) - { - Log.Error(ex, "Exception onPopulateItemLink hook."); - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); - } - } - - private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter) - { - var retVal = IntPtr.Zero; - - try - { - var sender = StdString.ReadFromPointer(pSenderName); - var parsedSender = SeString.Parse(sender.RawData); - var originalSenderData = (byte[])sender.RawData.Clone(); - var oldEditedSender = parsedSender.Encode(); - var senderPtr = pSenderName; - OwnedStdString allocatedString = null; - - var message = StdString.ReadFromPointer(pMessage); - var parsedMessage = SeString.Parse(message.RawData); - var originalMessageData = (byte[])message.RawData.Clone(); - var oldEdited = parsedMessage.Encode(); - var messagePtr = pMessage; - OwnedStdString allocatedStringSender = null; - - // Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue); - - // Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}"); - - // Call events - var isHandled = false; - this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); - - if (!isHandled) - { - this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); - } - - var newEdited = parsedMessage.Encode(); - if (!FastByteArrayCompare(oldEdited, newEdited)) - { - Log.Verbose("SeString was edited, taking precedence over StdString edit."); - message.RawData = newEdited; - // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); - } - - if (!FastByteArrayCompare(originalMessageData, message.RawData)) - { - allocatedString = Service.Get().NewString(message.RawData); - Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); - messagePtr = allocatedString.Address; - } - - var newEditedSender = parsedSender.Encode(); - if (!FastByteArrayCompare(oldEditedSender, newEditedSender)) - { - Log.Verbose("SeString was edited, taking precedence over StdString edit."); - sender.RawData = newEditedSender; - // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); - } - - if (!FastByteArrayCompare(originalSenderData, sender.RawData)) - { - allocatedStringSender = Service.Get().NewString(sender.RawData); - Log.Debug( - $"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})"); - senderPtr = allocatedStringSender.Address; - } - - // Print the original chat if it's handled. - if (isHandled) - { - this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); + Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); + this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads)); } else { - retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter); - this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); + Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); } - - if (this.baseAddress == IntPtr.Zero) - this.baseAddress = manager; - - allocatedString?.Dispose(); - allocatedStringSender?.Dispose(); } - catch (Exception ex) - { - Log.Error(ex, "Exception on OnChatMessage hook."); - retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter); - } - - return retVal; } - - private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) + catch (Exception ex) { - try - { - var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1); - - if (interactableType != Payload.EmbeddedInfoType.DalamudLink) - { - this.interactableLinkClickedHook.Original(managerPtr, messagePtr); - return; - } - - Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); - - var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10); - var messageSize = 0; - while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++; - var payloadBytes = new byte[messageSize]; - Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize); - var seStr = SeString.Parse(payloadBytes); - var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator); - var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads; - if (payloads.Count == 0) return; - var linkPayload = payloads[0]; - if (linkPayload is DalamudLinkPayload link) - { - if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) - { - Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); - this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads)); - } - else - { - Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); - } - } - } - catch (Exception ex) - { - Log.Error(ex, "Exception on InteractableLinkClicked hook"); - } + Log.Error(ex, "Exception on InteractableLinkClicked hook"); } } } diff --git a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs index 07c154f1f..e39a70e37 100644 --- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs @@ -1,120 +1,117 @@ using System; -using Dalamud.Game.Internal; +namespace Dalamud.Game.Gui; -namespace Dalamud.Game.Gui +/// +/// The address resolver for the class. +/// +public sealed class ChatGuiAddressResolver : BaseAddressResolver { /// - /// The address resolver for the class. + /// Initializes a new instance of the class. /// - public sealed class ChatGuiAddressResolver : BaseAddressResolver + /// The base address of the native ChatManager class. + public ChatGuiAddressResolver(IntPtr baseAddress) { - /// - /// Initializes a new instance of the class. - /// - /// The base address of the native ChatManager class. - public ChatGuiAddressResolver(IntPtr baseAddress) - { - this.BaseAddress = baseAddress; - } + this.BaseAddress = baseAddress; + } - /// - /// Gets the base address of the native ChatManager class. - /// - public IntPtr BaseAddress { get; } + /// + /// Gets the base address of the native ChatManager class. + /// + public IntPtr BaseAddress { get; } - /// - /// Gets the address of the native PrintMessage method. - /// - public IntPtr PrintMessage { get; private set; } + /// + /// Gets the address of the native PrintMessage method. + /// + public IntPtr PrintMessage { get; private set; } - /// - /// Gets the address of the native PopulateItemLinkObject method. - /// - public IntPtr PopulateItemLinkObject { get; private set; } + /// + /// Gets the address of the native PopulateItemLinkObject method. + /// + public IntPtr PopulateItemLinkObject { get; private set; } - /// - /// Gets the address of the native InteractableLinkClicked method. - /// - public IntPtr InteractableLinkClicked { get; private set; } + /// + /// Gets the address of the native InteractableLinkClicked method. + /// + public IntPtr InteractableLinkClicked { get; private set; } - /* - --- for reference: 4.57 --- - .text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal) - .text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near - .text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p - .text:00000001405CD210 ; sub_140141D10+220↑p ... - .text:00000001405CD210 - .text:00000001405CD210 var_220 = qword ptr -220h - .text:00000001405CD210 var_218 = byte ptr -218h - .text:00000001405CD210 var_210 = word ptr -210h - .text:00000001405CD210 var_208 = byte ptr -208h - .text:00000001405CD210 var_200 = word ptr -200h - .text:00000001405CD210 var_1FC = dword ptr -1FCh - .text:00000001405CD210 var_1F8 = qword ptr -1F8h - .text:00000001405CD210 var_1F0 = qword ptr -1F0h - .text:00000001405CD210 var_1E8 = qword ptr -1E8h - .text:00000001405CD210 var_1E0 = dword ptr -1E0h - .text:00000001405CD210 var_1DC = word ptr -1DCh - .text:00000001405CD210 var_1DA = word ptr -1DAh - .text:00000001405CD210 var_1D8 = qword ptr -1D8h - .text:00000001405CD210 var_1D0 = byte ptr -1D0h - .text:00000001405CD210 var_1C8 = qword ptr -1C8h - .text:00000001405CD210 var_1B0 = dword ptr -1B0h - .text:00000001405CD210 var_1AC = dword ptr -1ACh - .text:00000001405CD210 var_1A8 = dword ptr -1A8h - .text:00000001405CD210 var_1A4 = dword ptr -1A4h - .text:00000001405CD210 var_1A0 = dword ptr -1A0h - .text:00000001405CD210 var_160 = dword ptr -160h - .text:00000001405CD210 var_15C = dword ptr -15Ch - .text:00000001405CD210 var_140 = dword ptr -140h - .text:00000001405CD210 var_138 = dword ptr -138h - .text:00000001405CD210 var_130 = byte ptr -130h - .text:00000001405CD210 var_C0 = byte ptr -0C0h - .text:00000001405CD210 var_50 = qword ptr -50h - .text:00000001405CD210 var_38 = qword ptr -38h - .text:00000001405CD210 var_30 = qword ptr -30h - .text:00000001405CD210 var_28 = qword ptr -28h - .text:00000001405CD210 var_20 = qword ptr -20h - .text:00000001405CD210 senderActorId = dword ptr 30h - .text:00000001405CD210 isLocal = byte ptr 38h - .text:00000001405CD210 - .text:00000001405CD210 ; __unwind { // __GSHandlerCheck - .text:00000001405CD210 push rbp - .text:00000001405CD212 push rdi - .text:00000001405CD213 push r14 - .text:00000001405CD215 push r15 - .text:00000001405CD217 lea rbp, [rsp-128h] - .text:00000001405CD21F sub rsp, 228h - .text:00000001405CD226 mov rax, cs:__security_cookie - .text:00000001405CD22D xor rax, rsp - .text:00000001405CD230 mov [rbp+140h+var_50], rax - .text:00000001405CD237 xor r10b, r10b - .text:00000001405CD23A mov [rsp+240h+var_1F8], rcx - .text:00000001405CD23F xor eax, eax - .text:00000001405CD241 mov r11, r9 - .text:00000001405CD244 mov r14, r8 - .text:00000001405CD247 mov r9d, eax - .text:00000001405CD24A movzx r15d, dx - .text:00000001405CD24E lea r8, [rcx+0C10h] - .text:00000001405CD255 mov rdi, rcx - */ + /* + --- for reference: 4.57 --- + .text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal) + .text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near + .text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p + .text:00000001405CD210 ; sub_140141D10+220↑p ... + .text:00000001405CD210 + .text:00000001405CD210 var_220 = qword ptr -220h + .text:00000001405CD210 var_218 = byte ptr -218h + .text:00000001405CD210 var_210 = word ptr -210h + .text:00000001405CD210 var_208 = byte ptr -208h + .text:00000001405CD210 var_200 = word ptr -200h + .text:00000001405CD210 var_1FC = dword ptr -1FCh + .text:00000001405CD210 var_1F8 = qword ptr -1F8h + .text:00000001405CD210 var_1F0 = qword ptr -1F0h + .text:00000001405CD210 var_1E8 = qword ptr -1E8h + .text:00000001405CD210 var_1E0 = dword ptr -1E0h + .text:00000001405CD210 var_1DC = word ptr -1DCh + .text:00000001405CD210 var_1DA = word ptr -1DAh + .text:00000001405CD210 var_1D8 = qword ptr -1D8h + .text:00000001405CD210 var_1D0 = byte ptr -1D0h + .text:00000001405CD210 var_1C8 = qword ptr -1C8h + .text:00000001405CD210 var_1B0 = dword ptr -1B0h + .text:00000001405CD210 var_1AC = dword ptr -1ACh + .text:00000001405CD210 var_1A8 = dword ptr -1A8h + .text:00000001405CD210 var_1A4 = dword ptr -1A4h + .text:00000001405CD210 var_1A0 = dword ptr -1A0h + .text:00000001405CD210 var_160 = dword ptr -160h + .text:00000001405CD210 var_15C = dword ptr -15Ch + .text:00000001405CD210 var_140 = dword ptr -140h + .text:00000001405CD210 var_138 = dword ptr -138h + .text:00000001405CD210 var_130 = byte ptr -130h + .text:00000001405CD210 var_C0 = byte ptr -0C0h + .text:00000001405CD210 var_50 = qword ptr -50h + .text:00000001405CD210 var_38 = qword ptr -38h + .text:00000001405CD210 var_30 = qword ptr -30h + .text:00000001405CD210 var_28 = qword ptr -28h + .text:00000001405CD210 var_20 = qword ptr -20h + .text:00000001405CD210 senderActorId = dword ptr 30h + .text:00000001405CD210 isLocal = byte ptr 38h + .text:00000001405CD210 + .text:00000001405CD210 ; __unwind { // __GSHandlerCheck + .text:00000001405CD210 push rbp + .text:00000001405CD212 push rdi + .text:00000001405CD213 push r14 + .text:00000001405CD215 push r15 + .text:00000001405CD217 lea rbp, [rsp-128h] + .text:00000001405CD21F sub rsp, 228h + .text:00000001405CD226 mov rax, cs:__security_cookie + .text:00000001405CD22D xor rax, rsp + .text:00000001405CD230 mov [rbp+140h+var_50], rax + .text:00000001405CD237 xor r10b, r10b + .text:00000001405CD23A mov [rsp+240h+var_1F8], rcx + .text:00000001405CD23F xor eax, eax + .text:00000001405CD241 mov r11, r9 + .text:00000001405CD244 mov r14, r8 + .text:00000001405CD247 mov r9d, eax + .text:00000001405CD24A movzx r15d, dx + .text:00000001405CD24E lea r8, [rcx+0C10h] + .text:00000001405CD255 mov rdi, rcx + */ - /// - protected override void Setup64Bit(SigScanner sig) - { - // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1??? - this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05"); - // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old + /// + protected override void Setup64Bit(SigScanner sig) + { + // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1??? + this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05"); + // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old - // PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33"); + // PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33"); - // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); + // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); - // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0 - this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); + // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0 + this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); - this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9; - } + this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9; } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index c3191562a..02984ac37 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -9,303 +9,302 @@ using Dalamud.IoC.Internal; using Dalamud.Memory; using Serilog; -namespace Dalamud.Game.Gui.FlyText +namespace Dalamud.Game.Gui.FlyText; + +/// +/// This class facilitates interacting with and creating native in-game "fly text". +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class FlyTextGui : IDisposable { /// - /// This class facilitates interacting with and creating native in-game "fly text". + /// The native function responsible for adding fly text to the UI. See . /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class FlyTextGui : IDisposable + private readonly AddFlyTextDelegate addFlyTextNative; + + /// + /// The hook that fires when the game creates a fly text element. See . + /// + private readonly Hook createFlyTextHook; + + /// + /// Initializes a new instance of the class. + /// + internal FlyTextGui() { - /// - /// The native function responsible for adding fly text to the UI. See . - /// - private readonly AddFlyTextDelegate addFlyTextNative; + this.Address = new FlyTextGuiAddressResolver(); + this.Address.Setup(); - /// - /// The hook that fires when the game creates a fly text element. See . - /// - private readonly Hook createFlyTextHook; + this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); + this.createFlyTextHook = new Hook(this.Address.CreateFlyText, this.CreateFlyTextDetour); + } - /// - /// Initializes a new instance of the class. - /// - internal FlyTextGui() + /// + /// The delegate defining the type for the FlyText event. + /// + /// The FlyTextKind. See . + /// Value1 passed to the native flytext function. + /// Value2 passed to the native flytext function. Seems unused. + /// Text1 passed to the native flytext function. + /// Text2 passed to the native flytext function. + /// Color passed to the native flytext function. Changes flytext color. + /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. + /// The vertical offset to place the flytext at. 0 is default. Negative values result + /// in text appearing higher on the screen. This does not change where the element begins to fade. + /// Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear. + public delegate void OnFlyTextCreatedDelegate( + ref FlyTextKind kind, + ref int val1, + ref int val2, + ref SeString text1, + ref SeString text2, + ref uint color, + ref uint icon, + ref float yOffset, + ref bool handled); + + /// + /// Private delegate for the native CreateFlyText function's hook. + /// + private delegate IntPtr CreateFlyTextDelegate( + IntPtr addonFlyText, + FlyTextKind kind, + int val1, + int val2, + IntPtr text2, + uint color, + uint icon, + IntPtr text1, + float yOffset); + + /// + /// Private delegate for the native AddFlyText function pointer. + /// + private delegate void AddFlyTextDelegate( + IntPtr addonFlyText, + uint actorIndex, + uint messageMax, + IntPtr numbers, + uint offsetNum, + uint offsetNumMax, + IntPtr strings, + uint offsetStr, + uint offsetStrMax, + int unknown); + + /// + /// The FlyText event that can be subscribed to. + /// + public event OnFlyTextCreatedDelegate? FlyTextCreated; + + private Dalamud Dalamud { get; } + + private FlyTextGuiAddressResolver Address { get; } + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.createFlyTextHook.Dispose(); + } + + /// + /// Displays a fly text in-game on the local player. + /// + /// The FlyTextKind. See . + /// The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player. + /// Value1 passed to the native flytext function. + /// Value2 passed to the native flytext function. Seems unused. + /// Text1 passed to the native flytext function. + /// Text2 passed to the native flytext function. + /// Color passed to the native flytext function. Changes flytext color. + /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. + public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon) + { + // Known valid flytext region within the atk arrays + var numIndex = 28; + var strIndex = 25; + var numOffset = 147u; + var strOffset = 28u; + + // Get the UI module and flytext addon pointers + var gameGui = Service.Get(); + var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); + var flytext = gameGui.GetAddonByName("_FlyText", 1); + + if (ui == null || flytext == IntPtr.Zero) + return; + + // Get the number and string arrays we need + var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder; + var numArray = atkArrayDataHolder._NumberArrays[numIndex]; + var strArray = atkArrayDataHolder._StringArrays[strIndex]; + + // Write the values to the arrays using a known valid flytext region + numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section + numArray->IntArray[numOffset + 1] = (int)kind; + numArray->IntArray[numOffset + 2] = unchecked((int)val1); + numArray->IntArray[numOffset + 3] = unchecked((int)val2); + numArray->IntArray[numOffset + 4] = 5; // Unknown + numArray->IntArray[numOffset + 5] = unchecked((int)color); + numArray->IntArray[numOffset + 6] = unchecked((int)icon); + numArray->IntArray[numOffset + 7] = 0; // Unknown + numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset + + fixed (byte* pText1 = text1.Encode()) { - this.Address = new FlyTextGuiAddressResolver(); - this.Address.Setup(); - - this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); - this.createFlyTextHook = new Hook(this.Address.CreateFlyText, this.CreateFlyTextDetour); - } - - /// - /// The delegate defining the type for the FlyText event. - /// - /// The FlyTextKind. See . - /// Value1 passed to the native flytext function. - /// Value2 passed to the native flytext function. Seems unused. - /// Text1 passed to the native flytext function. - /// Text2 passed to the native flytext function. - /// Color passed to the native flytext function. Changes flytext color. - /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. - /// The vertical offset to place the flytext at. 0 is default. Negative values result - /// in text appearing higher on the screen. This does not change where the element begins to fade. - /// Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear. - public delegate void OnFlyTextCreatedDelegate( - ref FlyTextKind kind, - ref int val1, - ref int val2, - ref SeString text1, - ref SeString text2, - ref uint color, - ref uint icon, - ref float yOffset, - ref bool handled); - - /// - /// Private delegate for the native CreateFlyText function's hook. - /// - private delegate IntPtr CreateFlyTextDelegate( - IntPtr addonFlyText, - FlyTextKind kind, - int val1, - int val2, - IntPtr text2, - uint color, - uint icon, - IntPtr text1, - float yOffset); - - /// - /// Private delegate for the native AddFlyText function pointer. - /// - private delegate void AddFlyTextDelegate( - IntPtr addonFlyText, - uint actorIndex, - uint messageMax, - IntPtr numbers, - uint offsetNum, - uint offsetNumMax, - IntPtr strings, - uint offsetStr, - uint offsetStrMax, - int unknown); - - /// - /// The FlyText event that can be subscribed to. - /// - public event OnFlyTextCreatedDelegate? FlyTextCreated; - - private Dalamud Dalamud { get; } - - private FlyTextGuiAddressResolver Address { get; } - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.createFlyTextHook.Dispose(); - } - - /// - /// Displays a fly text in-game on the local player. - /// - /// The FlyTextKind. See . - /// The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player. - /// Value1 passed to the native flytext function. - /// Value2 passed to the native flytext function. Seems unused. - /// Text1 passed to the native flytext function. - /// Text2 passed to the native flytext function. - /// Color passed to the native flytext function. Changes flytext color. - /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. - public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon) - { - // Known valid flytext region within the atk arrays - var numIndex = 28; - var strIndex = 25; - var numOffset = 147u; - var strOffset = 28u; - - // Get the UI module and flytext addon pointers - var gameGui = Service.Get(); - var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); - var flytext = gameGui.GetAddonByName("_FlyText", 1); - - if (ui == null || flytext == IntPtr.Zero) - return; - - // Get the number and string arrays we need - var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder; - var numArray = atkArrayDataHolder._NumberArrays[numIndex]; - var strArray = atkArrayDataHolder._StringArrays[strIndex]; - - // Write the values to the arrays using a known valid flytext region - numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section - numArray->IntArray[numOffset + 1] = (int)kind; - numArray->IntArray[numOffset + 2] = unchecked((int)val1); - numArray->IntArray[numOffset + 3] = unchecked((int)val2); - numArray->IntArray[numOffset + 4] = 5; // Unknown - numArray->IntArray[numOffset + 5] = unchecked((int)color); - numArray->IntArray[numOffset + 6] = unchecked((int)icon); - numArray->IntArray[numOffset + 7] = 0; // Unknown - numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset - - fixed (byte* pText1 = text1.Encode()) + fixed (byte* pText2 = text2.Encode()) { - fixed (byte* pText2 = text2.Encode()) - { - strArray->StringArray[strOffset + 0] = pText1; - strArray->StringArray[strOffset + 1] = pText2; + strArray->StringArray[strOffset + 0] = pText1; + strArray->StringArray[strOffset + 1] = pText2; - this.addFlyTextNative( - flytext, - actorIndex, - 1, - (IntPtr)numArray, - numOffset, - 9, - (IntPtr)strArray, - strOffset, - 2, - 0); - } + this.addFlyTextNative( + flytext, + actorIndex, + 1, + (IntPtr)numArray, + numOffset, + 9, + (IntPtr)strArray, + strOffset, + 2, + 0); } } - - /// - /// Enables this module. - /// - internal void Enable() - { - this.createFlyTextHook.Enable(); - } - - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private IntPtr CreateFlyTextDetour( - IntPtr addonFlyText, - FlyTextKind kind, - int val1, - int val2, - IntPtr text2, - uint color, - uint icon, - IntPtr text1, - float yOffset) - { - var retVal = IntPtr.Zero; - try - { - Log.Verbose("[FlyText] Enter CreateFlyText detour!"); - - var handled = false; - - var tmpKind = kind; - var tmpVal1 = val1; - var tmpVal2 = val2; - var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1); - var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2); - var tmpColor = color; - var tmpIcon = icon; - var tmpYOffset = yOffset; - - var cmpText1 = tmpText1.ToString(); - var cmpText2 = tmpText2.ToString(); - - Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " + - $"kind({kind}) val1({val1}) val2({val2}) " + - $"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " + - $"color({color:X}) icon({icon}) yOffset({yOffset})"); - Log.Verbose("[FlyText] Calling flytext events!"); - this.FlyTextCreated?.Invoke( - ref tmpKind, - ref tmpVal1, - ref tmpVal2, - ref tmpText1, - ref tmpText2, - ref tmpColor, - ref tmpIcon, - ref tmpYOffset, - ref handled); - - // If handled, ignore the original call - if (handled) - { - Log.Verbose("[FlyText] FlyText was handled."); - - // Returning null to AddFlyText from CreateFlyText will result - // in the operation being dropped entirely. - return IntPtr.Zero; - } - - // Check if any values have changed - var dirty = tmpKind != kind || - tmpVal1 != val1 || - tmpVal2 != val2 || - tmpText1.ToString() != cmpText1 || - tmpText2.ToString() != cmpText2 || - tmpColor != color || - tmpIcon != icon || - Math.Abs(tmpYOffset - yOffset) > float.Epsilon; - - // If not dirty, make the original call - if (!dirty) - { - Log.Verbose("[FlyText] Calling flytext with original args."); - return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset); - } - - var terminated1 = Terminate(tmpText1.Encode()); - var terminated2 = Terminate(tmpText2.Encode()); - var pText1 = Marshal.AllocHGlobal(terminated1.Length); - var pText2 = Marshal.AllocHGlobal(terminated2.Length); - Marshal.Copy(terminated1, 0, pText1, terminated1.Length); - Marshal.Copy(terminated2, 0, pText2, terminated2.Length); - Log.Verbose("[FlyText] Allocated and set strings."); - - retVal = this.createFlyTextHook.Original( - addonFlyText, - tmpKind, - tmpVal1, - tmpVal2, - pText2, - tmpColor, - tmpIcon, - pText1, - tmpYOffset); - - Log.Verbose("[FlyText] Returned from original. Delaying free task."); - - Task.Delay(2000).ContinueWith(_ => - { - try - { - Marshal.FreeHGlobal(pText1); - Marshal.FreeHGlobal(pText2); - Log.Verbose("[FlyText] Freed strings."); - } - catch (Exception e) - { - Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task."); - } - }); - } - catch (Exception e) - { - Log.Error(e, "Exception occurred in CreateFlyTextDetour!"); - } - - return retVal; - } + } + + /// + /// Enables this module. + /// + internal void Enable() + { + this.createFlyTextHook.Enable(); + } + + private static byte[] Terminate(byte[] source) + { + var terminated = new byte[source.Length + 1]; + Array.Copy(source, 0, terminated, 0, source.Length); + terminated[^1] = 0; + + return terminated; + } + + private IntPtr CreateFlyTextDetour( + IntPtr addonFlyText, + FlyTextKind kind, + int val1, + int val2, + IntPtr text2, + uint color, + uint icon, + IntPtr text1, + float yOffset) + { + var retVal = IntPtr.Zero; + try + { + Log.Verbose("[FlyText] Enter CreateFlyText detour!"); + + var handled = false; + + var tmpKind = kind; + var tmpVal1 = val1; + var tmpVal2 = val2; + var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1); + var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2); + var tmpColor = color; + var tmpIcon = icon; + var tmpYOffset = yOffset; + + var cmpText1 = tmpText1.ToString(); + var cmpText2 = tmpText2.ToString(); + + Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " + + $"kind({kind}) val1({val1}) val2({val2}) " + + $"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " + + $"color({color:X}) icon({icon}) yOffset({yOffset})"); + Log.Verbose("[FlyText] Calling flytext events!"); + this.FlyTextCreated?.Invoke( + ref tmpKind, + ref tmpVal1, + ref tmpVal2, + ref tmpText1, + ref tmpText2, + ref tmpColor, + ref tmpIcon, + ref tmpYOffset, + ref handled); + + // If handled, ignore the original call + if (handled) + { + Log.Verbose("[FlyText] FlyText was handled."); + + // Returning null to AddFlyText from CreateFlyText will result + // in the operation being dropped entirely. + return IntPtr.Zero; + } + + // Check if any values have changed + var dirty = tmpKind != kind || + tmpVal1 != val1 || + tmpVal2 != val2 || + tmpText1.ToString() != cmpText1 || + tmpText2.ToString() != cmpText2 || + tmpColor != color || + tmpIcon != icon || + Math.Abs(tmpYOffset - yOffset) > float.Epsilon; + + // If not dirty, make the original call + if (!dirty) + { + Log.Verbose("[FlyText] Calling flytext with original args."); + return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset); + } + + var terminated1 = Terminate(tmpText1.Encode()); + var terminated2 = Terminate(tmpText2.Encode()); + var pText1 = Marshal.AllocHGlobal(terminated1.Length); + var pText2 = Marshal.AllocHGlobal(terminated2.Length); + Marshal.Copy(terminated1, 0, pText1, terminated1.Length); + Marshal.Copy(terminated2, 0, pText2, terminated2.Length); + Log.Verbose("[FlyText] Allocated and set strings."); + + retVal = this.createFlyTextHook.Original( + addonFlyText, + tmpKind, + tmpVal1, + tmpVal2, + pText2, + tmpColor, + tmpIcon, + pText1, + tmpYOffset); + + Log.Verbose("[FlyText] Returned from original. Delaying free task."); + + Task.Delay(2000).ContinueWith(_ => + { + try + { + Marshal.FreeHGlobal(pText1); + Marshal.FreeHGlobal(pText2); + Log.Verbose("[FlyText] Freed strings."); + } + catch (Exception e) + { + Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task."); + } + }); + } + catch (Exception e) + { + Log.Error(e, "Exception occurred in CreateFlyTextDetour!"); + } + + return retVal; } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs b/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs index ae0eb0035..f608c7d77 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs @@ -1,32 +1,31 @@ using System; -namespace Dalamud.Game.Gui.FlyText +namespace Dalamud.Game.Gui.FlyText; + +/// +/// An address resolver for the class. +/// +public class FlyTextGuiAddressResolver : BaseAddressResolver { /// - /// An address resolver for the class. + /// Gets the address of the native AddFlyText method, which occurs + /// when the game adds fly text elements to the UI. Multiple fly text + /// elements can be added in a single AddFlyText call. /// - public class FlyTextGuiAddressResolver : BaseAddressResolver + public IntPtr AddFlyText { get; private set; } + + /// + /// Gets the address of the native CreateFlyText method, which occurs + /// when the game creates a new fly text element. This method is called + /// once per fly text element, and can be called multiple times per + /// AddFlyText call. + /// + public IntPtr CreateFlyText { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the address of the native AddFlyText method, which occurs - /// when the game adds fly text elements to the UI. Multiple fly text - /// elements can be added in a single AddFlyText call. - /// - public IntPtr AddFlyText { get; private set; } - - /// - /// Gets the address of the native CreateFlyText method, which occurs - /// when the game creates a new fly text element. This method is called - /// once per fly text element, and can be called multiple times per - /// AddFlyText call. - /// - public IntPtr CreateFlyText { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) - { - this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7"); - this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA"); - } + this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7"); + this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA"); } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs index 2b059f168..30c4c5e53 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs @@ -1,283 +1,282 @@ -namespace Dalamud.Game.Gui.FlyText +namespace Dalamud.Game.Gui.FlyText; + +/// +/// Enum of FlyTextKind values. Members suffixed with +/// a number seem to be a duplicate, or perform duplicate behavior. +/// +public enum FlyTextKind : int { /// - /// Enum of FlyTextKind values. Members suffixed with - /// a number seem to be a duplicate, or perform duplicate behavior. + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Used for autos and incoming DoTs. /// - public enum FlyTextKind : int - { - /// - /// Val1 in serif font, Text2 in sans-serif as subtitle. - /// Used for autos and incoming DoTs. - /// - AutoAttack = 0, + AutoAttack = 0, - /// - /// Val1 in serif font, Text2 in sans-serif as subtitle. - /// Does a bounce effect on appearance. - /// - DirectHit = 1, + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Does a bounce effect on appearance. + /// + DirectHit = 1, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. - /// Does a bigger bounce effect on appearance. - /// - CriticalHit = 2, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. + /// Does a bigger bounce effect on appearance. + /// + CriticalHit = 2, - /// - /// Val1 in even larger serif font with 2 exclamations, Text2 in - /// sans-serif as subtitle. Does a large bounce effect on appearance. - /// Does not scroll up or down the screen. - /// - CriticalDirectHit = 3, + /// + /// Val1 in even larger serif font with 2 exclamations, Text2 in + /// sans-serif as subtitle. Does a large bounce effect on appearance. + /// Does not scroll up or down the screen. + /// + CriticalDirectHit = 3, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1. - /// - NamedAttack = 4, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1. + /// + NamedAttack = 4, - /// - /// DirectHit with sans-serif Text1 to the left of the Val1. - /// - NamedDirectHit = 5, + /// + /// DirectHit with sans-serif Text1 to the left of the Val1. + /// + NamedDirectHit = 5, - /// - /// CriticalHit with sans-serif Text1 to the left of the Val1. - /// - NamedCriticalHit = 6, + /// + /// CriticalHit with sans-serif Text1 to the left of the Val1. + /// + NamedCriticalHit = 6, - /// - /// CriticalDirectHit with sans-serif Text1 to the left of the Val1. - /// - NamedCriticalDirectHit = 7, + /// + /// CriticalDirectHit with sans-serif Text1 to the left of the Val1. + /// + NamedCriticalDirectHit = 7, - /// - /// All caps, serif MISS. - /// - Miss = 8, + /// + /// All caps, serif MISS. + /// + Miss = 8, - /// - /// Sans-serif Text1 next to all caps serif MISS. - /// - NamedMiss = 9, + /// + /// Sans-serif Text1 next to all caps serif MISS. + /// + NamedMiss = 9, - /// - /// All caps serif DODGE. - /// - Dodge = 10, + /// + /// All caps serif DODGE. + /// + Dodge = 10, - /// - /// Sans-serif Text1 next to all caps serif DODGE. - /// - NamedDodge = 11, + /// + /// Sans-serif Text1 next to all caps serif DODGE. + /// + NamedDodge = 11, - /// - /// Icon next to sans-serif Text1. - /// - NamedIcon = 12, + /// + /// Icon next to sans-serif Text1. + /// + NamedIcon = 12, - /// - /// Icon next to sans-serif Text1 (2). - /// - NamedIcon2 = 13, + /// + /// Icon next to sans-serif Text1 (2). + /// + NamedIcon2 = 13, - /// - /// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle. - /// - Exp = 14, + /// + /// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle. + /// + Exp = 14, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. - /// - NamedMp = 15, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. + /// + NamedMp = 15, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. - /// - NamedTp = 16, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. + /// + NamedTp = 16, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1 (2). - /// - NamedAttack2 = 17, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1 (2). + /// + NamedAttack2 = 17, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2). - /// - NamedMp2 = 18, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2). + /// + NamedMp2 = 18, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2). - /// - NamedTp2 = 19, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2). + /// + NamedTp2 = 19, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. - /// - NamedEp = 20, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. + /// + NamedEp = 20, - /// - /// Displays nothing. - /// - None = 21, + /// + /// Displays nothing. + /// + None = 21, - /// - /// All caps serif INVULNERABLE. - /// - Invulnerable = 22, + /// + /// All caps serif INVULNERABLE. + /// + Invulnerable = 22, - /// - /// All caps sans-serif condensed font INTERRUPTED! - /// Does a large bounce effect on appearance. - /// Does not scroll up or down the screen. - /// - Interrupted = 23, + /// + /// All caps sans-serif condensed font INTERRUPTED! + /// Does a large bounce effect on appearance. + /// Does not scroll up or down the screen. + /// + Interrupted = 23, - /// - /// AutoAttack with no Text2. - /// - AutoAttackNoText = 24, + /// + /// AutoAttack with no Text2. + /// + AutoAttackNoText = 24, - /// - /// AutoAttack with no Text2 (2). - /// - AutoAttackNoText2 = 25, + /// + /// AutoAttack with no Text2 (2). + /// + AutoAttackNoText2 = 25, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2). - /// - CriticalHit2 = 26, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2). + /// + CriticalHit2 = 26, - /// - /// AutoAttack with no Text2 (3). - /// - AutoAttackNoText3 = 27, + /// + /// AutoAttack with no Text2 (3). + /// + AutoAttackNoText3 = 27, - /// - /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). - /// - NamedCriticalHit2 = 28, + /// + /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). + /// + NamedCriticalHit2 = 28, - /// - /// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1. - /// Does a jiggle effect to the right on appearance. - /// - NamedCriticalHitWithMp = 29, + /// + /// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1. + /// Does a jiggle effect to the right on appearance. + /// + NamedCriticalHitWithMp = 29, - /// - /// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1. - /// Does a jiggle effect to the right on appearance. - /// - NamedCriticalHitWithTp = 30, + /// + /// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1. + /// Does a jiggle effect to the right on appearance. + /// + NamedCriticalHitWithTp = 30, - /// - /// Same as NamedIcon with sans-serif "has no effect!" to the right. - /// - NamedIconHasNoEffect = 31, + /// + /// Same as NamedIcon with sans-serif "has no effect!" to the right. + /// + NamedIconHasNoEffect = 31, - /// - /// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration. - /// - NamedIconFaded = 32, + /// + /// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration. + /// + NamedIconFaded = 32, - /// - /// Same as NamedIcon but Text1 is slightly faded (2). - /// Used for buff expiration. - /// - NamedIconFaded2 = 33, + /// + /// Same as NamedIcon but Text1 is slightly faded (2). + /// Used for buff expiration. + /// + NamedIconFaded2 = 33, - /// - /// Text1 in sans-serif font. - /// - Named = 34, + /// + /// Text1 in sans-serif font. + /// + Named = 34, - /// - /// Same as NamedIcon with sans-serif "(fully resisted)" to the right. - /// - NamedIconFullyResisted = 35, + /// + /// Same as NamedIcon with sans-serif "(fully resisted)" to the right. + /// + NamedIconFullyResisted = 35, - /// - /// All caps serif 'INCAPACITATED!'. - /// - Incapacitated = 36, + /// + /// All caps serif 'INCAPACITATED!'. + /// + Incapacitated = 36, - /// - /// Text1 with sans-serif "(fully resisted)" to the right. - /// - NamedFullyResisted = 37, + /// + /// Text1 with sans-serif "(fully resisted)" to the right. + /// + NamedFullyResisted = 37, - /// - /// Text1 with sans-serif "has no effect!" to the right. - /// - NamedHasNoEffect = 38, + /// + /// Text1 with sans-serif "has no effect!" to the right. + /// + NamedHasNoEffect = 38, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1 (3). - /// - NamedAttack3 = 39, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1 (3). + /// + NamedAttack3 = 39, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3). - /// - NamedMp3 = 40, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3). + /// + NamedMp3 = 40, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3). - /// - NamedTp3 = 41, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3). + /// + NamedTp3 = 41, - /// - /// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1. - /// - NamedIconInvulnerable = 42, + /// + /// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1. + /// + NamedIconInvulnerable = 42, - /// - /// All caps serif RESIST. - /// - Resist = 43, + /// + /// All caps serif RESIST. + /// + Resist = 43, - /// - /// Same as NamedIcon but places the given icon in the item icon outline. - /// - NamedIconWithItemOutline = 44, + /// + /// Same as NamedIcon but places the given icon in the item icon outline. + /// + NamedIconWithItemOutline = 44, - /// - /// AutoAttack with no Text2 (4). - /// - AutoAttackNoText4 = 45, + /// + /// AutoAttack with no Text2 (4). + /// + AutoAttackNoText4 = 45, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3). - /// Does a bigger bounce effect on appearance. - /// - CriticalHit3 = 46, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3). + /// Does a bigger bounce effect on appearance. + /// + CriticalHit3 = 46, - /// - /// All caps serif REFLECT. - /// - Reflect = 47, + /// + /// All caps serif REFLECT. + /// + Reflect = 47, - /// - /// All caps serif REFLECTED. - /// - Reflected = 48, + /// + /// All caps serif REFLECTED. + /// + Reflected = 48, - /// - /// Val1 in serif font, Text2 in sans-serif as subtitle (2). - /// Does a bounce effect on appearance. - /// - DirectHit2 = 49, + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle (2). + /// Does a bounce effect on appearance. + /// + DirectHit2 = 49, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4). - /// Does a bigger bounce effect on appearance. - /// - CriticalHit4 = 50, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4). + /// Does a bigger bounce effect on appearance. + /// + CriticalHit4 = 50, - /// - /// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2). - /// Does a large bounce effect on appearance. Does not scroll up or down the screen. - /// - CriticalDirectHit2 = 51, - } + /// + /// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2). + /// Does a large bounce effect on appearance. Does not scroll up or down the screen. + /// + CriticalDirectHit2 = 51, } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 0c1ae4e51..ad1d90421 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -14,584 +14,583 @@ using Dalamud.Utility; using ImGuiNET; using Serilog; -namespace Dalamud.Game.Gui +namespace Dalamud.Game.Gui; + +/// +/// A class handling many aspects of the in-game UI. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class GameGui : IDisposable { + private readonly GameGuiAddressResolver address; + + private readonly GetMatrixSingletonDelegate getMatrixSingleton; + private readonly ScreenToWorldNativeDelegate screenToWorldNative; + private readonly GetAgentModuleDelegate getAgentModule; + + private readonly Hook setGlobalBgmHook; + private readonly Hook handleItemHoverHook; + private readonly Hook handleItemOutHook; + private readonly Hook handleActionHoverHook; + private readonly Hook handleActionOutHook; + private readonly Hook handleImmHook; + private readonly Hook toggleUiHideHook; + + private GetUIMapObjectDelegate getUIMapObject; + private OpenMapWithFlagDelegate openMapWithFlag; + /// - /// A class handling many aspects of the in-game UI. + /// Initializes a new instance of the class. + /// This class is responsible for many aspects of interacting with the native game UI. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class GameGui : IDisposable + internal GameGui() { - private readonly GameGuiAddressResolver address; + this.address = new GameGuiAddressResolver(); + this.address.Setup(); - private readonly GetMatrixSingletonDelegate getMatrixSingleton; - private readonly ScreenToWorldNativeDelegate screenToWorldNative; - private readonly GetAgentModuleDelegate getAgentModule; + Log.Verbose("===== G A M E G U I ====="); + Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); + Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}"); + Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}"); + Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); + Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); + Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}"); - private readonly Hook setGlobalBgmHook; - private readonly Hook handleItemHoverHook; - private readonly Hook handleItemOutHook; - private readonly Hook handleActionHoverHook; - private readonly Hook handleActionOutHook; - private readonly Hook handleImmHook; - private readonly Hook toggleUiHideHook; + Service.Set(new ChatGui(this.address.ChatManager)); + Service.Set(); + Service.Set(); + Service.Set(); - private GetUIMapObjectDelegate getUIMapObject; - private OpenMapWithFlagDelegate openMapWithFlag; + this.setGlobalBgmHook = new Hook(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - /// - /// Initializes a new instance of the class. - /// This class is responsible for many aspects of interacting with the native game UI. - /// - internal GameGui() + this.handleItemHoverHook = new Hook(this.address.HandleItemHover, this.HandleItemHoverDetour); + this.handleItemOutHook = new Hook(this.address.HandleItemOut, this.HandleItemOutDetour); + + this.handleActionHoverHook = new Hook(this.address.HandleActionHover, this.HandleActionHoverDetour); + this.handleActionOutHook = new Hook(this.address.HandleActionOut, this.HandleActionOutDetour); + + this.handleImmHook = new Hook(this.address.HandleImm, this.HandleImmDetour); + + this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton); + + this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld); + + this.toggleUiHideHook = new Hook(this.address.ToggleUiHide, this.ToggleUiHideDetour); + + this.getAgentModule = Marshal.GetDelegateForFunctionPointer(this.address.GetAgentModule); + } + + // Marshaled delegates + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate IntPtr GetMatrixSingletonDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown); + + private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule); + + // Hooked delegates + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] + private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte); + + /// + /// Event which is fired when the game UI hiding is toggled. + /// + public event EventHandler UiHideToggled; + + /// + /// Event that is fired when the currently hovered item changes. + /// + public event EventHandler HoveredItemChanged; + + /// + /// Event that is fired when the currently hovered action changes. + /// + public event EventHandler HoveredActionChanged; + + /// + /// Gets a value indicating whether the game UI is hidden. + /// + public bool GameUiHidden { get; private set; } + + /// + /// Gets or sets the item ID that is currently hovered by the player. 0 when no item is hovered. + /// If > 1.000.000, subtract 1.000.000 and treat it as HQ. + /// + public ulong HoveredItem { get; set; } + + /// + /// Gets the action ID that is current hovered by the player. 0 when no action is hovered. + /// + public HoveredAction HoveredAction { get; } = new HoveredAction(); + + /// + /// Opens the in-game map with a flag on the location of the parameter. + /// + /// Link to the map to be opened. + /// True if there were no errors and it could open the map. + public bool OpenMapWithMapLink(MapLinkPayload mapLink) + { + var uiModule = this.GetUIModule(); + + if (uiModule == IntPtr.Zero) { - this.address = new GameGuiAddressResolver(); - this.address.Setup(); - - Log.Verbose("===== G A M E G U I ====="); - Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); - Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}"); - Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}"); - Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); - Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); - Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}"); - - Service.Set(new ChatGui(this.address.ChatManager)); - Service.Set(); - Service.Set(); - Service.Set(); - - this.setGlobalBgmHook = new Hook(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - - this.handleItemHoverHook = new Hook(this.address.HandleItemHover, this.HandleItemHoverDetour); - this.handleItemOutHook = new Hook(this.address.HandleItemOut, this.HandleItemOutDetour); - - this.handleActionHoverHook = new Hook(this.address.HandleActionHover, this.HandleActionHoverDetour); - this.handleActionOutHook = new Hook(this.address.HandleActionOut, this.HandleActionOutDetour); - - this.handleImmHook = new Hook(this.address.HandleImm, this.HandleImmDetour); - - this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton); - - this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld); - - this.toggleUiHideHook = new Hook(this.address.ToggleUiHide, this.ToggleUiHideDetour); - - this.getAgentModule = Marshal.GetDelegateForFunctionPointer(this.address.GetAgentModule); + Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()"); + return false; } - // Marshaled delegates + this.getUIMapObject = this.address.GetVirtualFunction(uiModule, 0, 8); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr GetMatrixSingletonDelegate(); + var uiMapObjectPtr = this.getUIMapObject(uiModule); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown); - - private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule); - - // Hooked delegates - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] - private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte); - - /// - /// Event which is fired when the game UI hiding is toggled. - /// - public event EventHandler UiHideToggled; - - /// - /// Event that is fired when the currently hovered item changes. - /// - public event EventHandler HoveredItemChanged; - - /// - /// Event that is fired when the currently hovered action changes. - /// - public event EventHandler HoveredActionChanged; - - /// - /// Gets a value indicating whether the game UI is hidden. - /// - public bool GameUiHidden { get; private set; } - - /// - /// Gets or sets the item ID that is currently hovered by the player. 0 when no item is hovered. - /// If > 1.000.000, subtract 1.000.000 and treat it as HQ. - /// - public ulong HoveredItem { get; set; } - - /// - /// Gets the action ID that is current hovered by the player. 0 when no action is hovered. - /// - public HoveredAction HoveredAction { get; } = new HoveredAction(); - - /// - /// Opens the in-game map with a flag on the location of the parameter. - /// - /// Link to the map to be opened. - /// True if there were no errors and it could open the map. - public bool OpenMapWithMapLink(MapLinkPayload mapLink) + if (uiMapObjectPtr == IntPtr.Zero) { - var uiModule = this.GetUIModule(); - - if (uiModule == IntPtr.Zero) - { - Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()"); - return false; - } - - this.getUIMapObject = this.address.GetVirtualFunction(uiModule, 0, 8); - - var uiMapObjectPtr = this.getUIMapObject(uiModule); - - if (uiMapObjectPtr == IntPtr.Zero) - { - Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()"); - return false; - } - - this.openMapWithFlag = this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63); - - var mapLinkString = mapLink.DataString; - - Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); - - return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); + Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()"); + return false; } - /// - /// Converts in-world coordinates to screen coordinates (upper left corner origin). - /// - /// Coordinates in the world. - /// Converted coordinates. - /// True if worldPos corresponds to a position in front of the camera. - public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) + this.openMapWithFlag = this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63); + + var mapLinkString = mapLink.DataString; + + Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); + + return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); + } + + /// + /// Converts in-world coordinates to screen coordinates (upper left corner origin). + /// + /// Coordinates in the world. + /// Converted coordinates. + /// True if worldPos corresponds to a position in front of the camera. + public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) + { + // Get base object with matrices + var matrixSingleton = this.getMatrixSingleton(); + + // Read current ViewProjectionMatrix plus game window size + var viewProjectionMatrix = default(SharpDX.Matrix); + float width, height; + var windowPos = ImGuiHelpers.MainViewport.Pos; + + unsafe { - // Get base object with matrices - var matrixSingleton = this.getMatrixSingleton(); + var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); - // Read current ViewProjectionMatrix plus game window size - var viewProjectionMatrix = default(SharpDX.Matrix); - float width, height; - var windowPos = ImGuiHelpers.MainViewport.Pos; + for (var i = 0; i < 16; i++, rawMatrix++) + viewProjectionMatrix[i] = *rawMatrix; - unsafe - { - var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); - - for (var i = 0; i < 16; i++, rawMatrix++) - viewProjectionMatrix[i] = *rawMatrix; - - width = *rawMatrix; - height = *(rawMatrix + 1); - } - - var worldPosDx = worldPos.ToSharpDX(); - SharpDX.Vector3.Transform(ref worldPosDx, ref viewProjectionMatrix, out SharpDX.Vector3 pCoords); - - screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z); - - screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X; - screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y; - - return pCoords.Z > 0 && - screenPos.X > windowPos.X && screenPos.X < windowPos.X + width && - screenPos.Y > windowPos.Y && screenPos.Y < windowPos.Y + height; + width = *rawMatrix; + height = *(rawMatrix + 1); } - /// - /// Converts screen coordinates to in-world coordinates via raycasting. - /// - /// Screen coordinates. - /// Converted coordinates. - /// How far to search for a collision. - /// True if successful. On false, worldPos's contents are undefined. - public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f) + var worldPosDx = worldPos.ToSharpDX(); + SharpDX.Vector3.Transform(ref worldPosDx, ref viewProjectionMatrix, out SharpDX.Vector3 pCoords); + + screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z); + + screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X; + screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y; + + return pCoords.Z > 0 && + screenPos.X > windowPos.X && screenPos.X < windowPos.X + width && + screenPos.Y > windowPos.Y && screenPos.Y < windowPos.Y + height; + } + + /// + /// Converts screen coordinates to in-world coordinates via raycasting. + /// + /// Screen coordinates. + /// Converted coordinates. + /// How far to search for a collision. + /// True if successful. On false, worldPos's contents are undefined. + public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f) + { + // The game is only visible in the main viewport, so if the cursor is outside + // of the game window, do not bother calculating anything + var windowPos = ImGuiHelpers.MainViewport.Pos; + var windowSize = ImGuiHelpers.MainViewport.Size; + + if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X || + screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y) { - // The game is only visible in the main viewport, so if the cursor is outside - // of the game window, do not bother calculating anything - var windowPos = ImGuiHelpers.MainViewport.Pos; - var windowSize = ImGuiHelpers.MainViewport.Size; + worldPos = default; + return false; + } - if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X || - screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y) + // Get base object with matrices + var matrixSingleton = this.getMatrixSingleton(); + + // Read current ViewProjectionMatrix plus game window size + var viewProjectionMatrix = default(SharpDX.Matrix); + float width, height; + unsafe + { + var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); + + for (var i = 0; i < 16; i++, rawMatrix++) + viewProjectionMatrix[i] = *rawMatrix; + + width = *rawMatrix; + height = *(rawMatrix + 1); + } + + viewProjectionMatrix.Invert(); + + var localScreenPos = new SharpDX.Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y); + var screenPos3D = new SharpDX.Vector3 + { + X = (localScreenPos.X / width * 2.0f) - 1.0f, + Y = -((localScreenPos.Y / height * 2.0f) - 1.0f), + Z = 0, + }; + + SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos); + + screenPos3D.Z = 1; + SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne); + + var clipPos = camPosOne - camPos; + clipPos.Normalize(); + + bool isSuccess; + unsafe + { + var camPosArray = camPos.ToArray(); + var clipPosArray = clipPos.ToArray(); + + // This array is larger than necessary because it contains more info than we currently use + var worldPosArray = stackalloc float[32]; + + // Theory: this is some kind of flag on what type of things the ray collides with + var unknown = stackalloc int[3] { - worldPos = default; - return false; - } - - // Get base object with matrices - var matrixSingleton = this.getMatrixSingleton(); - - // Read current ViewProjectionMatrix plus game window size - var viewProjectionMatrix = default(SharpDX.Matrix); - float width, height; - unsafe - { - var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); - - for (var i = 0; i < 16; i++, rawMatrix++) - viewProjectionMatrix[i] = *rawMatrix; - - width = *rawMatrix; - height = *(rawMatrix + 1); - } - - viewProjectionMatrix.Invert(); - - var localScreenPos = new SharpDX.Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y); - var screenPos3D = new SharpDX.Vector3 - { - X = (localScreenPos.X / width * 2.0f) - 1.0f, - Y = -((localScreenPos.Y / height * 2.0f) - 1.0f), - Z = 0, + 0x4000, + 0x4000, + 0x0, }; - SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos); - - screenPos3D.Z = 1; - SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne); - - var clipPos = camPosOne - camPos; - clipPos.Normalize(); - - bool isSuccess; - unsafe + fixed (float* pCamPos = camPosArray) { - var camPosArray = camPos.ToArray(); - var clipPosArray = clipPos.ToArray(); - - // This array is larger than necessary because it contains more info than we currently use - var worldPosArray = stackalloc float[32]; - - // Theory: this is some kind of flag on what type of things the ray collides with - var unknown = stackalloc int[3] + fixed (float* pClipPos = clipPosArray) { - 0x4000, - 0x4000, - 0x0, - }; - - fixed (float* pCamPos = camPosArray) - { - fixed (float* pClipPos = clipPosArray) - { - isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown); - } + isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown); } - - worldPos = new Vector3 - { - X = worldPosArray[0], - Y = worldPosArray[1], - Z = worldPosArray[2], - }; } - return isSuccess; - } - - /// - /// Gets a pointer to the game's UI module. - /// - /// IntPtr pointing to UI module. - public unsafe IntPtr 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; - } - - /// - /// Gets the pointer to the Addon with the given name and index. - /// - /// Name of addon to find. - /// Index of addon to find (1-indexed). - /// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the addon. - public unsafe IntPtr GetAddonByName(string name, int index) - { - var atkStage = FFXIVClientStructs.FFXIV.Component.GUI.AtkStage.GetSingleton(); - if (atkStage == null) - return IntPtr.Zero; - - 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; - } - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon name. - /// A pointer to the agent interface. - public IntPtr FindAgentInterface(string addonName) - { - var addon = this.GetAddonByName(addonName, 1); - return this.FindAgentInterface(addon); - } - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon address. - /// A pointer to the agent interface. - public unsafe IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon address. - /// A pointer to the agent interface. - public unsafe IntPtr FindAgentInterface(IntPtr addon) - { - if (addon == IntPtr.Zero) - return IntPtr.Zero; - - var uiModule = Service.Get().GetUIModule(); - if (uiModule == IntPtr.Zero) + worldPos = new Vector3 { - return IntPtr.Zero; - } + X = worldPosArray[0], + Y = worldPosArray[1], + Z = worldPosArray[2], + }; + } - var agentModule = this.getAgentModule(uiModule); - if (agentModule == IntPtr.Zero) - { - return IntPtr.Zero; - } + return isSuccess; + } - var unitBase = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)addon; - var id = unitBase->ParentID; - if (id == 0) - id = unitBase->ID; + /// + /// Gets a pointer to the game's UI module. + /// + /// IntPtr pointing to UI module. + public unsafe IntPtr GetUIModule() + { + var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); + if (framework == null) + return IntPtr.Zero; - if (id == 0) - return IntPtr.Zero; + var uiModule = framework->GetUiModule(); + if (uiModule == null) + return IntPtr.Zero; - for (var i = 0; i < 380; i++) - { - var agent = Marshal.ReadIntPtr(agentModule, 0x20 + (i * 8)); - if (agent == IntPtr.Zero) - continue; - if (Marshal.ReadInt32(agent, 0x20) == id) - return agent; - } + return (IntPtr)uiModule; + } + /// + /// Gets the pointer to the Addon with the given name and index. + /// + /// Name of addon to find. + /// Index of addon to find (1-indexed). + /// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the addon. + public unsafe IntPtr GetAddonByName(string name, int index) + { + var atkStage = FFXIVClientStructs.FFXIV.Component.GUI.AtkStage.GetSingleton(); + if (atkStage == null) + return IntPtr.Zero; + + 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; + } + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The addon name. + /// A pointer to the agent interface. + public IntPtr FindAgentInterface(string addonName) + { + var addon = this.GetAddonByName(addonName, 1); + return this.FindAgentInterface(addon); + } + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The addon address. + /// A pointer to the agent interface. + public unsafe IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The addon address. + /// A pointer to the agent interface. + public unsafe IntPtr FindAgentInterface(IntPtr addon) + { + if (addon == IntPtr.Zero) + return IntPtr.Zero; + + var uiModule = Service.Get().GetUIModule(); + if (uiModule == IntPtr.Zero) + { return IntPtr.Zero; } - /// - /// Set the current background music. - /// - /// The background music key. - public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); - - /// - /// Enables the hooks and submodules of this module. - /// - public void Enable() + var agentModule = this.getAgentModule(uiModule); + if (agentModule == IntPtr.Zero) { - Service.Get().Enable(); - Service.Get().Enable(); - Service.Get().Enable(); - Service.Get().Enable(); - this.setGlobalBgmHook.Enable(); - this.handleItemHoverHook.Enable(); - this.handleItemOutHook.Enable(); - this.handleImmHook.Enable(); - this.toggleUiHideHook.Enable(); - this.handleActionHoverHook.Enable(); - this.handleActionOutHook.Enable(); + return IntPtr.Zero; } - /// - /// Disables the hooks and submodules of this module. - /// - public void Dispose() + var unitBase = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)addon; + var id = unitBase->ParentID; + if (id == 0) + id = unitBase->ID; + + if (id == 0) + return IntPtr.Zero; + + for (var i = 0; i < 380; i++) { - Service.Get().Dispose(); - Service.Get().Dispose(); - Service.Get().Dispose(); - Service.Get().Dispose(); - this.setGlobalBgmHook.Dispose(); - this.handleItemHoverHook.Dispose(); - this.handleItemOutHook.Dispose(); - this.handleImmHook.Dispose(); - this.toggleUiHideHook.Dispose(); - this.handleActionHoverHook.Dispose(); - this.handleActionOutHook.Dispose(); + var agent = Marshal.ReadIntPtr(agentModule, 0x20 + (i * 8)); + if (agent == IntPtr.Zero) + continue; + if (Marshal.ReadInt32(agent, 0x20) == id) + return agent; } - private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6) + return IntPtr.Zero; + } + + /// + /// Set the current background music. + /// + /// The background music key. + public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); + + /// + /// Enables the hooks and submodules of this module. + /// + public void Enable() + { + Service.Get().Enable(); + Service.Get().Enable(); + Service.Get().Enable(); + Service.Get().Enable(); + this.setGlobalBgmHook.Enable(); + this.handleItemHoverHook.Enable(); + this.handleItemOutHook.Enable(); + this.handleImmHook.Enable(); + this.toggleUiHideHook.Enable(); + this.handleActionHoverHook.Enable(); + this.handleActionOutHook.Enable(); + } + + /// + /// Disables the hooks and submodules of this module. + /// + public void Dispose() + { + Service.Get().Dispose(); + Service.Get().Dispose(); + Service.Get().Dispose(); + Service.Get().Dispose(); + this.setGlobalBgmHook.Dispose(); + this.handleItemHoverHook.Dispose(); + this.handleItemOutHook.Dispose(); + this.handleImmHook.Dispose(); + this.toggleUiHideHook.Dispose(); + this.handleActionHoverHook.Dispose(); + this.handleActionOutHook.Dispose(); + } + + private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6) + { + var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6); + + Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal); + + return retVal; + } + + private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) + { + var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4); + + if (retVal.ToInt64() == 22) { - var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6); + var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); + this.HoveredItem = itemId; - Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal); - - return retVal; - } - - private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4); - - if (retVal.ToInt64() == 22) - { - var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); - this.HoveredItem = itemId; - - try - { - this.HoveredItemChanged?.Invoke(this, itemId); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } - - Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X")); - } - - return retVal; - } - - private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4); - - if (a3 != IntPtr.Zero && a4 == 1) - { - var a3Val = Marshal.ReadByte(a3, 0x8); - - if (a3Val == 255) - { - this.HoveredItem = 0ul; - - try - { - this.HoveredItemChanged?.Invoke(this, 0ul); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } - - Log.Verbose("HoverItemId: 0"); - } - } - - return retVal; - } - - private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) - { - this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); - this.HoveredAction.ActionKind = actionKind; - this.HoveredAction.BaseActionID = actionId; - this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); try { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); + this.HoveredItemChanged?.Invoke(this, itemId); } catch (Exception e) { Log.Error(e, "Could not dispatch HoveredItemChanged event."); } - Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X")); + Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X")); } - private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) + return retVal; + } + + private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) + { + var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4); + + if (a3 != IntPtr.Zero && a4 == 1) { - var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); + var a3Val = Marshal.ReadByte(a3, 0x8); - if (a3 != IntPtr.Zero && a4 == 1) + if (a3Val == 255) { - var a3Val = Marshal.ReadByte(a3, 0x8); + this.HoveredItem = 0ul; - if (a3Val == 255) + try { - this.HoveredAction.ActionKind = HoverActionKind.None; - this.HoveredAction.BaseActionID = 0; - this.HoveredAction.ActionID = 0; - - try - { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredActionChanged event."); - } - - Log.Verbose("HoverActionId: 0"); + this.HoveredItemChanged?.Invoke(this, 0ul); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredItemChanged event."); } - } - return retVal; + Log.Verbose("HoverItemId: 0"); + } } - private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) + return retVal; + } + + private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) + { + this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); + this.HoveredAction.ActionKind = actionKind; + this.HoveredAction.BaseActionID = actionId; + this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); + try { - this.GameUiHidden = !this.GameUiHidden; - - try - { - this.UiHideToggled?.Invoke(this, this.GameUiHidden); - } - catch (Exception ex) - { - Log.Error(ex, "Error on OnUiHideToggled event dispatch"); - } - - Log.Debug("UiHide toggled: {0}", this.GameUiHidden); - - return this.toggleUiHideHook.Original(thisPtr, unknownByte); + this.HoveredActionChanged?.Invoke(this, this.HoveredAction); } - - private char HandleImmDetour(IntPtr framework, char a2, byte a3) + catch (Exception e) { - var result = this.handleImmHook.Original(framework, a2, a3); - return ImGui.GetIO().WantTextInput - ? (char)0 - : result; + Log.Error(e, "Could not dispatch HoveredItemChanged event."); } + + Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X")); + } + + private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) + { + var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); + + if (a3 != IntPtr.Zero && a4 == 1) + { + var a3Val = Marshal.ReadByte(a3, 0x8); + + if (a3Val == 255) + { + this.HoveredAction.ActionKind = HoverActionKind.None; + this.HoveredAction.BaseActionID = 0; + this.HoveredAction.ActionID = 0; + + try + { + this.HoveredActionChanged?.Invoke(this, this.HoveredAction); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredActionChanged event."); + } + + Log.Verbose("HoverActionId: 0"); + } + } + + return retVal; + } + + private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) + { + this.GameUiHidden = !this.GameUiHidden; + + try + { + this.UiHideToggled?.Invoke(this, this.GameUiHidden); + } + catch (Exception ex) + { + Log.Error(ex, "Error on OnUiHideToggled event dispatch"); + } + + Log.Debug("UiHide toggled: {0}", this.GameUiHidden); + + return this.toggleUiHideHook.Original(thisPtr, unknownByte); + } + + private char HandleImmDetour(IntPtr framework, char a2, byte a3) + { + var result = this.handleImmHook.Original(framework, a2, a3); + return ImGui.GetIO().WantTextInput + ? (char)0 + : result; } } diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 122a9eea2..31f68b13f 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -1,104 +1,103 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Game.Gui +namespace Dalamud.Game.Gui; + +/// +/// The address resolver for the class. +/// +internal sealed class GameGuiAddressResolver : BaseAddressResolver { /// - /// The address resolver for the class. + /// Initializes a new instance of the class. /// - internal sealed class GameGuiAddressResolver : BaseAddressResolver + public GameGuiAddressResolver() { - /// - /// Initializes a new instance of the class. - /// - public GameGuiAddressResolver() - { - this.BaseAddress = Service.Get().Address.BaseAddress; - } + this.BaseAddress = Service.Get().Address.BaseAddress; + } - /// - /// Gets the base address of the native GuiManager class. - /// - public IntPtr BaseAddress { get; private set; } + /// + /// Gets the base address of the native GuiManager class. + /// + public IntPtr BaseAddress { get; private set; } - /// - /// Gets the address of the native ChatManager class. - /// - public IntPtr ChatManager { get; private set; } + /// + /// Gets the address of the native ChatManager class. + /// + public IntPtr ChatManager { get; private set; } - /// - /// Gets the address of the native SetGlobalBgm method. - /// - public IntPtr SetGlobalBgm { get; private set; } + /// + /// Gets the address of the native SetGlobalBgm method. + /// + public IntPtr SetGlobalBgm { get; private set; } - /// - /// Gets the address of the native HandleItemHover method. - /// - public IntPtr HandleItemHover { get; private set; } + /// + /// Gets the address of the native HandleItemHover method. + /// + public IntPtr HandleItemHover { get; private set; } - /// - /// Gets the address of the native HandleItemOut method. - /// - public IntPtr HandleItemOut { get; private set; } + /// + /// Gets the address of the native HandleItemOut method. + /// + public IntPtr HandleItemOut { get; private set; } - /// - /// Gets the address of the native HandleActionHover method. - /// - public IntPtr HandleActionHover { get; private set; } + /// + /// Gets the address of the native HandleActionHover method. + /// + public IntPtr HandleActionHover { get; private set; } - /// - /// Gets the address of the native HandleActionOut method. - /// - public IntPtr HandleActionOut { get; private set; } + /// + /// Gets the address of the native HandleActionOut method. + /// + public IntPtr HandleActionOut { get; private set; } - /// - /// Gets the address of the native HandleImm method. - /// - public IntPtr HandleImm { get; private set; } + /// + /// Gets the address of the native HandleImm method. + /// + public IntPtr HandleImm { get; private set; } - /// - /// Gets the address of the native GetMatrixSingleton method. - /// - public IntPtr GetMatrixSingleton { get; private set; } + /// + /// Gets the address of the native GetMatrixSingleton method. + /// + public IntPtr GetMatrixSingleton { get; private set; } - /// - /// Gets the address of the native ScreenToWorld method. - /// - public IntPtr ScreenToWorld { get; private set; } + /// + /// Gets the address of the native ScreenToWorld method. + /// + public IntPtr ScreenToWorld { get; private set; } - /// - /// Gets the address of the native ToggleUiHide method. - /// - public IntPtr ToggleUiHide { get; private set; } + /// + /// Gets the address of the native ToggleUiHide method. + /// + public IntPtr ToggleUiHide { get; private set; } - /// - /// Gets the address of the native GetAgentModule method. - /// - public IntPtr GetAgentModule { get; private set; } + /// + /// Gets the address of the native GetAgentModule method. + /// + public IntPtr GetAgentModule { get; private set; } - /// - protected override void Setup64Bit(SigScanner sig) - { - this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58"); - this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??"); - this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); - this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); - this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); - this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); - this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??"); - this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1"); - this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??"); + /// + protected override void Setup64Bit(SigScanner sig) + { + this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58"); + this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??"); + this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); + this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); + this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); + this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); + this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??"); + this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1"); + this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??"); - var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28"); - this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size); - } + var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28"); + this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size); + } - /// - protected override void SetupInternal(SigScanner scanner) - { - // Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h] - // Xiv__UiManager__GetChatManager+7 000 retn - this.ChatManager = this.BaseAddress + 0x13E0; - } + /// + protected override void SetupInternal(SigScanner scanner) + { + // Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h] + // Xiv__UiManager__GetChatManager+7 000 retn + this.ChatManager = this.BaseAddress + 0x13E0; } } diff --git a/Dalamud/Game/Gui/HoverActionKind.cs b/Dalamud/Game/Gui/HoverActionKind.cs index 217ea18fb..90ff9d46c 100644 --- a/Dalamud/Game/Gui/HoverActionKind.cs +++ b/Dalamud/Game/Gui/HoverActionKind.cs @@ -1,49 +1,48 @@ -namespace Dalamud.Game.Gui +namespace Dalamud.Game.Gui; + +/// +/// ActionKinds used in AgentActionDetail. +/// These describe the possible kinds of actions being hovered. +/// +public enum HoverActionKind { /// - /// ActionKinds used in AgentActionDetail. - /// These describe the possible kinds of actions being hovered. + /// No action is hovered. /// - public enum HoverActionKind - { - /// - /// No action is hovered. - /// - None = 0, + None = 0, - /// - /// A regular action is hovered. - /// - Action = 21, + /// + /// A regular action is hovered. + /// + Action = 21, - /// - /// A general action is hovered. - /// - GeneralAction = 23, + /// + /// A general action is hovered. + /// + GeneralAction = 23, - /// - /// A companion order type of action is hovered. - /// - CompanionOrder = 24, + /// + /// A companion order type of action is hovered. + /// + CompanionOrder = 24, - /// - /// A main command type of action is hovered. - /// - MainCommand = 25, + /// + /// A main command type of action is hovered. + /// + MainCommand = 25, - /// - /// An extras command type of action is hovered. - /// - ExtraCommand = 26, + /// + /// An extras command type of action is hovered. + /// + ExtraCommand = 26, - /// - /// A pet order type of action is hovered. - /// - PetOrder = 28, + /// + /// A pet order type of action is hovered. + /// + PetOrder = 28, - /// - /// A trait is hovered. - /// - Trait = 29, - } + /// + /// A trait is hovered. + /// + Trait = 29, } diff --git a/Dalamud/Game/Gui/HoveredAction.cs b/Dalamud/Game/Gui/HoveredAction.cs index 1a7336610..dcfab5b00 100644 --- a/Dalamud/Game/Gui/HoveredAction.cs +++ b/Dalamud/Game/Gui/HoveredAction.cs @@ -1,23 +1,22 @@ -namespace Dalamud.Game.Gui +namespace Dalamud.Game.Gui; + +/// +/// This class represents the hotbar action currently hovered over by the cursor. +/// +public class HoveredAction { /// - /// This class represents the hotbar action currently hovered over by the cursor. + /// Gets or sets the base action ID. /// - public class HoveredAction - { - /// - /// Gets or sets the base action ID. - /// - public uint BaseActionID { get; set; } = 0; + public uint BaseActionID { get; set; } = 0; - /// - /// Gets or sets the action ID accounting for automatic upgrades. - /// - public uint ActionID { get; set; } = 0; + /// + /// Gets or sets the action ID accounting for automatic upgrades. + /// + public uint ActionID { get; set; } = 0; - /// - /// Gets or sets the type of action. - /// - public HoverActionKind ActionKind { get; set; } = HoverActionKind.None; - } + /// + /// Gets or sets the type of action. + /// + public HoverActionKind ActionKind { get; set; } = HoverActionKind.None; } diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs index 4aafa5f3b..8cd104b1a 100644 --- a/Dalamud/Game/Gui/Internal/DalamudIME.cs +++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs @@ -9,235 +9,234 @@ using ImGuiNET; using static Dalamud.NativeFunctions; -namespace Dalamud.Game.Gui.Internal +namespace Dalamud.Game.Gui.Internal; + +/// +/// This class handles IME for non-English users. +/// +internal class DalamudIME : IDisposable { + private static readonly ModuleLog Log = new("IME"); + + private IntPtr interfaceHandle; + private IntPtr wndProcPtr; + private IntPtr oldWndProcPtr; + private WndProcDelegate wndProcDelegate; + /// - /// This class handles IME for non-English users. + /// Initializes a new instance of the class. /// - internal class DalamudIME : IDisposable + internal DalamudIME() { - private static readonly ModuleLog Log = new("IME"); + } - private IntPtr interfaceHandle; - private IntPtr wndProcPtr; - private IntPtr oldWndProcPtr; - private WndProcDelegate wndProcDelegate; + private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam); - /// - /// Initializes a new instance of the class. - /// - internal DalamudIME() + /// + /// Gets a value indicating whether the module is enabled. + /// + internal bool IsEnabled { get; private set; } + + /// + /// Gets the index of the first imm candidate in relation to the full list. + /// + internal CandidateList ImmCandNative { get; private set; } = default; + + /// + /// Gets the imm candidates. + /// + internal List ImmCand { get; private set; } = new(); + + /// + /// Gets the selected imm component. + /// + internal string ImmComp { get; private set; } = string.Empty; + + /// + public void Dispose() + { + if (this.oldWndProcPtr != IntPtr.Zero) { - } - - private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam); - - /// - /// Gets a value indicating whether the module is enabled. - /// - internal bool IsEnabled { get; private set; } - - /// - /// Gets the index of the first imm candidate in relation to the full list. - /// - internal CandidateList ImmCandNative { get; private set; } = default; - - /// - /// Gets the imm candidates. - /// - internal List ImmCand { get; private set; } = new(); - - /// - /// Gets the selected imm component. - /// - internal string ImmComp { get; private set; } = string.Empty; - - /// - public void Dispose() - { - if (this.oldWndProcPtr != IntPtr.Zero) - { - SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr); - this.oldWndProcPtr = IntPtr.Zero; - } - } - - /// - /// Enables the IME module. - /// - internal void Enable() - { - try - { - this.wndProcDelegate = this.WndProcDetour; - this.interfaceHandle = Service.Get().WindowHandlePtr; - this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); - this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); - this.IsEnabled = true; - Log.Information("Enabled!"); - } - catch (Exception ex) - { - Log.Information(ex, "Enable failed"); - } - } - - private void ToggleWindow(bool visible) - { - if (visible) - Service.Get().OpenIMEWindow(); - else - Service.Get().CloseIMEWindow(); - } - - private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam) - { - try - { - if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput) - { - var io = ImGui.GetIO(); - var wmsg = (WindowsMessage)msg; - - switch (wmsg) - { - case WindowsMessage.WM_IME_NOTIFY: - switch ((IMECommand)wParam) - { - case IMECommand.ChangeCandidate: - this.ToggleWindow(true); - - if (hWnd == IntPtr.Zero) - return 0; - - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return 0; - - var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0); - if (size == 0) - break; - - var candlistPtr = Marshal.AllocHGlobal((int)size); - size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size); - - var candlist = this.ImmCandNative = Marshal.PtrToStructure(candlistPtr); - var pageSize = candlist.PageSize; - var candCount = candlist.Count; - - if (pageSize > 0 && candCount > 1) - { - var dwOffsets = new int[candCount]; - for (var i = 0; i < candCount; i++) - { - dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int))); - } - - var pageStart = candlist.PageStart; - - var cand = new string[pageSize]; - this.ImmCand.Clear(); - - for (var i = 0; i < pageSize; i++) - { - var offStart = dwOffsets[i + pageStart]; - var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size; - - var pStrStart = candlistPtr + (int)offStart; - var pStrEnd = candlistPtr + (int)offEnd; - - var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64()); - if (len > 0) - { - var candBytes = new byte[len]; - Marshal.Copy(pStrStart, candBytes, 0, len); - - var candStr = Encoding.Unicode.GetString(candBytes); - cand[i] = candStr; - - this.ImmCand.Add(candStr); - } - } - - Marshal.FreeHGlobal(candlistPtr); - } - - break; - case IMECommand.OpenCandidate: - this.ToggleWindow(true); - this.ImmCandNative = default; - this.ImmCand.Clear(); - break; - - case IMECommand.CloseCandidate: - this.ToggleWindow(false); - this.ImmCandNative = default; - this.ImmCand.Clear(); - break; - - default: - break; - } - - break; - case WindowsMessage.WM_IME_COMPOSITION: - if ((lParam & (long)IMEComposition.ResultStr) > 0) - { - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return 0; - - var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0); - var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); - ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize); - - var bytes = new byte[dwSize]; - Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); - Marshal.FreeHGlobal(unmanagedPointer); - - var lpstr = Encoding.Unicode.GetString(bytes); - io.AddInputCharactersUTF8(lpstr); - - this.ImmComp = string.Empty; - this.ImmCandNative = default; - this.ImmCand.Clear(); - this.ToggleWindow(false); - } - - if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause | - IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0) - { - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return 0; - - var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0); - var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); - ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize); - - var bytes = new byte[dwSize]; - Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); - Marshal.FreeHGlobal(unmanagedPointer); - - var lpstr = Encoding.Unicode.GetString(bytes); - this.ImmComp = lpstr; - if (lpstr == string.Empty) - this.ToggleWindow(false); - } - - break; - - default: - break; - } - } - } - catch (Exception ex) - { - Log.Error(ex, "Prevented a crash in an IME hook"); - } - - return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam); + SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr); + this.oldWndProcPtr = IntPtr.Zero; } } + + /// + /// Enables the IME module. + /// + internal void Enable() + { + try + { + this.wndProcDelegate = this.WndProcDetour; + this.interfaceHandle = Service.Get().WindowHandlePtr; + this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); + this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); + this.IsEnabled = true; + Log.Information("Enabled!"); + } + catch (Exception ex) + { + Log.Information(ex, "Enable failed"); + } + } + + private void ToggleWindow(bool visible) + { + if (visible) + Service.Get().OpenIMEWindow(); + else + Service.Get().CloseIMEWindow(); + } + + private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam) + { + try + { + if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput) + { + var io = ImGui.GetIO(); + var wmsg = (WindowsMessage)msg; + + switch (wmsg) + { + case WindowsMessage.WM_IME_NOTIFY: + switch ((IMECommand)wParam) + { + case IMECommand.ChangeCandidate: + this.ToggleWindow(true); + + if (hWnd == IntPtr.Zero) + return 0; + + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return 0; + + var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0); + if (size == 0) + break; + + var candlistPtr = Marshal.AllocHGlobal((int)size); + size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size); + + var candlist = this.ImmCandNative = Marshal.PtrToStructure(candlistPtr); + var pageSize = candlist.PageSize; + var candCount = candlist.Count; + + if (pageSize > 0 && candCount > 1) + { + var dwOffsets = new int[candCount]; + for (var i = 0; i < candCount; i++) + { + dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int))); + } + + var pageStart = candlist.PageStart; + + var cand = new string[pageSize]; + this.ImmCand.Clear(); + + for (var i = 0; i < pageSize; i++) + { + var offStart = dwOffsets[i + pageStart]; + var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size; + + var pStrStart = candlistPtr + (int)offStart; + var pStrEnd = candlistPtr + (int)offEnd; + + var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64()); + if (len > 0) + { + var candBytes = new byte[len]; + Marshal.Copy(pStrStart, candBytes, 0, len); + + var candStr = Encoding.Unicode.GetString(candBytes); + cand[i] = candStr; + + this.ImmCand.Add(candStr); + } + } + + Marshal.FreeHGlobal(candlistPtr); + } + + break; + case IMECommand.OpenCandidate: + this.ToggleWindow(true); + this.ImmCandNative = default; + this.ImmCand.Clear(); + break; + + case IMECommand.CloseCandidate: + this.ToggleWindow(false); + this.ImmCandNative = default; + this.ImmCand.Clear(); + break; + + default: + break; + } + + break; + case WindowsMessage.WM_IME_COMPOSITION: + if ((lParam & (long)IMEComposition.ResultStr) > 0) + { + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return 0; + + var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0); + var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); + ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize); + + var bytes = new byte[dwSize]; + Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); + Marshal.FreeHGlobal(unmanagedPointer); + + var lpstr = Encoding.Unicode.GetString(bytes); + io.AddInputCharactersUTF8(lpstr); + + this.ImmComp = string.Empty; + this.ImmCandNative = default; + this.ImmCand.Clear(); + this.ToggleWindow(false); + } + + if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause | + IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0) + { + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return 0; + + var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0); + var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); + ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize); + + var bytes = new byte[dwSize]; + Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); + Marshal.FreeHGlobal(unmanagedPointer); + + var lpstr = Encoding.Unicode.GetString(bytes); + this.ImmComp = lpstr; + if (lpstr == string.Empty) + this.ToggleWindow(false); + } + + break; + + default: + break; + } + } + } + catch (Exception ex) + { + Log.Error(ex, "Prevented a crash in an IME hook"); + } + + return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam); + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs index 9f8834ecd..a7ab12e4b 100644 --- a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs +++ b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs @@ -1,28 +1,27 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace Dalamud.Game.Gui.PartyFinder.Internal +namespace Dalamud.Game.Gui.PartyFinder.Internal; + +/// +/// The structure of the PartyFinder packet. +/// +[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")] +[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] +[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] +[StructLayout(LayoutKind.Sequential)] +internal readonly struct PartyFinderPacket { /// - /// The structure of the PartyFinder packet. + /// Gets the size of this packet. /// - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")] - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] - [StructLayout(LayoutKind.Sequential)] - internal readonly struct PartyFinderPacket - { - /// - /// Gets the size of this packet. - /// - internal static int PacketSize { get; } = Marshal.SizeOf(); + internal static int PacketSize { get; } = Marshal.SizeOf(); - internal readonly int BatchNumber; + internal readonly int BatchNumber; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - private readonly byte[] padding1; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + private readonly byte[] padding1; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - internal readonly PartyFinderPacketListing[] Listings; - } + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + internal readonly PartyFinderPacketListing[] Listings; } diff --git a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs index 7e8f1e1ef..53d2831ef 100644 --- a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs +++ b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs @@ -2,98 +2,97 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; -namespace Dalamud.Game.Gui.PartyFinder.Internal +namespace Dalamud.Game.Gui.PartyFinder.Internal; + +/// +/// The structure of an individual listing within a packet. +/// +[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] +[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] +[StructLayout(LayoutKind.Sequential)] +internal readonly struct PartyFinderPacketListing { - /// - /// The structure of an individual listing within a packet. - /// - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] - [StructLayout(LayoutKind.Sequential)] - internal readonly struct PartyFinderPacketListing + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + private readonly byte[] header1; + internal readonly uint Id; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + private readonly byte[] header2; + + internal readonly uint ContentIdLower; + private readonly ushort unknownShort1; + private readonly ushort unknownShort2; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + private readonly byte[] header3; + + internal readonly byte Category; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + private readonly byte[] header4; + + internal readonly ushort Duty; + internal readonly byte DutyType; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] + private readonly byte[] header5; + + internal readonly ushort World; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + private readonly byte[] header6; + + internal readonly byte Objective; + internal readonly byte BeginnersWelcome; + internal readonly byte Conditions; + internal readonly byte DutyFinderSettings; + internal readonly byte LootRules; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + private readonly byte[] header7; // all zero in every pf I've examined + + internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted? + internal readonly ushort SecondsRemaining; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined + + internal readonly ushort MinimumItemLevel; + internal readonly ushort HomeWorld; + internal readonly ushort CurrentWorld; + + private readonly byte header9; + + internal readonly byte NumSlots; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + private readonly byte[] header10; + + internal readonly byte SearchArea; + + private readonly byte header11; + + internal readonly byte NumParties; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32? + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + internal readonly uint[] Slots; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + internal readonly byte[] JobsPresent; + + // Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + internal readonly byte[] Name; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)] + internal readonly byte[] Description; + + internal bool IsNull() { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - private readonly byte[] header1; - internal readonly uint Id; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - private readonly byte[] header2; - - internal readonly uint ContentIdLower; - private readonly ushort unknownShort1; - private readonly ushort unknownShort2; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] - private readonly byte[] header3; - - internal readonly byte Category; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - private readonly byte[] header4; - - internal readonly ushort Duty; - internal readonly byte DutyType; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] - private readonly byte[] header5; - - internal readonly ushort World; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - private readonly byte[] header6; - - internal readonly byte Objective; - internal readonly byte BeginnersWelcome; - internal readonly byte Conditions; - internal readonly byte DutyFinderSettings; - internal readonly byte LootRules; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - private readonly byte[] header7; // all zero in every pf I've examined - - internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted? - internal readonly ushort SecondsRemaining; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined - - internal readonly ushort MinimumItemLevel; - internal readonly ushort HomeWorld; - internal readonly ushort CurrentWorld; - - private readonly byte header9; - - internal readonly byte NumSlots; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - private readonly byte[] header10; - - internal readonly byte SearchArea; - - private readonly byte header11; - - internal readonly byte NumParties; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32? - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - internal readonly uint[] Slots; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - internal readonly byte[] JobsPresent; - - // Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - internal readonly byte[] Name; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)] - internal readonly byte[] Description; - - internal bool IsNull() - { - // a valid party finder must have at least one slot set - return this.Slots.All(slot => slot == 0); - } + // a valid party finder must have at least one slot set + return this.Slots.All(slot => slot == 0); } } diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs index 75da83180..aa9d28cb1 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs @@ -1,21 +1,20 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder +namespace Dalamud.Game.Gui.PartyFinder; + +/// +/// The address resolver for the class. +/// +public class PartyFinderAddressResolver : BaseAddressResolver { /// - /// The address resolver for the class. + /// Gets the address of the native ReceiveListing method. /// - public class PartyFinderAddressResolver : BaseAddressResolver - { - /// - /// Gets the address of the native ReceiveListing method. - /// - public IntPtr ReceiveListing { get; private set; } + public IntPtr ReceiveListing { get; private set; } - /// - protected override void Setup64Bit(SigScanner sig) - { - this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9"); - } + /// + protected override void Setup64Bit(SigScanner sig) + { + this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9"); } } diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index 6d2a7818d..7fd7063dc 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -8,133 +8,132 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Gui.PartyFinder +namespace Dalamud.Game.Gui.PartyFinder; + +/// +/// This class handles interacting with the native PartyFinder window. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class PartyFinderGui : IDisposable { + private readonly PartyFinderAddressResolver address; + private readonly IntPtr memory; + + private readonly Hook receiveListingHook; + /// - /// This class handles interacting with the native PartyFinder window. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class PartyFinderGui : IDisposable + internal PartyFinderGui() { - private readonly PartyFinderAddressResolver address; - private readonly IntPtr memory; + this.address = new PartyFinderAddressResolver(); + this.address.Setup(); - private readonly Hook receiveListingHook; + this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); - /// - /// Initializes a new instance of the class. - /// - internal PartyFinderGui() + this.receiveListingHook = new Hook(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour)); + } + + /// + /// Event type fired each time the game receives an individual Party Finder listing. + /// Cannot modify listings but can hide them. + /// + /// The listings received. + /// Additional arguments passed by the game. + public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data); + + /// + /// Event fired each time the game receives an individual Party Finder listing. + /// Cannot modify listings but can hide them. + /// + public event PartyFinderListingEventDelegate ReceiveListing; + + /// + /// Enables this module. + /// + public void Enable() + { + this.receiveListingHook.Enable(); + } + + /// + /// Dispose of m anaged and unmanaged resources. + /// + public void Dispose() + { + this.receiveListingHook.Dispose(); + + try { - this.address = new PartyFinderAddressResolver(); - this.address.Setup(); + Marshal.FreeHGlobal(this.memory); + } + catch (BadImageFormatException) + { + Log.Warning("Could not free PartyFinderGui memory."); + } + } - this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); - - this.receiveListingHook = new Hook(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour)); + private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) + { + try + { + this.HandleListingEvents(data); + } + catch (Exception ex) + { + Log.Error(ex, "Exception on ReceiveListing hook."); } - /// - /// Event type fired each time the game receives an individual Party Finder listing. - /// Cannot modify listings but can hide them. - /// - /// The listings received. - /// Additional arguments passed by the game. - public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args); + this.receiveListingHook.Original(managerPtr, data); + } - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data); + private void HandleListingEvents(IntPtr data) + { + var dataPtr = data + 0x10; - /// - /// Event fired each time the game receives an individual Party Finder listing. - /// Cannot modify listings but can hide them. - /// - public event PartyFinderListingEventDelegate ReceiveListing; + var packet = Marshal.PtrToStructure(dataPtr); - /// - /// Enables this module. - /// - public void Enable() + // rewriting is an expensive operation, so only do it if necessary + var needToRewrite = false; + + for (var i = 0; i < packet.Listings.Length; i++) { - this.receiveListingHook.Enable(); + // these are empty slots that are not shown to the player + if (packet.Listings[i].IsNull()) + { + continue; + } + + var listing = new PartyFinderListing(packet.Listings[i]); + var args = new PartyFinderListingEventArgs(packet.BatchNumber); + this.ReceiveListing?.Invoke(listing, args); + + if (args.Visible) + { + continue; + } + + // hide the listing from the player by setting it to a null listing + packet.Listings[i] = default; + needToRewrite = true; } - /// - /// Dispose of m anaged and unmanaged resources. - /// - public void Dispose() + if (!needToRewrite) { - this.receiveListingHook.Dispose(); - - try - { - Marshal.FreeHGlobal(this.memory); - } - catch (BadImageFormatException) - { - Log.Warning("Could not free PartyFinderGui memory."); - } + return; } - private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) + // write our struct into the memory (doing this directly crashes the game) + Marshal.StructureToPtr(packet, this.memory, false); + + // copy our new memory over the game's + unsafe { - try - { - this.HandleListingEvents(data); - } - catch (Exception ex) - { - Log.Error(ex, "Exception on ReceiveListing hook."); - } - - this.receiveListingHook.Original(managerPtr, data); - } - - private void HandleListingEvents(IntPtr data) - { - var dataPtr = data + 0x10; - - var packet = Marshal.PtrToStructure(dataPtr); - - // rewriting is an expensive operation, so only do it if necessary - var needToRewrite = false; - - for (var i = 0; i < packet.Listings.Length; i++) - { - // these are empty slots that are not shown to the player - if (packet.Listings[i].IsNull()) - { - continue; - } - - var listing = new PartyFinderListing(packet.Listings[i]); - var args = new PartyFinderListingEventArgs(packet.BatchNumber); - this.ReceiveListing?.Invoke(listing, args); - - if (args.Visible) - { - continue; - } - - // hide the listing from the player by setting it to a null listing - packet.Listings[i] = default; - needToRewrite = true; - } - - if (!needToRewrite) - { - return; - } - - // write our struct into the memory (doing this directly crashes the game) - Marshal.StructureToPtr(packet, this.memory, false); - - // copy our new memory over the game's - unsafe - { - Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize); - } + Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize); } } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs index 3eb5ec1b6..358fd1c54 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs @@ -1,26 +1,25 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Condition flags for the class. +/// +[Flags] +public enum ConditionFlags : uint { /// - /// Condition flags for the class. + /// No duty condition. /// - [Flags] - public enum ConditionFlags : uint - { - /// - /// No duty condition. - /// - None = 1, + None = 1, - /// - /// The duty complete condition. - /// - DutyComplete = 2, + /// + /// The duty complete condition. + /// + DutyComplete = 2, - /// - /// The duty incomplete condition. - /// - DutyIncomplete = 4, - } + /// + /// The duty incomplete condition. + /// + DutyIncomplete = 4, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs b/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs index 920325d64..c4a05704d 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs @@ -1,48 +1,47 @@ -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Category flags for the class. +/// +public enum DutyCategory { /// - /// Category flags for the class. + /// The duty category. /// - public enum DutyCategory - { - /// - /// The duty category. - /// - Duty = 0, + Duty = 0, - /// - /// The quest battle category. - /// - QuestBattles = 1 << 0, + /// + /// The quest battle category. + /// + QuestBattles = 1 << 0, - /// - /// The fate category. - /// - Fates = 1 << 1, + /// + /// The fate category. + /// + Fates = 1 << 1, - /// - /// The treasure hunt category. - /// - TreasureHunt = 1 << 2, + /// + /// The treasure hunt category. + /// + TreasureHunt = 1 << 2, - /// - /// The hunt category. - /// - TheHunt = 1 << 3, + /// + /// The hunt category. + /// + TheHunt = 1 << 3, - /// - /// The gathering forays category. - /// - GatheringForays = 1 << 4, + /// + /// The gathering forays category. + /// + GatheringForays = 1 << 4, - /// - /// The deep dungeons category. - /// - DeepDungeons = 1 << 5, + /// + /// The deep dungeons category. + /// + DeepDungeons = 1 << 5, - /// - /// The adventuring forays category. - /// - AdventuringForays = 1 << 6, - } + /// + /// The adventuring forays category. + /// + AdventuringForays = 1 << 6, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs index 03335dbaf..e3ab56ed3 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs @@ -1,31 +1,30 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Duty finder settings flags for the class. +/// +[Flags] +public enum DutyFinderSettingsFlags : uint { /// - /// Duty finder settings flags for the class. + /// No duty finder settings. /// - [Flags] - public enum DutyFinderSettingsFlags : uint - { - /// - /// No duty finder settings. - /// - None = 0, + None = 0, - /// - /// The undersized party setting. - /// - UndersizedParty = 1 << 0, + /// + /// The undersized party setting. + /// + UndersizedParty = 1 << 0, - /// - /// The minimum item level setting. - /// - MinimumItemLevel = 1 << 1, + /// + /// The minimum item level setting. + /// + MinimumItemLevel = 1 << 1, - /// - /// The silence echo setting. - /// - SilenceEcho = 1 << 2, - } + /// + /// The silence echo setting. + /// + SilenceEcho = 1 << 2, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs b/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs index 589d0b91b..41b4c5da3 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs @@ -1,23 +1,22 @@ -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Duty type flags for the class. +/// +public enum DutyType { /// - /// Duty type flags for the class. + /// No duty type. /// - public enum DutyType - { - /// - /// No duty type. - /// - Other = 0, + Other = 0, - /// - /// The roulette duty type. - /// - Roulette = 1 << 0, + /// + /// The roulette duty type. + /// + Roulette = 1 << 0, - /// - /// The normal duty type. - /// - Normal = 1 << 1, - } + /// + /// The normal duty type. + /// + Normal = 1 << 1, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs index 98fa1b1fe..285017e53 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs @@ -1,146 +1,145 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Job flags for the class. +/// +[Flags] +public enum JobFlags { /// - /// Job flags for the class. + /// Gladiator (GLD). /// - [Flags] - public enum JobFlags - { - /// - /// Gladiator (GLD). - /// - Gladiator = 1 << 1, + Gladiator = 1 << 1, - /// - /// Pugilist (PGL). - /// - Pugilist = 1 << 2, + /// + /// Pugilist (PGL). + /// + Pugilist = 1 << 2, - /// - /// Marauder (MRD). - /// - Marauder = 1 << 3, + /// + /// Marauder (MRD). + /// + Marauder = 1 << 3, - /// - /// Lancer (LNC). - /// - Lancer = 1 << 4, + /// + /// Lancer (LNC). + /// + Lancer = 1 << 4, - /// - /// Archer (ARC). - /// - Archer = 1 << 5, + /// + /// Archer (ARC). + /// + Archer = 1 << 5, - /// - /// Conjurer (CNJ). - /// - Conjurer = 1 << 6, + /// + /// Conjurer (CNJ). + /// + Conjurer = 1 << 6, - /// - /// Thaumaturge (THM). - /// - Thaumaturge = 1 << 7, + /// + /// Thaumaturge (THM). + /// + Thaumaturge = 1 << 7, - /// - /// Paladin (PLD). - /// - Paladin = 1 << 8, + /// + /// Paladin (PLD). + /// + Paladin = 1 << 8, - /// - /// Monk (MNK). - /// - Monk = 1 << 9, + /// + /// Monk (MNK). + /// + Monk = 1 << 9, - /// - /// Warrior (WAR). - /// - Warrior = 1 << 10, + /// + /// Warrior (WAR). + /// + Warrior = 1 << 10, - /// - /// Dragoon (DRG). - /// - Dragoon = 1 << 11, + /// + /// Dragoon (DRG). + /// + Dragoon = 1 << 11, - /// - /// Bard (BRD). - /// - Bard = 1 << 12, + /// + /// Bard (BRD). + /// + Bard = 1 << 12, - /// - /// White mage (WHM). - /// - WhiteMage = 1 << 13, + /// + /// White mage (WHM). + /// + WhiteMage = 1 << 13, - /// - /// Black mage (BLM). - /// - BlackMage = 1 << 14, + /// + /// Black mage (BLM). + /// + BlackMage = 1 << 14, - /// - /// Arcanist (ACN). - /// - Arcanist = 1 << 15, + /// + /// Arcanist (ACN). + /// + Arcanist = 1 << 15, - /// - /// Summoner (SMN). - /// - Summoner = 1 << 16, + /// + /// Summoner (SMN). + /// + Summoner = 1 << 16, - /// - /// Scholar (SCH). - /// - Scholar = 1 << 17, + /// + /// Scholar (SCH). + /// + Scholar = 1 << 17, - /// - /// Rogue (ROG). - /// - Rogue = 1 << 18, + /// + /// Rogue (ROG). + /// + Rogue = 1 << 18, - /// - /// Ninja (NIN). - /// - Ninja = 1 << 19, + /// + /// Ninja (NIN). + /// + Ninja = 1 << 19, - /// - /// Machinist (MCH). - /// - Machinist = 1 << 20, + /// + /// Machinist (MCH). + /// + Machinist = 1 << 20, - /// - /// Dark Knight (DRK). - /// - DarkKnight = 1 << 21, + /// + /// Dark Knight (DRK). + /// + DarkKnight = 1 << 21, - /// - /// Astrologian (AST). - /// - Astrologian = 1 << 22, + /// + /// Astrologian (AST). + /// + Astrologian = 1 << 22, - /// - /// Samurai (SAM). - /// - Samurai = 1 << 23, + /// + /// Samurai (SAM). + /// + Samurai = 1 << 23, - /// - /// Red mage (RDM). - /// - RedMage = 1 << 24, + /// + /// Red mage (RDM). + /// + RedMage = 1 << 24, - /// - /// Blue mage (BLM). - /// - BlueMage = 1 << 25, + /// + /// Blue mage (BLM). + /// + BlueMage = 1 << 25, - /// - /// Gunbreaker (GNB). - /// - Gunbreaker = 1 << 26, + /// + /// Gunbreaker (GNB). + /// + Gunbreaker = 1 << 26, - /// - /// Dancer (DNC). - /// - Dancer = 1 << 27, - } + /// + /// Dancer (DNC). + /// + Dancer = 1 << 27, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs index 3bb80d0f5..f66272294 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs @@ -3,56 +3,55 @@ using System; using Dalamud.Data; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Extensions for the enum. +/// +public static class JobFlagsExtensions { /// - /// Extensions for the enum. + /// Get the actual ClassJob from the in-game sheets for this JobFlags. /// - public static class JobFlagsExtensions + /// A JobFlags enum member. + /// A DataManager to get the ClassJob from. + /// A ClassJob if found or null if not. + public static ClassJob ClassJob(this JobFlags job, DataManager data) { - /// - /// Get the actual ClassJob from the in-game sheets for this JobFlags. - /// - /// A JobFlags enum member. - /// A DataManager to get the ClassJob from. - /// A ClassJob if found or null if not. - public static ClassJob ClassJob(this JobFlags job, DataManager data) + var jobs = data.GetExcelSheet(); + + uint? row = job switch { - var jobs = data.GetExcelSheet(); + JobFlags.Gladiator => 1, + JobFlags.Pugilist => 2, + JobFlags.Marauder => 3, + JobFlags.Lancer => 4, + JobFlags.Archer => 5, + JobFlags.Conjurer => 6, + JobFlags.Thaumaturge => 7, + JobFlags.Paladin => 19, + JobFlags.Monk => 20, + JobFlags.Warrior => 21, + JobFlags.Dragoon => 22, + JobFlags.Bard => 23, + JobFlags.WhiteMage => 24, + JobFlags.BlackMage => 25, + JobFlags.Arcanist => 26, + JobFlags.Summoner => 27, + JobFlags.Scholar => 28, + JobFlags.Rogue => 29, + JobFlags.Ninja => 30, + JobFlags.Machinist => 31, + JobFlags.DarkKnight => 32, + JobFlags.Astrologian => 33, + JobFlags.Samurai => 34, + JobFlags.RedMage => 35, + JobFlags.BlueMage => 36, + JobFlags.Gunbreaker => 37, + JobFlags.Dancer => 38, + _ => null, + }; - uint? row = job switch - { - JobFlags.Gladiator => 1, - JobFlags.Pugilist => 2, - JobFlags.Marauder => 3, - JobFlags.Lancer => 4, - JobFlags.Archer => 5, - JobFlags.Conjurer => 6, - JobFlags.Thaumaturge => 7, - JobFlags.Paladin => 19, - JobFlags.Monk => 20, - JobFlags.Warrior => 21, - JobFlags.Dragoon => 22, - JobFlags.Bard => 23, - JobFlags.WhiteMage => 24, - JobFlags.BlackMage => 25, - JobFlags.Arcanist => 26, - JobFlags.Summoner => 27, - JobFlags.Scholar => 28, - JobFlags.Rogue => 29, - JobFlags.Ninja => 30, - JobFlags.Machinist => 31, - JobFlags.DarkKnight => 32, - JobFlags.Astrologian => 33, - JobFlags.Samurai => 34, - JobFlags.RedMage => 35, - JobFlags.BlueMage => 36, - JobFlags.Gunbreaker => 37, - JobFlags.Dancer => 38, - _ => null, - }; - - return row == null ? null : jobs.GetRow((uint)row); - } + return row == null ? null : jobs.GetRow((uint)row); } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs index a1a29ceb8..6e43ecf4c 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs @@ -1,26 +1,25 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Loot rule flags for the class. +/// +[Flags] +public enum LootRuleFlags : uint { /// - /// Loot rule flags for the class. + /// No loot rules. /// - [Flags] - public enum LootRuleFlags : uint - { - /// - /// No loot rules. - /// - None = 0, + None = 0, - /// - /// The greed only rule. - /// - GreedOnly = 1, + /// + /// The greed only rule. + /// + GreedOnly = 1, - /// - /// The lootmaster rule. - /// - Lootmaster = 2, - } + /// + /// The lootmaster rule. + /// + Lootmaster = 2, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs index de3b8fc6b..19f56f84c 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs @@ -1,31 +1,30 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Objective flags for the class. +/// +[Flags] +public enum ObjectiveFlags : uint { /// - /// Objective flags for the class. + /// No objective. /// - [Flags] - public enum ObjectiveFlags : uint - { - /// - /// No objective. - /// - None = 0, + None = 0, - /// - /// The duty completion objective. - /// - DutyCompletion = 1, + /// + /// The duty completion objective. + /// + DutyCompletion = 1, - /// - /// The practice objective. - /// - Practice = 2, + /// + /// The practice objective. + /// + Practice = 2, - /// - /// The loot objective. - /// - Loot = 4, - } + /// + /// The loot objective. + /// + Loot = 4, } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs index b0dce07b9..d21b7b803 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs @@ -7,228 +7,227 @@ using Dalamud.Game.Gui.PartyFinder.Internal; using Dalamud.Game.Text.SeStringHandling; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// A single listing in party finder. +/// +public class PartyFinderListing { + private readonly byte objective; + private readonly byte conditions; + private readonly byte dutyFinderSettings; + private readonly byte lootRules; + private readonly byte searchArea; + private readonly PartyFinderSlot[] slots; + private readonly byte[] jobsPresent; + /// - /// A single listing in party finder. + /// Initializes a new instance of the class. /// - public class PartyFinderListing + /// The interop listing data. + internal PartyFinderListing(PartyFinderPacketListing listing) { - private readonly byte objective; - private readonly byte conditions; - private readonly byte dutyFinderSettings; - private readonly byte lootRules; - private readonly byte searchArea; - private readonly PartyFinderSlot[] slots; - private readonly byte[] jobsPresent; + var dataManager = Service.Get(); - /// - /// Initializes a new instance of the class. - /// - /// The interop listing data. - internal PartyFinderListing(PartyFinderPacketListing listing) - { - var dataManager = Service.Get(); - - this.objective = listing.Objective; - this.conditions = listing.Conditions; - this.dutyFinderSettings = listing.DutyFinderSettings; - this.lootRules = listing.LootRules; - this.searchArea = listing.SearchArea; - this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray(); - this.jobsPresent = listing.JobsPresent; - - this.Id = listing.Id; - this.ContentIdLower = listing.ContentIdLower; - this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); - this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); - this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World)); - this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld)); - this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld)); - this.Category = (DutyCategory)listing.Category; - this.RawDuty = listing.Duty; - this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty)); - this.DutyType = (DutyType)listing.DutyType; - this.BeginnersWelcome = listing.BeginnersWelcome == 1; - this.SecondsRemaining = listing.SecondsRemaining; - this.MinimumItemLevel = listing.MinimumItemLevel; - this.Parties = listing.NumParties; - this.SlotsAvailable = listing.NumSlots; - this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp; - this.JobsPresent = listing.JobsPresent - .Select(id => new Lazy( - () => id == 0 - ? null - : dataManager.GetExcelSheet().GetRow(id))) - .ToArray(); - } - - /// - /// Gets the ID assigned to this listing by the game's server. - /// - public uint Id { get; } - - /// - /// Gets the lower bits of the player's content ID. - /// - public uint ContentIdLower { get; } - - /// - /// Gets the name of the player hosting this listing. - /// - public SeString Name { get; } - - /// - /// Gets the description of this listing as set by the host. May be multiple lines. - /// - public SeString Description { get; } - - /// - /// Gets the world that this listing was created on. - /// - public Lazy World { get; } - - /// - /// Gets the home world of the listing's host. - /// - public Lazy HomeWorld { get; } - - /// - /// Gets the current world of the listing's host. - /// - public Lazy CurrentWorld { get; } - - /// - /// Gets the Party Finder category this listing is listed under. - /// - public DutyCategory Category { get; } - - /// - /// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings. - /// - public ushort RawDuty { get; } - - /// - /// Gets the duty this listing is for. May be null for non-duty listings. - /// - public Lazy Duty { get; } - - /// - /// Gets the type of duty this listing is for. - /// - public DutyType DutyType { get; } - - /// - /// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game. - /// - public bool BeginnersWelcome { get; } - - /// - /// Gets how many seconds this listing will continue to be available for. It may end before this time if the party - /// fills or the host ends it early. - /// - public ushort SecondsRemaining { get; } - - /// - /// Gets the minimum item level required to join this listing. - /// - public ushort MinimumItemLevel { get; } - - /// - /// Gets the number of parties this listing is recruiting for. - /// - public byte Parties { get; } - - /// - /// Gets the number of player slots this listing is recruiting for. - /// - public byte SlotsAvailable { get; } - - /// - /// Gets the time at which the server this listings is on last restarted for a patch/hotfix. - /// Probably. - /// - public uint LastPatchHotfixTimestamp { get; } - - /// - /// Gets a list of player slots that the Party Finder is accepting. - /// - public IReadOnlyCollection Slots => this.slots; - - /// - /// Gets the objective of this listing. - /// - public ObjectiveFlags Objective => (ObjectiveFlags)this.objective; - - /// - /// Gets the conditions of this listing. - /// - public ConditionFlags Conditions => (ConditionFlags)this.conditions; - - /// - /// Gets the Duty Finder settings that will be used for this listing. - /// - public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings; - - /// - /// Gets the loot rules that will be used for this listing. - /// - public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules; - - /// - /// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one - /// player per job. - /// - public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea; - - /// - /// Gets a list of the class/job IDs that are currently present in the party. - /// - public IReadOnlyCollection RawJobsPresent => this.jobsPresent; - - /// - /// Gets a list of the classes/jobs that are currently present in the party. - /// - public IReadOnlyCollection> JobsPresent { get; } - - #region Indexers - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0; - - #endregion + this.objective = listing.Objective; + this.conditions = listing.Conditions; + this.dutyFinderSettings = listing.DutyFinderSettings; + this.lootRules = listing.LootRules; + this.searchArea = listing.SearchArea; + this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray(); + this.jobsPresent = listing.JobsPresent; + this.Id = listing.Id; + this.ContentIdLower = listing.ContentIdLower; + this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); + this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); + this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World)); + this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld)); + this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld)); + this.Category = (DutyCategory)listing.Category; + this.RawDuty = listing.Duty; + this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty)); + this.DutyType = (DutyType)listing.DutyType; + this.BeginnersWelcome = listing.BeginnersWelcome == 1; + this.SecondsRemaining = listing.SecondsRemaining; + this.MinimumItemLevel = listing.MinimumItemLevel; + this.Parties = listing.NumParties; + this.SlotsAvailable = listing.NumSlots; + this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp; + this.JobsPresent = listing.JobsPresent + .Select(id => new Lazy( + () => id == 0 + ? null + : dataManager.GetExcelSheet().GetRow(id))) + .ToArray(); } + + /// + /// Gets the ID assigned to this listing by the game's server. + /// + public uint Id { get; } + + /// + /// Gets the lower bits of the player's content ID. + /// + public uint ContentIdLower { get; } + + /// + /// Gets the name of the player hosting this listing. + /// + public SeString Name { get; } + + /// + /// Gets the description of this listing as set by the host. May be multiple lines. + /// + public SeString Description { get; } + + /// + /// Gets the world that this listing was created on. + /// + public Lazy World { get; } + + /// + /// Gets the home world of the listing's host. + /// + public Lazy HomeWorld { get; } + + /// + /// Gets the current world of the listing's host. + /// + public Lazy CurrentWorld { get; } + + /// + /// Gets the Party Finder category this listing is listed under. + /// + public DutyCategory Category { get; } + + /// + /// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings. + /// + public ushort RawDuty { get; } + + /// + /// Gets the duty this listing is for. May be null for non-duty listings. + /// + public Lazy Duty { get; } + + /// + /// Gets the type of duty this listing is for. + /// + public DutyType DutyType { get; } + + /// + /// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game. + /// + public bool BeginnersWelcome { get; } + + /// + /// Gets how many seconds this listing will continue to be available for. It may end before this time if the party + /// fills or the host ends it early. + /// + public ushort SecondsRemaining { get; } + + /// + /// Gets the minimum item level required to join this listing. + /// + public ushort MinimumItemLevel { get; } + + /// + /// Gets the number of parties this listing is recruiting for. + /// + public byte Parties { get; } + + /// + /// Gets the number of player slots this listing is recruiting for. + /// + public byte SlotsAvailable { get; } + + /// + /// Gets the time at which the server this listings is on last restarted for a patch/hotfix. + /// Probably. + /// + public uint LastPatchHotfixTimestamp { get; } + + /// + /// Gets a list of player slots that the Party Finder is accepting. + /// + public IReadOnlyCollection Slots => this.slots; + + /// + /// Gets the objective of this listing. + /// + public ObjectiveFlags Objective => (ObjectiveFlags)this.objective; + + /// + /// Gets the conditions of this listing. + /// + public ConditionFlags Conditions => (ConditionFlags)this.conditions; + + /// + /// Gets the Duty Finder settings that will be used for this listing. + /// + public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings; + + /// + /// Gets the loot rules that will be used for this listing. + /// + public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules; + + /// + /// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one + /// player per job. + /// + public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea; + + /// + /// Gets a list of the class/job IDs that are currently present in the party. + /// + public IReadOnlyCollection RawJobsPresent => this.jobsPresent; + + /// + /// Gets a list of the classes/jobs that are currently present in the party. + /// + public IReadOnlyCollection> JobsPresent { get; } + + #region Indexers + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0; + + #endregion + } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs index ff6bd607d..4bc603d7a 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs @@ -1,27 +1,26 @@ -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// This class represents additional arguments passed by the game. +/// +public class PartyFinderListingEventArgs { /// - /// This class represents additional arguments passed by the game. + /// Initializes a new instance of the class. /// - public class PartyFinderListingEventArgs + /// The batch number. + internal PartyFinderListingEventArgs(int batchNumber) { - /// - /// Initializes a new instance of the class. - /// - /// The batch number. - internal PartyFinderListingEventArgs(int batchNumber) - { - this.BatchNumber = batchNumber; - } - - /// - /// Gets the batch number. - /// - public int BatchNumber { get; } - - /// - /// Gets or sets a value indicating whether the listing is visible. - /// - public bool Visible { get; set; } = true; + this.BatchNumber = batchNumber; } + + /// + /// Gets the batch number. + /// + public int BatchNumber { get; } + + /// + /// Gets or sets a value indicating whether the listing is visible. + /// + public bool Visible { get; set; } = true; } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs index d740c0c0a..03a3fafe1 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs @@ -2,50 +2,49 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// A player slot in a Party Finder listing. +/// +public class PartyFinderSlot { + private readonly uint accepting; + private JobFlags[] listAccepting; + /// - /// A player slot in a Party Finder listing. + /// Initializes a new instance of the class. /// - public class PartyFinderSlot + /// The flag value of accepted jobs. + internal PartyFinderSlot(uint accepting) { - private readonly uint accepting; - private JobFlags[] listAccepting; + this.accepting = accepting; + } - /// - /// Initializes a new instance of the class. - /// - /// The flag value of accepted jobs. - internal PartyFinderSlot(uint accepting) + /// + /// Gets a list of jobs that this slot is accepting. + /// + public IReadOnlyCollection Accepting + { + get { - this.accepting = accepting; - } - - /// - /// Gets a list of jobs that this slot is accepting. - /// - public IReadOnlyCollection Accepting - { - get + if (this.listAccepting != null) { - if (this.listAccepting != null) - { - return this.listAccepting; - } - - this.listAccepting = Enum.GetValues(typeof(JobFlags)) - .Cast() - .Where(flag => this[flag]) - .ToArray(); - return this.listAccepting; } - } - /// - /// Tests if this slot is accepting a job. - /// - /// Job to test. - public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0; + this.listAccepting = Enum.GetValues(typeof(JobFlags)) + .Cast() + .Where(flag => this[flag]) + .ToArray(); + + return this.listAccepting; + } } + + /// + /// Tests if this slot is accepting a job. + /// + /// Job to test. + public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0; } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs index 85308c8d1..27a3f5ee8 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs @@ -1,36 +1,35 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types +namespace Dalamud.Game.Gui.PartyFinder.Types; + +/// +/// Search area flags for the class. +/// +[Flags] +public enum SearchAreaFlags : uint { /// - /// Search area flags for the class. + /// Datacenter. /// - [Flags] - public enum SearchAreaFlags : uint - { - /// - /// Datacenter. - /// - DataCentre = 1 << 0, + DataCentre = 1 << 0, - /// - /// Private. - /// - Private = 1 << 1, + /// + /// Private. + /// + Private = 1 << 1, - /// - /// Alliance raid. - /// - AllianceRaid = 1 << 2, + /// + /// Alliance raid. + /// + AllianceRaid = 1 << 2, - /// - /// World. - /// - World = 1 << 3, + /// + /// World. + /// + World = 1 << 3, - /// - /// One player per job. - /// - OnePlayerPerJob = 1 << 5, - } + /// + /// One player per job. + /// + OnePlayerPerJob = 1 << 5, } diff --git a/Dalamud/Game/Gui/Toast/QuestToastOptions.cs b/Dalamud/Game/Gui/Toast/QuestToastOptions.cs index 11f09a523..5c2fd14fe 100644 --- a/Dalamud/Game/Gui/Toast/QuestToastOptions.cs +++ b/Dalamud/Game/Gui/Toast/QuestToastOptions.cs @@ -1,32 +1,31 @@ -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// This class represents options that can be used with the class for the quest toast variant. +/// +public sealed class QuestToastOptions { /// - /// This class represents options that can be used with the class for the quest toast variant. + /// Gets or sets the position of the toast on the screen. /// - public sealed class QuestToastOptions - { - /// - /// Gets or sets the position of the toast on the screen. - /// - public QuestToastPosition Position { get; set; } = QuestToastPosition.Centre; + public QuestToastPosition Position { get; set; } = QuestToastPosition.Centre; - /// - /// Gets or sets the ID of the icon that will appear in the toast. - /// - /// This may be 0 for no icon. - /// - public uint IconId { get; set; } = 0; + /// + /// Gets or sets the ID of the icon that will appear in the toast. + /// + /// This may be 0 for no icon. + /// + public uint IconId { get; set; } = 0; - /// - /// Gets or sets a value indicating whether the toast will show a checkmark after appearing. - /// - public bool DisplayCheckmark { get; set; } = false; + /// + /// Gets or sets a value indicating whether the toast will show a checkmark after appearing. + /// + public bool DisplayCheckmark { get; set; } = false; - /// - /// Gets or sets a value indicating whether the toast will play a completion sound. - /// - /// This only works if is non-zero or is true. - /// - public bool PlaySound { get; set; } = false; - } + /// + /// Gets or sets a value indicating whether the toast will play a completion sound. + /// + /// This only works if is non-zero or is true. + /// + public bool PlaySound { get; set; } = false; } diff --git a/Dalamud/Game/Gui/Toast/QuestToastPosition.cs b/Dalamud/Game/Gui/Toast/QuestToastPosition.cs index cc107ab6e..4c7d91c36 100644 --- a/Dalamud/Game/Gui/Toast/QuestToastPosition.cs +++ b/Dalamud/Game/Gui/Toast/QuestToastPosition.cs @@ -1,23 +1,22 @@ -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// The alignment of native quest toast windows. +/// +public enum QuestToastPosition { /// - /// The alignment of native quest toast windows. + /// The toast will be aligned screen centre. /// - public enum QuestToastPosition - { - /// - /// The toast will be aligned screen centre. - /// - Centre = 0, + Centre = 0, - /// - /// The toast will be aligned screen right. - /// - Right = 1, + /// + /// The toast will be aligned screen right. + /// + Right = 1, - /// - /// The toast will be aligned screen left. - /// - Left = 2, - } + /// + /// The toast will be aligned screen left. + /// + Left = 2, } diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index ca24df197..6094fcb6f 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -7,422 +7,421 @@ using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// This class facilitates interacting with and creating native toast windows. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed partial class ToastGui : IDisposable +{ + private const uint QuestToastCheckmarkMagic = 60081; + + private readonly ToastGuiAddressResolver address; + + private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new(); + private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new(); + private readonly Queue errorQueue = new(); + + private readonly Hook showNormalToastHook; + private readonly Hook showQuestToastHook; + private readonly Hook showErrorToastHook; + + /// + /// Initializes a new instance of the class. + /// + internal ToastGui() + { + this.address = new ToastGuiAddressResolver(); + this.address.Setup(); + + this.showNormalToastHook = new Hook(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); + this.showQuestToastHook = new Hook(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); + this.showErrorToastHook = new Hook(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour)); + } + + #region Event delegates + + /// + /// A delegate type used when a normal toast window appears. + /// + /// The message displayed. + /// Assorted toast options. + /// Whether the toast has been handled or should be propagated. + public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled); + + /// + /// A delegate type used when a quest toast window appears. + /// + /// The message displayed. + /// Assorted toast options. + /// Whether the toast has been handled or should be propagated. + public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled); + + /// + /// A delegate type used when an error toast window appears. + /// + /// The message displayed. + /// Whether the toast has been handled or should be propagated. + public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled); + + #endregion + + #region Marshal delegates + + private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId); + + private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); + + private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe); + + #endregion + + #region Events + + /// + /// Event that will be fired when a toast is sent by the game or a plugin. + /// + public event OnNormalToastDelegate Toast; + + /// + /// Event that will be fired when a quest toast is sent by the game or a plugin. + /// + public event OnQuestToastDelegate QuestToast; + + /// + /// Event that will be fired when an error toast is sent by the game or a plugin. + /// + public event OnErrorToastDelegate ErrorToast; + + #endregion + + /// + /// Enables this module. + /// + public void Enable() + { + this.showNormalToastHook.Enable(); + this.showQuestToastHook.Enable(); + this.showErrorToastHook.Enable(); + } + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.showNormalToastHook.Dispose(); + this.showQuestToastHook.Dispose(); + this.showErrorToastHook.Dispose(); + } + + /// + /// Process the toast queue. + /// + internal void UpdateQueue() + { + while (this.normalQueue.Count > 0) + { + var (message, options) = this.normalQueue.Dequeue(); + this.ShowNormal(message, options); + } + + while (this.questQueue.Count > 0) + { + var (message, options) = this.questQueue.Dequeue(); + this.ShowQuest(message, options); + } + + while (this.errorQueue.Count > 0) + { + var message = this.errorQueue.Dequeue(); + this.ShowError(message); + } + } + + private static byte[] Terminate(byte[] source) + { + var terminated = new byte[source.Length + 1]; + Array.Copy(source, 0, terminated, 0, source.Length); + terminated[^1] = 0; + + return terminated; + } + + private SeString ParseString(IntPtr text) + { + var bytes = new List(); + unsafe + { + var ptr = (byte*)text; + while (*ptr != 0) + { + bytes.Add(*ptr); + ptr += 1; + } + } + + // call events + return SeString.Parse(bytes.ToArray()); + } +} + +/// +/// Handles normal toasts. +/// +public sealed partial class ToastGui { /// - /// This class facilitates interacting with and creating native toast windows. + /// Show a toast message with the given content. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed partial class ToastGui : IDisposable + /// The message to be shown. + /// Options for the toast. + public void ShowNormal(string message, ToastOptions options = null) { - private const uint QuestToastCheckmarkMagic = 60081; - - private readonly ToastGuiAddressResolver address; - - private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new(); - private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new(); - private readonly Queue errorQueue = new(); - - private readonly Hook showNormalToastHook; - private readonly Hook showQuestToastHook; - private readonly Hook showErrorToastHook; - - /// - /// Initializes a new instance of the class. - /// - internal ToastGui() - { - this.address = new ToastGuiAddressResolver(); - this.address.Setup(); - - this.showNormalToastHook = new Hook(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); - this.showQuestToastHook = new Hook(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); - this.showErrorToastHook = new Hook(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour)); - } - - #region Event delegates - - /// - /// A delegate type used when a normal toast window appears. - /// - /// The message displayed. - /// Assorted toast options. - /// Whether the toast has been handled or should be propagated. - public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled); - - /// - /// A delegate type used when a quest toast window appears. - /// - /// The message displayed. - /// Assorted toast options. - /// Whether the toast has been handled or should be propagated. - public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled); - - /// - /// A delegate type used when an error toast window appears. - /// - /// The message displayed. - /// Whether the toast has been handled or should be propagated. - public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled); - - #endregion - - #region Marshal delegates - - private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId); - - private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); - - private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe); - - #endregion - - #region Events - - /// - /// Event that will be fired when a toast is sent by the game or a plugin. - /// - public event OnNormalToastDelegate Toast; - - /// - /// Event that will be fired when a quest toast is sent by the game or a plugin. - /// - public event OnQuestToastDelegate QuestToast; - - /// - /// Event that will be fired when an error toast is sent by the game or a plugin. - /// - public event OnErrorToastDelegate ErrorToast; - - #endregion - - /// - /// Enables this module. - /// - public void Enable() - { - this.showNormalToastHook.Enable(); - this.showQuestToastHook.Enable(); - this.showErrorToastHook.Enable(); - } - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.showNormalToastHook.Dispose(); - this.showQuestToastHook.Dispose(); - this.showErrorToastHook.Dispose(); - } - - /// - /// Process the toast queue. - /// - internal void UpdateQueue() - { - while (this.normalQueue.Count > 0) - { - var (message, options) = this.normalQueue.Dequeue(); - this.ShowNormal(message, options); - } - - while (this.questQueue.Count > 0) - { - var (message, options) = this.questQueue.Dequeue(); - this.ShowQuest(message, options); - } - - while (this.errorQueue.Count > 0) - { - var message = this.errorQueue.Dequeue(); - this.ShowError(message); - } - } - - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private SeString ParseString(IntPtr text) - { - var bytes = new List(); - unsafe - { - var ptr = (byte*)text; - while (*ptr != 0) - { - bytes.Add(*ptr); - ptr += 1; - } - } - - // call events - return SeString.Parse(bytes.ToArray()); - } + options ??= new ToastOptions(); + this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); } /// - /// Handles normal toasts. + /// Show a toast message with the given content. /// - public sealed partial class ToastGui + /// The message to be shown. + /// Options for the toast. + public void ShowNormal(SeString message, ToastOptions options = null) { - /// - /// Show a toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowNormal(string message, ToastOptions options = null) + options ??= new ToastOptions(); + this.normalQueue.Enqueue((message.Encode(), options)); + } + + private void ShowNormal(byte[] bytes, ToastOptions options = null) + { + options ??= new ToastOptions(); + + var manager = Service.Get().GetUIModule(); + + // terminate the string + var terminated = Terminate(bytes); + + unsafe { - options ??= new ToastOptions(); - this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); - } - - /// - /// Show a toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowNormal(SeString message, ToastOptions options = null) - { - options ??= new ToastOptions(); - this.normalQueue.Enqueue((message.Encode(), options)); - } - - private void ShowNormal(byte[] bytes, ToastOptions options = null) - { - options ??= new ToastOptions(); - - var manager = Service.Get().GetUIModule(); - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + fixed (byte* ptr = terminated) { - fixed (byte* ptr = terminated) - { - this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); - } - } - } - - private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId) - { - if (text == IntPtr.Zero) - { - return IntPtr.Zero; - } - - // call events - var isHandled = false; - var str = this.ParseString(text); - var options = new ToastOptions - { - Position = (ToastPosition)isTop, - Speed = (ToastSpeed)isFast, - }; - - this.Toast?.Invoke(ref str, ref options, ref isHandled); - - // do nothing if handled - if (isHandled) - { - return IntPtr.Zero; - } - - var terminated = Terminate(str.Encode()); - - unsafe - { - fixed (byte* message = terminated) - { - return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId); - } + this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); } } } - /// - /// Handles quest toasts. - /// - public sealed partial class ToastGui + private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId) { - /// - /// Show a quest toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowQuest(string message, QuestToastOptions options = null) + if (text == IntPtr.Zero) { - options ??= new QuestToastOptions(); - this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); + return IntPtr.Zero; } - /// - /// Show a quest toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowQuest(SeString message, QuestToastOptions options = null) + // call events + var isHandled = false; + var str = this.ParseString(text); + var options = new ToastOptions { - options ??= new QuestToastOptions(); - this.questQueue.Enqueue((message.Encode(), options)); + Position = (ToastPosition)isTop, + Speed = (ToastSpeed)isFast, + }; + + this.Toast?.Invoke(ref str, ref options, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return IntPtr.Zero; } - private void ShowQuest(byte[] bytes, QuestToastOptions options = null) + var terminated = Terminate(str.Encode()); + + unsafe { - options ??= new QuestToastOptions(); - - var manager = Service.Get().GetUIModule(); - - // terminate the string - var terminated = Terminate(bytes); - - var (ioc1, ioc2) = this.DetermineParameterOrder(options); - - unsafe + fixed (byte* message = terminated) { - fixed (byte* ptr = terminated) - { - this.HandleQuestToastDetour( - manager, - (int)options.Position, - (IntPtr)ptr, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } - } - } - - private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) - { - if (text == IntPtr.Zero) - { - return 0; - } - - // call events - var isHandled = false; - var str = this.ParseString(text); - var options = new QuestToastOptions - { - Position = (QuestToastPosition)position, - DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic, - IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1, - PlaySound = playSound == 1, - }; - - this.QuestToast?.Invoke(ref str, ref options, ref isHandled); - - // do nothing if handled - if (isHandled) - { - return 0; - } - - var terminated = Terminate(str.Encode()); - - var (ioc1, ioc2) = this.DetermineParameterOrder(options); - - unsafe - { - fixed (byte* message = terminated) - { - return this.showQuestToastHook.Original( - manager, - (int)options.Position, - (IntPtr)message, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } - } - } - - private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options) - { - return options.DisplayCheckmark - ? (QuestToastCheckmarkMagic, options.IconId) - : (options.IconId, 0); - } - } - - /// - /// Handles error toasts. - /// - public sealed partial class ToastGui - { - /// - /// Show an error toast message with the given content. - /// - /// The message to be shown. - public void ShowError(string message) - { - this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message)); - } - - /// - /// Show an error toast message with the given content. - /// - /// The message to be shown. - public void ShowError(SeString message) - { - this.errorQueue.Enqueue(message.Encode()); - } - - private void ShowError(byte[] bytes) - { - var manager = Service.Get().GetUIModule(); - - // terminate the string - var terminated = Terminate(bytes); - - unsafe - { - fixed (byte* ptr = terminated) - { - this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0); - } - } - } - - private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe) - { - if (text == IntPtr.Zero) - { - return 0; - } - - // call events - var isHandled = false; - var str = this.ParseString(text); - - this.ErrorToast?.Invoke(ref str, ref isHandled); - - // do nothing if handled - if (isHandled) - { - return 0; - } - - var terminated = Terminate(str.Encode()); - - unsafe - { - fixed (byte* message = terminated) - { - return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe); - } + return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId); + } + } + } +} + +/// +/// Handles quest toasts. +/// +public sealed partial class ToastGui +{ + /// + /// Show a quest toast message with the given content. + /// + /// The message to be shown. + /// Options for the toast. + public void ShowQuest(string message, QuestToastOptions options = null) + { + options ??= new QuestToastOptions(); + this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); + } + + /// + /// Show a quest toast message with the given content. + /// + /// The message to be shown. + /// Options for the toast. + public void ShowQuest(SeString message, QuestToastOptions options = null) + { + options ??= new QuestToastOptions(); + this.questQueue.Enqueue((message.Encode(), options)); + } + + private void ShowQuest(byte[] bytes, QuestToastOptions options = null) + { + options ??= new QuestToastOptions(); + + var manager = Service.Get().GetUIModule(); + + // terminate the string + var terminated = Terminate(bytes); + + var (ioc1, ioc2) = this.DetermineParameterOrder(options); + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleQuestToastDetour( + manager, + (int)options.Position, + (IntPtr)ptr, + ioc1, + options.PlaySound ? (byte)1 : (byte)0, + ioc2, + 0); + } + } + } + + private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) + { + if (text == IntPtr.Zero) + { + return 0; + } + + // call events + var isHandled = false; + var str = this.ParseString(text); + var options = new QuestToastOptions + { + Position = (QuestToastPosition)position, + DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic, + IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1, + PlaySound = playSound == 1, + }; + + this.QuestToast?.Invoke(ref str, ref options, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return 0; + } + + var terminated = Terminate(str.Encode()); + + var (ioc1, ioc2) = this.DetermineParameterOrder(options); + + unsafe + { + fixed (byte* message = terminated) + { + return this.showQuestToastHook.Original( + manager, + (int)options.Position, + (IntPtr)message, + ioc1, + options.PlaySound ? (byte)1 : (byte)0, + ioc2, + 0); + } + } + } + + private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options) + { + return options.DisplayCheckmark + ? (QuestToastCheckmarkMagic, options.IconId) + : (options.IconId, 0); + } +} + +/// +/// Handles error toasts. +/// +public sealed partial class ToastGui +{ + /// + /// Show an error toast message with the given content. + /// + /// The message to be shown. + public void ShowError(string message) + { + this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message)); + } + + /// + /// Show an error toast message with the given content. + /// + /// The message to be shown. + public void ShowError(SeString message) + { + this.errorQueue.Enqueue(message.Encode()); + } + + private void ShowError(byte[] bytes) + { + var manager = Service.Get().GetUIModule(); + + // terminate the string + var terminated = Terminate(bytes); + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0); + } + } + } + + private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe) + { + if (text == IntPtr.Zero) + { + return 0; + } + + // call events + var isHandled = false; + var str = this.ParseString(text); + + this.ErrorToast?.Invoke(ref str, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return 0; + } + + var terminated = Terminate(str.Encode()); + + unsafe + { + fixed (byte* message = terminated) + { + return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe); } } } diff --git a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs index 75a1e96b6..4f935b465 100644 --- a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs @@ -1,33 +1,32 @@ using System; -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// An address resolver for the class. +/// +public class ToastGuiAddressResolver : BaseAddressResolver { /// - /// An address resolver for the class. + /// Gets the address of the native ShowNormalToast method. /// - public class ToastGuiAddressResolver : BaseAddressResolver + public IntPtr ShowNormalToast { get; private set; } + + /// + /// Gets the address of the native ShowQuestToast method. + /// + public IntPtr ShowQuestToast { get; private set; } + + /// + /// Gets the address of the ShowErrorToast method. + /// + public IntPtr ShowErrorToast { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the address of the native ShowNormalToast method. - /// - public IntPtr ShowNormalToast { get; private set; } - - /// - /// Gets the address of the native ShowQuestToast method. - /// - public IntPtr ShowQuestToast { get; private set; } - - /// - /// Gets the address of the ShowErrorToast method. - /// - public IntPtr ShowErrorToast { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) - { - this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); - this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??"); - this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0"); - } + this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); + this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??"); + this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0"); } } diff --git a/Dalamud/Game/Gui/Toast/ToastOptions.cs b/Dalamud/Game/Gui/Toast/ToastOptions.cs index 0939bb5bb..32b5ae646 100644 --- a/Dalamud/Game/Gui/Toast/ToastOptions.cs +++ b/Dalamud/Game/Gui/Toast/ToastOptions.cs @@ -1,18 +1,17 @@ -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// This class represents options that can be used with the class. +/// +public sealed class ToastOptions { /// - /// This class represents options that can be used with the class. + /// Gets or sets the position of the toast on the screen. /// - public sealed class ToastOptions - { - /// - /// Gets or sets the position of the toast on the screen. - /// - public ToastPosition Position { get; set; } = ToastPosition.Bottom; + public ToastPosition Position { get; set; } = ToastPosition.Bottom; - /// - /// Gets or sets the speed of the toast. - /// - public ToastSpeed Speed { get; set; } = ToastSpeed.Slow; - } + /// + /// Gets or sets the speed of the toast. + /// + public ToastSpeed Speed { get; set; } = ToastSpeed.Slow; } diff --git a/Dalamud/Game/Gui/Toast/ToastPosition.cs b/Dalamud/Game/Gui/Toast/ToastPosition.cs index 14f489711..3e5696c3e 100644 --- a/Dalamud/Game/Gui/Toast/ToastPosition.cs +++ b/Dalamud/Game/Gui/Toast/ToastPosition.cs @@ -1,18 +1,17 @@ -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// The positioning of native toast windows. +/// +public enum ToastPosition : byte { /// - /// The positioning of native toast windows. + /// The toast will be towards the bottom. /// - public enum ToastPosition : byte - { - /// - /// The toast will be towards the bottom. - /// - Bottom = 0, + Bottom = 0, - /// - /// The toast will be towards the top. - /// - Top = 1, - } + /// + /// The toast will be towards the top. + /// + Top = 1, } diff --git a/Dalamud/Game/Gui/Toast/ToastSpeed.cs b/Dalamud/Game/Gui/Toast/ToastSpeed.cs index 0f54df273..1858764cc 100644 --- a/Dalamud/Game/Gui/Toast/ToastSpeed.cs +++ b/Dalamud/Game/Gui/Toast/ToastSpeed.cs @@ -1,18 +1,17 @@ -namespace Dalamud.Game.Gui.Toast +namespace Dalamud.Game.Gui.Toast; + +/// +/// The speed at which native toast windows will persist. +/// +public enum ToastSpeed : byte { /// - /// The speed at which native toast windows will persist. + /// The toast will take longer to disappear (around four seconds). /// - public enum ToastSpeed : byte - { - /// - /// The toast will take longer to disappear (around four seconds). - /// - Slow = 0, + Slow = 0, - /// - /// The toast will disappear more quickly (around two seconds). - /// - Fast = 1, - } + /// + /// The toast will disappear more quickly (around two seconds). + /// + Fast = 1, } diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index 7cac7b7dc..d435fad9c 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -3,119 +3,118 @@ using System.Collections.Generic; using Serilog; -namespace Dalamud.Game.Internal +namespace Dalamud.Game.Internal; + +/// +/// This class disables anti-debug functionality in the game client. +/// +internal sealed partial class AntiDebug { + private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; + private byte[] original; + private IntPtr debugCheckAddress; + /// - /// This class disables anti-debug functionality in the game client. + /// Initializes a new instance of the class. /// - internal sealed partial class AntiDebug + public AntiDebug() { - private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; - private byte[] original; - private IntPtr debugCheckAddress; + var scanner = Service.Get(); - /// - /// Initializes a new instance of the class. - /// - public AntiDebug() + try { - var scanner = Service.Get(); - - try - { - this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); - } - catch (KeyNotFoundException) - { - this.debugCheckAddress = IntPtr.Zero; - } - - Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); + this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); + } + catch (KeyNotFoundException) + { + this.debugCheckAddress = IntPtr.Zero; } - /// - /// Gets a value indicating whether the anti-debugging is enabled. - /// - public bool IsEnabled { get; private set; } = false; - - /// - /// Enables the anti-debugging by overwriting code in memory. - /// - public void Enable() - { - this.original = new byte[this.nop.Length]; - if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) - { - Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); - SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original); - SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); - } - else - { - Log.Information("Debug check already overwritten?"); - } - - this.IsEnabled = true; - } - - /// - /// Disable the anti-debugging by reverting the overwritten code in memory. - /// - public void Disable() - { - if (this.debugCheckAddress != IntPtr.Zero && this.original != null) - { - Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); - SafeMemory.WriteBytes(this.debugCheckAddress, this.original); - } - else - { - Log.Information("Debug check was not overwritten?"); - } - - this.IsEnabled = false; - } + Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); } /// - /// Implementing IDisposable. + /// Gets a value indicating whether the anti-debugging is enabled. /// - internal sealed partial class AntiDebug : IDisposable + public bool IsEnabled { get; private set; } = false; + + /// + /// Enables the anti-debugging by overwriting code in memory. + /// + public void Enable() { - private bool disposed = false; - - /// - /// Finalizes an instance of the class. - /// - ~AntiDebug() => this.Dispose(false); - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() + this.original = new byte[this.nop.Length]; + if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) { - this.Dispose(true); - GC.SuppressFinalize(this); + Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); + SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original); + SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); + } + else + { + Log.Information("Debug check already overwritten?"); } - /// - /// Disposes of managed and unmanaged resources. - /// - /// If this was disposed through calling Dispose() or from being finalized. - private void Dispose(bool disposing) + this.IsEnabled = true; + } + + /// + /// Disable the anti-debugging by reverting the overwritten code in memory. + /// + public void Disable() + { + if (this.debugCheckAddress != IntPtr.Zero && this.original != null) { - if (this.disposed) - return; - - if (disposing) - { - // If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded. - // If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the - // check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it. - this.Disable(); - } - - this.disposed = true; + Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); + SafeMemory.WriteBytes(this.debugCheckAddress, this.original); } + else + { + Log.Information("Debug check was not overwritten?"); + } + + this.IsEnabled = false; + } +} + +/// +/// Implementing IDisposable. +/// +internal sealed partial class AntiDebug : IDisposable +{ + private bool disposed = false; + + /// + /// Finalizes an instance of the class. + /// + ~AntiDebug() => this.Dispose(false); + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of managed and unmanaged resources. + /// + /// If this was disposed through calling Dispose() or from being finalized. + private void Dispose(bool disposing) + { + if (this.disposed) + return; + + if (disposing) + { + // If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded. + // If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the + // check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it. + this.Disable(); + } + + this.disposed = true; } } diff --git a/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs b/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs index 7703713b3..4bd369dd3 100644 --- a/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs +++ b/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs @@ -1,227 +1,226 @@ -namespace Dalamud.Game.Internal.DXGI.Definitions +namespace Dalamud.Game.Internal.DXGI.Definitions; + +/// +/// Contains a full list of ID3D11Device functions to be used as an indexer into the DirectX Virtual Function Table entries. +/// +internal enum ID3D11DeviceVtbl { + // IUnknown + /// - /// Contains a full list of ID3D11Device functions to be used as an indexer into the DirectX Virtual Function Table entries. + /// IUnknown::QueryInterface method (unknwn.h). /// - internal enum ID3D11DeviceVtbl - { - // IUnknown + QueryInterface = 0, - /// - /// IUnknown::QueryInterface method (unknwn.h). - /// - QueryInterface = 0, + /// + /// IUnknown::AddRef method (unknwn.h). + /// + AddRef = 1, - /// - /// IUnknown::AddRef method (unknwn.h). - /// - AddRef = 1, + /// + /// IUnknown::Release method (unknwn.h). + /// + Release = 2, - /// - /// IUnknown::Release method (unknwn.h). - /// - Release = 2, + // ID3D11Device - // ID3D11Device + /// + /// ID3D11Device::CreateBuffer method (d3d11.h). + /// + CreateBuffer = 3, - /// - /// ID3D11Device::CreateBuffer method (d3d11.h). - /// - CreateBuffer = 3, + /// + /// ID3D11Device::CreateTexture1D method (d3d11.h). + /// + CreateTexture1D = 4, - /// - /// ID3D11Device::CreateTexture1D method (d3d11.h). - /// - CreateTexture1D = 4, + /// + /// ID3D11Device::CreateTexture2D method (d3d11.h). + /// + CreateTexture2D = 5, - /// - /// ID3D11Device::CreateTexture2D method (d3d11.h). - /// - CreateTexture2D = 5, + /// + /// ID3D11Device::CreateTexture3D method (d3d11.h). + /// + CreateTexture3D = 6, - /// - /// ID3D11Device::CreateTexture3D method (d3d11.h). - /// - CreateTexture3D = 6, + /// + /// ID3D11Device::CreateShaderResourceView method (d3d11.h). + /// + CreateShaderResourceView = 7, - /// - /// ID3D11Device::CreateShaderResourceView method (d3d11.h). - /// - CreateShaderResourceView = 7, + /// + /// ID3D11Device::CreateUnorderedAccessView method (d3d11.h). + /// + CreateUnorderedAccessView = 8, - /// - /// ID3D11Device::CreateUnorderedAccessView method (d3d11.h). - /// - CreateUnorderedAccessView = 8, + /// + /// ID3D11Device::CreateRenderTargetView method (d3d11.h). + /// + CreateRenderTargetView = 9, - /// - /// ID3D11Device::CreateRenderTargetView method (d3d11.h). - /// - CreateRenderTargetView = 9, + /// + /// ID3D11Device::CreateDepthStencilView method (d3d11.h). + /// + CreateDepthStencilView = 10, - /// - /// ID3D11Device::CreateDepthStencilView method (d3d11.h). - /// - CreateDepthStencilView = 10, + /// + /// ID3D11Device::CreateInputLayout method (d3d11.h). + /// + CreateInputLayout = 11, - /// - /// ID3D11Device::CreateInputLayout method (d3d11.h). - /// - CreateInputLayout = 11, + /// + /// ID3D11Device::CreateVertexShader method (d3d11.h). + /// + CreateVertexShader = 12, - /// - /// ID3D11Device::CreateVertexShader method (d3d11.h). - /// - CreateVertexShader = 12, + /// + /// ID3D11Device::CreateGeometryShader method (d3d11.h). + /// + CreateGeometryShader = 13, - /// - /// ID3D11Device::CreateGeometryShader method (d3d11.h). - /// - CreateGeometryShader = 13, + /// + /// ID3D11Device::CreateGeometryShaderWithStreamOutput method (d3d11.h). + /// + CreateGeometryShaderWithStreamOutput = 14, - /// - /// ID3D11Device::CreateGeometryShaderWithStreamOutput method (d3d11.h). - /// - CreateGeometryShaderWithStreamOutput = 14, + /// + /// ID3D11Device::CreatePixelShader method (d3d11.h). + /// + CreatePixelShader = 15, - /// - /// ID3D11Device::CreatePixelShader method (d3d11.h). - /// - CreatePixelShader = 15, + /// + /// ID3D11Device::CreateHullShader method (d3d11.h). + /// + CreateHullShader = 16, - /// - /// ID3D11Device::CreateHullShader method (d3d11.h). - /// - CreateHullShader = 16, + /// + /// ID3D11Device::CreateDomainShader method (d3d11.h). + /// + CreateDomainShader = 17, - /// - /// ID3D11Device::CreateDomainShader method (d3d11.h). - /// - CreateDomainShader = 17, + /// + /// ID3D11Device::CreateComputeShader method (d3d11.h). + /// + CreateComputeShader = 18, - /// - /// ID3D11Device::CreateComputeShader method (d3d11.h). - /// - CreateComputeShader = 18, + /// + /// ID3D11Device::CreateClassLinkage method (d3d11.h). + /// + CreateClassLinkage = 19, - /// - /// ID3D11Device::CreateClassLinkage method (d3d11.h). - /// - CreateClassLinkage = 19, + /// + /// ID3D11Device::CreateBlendState method (d3d11.h). + /// + CreateBlendState = 20, - /// - /// ID3D11Device::CreateBlendState method (d3d11.h). - /// - CreateBlendState = 20, + /// + /// ID3D11Device::CreateDepthStencilState method (d3d11.h). + /// + CreateDepthStencilState = 21, - /// - /// ID3D11Device::CreateDepthStencilState method (d3d11.h). - /// - CreateDepthStencilState = 21, + /// + /// ID3D11Device::CreateRasterizerState method (d3d11.h). + /// + CreateRasterizerState = 22, - /// - /// ID3D11Device::CreateRasterizerState method (d3d11.h). - /// - CreateRasterizerState = 22, + /// + /// ID3D11Device::CreateSamplerState method (d3d11.h). + /// + CreateSamplerState = 23, - /// - /// ID3D11Device::CreateSamplerState method (d3d11.h). - /// - CreateSamplerState = 23, + /// + /// ID3D11Device::CreateQuery method (d3d11.h). + /// + CreateQuery = 24, - /// - /// ID3D11Device::CreateQuery method (d3d11.h). - /// - CreateQuery = 24, + /// + /// ID3D11Device::CreatePredicate method (d3d11.h). + /// + CreatePredicate = 25, - /// - /// ID3D11Device::CreatePredicate method (d3d11.h). - /// - CreatePredicate = 25, + /// + /// ID3D11Device::CreateCounter method (d3d11.h). + /// + CreateCounter = 26, - /// - /// ID3D11Device::CreateCounter method (d3d11.h). - /// - CreateCounter = 26, + /// + /// ID3D11Device::CreateDeferredContext method (d3d11.h). + /// + CreateDeferredContext = 27, - /// - /// ID3D11Device::CreateDeferredContext method (d3d11.h). - /// - CreateDeferredContext = 27, + /// + /// ID3D11Device::OpenSharedResource method (d3d11.h). + /// + OpenSharedResource = 28, - /// - /// ID3D11Device::OpenSharedResource method (d3d11.h). - /// - OpenSharedResource = 28, + /// + /// ID3D11Device::CheckFormatSupport method (d3d11.h). + /// + CheckFormatSupport = 29, - /// - /// ID3D11Device::CheckFormatSupport method (d3d11.h). - /// - CheckFormatSupport = 29, + /// + /// ID3D11Device::CheckMultisampleQualityLevels method (d3d11.h). + /// + CheckMultisampleQualityLevels = 30, - /// - /// ID3D11Device::CheckMultisampleQualityLevels method (d3d11.h). - /// - CheckMultisampleQualityLevels = 30, + /// + /// ID3D11Device::CheckCounterInfo method (d3d11.h). + /// + CheckCounterInfo = 31, - /// - /// ID3D11Device::CheckCounterInfo method (d3d11.h). - /// - CheckCounterInfo = 31, + /// + /// ID3D11Device::CheckCounter method (d3d11.h). + /// + CheckCounter = 32, - /// - /// ID3D11Device::CheckCounter method (d3d11.h). - /// - CheckCounter = 32, + /// + /// ID3D11Device::CheckFeatureSupport method (d3d11.h). + /// + CheckFeatureSupport = 33, - /// - /// ID3D11Device::CheckFeatureSupport method (d3d11.h). - /// - CheckFeatureSupport = 33, + /// + /// ID3D11Device::GetPrivateData method (d3d11.h). + /// + GetPrivateData = 34, - /// - /// ID3D11Device::GetPrivateData method (d3d11.h). - /// - GetPrivateData = 34, + /// + /// ID3D11Device::SetPrivateData method (d3d11.h). + /// + SetPrivateData = 35, - /// - /// ID3D11Device::SetPrivateData method (d3d11.h). - /// - SetPrivateData = 35, + /// + /// ID3D11Device::SetPrivateDataInterface method (d3d11.h). + /// + SetPrivateDataInterface = 36, - /// - /// ID3D11Device::SetPrivateDataInterface method (d3d11.h). - /// - SetPrivateDataInterface = 36, + /// + /// ID3D11Device::GetFeatureLevel method (d3d11.h). + /// + GetFeatureLevel = 37, - /// - /// ID3D11Device::GetFeatureLevel method (d3d11.h). - /// - GetFeatureLevel = 37, + /// + /// ID3D11Device::GetCreationFlags method (d3d11.h). + /// + GetCreationFlags = 38, - /// - /// ID3D11Device::GetCreationFlags method (d3d11.h). - /// - GetCreationFlags = 38, + /// + /// ID3D11Device::GetDeviceRemovedReason method (d3d11.h). + /// + GetDeviceRemovedReason = 39, - /// - /// ID3D11Device::GetDeviceRemovedReason method (d3d11.h). - /// - GetDeviceRemovedReason = 39, + /// + /// ID3D11Device::GetImmediateContext method (d3d11.h). + /// + GetImmediateContext = 40, - /// - /// ID3D11Device::GetImmediateContext method (d3d11.h). - /// - GetImmediateContext = 40, + /// + /// ID3D11Device::SetExceptionMode method (d3d11.h). + /// + SetExceptionMode = 41, - /// - /// ID3D11Device::SetExceptionMode method (d3d11.h). - /// - SetExceptionMode = 41, - - /// - /// ID3D11Device::GetExceptionMode method (d3d11.h). - /// - GetExceptionMode = 42, - } + /// + /// ID3D11Device::GetExceptionMode method (d3d11.h). + /// + GetExceptionMode = 42, } diff --git a/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs b/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs index e3d627ce3..4bcb6fb72 100644 --- a/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs +++ b/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs @@ -1,107 +1,106 @@ -namespace Dalamud.Game.Internal.DXGI.Definitions +namespace Dalamud.Game.Internal.DXGI.Definitions; + +/// +/// Contains a full list of IDXGISwapChain functions to be used as an indexer into the SwapChain Virtual Function Table +/// entries. +/// +internal enum IDXGISwapChainVtbl { + // IUnknown + /// - /// Contains a full list of IDXGISwapChain functions to be used as an indexer into the SwapChain Virtual Function Table - /// entries. + /// IUnknown::QueryInterface method (unknwn.h). /// - internal enum IDXGISwapChainVtbl - { - // IUnknown + QueryInterface = 0, - /// - /// IUnknown::QueryInterface method (unknwn.h). - /// - QueryInterface = 0, + /// + /// IUnknown::AddRef method (unknwn.h). + /// + AddRef = 1, - /// - /// IUnknown::AddRef method (unknwn.h). - /// - AddRef = 1, + /// + /// IUnknown::Release method (unknwn.h). + /// + Release = 2, - /// - /// IUnknown::Release method (unknwn.h). - /// - Release = 2, + // IDXGIObject - // IDXGIObject + /// + /// IDXGIObject::SetPrivateData method (dxgi.h). + /// + SetPrivateData = 3, - /// - /// IDXGIObject::SetPrivateData method (dxgi.h). - /// - SetPrivateData = 3, + /// + /// IDXGIObject::SetPrivateDataInterface method (dxgi.h). + /// + SetPrivateDataInterface = 4, - /// - /// IDXGIObject::SetPrivateDataInterface method (dxgi.h). - /// - SetPrivateDataInterface = 4, + /// + /// IDXGIObject::GetPrivateData method (dxgi.h). + /// + GetPrivateData = 5, - /// - /// IDXGIObject::GetPrivateData method (dxgi.h). - /// - GetPrivateData = 5, + /// + /// IDXGIObject::GetParent method (dxgi.h). + /// + GetParent = 6, - /// - /// IDXGIObject::GetParent method (dxgi.h). - /// - GetParent = 6, + // IDXGIDeviceSubObject - // IDXGIDeviceSubObject + /// + /// IDXGIDeviceSubObject::GetDevice method (dxgi.h). + /// + GetDevice = 7, - /// - /// IDXGIDeviceSubObject::GetDevice method (dxgi.h). - /// - GetDevice = 7, + // IDXGISwapChain - // IDXGISwapChain + /// + /// IDXGISwapChain::Present method (dxgi.h). + /// + Present = 8, - /// - /// IDXGISwapChain::Present method (dxgi.h). - /// - Present = 8, + /// + /// IUnknIDXGISwapChainown::GetBuffer method (dxgi.h). + /// + GetBuffer = 9, - /// - /// IUnknIDXGISwapChainown::GetBuffer method (dxgi.h). - /// - GetBuffer = 9, + /// + /// IDXGISwapChain::SetFullscreenState method (dxgi.h). + /// + SetFullscreenState = 10, - /// - /// IDXGISwapChain::SetFullscreenState method (dxgi.h). - /// - SetFullscreenState = 10, + /// + /// IDXGISwapChain::GetFullscreenState method (dxgi.h). + /// + GetFullscreenState = 11, - /// - /// IDXGISwapChain::GetFullscreenState method (dxgi.h). - /// - GetFullscreenState = 11, + /// + /// IDXGISwapChain::GetDesc method (dxgi.h). + /// + GetDesc = 12, - /// - /// IDXGISwapChain::GetDesc method (dxgi.h). - /// - GetDesc = 12, + /// + /// IDXGISwapChain::ResizeBuffers method (dxgi.h). + /// + ResizeBuffers = 13, - /// - /// IDXGISwapChain::ResizeBuffers method (dxgi.h). - /// - ResizeBuffers = 13, + /// + /// IDXGISwapChain::ResizeTarget method (dxgi.h). + /// + ResizeTarget = 14, - /// - /// IDXGISwapChain::ResizeTarget method (dxgi.h). - /// - ResizeTarget = 14, + /// + /// IDXGISwapChain::GetContainingOutput method (dxgi.h). + /// + GetContainingOutput = 15, - /// - /// IDXGISwapChain::GetContainingOutput method (dxgi.h). - /// - GetContainingOutput = 15, + /// + /// IDXGISwapChain::GetFrameStatistics method (dxgi.h). + /// + GetFrameStatistics = 16, - /// - /// IDXGISwapChain::GetFrameStatistics method (dxgi.h). - /// - GetFrameStatistics = 16, - - /// - /// IDXGISwapChain::GetLastPresentCount method (dxgi.h). - /// - GetLastPresentCount = 17, - } + /// + /// IDXGISwapChain::GetLastPresentCount method (dxgi.h). + /// + GetLastPresentCount = 17, } diff --git a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs index eb867dd5c..867119be5 100644 --- a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs +++ b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs @@ -1,20 +1,19 @@ using System; -namespace Dalamud.Game.Internal.DXGI +namespace Dalamud.Game.Internal.DXGI; + +/// +/// An interface binding for the address resolvers that attempt to find native D3D11 methods. +/// +public interface ISwapChainAddressResolver { /// - /// An interface binding for the address resolvers that attempt to find native D3D11 methods. + /// Gets or sets the address of the native D3D11.Present method. /// - public interface ISwapChainAddressResolver - { - /// - /// Gets or sets the address of the native D3D11.Present method. - /// - IntPtr Present { get; set; } + IntPtr Present { get; set; } - /// - /// Gets or sets the address of the native D3D11.ResizeBuffers method. - /// - IntPtr ResizeBuffers { get; set; } - } + /// + /// Gets or sets the address of the native D3D11.ResizeBuffers method. + /// + IntPtr ResizeBuffers { get; set; } } diff --git a/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs index ac1a419c2..ad79dff9f 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs @@ -4,33 +4,32 @@ using System.Linq; using Serilog; -namespace Dalamud.Game.Internal.DXGI +namespace Dalamud.Game.Internal.DXGI; + +/// +/// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI. +/// +[Obsolete("This has been deprecated in favor of the VTable resolver.")] +public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver { - /// - /// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI. - /// - [Obsolete("This has been deprecated in favor of the VTable resolver.")] - public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver + /// + public IntPtr Present { get; set; } + + /// + public IntPtr ResizeBuffers { get; set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - public IntPtr Present { get; set; } + var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "dxgi.dll"); - /// - public IntPtr ResizeBuffers { get; set; } + Log.Debug($"Found DXGI: 0x{module.BaseAddress.ToInt64():X}"); - /// - protected override void Setup64Bit(SigScanner sig) - { - var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "dxgi.dll"); + var scanner = new SigScanner(module); - Log.Debug($"Found DXGI: 0x{module.BaseAddress.ToInt64():X}"); + // This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function. + this.Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37; - var scanner = new SigScanner(module); - - // This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function. - this.Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37; - - this.ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24"); - } + this.ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24"); } } diff --git a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs index 78abae0f0..a17cf293b 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs @@ -6,78 +6,77 @@ using System.Runtime.InteropServices; using Dalamud.Game.Internal.DXGI.Definitions; using Serilog; -namespace Dalamud.Game.Internal.DXGI +namespace Dalamud.Game.Internal.DXGI; + +/// +/// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it. +/// +/// +/// If the normal signature based method of resolution fails, this is the backup. +/// +public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver { + /// + public IntPtr Present { get; set; } + + /// + public IntPtr ResizeBuffers { get; set; } + /// - /// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it. + /// Gets a value indicating whether or not ReShade is loaded/used. /// - /// - /// If the normal signature based method of resolution fails, this is the backup. - /// - public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver + public bool IsReshade { get; private set; } + + /// + protected override unsafe void Setup64Bit(SigScanner sig) { - /// - public IntPtr Present { get; set; } + var kernelDev = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance(); - /// - public IntPtr ResizeBuffers { get; set; } + var scVtbl = GetVTblAddresses(new IntPtr(kernelDev->SwapChain->DXGISwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); - /// - /// Gets a value indicating whether or not ReShade is loaded/used. - /// - public bool IsReshade { get; private set; } + this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; - /// - protected override unsafe void Setup64Bit(SigScanner sig) + var modules = Process.GetCurrentProcess().Modules; + foreach (ProcessModule processModule in modules) { - var kernelDev = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance(); - - var scVtbl = GetVTblAddresses(new IntPtr(kernelDev->SwapChain->DXGISwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); - - this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; - - var modules = Process.GetCurrentProcess().Modules; - foreach (ProcessModule processModule in modules) + if (processModule.FileName != null && processModule.FileName.EndsWith("game\\dxgi.dll")) { - if (processModule.FileName != null && processModule.FileName.EndsWith("game\\dxgi.dll")) + // reshade master@4232872 RVA + // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present + // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present + + var scanner = new SigScanner(processModule); + try { - // reshade master@4232872 RVA - // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present - // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present + var p = scanner.ScanText("F6 C2 01 0F 85 ?? ?? ?? ??"); + Log.Information($"ReShade DLL: {processModule.FileName} with DXGISwapChain::runtime_present at {p:X}"); - var scanner = new SigScanner(processModule); - try - { - var p = scanner.ScanText("F6 C2 01 0F 85 ?? ?? ?? ??"); - Log.Information($"ReShade DLL: {processModule.FileName} with DXGISwapChain::runtime_present at {p:X}"); - - this.Present = p; - this.IsReshade = true; - break; - } - catch (Exception ex) - { - Log.Error(ex, "Could not find reshade DXGISwapChain::runtime_present offset!"); - } + this.Present = p; + this.IsReshade = true; + break; + } + catch (Exception ex) + { + Log.Error(ex, "Could not find reshade DXGISwapChain::runtime_present offset!"); } } - - this.ResizeBuffers = scVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; } - private static List GetVTblAddresses(IntPtr pointer, int numberOfMethods) - { - return GetVTblAddresses(pointer, 0, numberOfMethods); - } + this.ResizeBuffers = scVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; + } - private static List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods) - { - var vtblAddresses = new List(); - var vTable = Marshal.ReadIntPtr(pointer); - for (var i = startIndex; i < startIndex + numberOfMethods; i++) - vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes + private static List GetVTblAddresses(IntPtr pointer, int numberOfMethods) + { + return GetVTblAddresses(pointer, 0, numberOfMethods); + } - return vtblAddresses; - } + private static List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods) + { + var vtblAddresses = new List(); + var vTable = Marshal.ReadIntPtr(pointer); + for (var i = startIndex; i < startIndex + numberOfMethods; i++) + vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes + + return vtblAddresses; } } diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 137cafb27..d12f389b0 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -12,216 +12,215 @@ using Serilog; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; -namespace Dalamud.Game.Internal +namespace Dalamud.Game.Internal; + +/// +/// This class implements in-game Dalamud options in the in-game System menu. +/// +internal sealed unsafe partial class DalamudAtkTweaks { + private readonly AtkValueChangeType atkValueChangeType; + private readonly AtkValueSetString atkValueSetString; + private readonly Hook hookAgentHudOpenSystemMenu; + + // TODO: Make this into events in Framework.Gui + private readonly Hook hookUiModuleRequestMainCommand; + + private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + /// - /// This class implements in-game Dalamud options in the in-game System menu. + /// Initializes a new instance of the class. /// - internal sealed unsafe partial class DalamudAtkTweaks + public DalamudAtkTweaks() { - private readonly AtkValueChangeType atkValueChangeType; - private readonly AtkValueSetString atkValueSetString; - private readonly Hook hookAgentHudOpenSystemMenu; + var sigScanner = Service.Get(); - // TODO: Make this into events in Framework.Gui - private readonly Hook hookUiModuleRequestMainCommand; + var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); - private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + this.hookAgentHudOpenSystemMenu = new Hook(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); - /// - /// Initializes a new instance of the class. - /// - public DalamudAtkTweaks() - { - var sigScanner = Service.Get(); + var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); + this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer(atkValueChangeTypeAddress); - var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); + var atkValueSetStringAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); + this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress); - this.hookAgentHudOpenSystemMenu = new Hook(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); + var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); + this.hookUiModuleRequestMainCommand = new Hook(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); - this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer(atkValueChangeTypeAddress); - - var atkValueSetStringAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); - this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress); - - var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); - this.hookUiModuleRequestMainCommand = new Hook(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - - var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 "); - this.hookAtkUnitBaseReceiveGlobalEvent = new Hook(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); - } - - private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); - - private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); - - private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes); - - private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); - - private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); - - /// - /// Enables the . - /// - public void Enable() - { - this.hookAgentHudOpenSystemMenu.Enable(); - this.hookUiModuleRequestMainCommand.Enable(); - this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); - } - - private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) - { - // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", Marshal.PtrToStringAnsi(new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); - - // "SendHotkey" - // 3 == Close - if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && Service.Get().IsFocusManagementEnabled) - { - Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return IntPtr.Zero; - } - - return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg); - } - - private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) - { - if (WindowSystem.HasAnyWindowSystemFocus && Service.Get().IsFocusManagementEnabled) - { - Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return; - } - - var configuration = Service.Get(); - - if (!configuration.DoButtonsSystemMenu) - { - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); - return; - } - - // the max size (hardcoded) is 0xE/15, but the system menu currently uses 0xC/12 - // this is a just in case that doesnt really matter - // see if we can add 2 entries - if (menuSize >= 0xD) - { - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); - return; - } - - // atkValueArgs is actually an array of AtkValues used as args. all their UI code works like this. - // in this case, menu size is stored in atkValueArgs[4], and the next 15 slots are the MainCommand - // the 15 slots after that, if they exist, are the entry names, but they are otherwise pulled from MainCommand EXD - // reference the original function for more details :) - - // step 1) move all the current menu items down so we can put Dalamud at the top like it deserves - this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int - this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int); - - for (var i = menuSize + 2; i > 1; i--) - { - var curEntry = &atkValueArgs[i + 5 - 2]; - var nextEntry = &atkValueArgs[i + 5]; - - nextEntry->Int = curEntry->Int; - } - - // step 2) set our new entries to dummy commands - var firstEntry = &atkValueArgs[5]; - firstEntry->Int = 69420; - var secondEntry = &atkValueArgs[6]; - secondEntry->Int = 69421; - - // step 3) create strings for them - // since the game first checks for strings in the AtkValue argument before pulling them from the exd, if we create strings we dont have to worry - // about hooking the exd reader, thank god - var firstStringEntry = &atkValueArgs[5 + 15]; - this.atkValueChangeType(firstStringEntry, ValueType.String); - var secondStringEntry = &atkValueArgs[6 + 15]; - this.atkValueChangeType(secondStringEntry, ValueType.String); - - var strPlugins = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuPlugins", "Dalamud Plugins")); - var strSettings = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuSettings", "Dalamud Settings")); - - // do this the most terrible way possible since im lazy - var bytes = stackalloc byte[strPlugins.Length + 1]; - Marshal.Copy(strPlugins, 0, new IntPtr(bytes), strPlugins.Length); - bytes[strPlugins.Length] = 0x0; - - this.atkValueSetString(firstStringEntry, bytes); // this allocs the string properly using the game's allocators and copies it, so we dont have to worry about memory fuckups - - var bytes2 = stackalloc byte[strSettings.Length + 1]; - Marshal.Copy(strSettings, 0, new IntPtr(bytes2), strSettings.Length); - bytes2[strSettings.Length] = 0x0; - - this.atkValueSetString(secondStringEntry, bytes2); - - // open menu with new size - var sizeEntry = &atkValueArgs[4]; - sizeEntry->UInt = menuSize + 2; - - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); - } - - private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) - { - var dalamudInterface = Service.Get(); - - switch (commandId) - { - case 69420: - dalamudInterface.TogglePluginInstallerWindow(); - break; - case 69421: - dalamudInterface.ToggleSettingsWindow(); - break; - default: - this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); - break; - } - } + var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 "); + this.hookAtkUnitBaseReceiveGlobalEvent = new Hook(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); } + private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); + + private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); + + private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes); + + private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); + + private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); + /// - /// Implements IDisposable. + /// Enables the . /// - internal sealed partial class DalamudAtkTweaks : IDisposable + public void Enable() { - private bool disposed = false; + this.hookAgentHudOpenSystemMenu.Enable(); + this.hookUiModuleRequestMainCommand.Enable(); + this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); + } - /// - /// Finalizes an instance of the class. - /// - ~DalamudAtkTweaks() => this.Dispose(false); + private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) + { + // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", Marshal.PtrToStringAnsi(new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() + // "SendHotkey" + // 3 == Close + if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && Service.Get().IsFocusManagementEnabled) { - this.Dispose(true); - GC.SuppressFinalize(this); + Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); + return IntPtr.Zero; } - /// - /// Dispose of managed and unmanaged resources. - /// - private void Dispose(bool disposing) + return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg); + } + + private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) + { + if (WindowSystem.HasAnyWindowSystemFocus && Service.Get().IsFocusManagementEnabled) { - if (this.disposed) - return; + Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); + return; + } - if (disposing) - { - this.hookAgentHudOpenSystemMenu.Dispose(); - this.hookUiModuleRequestMainCommand.Dispose(); - this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); - } + var configuration = Service.Get(); - this.disposed = true; + if (!configuration.DoButtonsSystemMenu) + { + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } + + // the max size (hardcoded) is 0xE/15, but the system menu currently uses 0xC/12 + // this is a just in case that doesnt really matter + // see if we can add 2 entries + if (menuSize >= 0xD) + { + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } + + // atkValueArgs is actually an array of AtkValues used as args. all their UI code works like this. + // in this case, menu size is stored in atkValueArgs[4], and the next 15 slots are the MainCommand + // the 15 slots after that, if they exist, are the entry names, but they are otherwise pulled from MainCommand EXD + // reference the original function for more details :) + + // step 1) move all the current menu items down so we can put Dalamud at the top like it deserves + this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int + this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int); + + for (var i = menuSize + 2; i > 1; i--) + { + var curEntry = &atkValueArgs[i + 5 - 2]; + var nextEntry = &atkValueArgs[i + 5]; + + nextEntry->Int = curEntry->Int; + } + + // step 2) set our new entries to dummy commands + var firstEntry = &atkValueArgs[5]; + firstEntry->Int = 69420; + var secondEntry = &atkValueArgs[6]; + secondEntry->Int = 69421; + + // step 3) create strings for them + // since the game first checks for strings in the AtkValue argument before pulling them from the exd, if we create strings we dont have to worry + // about hooking the exd reader, thank god + var firstStringEntry = &atkValueArgs[5 + 15]; + this.atkValueChangeType(firstStringEntry, ValueType.String); + var secondStringEntry = &atkValueArgs[6 + 15]; + this.atkValueChangeType(secondStringEntry, ValueType.String); + + var strPlugins = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuPlugins", "Dalamud Plugins")); + var strSettings = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuSettings", "Dalamud Settings")); + + // do this the most terrible way possible since im lazy + var bytes = stackalloc byte[strPlugins.Length + 1]; + Marshal.Copy(strPlugins, 0, new IntPtr(bytes), strPlugins.Length); + bytes[strPlugins.Length] = 0x0; + + this.atkValueSetString(firstStringEntry, bytes); // this allocs the string properly using the game's allocators and copies it, so we dont have to worry about memory fuckups + + var bytes2 = stackalloc byte[strSettings.Length + 1]; + Marshal.Copy(strSettings, 0, new IntPtr(bytes2), strSettings.Length); + bytes2[strSettings.Length] = 0x0; + + this.atkValueSetString(secondStringEntry, bytes2); + + // open menu with new size + var sizeEntry = &atkValueArgs[4]; + sizeEntry->UInt = menuSize + 2; + + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); + } + + private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) + { + var dalamudInterface = Service.Get(); + + switch (commandId) + { + case 69420: + dalamudInterface.TogglePluginInstallerWindow(); + break; + case 69421: + dalamudInterface.ToggleSettingsWindow(); + break; + default: + this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); + break; } } } + +/// +/// Implements IDisposable. +/// +internal sealed partial class DalamudAtkTweaks : IDisposable +{ + private bool disposed = false; + + /// + /// Finalizes an instance of the class. + /// + ~DalamudAtkTweaks() => this.Dispose(false); + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + private void Dispose(bool disposing) + { + if (this.disposed) + return; + + if (disposing) + { + this.hookAgentHudOpenSystemMenu.Dispose(); + this.hookUiModuleRequestMainCommand.Dispose(); + this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); + } + + this.disposed = true; + } +} diff --git a/Dalamud/Game/Libc/LibcFunction.cs b/Dalamud/Game/Libc/LibcFunction.cs index 3c2361277..f4617c7e6 100644 --- a/Dalamud/Game/Libc/LibcFunction.cs +++ b/Dalamud/Game/Libc/LibcFunction.cs @@ -5,75 +5,74 @@ using System.Text; using Dalamud.IoC; using Dalamud.IoC.Internal; -namespace Dalamud.Game.Libc +namespace Dalamud.Game.Libc; + +/// +/// This class handles creating cstrings utilizing native game methods. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class LibcFunction { + private readonly LibcFunctionAddressResolver address; + private readonly StdStringFromCStringDelegate stdStringCtorCString; + private readonly StdStringDeallocateDelegate stdStringDeallocate; + /// - /// This class handles creating cstrings utilizing native game methods. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class LibcFunction + public LibcFunction() { - private readonly LibcFunctionAddressResolver address; - private readonly StdStringFromCStringDelegate stdStringCtorCString; - private readonly StdStringDeallocateDelegate stdStringDeallocate; + this.address = new LibcFunctionAddressResolver(); + this.address.Setup(); - /// - /// Initializes a new instance of the class. - /// - public LibcFunction() - { - this.address = new LibcFunctionAddressResolver(); - this.address.Setup(); + this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(this.address.StdStringFromCstring); + this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(this.address.StdStringDeallocate); + } - this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(this.address.StdStringFromCstring); - this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(this.address.StdStringDeallocate); - } + // TODO: prolly callconv is not okay in x86 + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size); - // TODO: prolly callconv is not okay in x86 - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size); + // TODO: prolly callconv is not okay in x86 + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr StdStringDeallocateDelegate(IntPtr address); - // TODO: prolly callconv is not okay in x86 - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr StdStringDeallocateDelegate(IntPtr address); + /// + /// Create a new string from the given bytes. + /// + /// The bytes to convert. + /// An owned std string object. + public OwnedStdString NewString(byte[] content) + { + // While 0x70 bytes in the memory should be enough in DX11 version, + // I don't trust my analysis so we're just going to allocate almost two times more than that. + var pString = Marshal.AllocHGlobal(256); - /// - /// Create a new string from the given bytes. - /// - /// The bytes to convert. - /// An owned std string object. - public OwnedStdString NewString(byte[] content) - { - // While 0x70 bytes in the memory should be enough in DX11 version, - // I don't trust my analysis so we're just going to allocate almost two times more than that. - var pString = Marshal.AllocHGlobal(256); + // Initialize a string + var size = new IntPtr(content.Length); + var pReallocString = this.stdStringCtorCString(pString, content, size); - // Initialize a string - var size = new IntPtr(content.Length); - var pReallocString = this.stdStringCtorCString(pString, content, size); + // Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString); - // Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString); + return new OwnedStdString(pReallocString, this.DeallocateStdString); + } - return new OwnedStdString(pReallocString, this.DeallocateStdString); - } + /// + /// Create a new string form the given bytes. + /// + /// The bytes to convert. + /// A non-default encoding. + /// An owned std string object. + public OwnedStdString NewString(string content, Encoding encoding = null) + { + encoding ??= Encoding.UTF8; - /// - /// Create a new string form the given bytes. - /// - /// The bytes to convert. - /// A non-default encoding. - /// An owned std string object. - public OwnedStdString NewString(string content, Encoding encoding = null) - { - encoding ??= Encoding.UTF8; + return this.NewString(encoding.GetBytes(content)); + } - return this.NewString(encoding.GetBytes(content)); - } - - private void DeallocateStdString(IntPtr address) - { - this.stdStringDeallocate(address); - } + private void DeallocateStdString(IntPtr address) + { + this.stdStringDeallocate(address); } } diff --git a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs index 4d30a1e74..dcf687203 100644 --- a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs +++ b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs @@ -2,30 +2,29 @@ using System; using Dalamud.Game.Internal; -namespace Dalamud.Game.Libc +namespace Dalamud.Game.Libc; + +/// +/// The address resolver for the class. +/// +public sealed class LibcFunctionAddressResolver : BaseAddressResolver { + private delegate IntPtr StringFromCString(); + /// - /// The address resolver for the class. + /// Gets the address of the native StdStringFromCstring method. /// - public sealed class LibcFunctionAddressResolver : BaseAddressResolver + public IntPtr StdStringFromCstring { get; private set; } + + /// + /// Gets the address of the native StdStringDeallocate method. + /// + public IntPtr StdStringDeallocate { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - private delegate IntPtr StringFromCString(); - - /// - /// Gets the address of the native StdStringFromCstring method. - /// - public IntPtr StdStringFromCstring { get; private set; } - - /// - /// Gets the address of the native StdStringDeallocate method. - /// - public IntPtr StdStringDeallocate { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) - { - this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8"); - this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3"); - } + this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8"); + this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3"); } } diff --git a/Dalamud/Game/Libc/OwnedStdString.cs b/Dalamud/Game/Libc/OwnedStdString.cs index 1939e068e..db92257b3 100644 --- a/Dalamud/Game/Libc/OwnedStdString.cs +++ b/Dalamud/Game/Libc/OwnedStdString.cs @@ -1,101 +1,100 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Game.Libc +namespace Dalamud.Game.Libc; + +/// +/// An address wrapper around the class. +/// +public sealed partial class OwnedStdString { + private readonly DeallocatorDelegate dealloc; + /// - /// An address wrapper around the class. + /// Initializes a new instance of the class. + /// Construct a wrapper around std::string. /// - public sealed partial class OwnedStdString + /// + /// Violating any of these might cause an undefined hehaviour. + /// 1. This function takes the ownership of the address. + /// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address. + /// 3. std::string object pointed by address must be initialized before calling this function. + /// + /// The address of the owned std string. + /// A deallocator function. + internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) { - private readonly DeallocatorDelegate dealloc; - - /// - /// Initializes a new instance of the class. - /// Construct a wrapper around std::string. - /// - /// - /// Violating any of these might cause an undefined hehaviour. - /// 1. This function takes the ownership of the address. - /// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address. - /// 3. std::string object pointed by address must be initialized before calling this function. - /// - /// The address of the owned std string. - /// A deallocator function. - internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) - { - this.Address = address; - this.dealloc = dealloc; - } - - /// - /// The delegate type that deallocates a std string. - /// - /// Address to deallocate. - internal delegate void DeallocatorDelegate(IntPtr address); - - /// - /// Gets the address of the std string. - /// - public IntPtr Address { get; private set; } - - /// - /// Read the wrapped StdString. - /// - /// The StdString. - public StdString Read() => StdString.ReadFromPointer(this.Address); + this.Address = address; + this.dealloc = dealloc; } /// - /// Implements IDisposable. + /// The delegate type that deallocates a std string. /// - public sealed partial class OwnedStdString : IDisposable + /// Address to deallocate. + internal delegate void DeallocatorDelegate(IntPtr address); + + /// + /// Gets the address of the std string. + /// + public IntPtr Address { get; private set; } + + /// + /// Read the wrapped StdString. + /// + /// The StdString. + public StdString Read() => StdString.ReadFromPointer(this.Address); +} + +/// +/// Implements IDisposable. +/// +public sealed partial class OwnedStdString : IDisposable +{ + private bool isDisposed; + + /// + /// Finalizes an instance of the class. + /// + ~OwnedStdString() => this.Dispose(false); + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() { - private bool isDisposed; + GC.SuppressFinalize(this); + this.Dispose(true); + } - /// - /// Finalizes an instance of the class. - /// - ~OwnedStdString() => this.Dispose(false); + /// + /// Dispose of managed and unmanaged resources. + /// + /// A value indicating whether this was called via Dispose or finalized. + public void Dispose(bool disposing) + { + if (this.isDisposed) + return; - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() + this.isDisposed = true; + + if (disposing) { - GC.SuppressFinalize(this); - this.Dispose(true); } - /// - /// Dispose of managed and unmanaged resources. - /// - /// A value indicating whether this was called via Dispose or finalized. - public void Dispose(bool disposing) + if (this.Address == IntPtr.Zero) { - if (this.isDisposed) - return; - - this.isDisposed = true; - - if (disposing) - { - } - - if (this.Address == IntPtr.Zero) - { - // Something got seriously fucked. - throw new AccessViolationException(); - } - - // Deallocate inner string first - this.dealloc(this.Address); - - // Free the heap - Marshal.FreeHGlobal(this.Address); - - // Better safe (running on a nullptr) than sorry. (running on a dangling pointer) - this.Address = IntPtr.Zero; + // Something got seriously fucked. + throw new AccessViolationException(); } + + // Deallocate inner string first + this.dealloc(this.Address); + + // Free the heap + Marshal.FreeHGlobal(this.Address); + + // Better safe (running on a nullptr) than sorry. (running on a dangling pointer) + this.Address = IntPtr.Zero; } } diff --git a/Dalamud/Game/Libc/StdString.cs b/Dalamud/Game/Libc/StdString.cs index 4d4478687..816219a82 100644 --- a/Dalamud/Game/Libc/StdString.cs +++ b/Dalamud/Game/Libc/StdString.cs @@ -2,68 +2,67 @@ using System; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud.Game.Libc +namespace Dalamud.Game.Libc; + +/// +/// Interation with std::string. +/// +public class StdString { /// - /// Interation with std::string. + /// Initializes a new instance of the class. /// - public class StdString + private StdString() { - /// - /// Initializes a new instance of the class. - /// - private StdString() + } + + /// + /// Gets the value of the cstring. + /// + public string Value { get; private set; } + + /// + /// Gets or sets the raw byte representation of the cstring. + /// + public byte[] RawData { get; set; } + + /// + /// Marshal a null terminated cstring from memory to a UTF-8 encoded string. + /// + /// Address of the cstring. + /// A UTF-8 encoded string. + public static StdString ReadFromPointer(IntPtr cstring) + { + unsafe { - } - - /// - /// Gets the value of the cstring. - /// - public string Value { get; private set; } - - /// - /// Gets or sets the raw byte representation of the cstring. - /// - public byte[] RawData { get; set; } - - /// - /// Marshal a null terminated cstring from memory to a UTF-8 encoded string. - /// - /// Address of the cstring. - /// A UTF-8 encoded string. - public static StdString ReadFromPointer(IntPtr cstring) - { - unsafe + if (cstring == IntPtr.Zero) { - if (cstring == IntPtr.Zero) - { - throw new ArgumentNullException(nameof(cstring)); - } - - var innerAddress = Marshal.ReadIntPtr(cstring); - if (innerAddress == IntPtr.Zero) - { - throw new NullReferenceException("Inner reference to the cstring is null."); - } - - // Count the number of chars. String is assumed to be zero-terminated. - - var count = 0; - while (Marshal.ReadByte(innerAddress, count) != 0) - { - count += 1; - } - - // raw copy, as UTF8 string conversion is lossy - var rawData = new byte[count]; - Marshal.Copy(innerAddress, rawData, 0, count); - - return new StdString - { - RawData = rawData, - Value = Encoding.UTF8.GetString(rawData), - }; + throw new ArgumentNullException(nameof(cstring)); } + + var innerAddress = Marshal.ReadIntPtr(cstring); + if (innerAddress == IntPtr.Zero) + { + throw new NullReferenceException("Inner reference to the cstring is null."); + } + + // Count the number of chars. String is assumed to be zero-terminated. + + var count = 0; + while (Marshal.ReadByte(innerAddress, count) != 0) + { + count += 1; + } + + // raw copy, as UTF8 string conversion is lossy + var rawData = new byte[count]; + Marshal.Copy(innerAddress, rawData, 0, count); + + return new StdString + { + RawData = rawData, + Value = Encoding.UTF8.GetString(rawData), + }; } } } diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index e4feac4d0..6c717c474 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -7,179 +7,178 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Network +namespace Dalamud.Game.Network; + +/// +/// This class handles interacting with game network events. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class GameNetwork : IDisposable { + private readonly GameNetworkAddressResolver address; + private readonly Hook processZonePacketDownHook; + private readonly Hook processZonePacketUpHook; + private readonly Queue zoneInjectQueue = new(); + + private IntPtr baseAddress; + /// - /// This class handles interacting with game network events. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class GameNetwork : IDisposable + internal GameNetwork() { - private readonly GameNetworkAddressResolver address; - private readonly Hook processZonePacketDownHook; - private readonly Hook processZonePacketUpHook; - private readonly Queue zoneInjectQueue = new(); + this.address = new GameNetworkAddressResolver(); + this.address.Setup(); - private IntPtr baseAddress; + Log.Verbose("===== G A M E N E T W O R K ====="); + Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); + Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}"); - /// - /// Initializes a new instance of the class. - /// - internal GameNetwork() - { - this.address = new GameNetworkAddressResolver(); - this.address.Setup(); - - Log.Verbose("===== G A M E N E T W O R K ====="); - Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); - Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}"); - - this.processZonePacketDownHook = new Hook(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); - this.processZonePacketUpHook = new Hook(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); - } - - /// - /// 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(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); - - /// - /// Event that is called when a network message is sent/received. - /// - public event OnNetworkMessageDelegate NetworkMessage; - - /// - /// Enable this module. - /// - public void Enable() - { - this.processZonePacketDownHook.Enable(); - this.processZonePacketUpHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.processZonePacketDownHook.Dispose(); - this.processZonePacketUpHook.Dispose(); - } - - /// - /// Process a chat queue. - /// - internal void UpdateQueue() - { - while (this.zoneInjectQueue.Count > 0) - { - var packetData = this.zoneInjectQueue.Dequeue(); - - var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length); - Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length); - - if (this.baseAddress != IntPtr.Zero) - { - this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData); - } - - Marshal.FreeHGlobal(unmanagedPacketData); - } - } - - private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) - { - this.baseAddress = a; - - // Go back 0x10 to get back to the start of the packet header - dataPtr -= 0x10; - - try - { - // Call events - this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); - - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); - } - catch (Exception ex) - { - string header; - try - { - var data = new byte[32]; - Marshal.Copy(dataPtr, data, 0, 32); - header = BitConverter.ToString(data); - } - catch (Exception) - { - header = "failed"; - } - - Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); - - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); - } - } - - private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) - { - try - { - // Call events - // TODO: Implement actor IDs - this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp); - } - catch (Exception ex) - { - string header; - try - { - var data = new byte[32]; - Marshal.Copy(dataPtr, data, 0, 32); - header = BitConverter.ToString(data); - } - catch (Exception) - { - header = "failed"; - } - - Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header); - } - - return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4); - } - - // private void InjectZoneProtoPacket(byte[] data) - // { - // this.zoneInjectQueue.Enqueue(data); - // } - - // private void InjectActorControl(short cat, int param1) - // { - // var packetData = new byte[] - // { - // 0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00, - // 0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00, - // }; - // - // BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10); - // - // BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14); - // - // this.InjectZoneProtoPacket(packetData); - // } + this.processZonePacketDownHook = new Hook(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); + this.processZonePacketUpHook = new Hook(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); } + + /// + /// 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(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); + + /// + /// Event that is called when a network message is sent/received. + /// + public event OnNetworkMessageDelegate NetworkMessage; + + /// + /// Enable this module. + /// + public void Enable() + { + this.processZonePacketDownHook.Enable(); + this.processZonePacketUpHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.processZonePacketDownHook.Dispose(); + this.processZonePacketUpHook.Dispose(); + } + + /// + /// Process a chat queue. + /// + internal void UpdateQueue() + { + while (this.zoneInjectQueue.Count > 0) + { + var packetData = this.zoneInjectQueue.Dequeue(); + + var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length); + Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length); + + if (this.baseAddress != IntPtr.Zero) + { + this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData); + } + + Marshal.FreeHGlobal(unmanagedPacketData); + } + } + + private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) + { + this.baseAddress = a; + + // Go back 0x10 to get back to the start of the packet header + dataPtr -= 0x10; + + try + { + // Call events + this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); + + this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); + } + catch (Exception ex) + { + string header; + try + { + var data = new byte[32]; + Marshal.Copy(dataPtr, data, 0, 32); + header = BitConverter.ToString(data); + } + catch (Exception) + { + header = "failed"; + } + + Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); + + this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); + } + } + + private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) + { + try + { + // Call events + // TODO: Implement actor IDs + this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp); + } + catch (Exception ex) + { + string header; + try + { + var data = new byte[32]; + Marshal.Copy(dataPtr, data, 0, 32); + header = BitConverter.ToString(data); + } + catch (Exception) + { + header = "failed"; + } + + Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header); + } + + return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4); + } + + // private void InjectZoneProtoPacket(byte[] data) + // { + // this.zoneInjectQueue.Enqueue(data); + // } + + // private void InjectActorControl(short cat, int param1) + // { + // var packetData = new byte[] + // { + // 0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00, + // 0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00, + // }; + // + // BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10); + // + // BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14); + // + // this.InjectZoneProtoPacket(packetData); + // } } diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs index 130986197..5be85bd35 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -1,31 +1,28 @@ using System; -using Dalamud.Game.Internal; +namespace Dalamud.Game.Network; -namespace Dalamud.Game.Network +/// +/// The address resolver for the class. +/// +public sealed class GameNetworkAddressResolver : BaseAddressResolver { /// - /// The address resolver for the class. + /// Gets the address of the ProcessZonePacketDown method. /// - public sealed class GameNetworkAddressResolver : BaseAddressResolver + public IntPtr ProcessZonePacketDown { get; private set; } + + /// + /// Gets the address of the ProcessZonePacketUp method. + /// + public IntPtr ProcessZonePacketUp { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) { - /// - /// Gets the address of the ProcessZonePacketDown method. - /// - public IntPtr ProcessZonePacketDown { get; private set; } - - /// - /// Gets the address of the ProcessZonePacketUp method. - /// - public IntPtr ProcessZonePacketUp { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) - { - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); - this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2"); - this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??"); - } + // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); + // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); + this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2"); + this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??"); } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs index 6ec2a997b..dadfef604 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs @@ -2,32 +2,31 @@ using System.Threading.Tasks; using Dalamud.Game.Network.Structures; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders; + +/// +/// An interface binding for the Universalis uploader. +/// +internal interface IMarketBoardUploader { /// - /// An interface binding for the Universalis uploader. + /// Upload data about an item. /// - internal interface IMarketBoardUploader - { - /// - /// Upload data about an item. - /// - /// The item request data being uploaded. - /// An async task. - Task Upload(MarketBoardItemRequest item); + /// The item request data being uploaded. + /// An async task. + Task Upload(MarketBoardItemRequest item); - /// - /// Upload tax rate data. - /// - /// The tax rate data being uploaded. - /// An async task. - Task UploadTax(MarketTaxRates taxRates); + /// + /// Upload tax rate data. + /// + /// The tax rate data being uploaded. + /// An async task. + Task UploadTax(MarketTaxRates taxRates); - /// - /// Upload information about a purchase this client has made. - /// - /// The purchase handler data associated with the sale. - /// An async task. - Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); - } + /// + /// Upload information about a purchase this client has made. + /// + /// The purchase handler data associated with the sale. + /// An async task. + Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs index bdd07a8af..18ed5c4b5 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs @@ -4,64 +4,63 @@ using System.IO; using Dalamud.Game.Network.Structures; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders; + +/// +/// This represents a submission to a marketboard aggregation website. +/// +internal class MarketBoardItemRequest { - /// - /// This represents a submission to a marketboard aggregation website. - /// - internal class MarketBoardItemRequest + private MarketBoardItemRequest() { - private MarketBoardItemRequest() - { - } + } - /// - /// Gets the catalog ID. - /// - public uint CatalogId { get; private set; } + /// + /// Gets the catalog ID. + /// + public uint CatalogId { get; private set; } - /// - /// Gets the amount to arrive. - /// - public byte AmountToArrive { get; private set; } + /// + /// Gets the amount to arrive. + /// + public byte AmountToArrive { get; private set; } - /// - /// Gets the offered item listings. - /// - public List Listings { get; } = new(); + /// + /// Gets the offered item listings. + /// + public List Listings { get; } = new(); - /// - /// Gets the historical item listings. - /// - public List History { get; } = new(); + /// + /// Gets the historical item listings. + /// + public List History { get; } = new(); - /// - /// Gets or sets the listing request ID. - /// - public int ListingsRequestId { get; set; } = -1; + /// + /// Gets or sets the listing request ID. + /// + public int ListingsRequestId { get; set; } = -1; - /// - /// Gets a value indicating whether the upload is complete. - /// - public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0; + /// + /// Gets a value indicating whether the upload is complete. + /// + public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0; - /// - /// Read a packet off the wire. - /// - /// Packet data. - /// An object representing the data read. - public static unsafe MarketBoardItemRequest Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Read a packet off the wire. + /// + /// Packet data. + /// An object representing the data read. + public static unsafe MarketBoardItemRequest Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketBoardItemRequest(); + var output = new MarketBoardItemRequest(); - output.CatalogId = reader.ReadUInt32(); - stream.Position += 0x7; - output.AmountToArrive = reader.ReadByte(); + output.CatalogId = reader.ReadUInt32(); + stream.Position += 0x7; + output.AmountToArrive = reader.ReadByte(); - return output; - } + return output; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs index 37467377c..3b95349a9 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs @@ -1,58 +1,57 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisHistoryEntry { /// - /// A Universalis API structure. + /// Gets or sets a value indicating whether the item is HQ or not. /// - internal class UniversalisHistoryEntry - { - /// - /// Gets or sets a value indicating whether the item is HQ or not. - /// - [JsonProperty("hq")] - public bool Hq { get; set; } + [JsonProperty("hq")] + public bool Hq { get; set; } - /// - /// Gets or sets the item price per unit. - /// - [JsonProperty("pricePerUnit")] - public uint PricePerUnit { get; set; } + /// + /// Gets or sets the item price per unit. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } - /// - /// Gets or sets the quantity of items available. - /// - [JsonProperty("quantity")] - public uint Quantity { get; set; } + /// + /// Gets or sets the quantity of items available. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } - /// - /// Gets or sets the name of the buyer. - /// - [JsonProperty("buyerName")] - public string BuyerName { get; set; } + /// + /// Gets or sets the name of the buyer. + /// + [JsonProperty("buyerName")] + public string BuyerName { get; set; } - /// - /// Gets or sets a value indicating whether this item was on a mannequin. - /// - [JsonProperty("onMannequin")] - public bool OnMannequin { get; set; } + /// + /// Gets or sets a value indicating whether this item was on a mannequin. + /// + [JsonProperty("onMannequin")] + public bool OnMannequin { get; set; } - /// - /// Gets or sets the seller ID. - /// - [JsonProperty("sellerID")] - public string SellerId { get; set; } + /// + /// Gets or sets the seller ID. + /// + [JsonProperty("sellerID")] + public string SellerId { get; set; } - /// - /// Gets or sets the buyer ID. - /// - [JsonProperty("buyerID")] - public string BuyerId { get; set; } + /// + /// Gets or sets the buyer ID. + /// + [JsonProperty("buyerID")] + public string BuyerId { get; set; } - /// - /// Gets or sets the timestamp of the transaction. - /// - [JsonProperty("timestamp")] - public long Timestamp { get; set; } - } + /// + /// Gets or sets the timestamp of the transaction. + /// + [JsonProperty("timestamp")] + public long Timestamp { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs index afa8bf417..ba5e4ad47 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs @@ -2,35 +2,34 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisHistoryUploadRequest { /// - /// A Universalis API structure. + /// Gets or sets the world ID. /// - internal class UniversalisHistoryUploadRequest - { - /// - /// Gets or sets the world ID. - /// - [JsonProperty("worldID")] - public uint WorldId { get; set; } + [JsonProperty("worldID")] + public uint WorldId { get; set; } - /// - /// Gets or sets the item ID. - /// - [JsonProperty("itemID")] - public uint ItemId { get; set; } + /// + /// Gets or sets the item ID. + /// + [JsonProperty("itemID")] + public uint ItemId { get; set; } - /// - /// Gets or sets the list of available entries. - /// - [JsonProperty("entries")] - public List Entries { get; set; } + /// + /// Gets or sets the list of available entries. + /// + [JsonProperty("entries")] + public List Entries { get; set; } - /// - /// Gets or sets the uploader ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } - } + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs index ea96f934a..ed8a82bfa 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs @@ -1,40 +1,39 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// Request payload for market board purchases. +/// +internal class UniversalisItemListingDeleteRequest { /// - /// Request payload for market board purchases. + /// Gets or sets the object ID of the retainer associated with the sale. /// - internal class UniversalisItemListingDeleteRequest - { - /// - /// Gets or sets the object ID of the retainer associated with the sale. - /// - [JsonProperty("retainerID")] - public string RetainerId { get; set; } + [JsonProperty("retainerID")] + public string RetainerId { get; set; } - /// - /// Gets or sets the object ID of the item listing. - /// - [JsonProperty("listingID")] - public string ListingId { get; set; } + /// + /// Gets or sets the object ID of the item listing. + /// + [JsonProperty("listingID")] + public string ListingId { get; set; } - /// - /// Gets or sets the quantity of the item that was purchased. - /// - [JsonProperty("quantity")] - public uint Quantity { get; set; } + /// + /// Gets or sets the quantity of the item that was purchased. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } - /// - /// Gets or sets the unit price of the item. - /// - [JsonProperty("pricePerUnit")] - public uint PricePerUnit { get; set; } + /// + /// Gets or sets the unit price of the item. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } - /// - /// Gets or sets the uploader ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } - } + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs index 42ff15668..c4a51b49d 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs @@ -2,95 +2,94 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisItemListingsEntry { /// - /// A Universalis API structure. + /// Gets or sets the listing ID. /// - internal class UniversalisItemListingsEntry - { - /// - /// Gets or sets the listing ID. - /// - [JsonProperty("listingID")] - public string ListingId { get; set; } + [JsonProperty("listingID")] + public string ListingId { get; set; } - /// - /// Gets or sets a value indicating whether the item is HQ. - /// - [JsonProperty("hq")] - public bool Hq { get; set; } + /// + /// Gets or sets a value indicating whether the item is HQ. + /// + [JsonProperty("hq")] + public bool Hq { get; set; } - /// - /// Gets or sets the item price per unit. - /// - [JsonProperty("pricePerUnit")] - public uint PricePerUnit { get; set; } + /// + /// Gets or sets the item price per unit. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } - /// - /// Gets or sets the item quantity. - /// - [JsonProperty("quantity")] - public uint Quantity { get; set; } + /// + /// Gets or sets the item quantity. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } - /// - /// Gets or sets the name of the retainer selling the item. - /// - [JsonProperty("retainerName")] - public string RetainerName { get; set; } + /// + /// Gets or sets the name of the retainer selling the item. + /// + [JsonProperty("retainerName")] + public string RetainerName { get; set; } - /// - /// Gets or sets the ID of the retainer selling the item. - /// - [JsonProperty("retainerID")] - public string RetainerId { get; set; } + /// + /// Gets or sets the ID of the retainer selling the item. + /// + [JsonProperty("retainerID")] + public string RetainerId { get; set; } - /// - /// Gets or sets the name of the user who created the entry. - /// - [JsonProperty("creatorName")] - public string CreatorName { get; set; } + /// + /// Gets or sets the name of the user who created the entry. + /// + [JsonProperty("creatorName")] + public string CreatorName { get; set; } - /// - /// Gets or sets a value indicating whether the item is on a mannequin. - /// - [JsonProperty("onMannequin")] - public bool OnMannequin { get; set; } + /// + /// Gets or sets a value indicating whether the item is on a mannequin. + /// + [JsonProperty("onMannequin")] + public bool OnMannequin { get; set; } - /// - /// Gets or sets the seller ID. - /// - [JsonProperty("sellerID")] - public string SellerId { get; set; } + /// + /// Gets or sets the seller ID. + /// + [JsonProperty("sellerID")] + public string SellerId { get; set; } - /// - /// Gets or sets the ID of the user who created the entry. - /// - [JsonProperty("creatorID")] - public string CreatorId { get; set; } + /// + /// Gets or sets the ID of the user who created the entry. + /// + [JsonProperty("creatorID")] + public string CreatorId { get; set; } - /// - /// Gets or sets the ID of the dye on the item. - /// - [JsonProperty("stainID")] - public int StainId { get; set; } + /// + /// Gets or sets the ID of the dye on the item. + /// + [JsonProperty("stainID")] + public int StainId { get; set; } - /// - /// Gets or sets the city where the selling retainer resides. - /// - [JsonProperty("retainerCity")] - public int RetainerCity { get; set; } + /// + /// Gets or sets the city where the selling retainer resides. + /// + [JsonProperty("retainerCity")] + public int RetainerCity { get; set; } - /// - /// Gets or sets the last time the entry was reviewed. - /// - [JsonProperty("lastReviewTime")] - public long LastReviewTime { get; set; } + /// + /// Gets or sets the last time the entry was reviewed. + /// + [JsonProperty("lastReviewTime")] + public long LastReviewTime { get; set; } - /// - /// Gets or sets the materia attached to the item. - /// - [JsonProperty("materia")] - public List Materia { get; set; } - } + /// + /// Gets or sets the materia attached to the item. + /// + [JsonProperty("materia")] + public List Materia { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs index c055b30d9..e70b43588 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs @@ -2,35 +2,34 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisItemListingsUploadRequest { /// - /// A Universalis API structure. + /// Gets or sets the world ID. /// - internal class UniversalisItemListingsUploadRequest - { - /// - /// Gets or sets the world ID. - /// - [JsonProperty("worldID")] - public uint WorldId { get; set; } + [JsonProperty("worldID")] + public uint WorldId { get; set; } - /// - /// Gets or sets the item ID. - /// - [JsonProperty("itemID")] - public uint ItemId { get; set; } + /// + /// Gets or sets the item ID. + /// + [JsonProperty("itemID")] + public uint ItemId { get; set; } - /// - /// Gets or sets the list of available items. - /// - [JsonProperty("listings")] - public List Listings { get; set; } + /// + /// Gets or sets the list of available items. + /// + [JsonProperty("listings")] + public List Listings { get; set; } - /// - /// Gets or sets the uploader ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } - } + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs index 8c9c99bb5..3480470eb 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs @@ -1,22 +1,21 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisItemMateria { /// - /// A Universalis API structure. + /// Gets or sets the item slot ID. /// - internal class UniversalisItemMateria - { - /// - /// Gets or sets the item slot ID. - /// - [JsonProperty("slotID")] - public int SlotId { get; set; } + [JsonProperty("slotID")] + public int SlotId { get; set; } - /// - /// Gets or sets the materia ID. - /// - [JsonProperty("materiaID")] - public int MateriaId { get; set; } - } + /// + /// Gets or sets the materia ID. + /// + [JsonProperty("materiaID")] + public int MateriaId { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs index 0a93f788b..ccb790f72 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs @@ -1,46 +1,45 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisTaxData { /// - /// A Universalis API structure. + /// Gets or sets Limsa Lominsa's current tax rate. /// - internal class UniversalisTaxData - { - /// - /// Gets or sets Limsa Lominsa's current tax rate. - /// - [JsonProperty("limsaLominsa")] - public uint LimsaLominsa { get; set; } + [JsonProperty("limsaLominsa")] + public uint LimsaLominsa { get; set; } - /// - /// Gets or sets Gridania's current tax rate. - /// - [JsonProperty("gridania")] - public uint Gridania { get; set; } + /// + /// Gets or sets Gridania's current tax rate. + /// + [JsonProperty("gridania")] + public uint Gridania { get; set; } - /// - /// Gets or sets Ul'dah's current tax rate. - /// - [JsonProperty("uldah")] - public uint Uldah { get; set; } + /// + /// Gets or sets Ul'dah's current tax rate. + /// + [JsonProperty("uldah")] + public uint Uldah { get; set; } - /// - /// Gets or sets Ishgard's current tax rate. - /// - [JsonProperty("ishgard")] - public uint Ishgard { get; set; } + /// + /// Gets or sets Ishgard's current tax rate. + /// + [JsonProperty("ishgard")] + public uint Ishgard { get; set; } - /// - /// Gets or sets Kugane's current tax rate. - /// - [JsonProperty("kugane")] - public uint Kugane { get; set; } + /// + /// Gets or sets Kugane's current tax rate. + /// + [JsonProperty("kugane")] + public uint Kugane { get; set; } - /// - /// Gets or sets The Crystarium's current tax rate. - /// - [JsonProperty("crystarium")] - public uint Crystarium { get; set; } - } + /// + /// Gets or sets The Crystarium's current tax rate. + /// + [JsonProperty("crystarium")] + public uint Crystarium { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs index a11ac0e89..d7abb5f89 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs @@ -1,28 +1,27 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; + +/// +/// A Universalis API structure. +/// +internal class UniversalisTaxUploadRequest { /// - /// A Universalis API structure. + /// Gets or sets the uploader's ID. /// - internal class UniversalisTaxUploadRequest - { - /// - /// Gets or sets the uploader's ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } - /// - /// Gets or sets the world to retrieve data from. - /// - [JsonProperty("worldID")] - public uint WorldId { get; set; } + /// + /// Gets or sets the world to retrieve data from. + /// + [JsonProperty("worldID")] + public uint WorldId { get; set; } - /// - /// Gets or sets tax data for each city's market. - /// - [JsonProperty("marketTaxRates")] - public UniversalisTaxData TaxData { get; set; } - } + /// + /// Gets or sets tax data for each city's market. + /// + [JsonProperty("marketTaxRates")] + public UniversalisTaxData TaxData { get; set; } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index 998502739..be1b5cc40 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -10,182 +10,181 @@ using Dalamud.Utility; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis; + +/// +/// This class represents an uploader for contributing data to Universalis. +/// +internal class UniversalisMarketBoardUploader : IMarketBoardUploader { + private const string ApiBase = "https://universalis.app"; + // private const string ApiBase = "https://127.0.0.1:443"; + + private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT"; + /// - /// This class represents an uploader for contributing data to Universalis. + /// Initializes a new instance of the class. /// - internal class UniversalisMarketBoardUploader : IMarketBoardUploader + public UniversalisMarketBoardUploader() { - private const string ApiBase = "https://universalis.app"; - // private const string ApiBase = "https://127.0.0.1:443"; + } - private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT"; + /// + public async Task Upload(MarketBoardItemRequest request) + { + var clientState = Service.Get(); - /// - /// Initializes a new instance of the class. - /// - public UniversalisMarketBoardUploader() + Log.Verbose("Starting Universalis upload."); + var uploader = clientState.LocalContentId; + + // ==================================================================================== + + var listingsUploadObject = new UniversalisItemListingsUploadRequest { - } + WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = uploader.ToString(), + ItemId = request.CatalogId, + Listings = new List(), + }; - /// - public async Task Upload(MarketBoardItemRequest request) + foreach (var marketBoardItemListing in request.Listings) { - var clientState = Service.Get(); - - Log.Verbose("Starting Universalis upload."); - var uploader = clientState.LocalContentId; - - // ==================================================================================== - - var listingsUploadObject = new UniversalisItemListingsUploadRequest + var universalisListing = new UniversalisItemListingsEntry { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = uploader.ToString(), - ItemId = request.CatalogId, - Listings = new List(), + Hq = marketBoardItemListing.IsHq, + SellerId = marketBoardItemListing.RetainerOwnerId.ToString(), + RetainerName = marketBoardItemListing.RetainerName, + RetainerId = marketBoardItemListing.RetainerId.ToString(), + CreatorId = marketBoardItemListing.ArtisanId.ToString(), + CreatorName = marketBoardItemListing.PlayerName, + OnMannequin = marketBoardItemListing.OnMannequin, + LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(), + PricePerUnit = marketBoardItemListing.PricePerUnit, + Quantity = marketBoardItemListing.ItemQuantity, + RetainerCity = marketBoardItemListing.RetainerCityId, + Materia = new List(), }; - foreach (var marketBoardItemListing in request.Listings) + foreach (var itemMateria in marketBoardItemListing.Materia) { - var universalisListing = new UniversalisItemListingsEntry + universalisListing.Materia.Add(new UniversalisItemMateria { - Hq = marketBoardItemListing.IsHq, - SellerId = marketBoardItemListing.RetainerOwnerId.ToString(), - RetainerName = marketBoardItemListing.RetainerName, - RetainerId = marketBoardItemListing.RetainerId.ToString(), - CreatorId = marketBoardItemListing.ArtisanId.ToString(), - CreatorName = marketBoardItemListing.PlayerName, - OnMannequin = marketBoardItemListing.OnMannequin, - LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(), - PricePerUnit = marketBoardItemListing.PricePerUnit, - Quantity = marketBoardItemListing.ItemQuantity, - RetainerCity = marketBoardItemListing.RetainerCityId, - Materia = new List(), - }; - - foreach (var itemMateria in marketBoardItemListing.Materia) - { - universalisListing.Materia.Add(new UniversalisItemMateria - { - MateriaId = itemMateria.MateriaId, - SlotId = itemMateria.Index, - }); - } - - listingsUploadObject.Listings.Add(universalisListing); - } - - var listingPath = "/upload"; - var listingUpload = JsonConvert.SerializeObject(listingsUploadObject); - Log.Verbose($"{listingPath}: {listingUpload}"); - await Util.HttpClient.PostAsync($"{ApiBase}{listingPath}/{ApiKey}", new StringContent(listingUpload, Encoding.UTF8, "application/json")); - - // ==================================================================================== - - var historyUploadObject = new UniversalisHistoryUploadRequest - { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = uploader.ToString(), - ItemId = request.CatalogId, - Entries = new List(), - }; - - foreach (var marketBoardHistoryListing in request.History) - { - historyUploadObject.Entries.Add(new UniversalisHistoryEntry - { - BuyerName = marketBoardHistoryListing.BuyerName, - Hq = marketBoardHistoryListing.IsHq, - OnMannequin = marketBoardHistoryListing.OnMannequin, - PricePerUnit = marketBoardHistoryListing.SalePrice, - Quantity = marketBoardHistoryListing.Quantity, - Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(), + MateriaId = itemMateria.MateriaId, + SlotId = itemMateria.Index, }); } - var historyPath = "/upload"; - var historyUpload = JsonConvert.SerializeObject(historyUploadObject); - Log.Verbose($"{historyPath}: {historyUpload}"); - await Util.HttpClient.PostAsync($"{ApiBase}{historyPath}/{ApiKey}", new StringContent(historyUpload, Encoding.UTF8, "application/json")); - - // ==================================================================================== - - Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId); + listingsUploadObject.Listings.Add(universalisListing); } - /// - public async Task UploadTax(MarketTaxRates taxRates) + var listingPath = "/upload"; + var listingUpload = JsonConvert.SerializeObject(listingsUploadObject); + Log.Verbose($"{listingPath}: {listingUpload}"); + await Util.HttpClient.PostAsync($"{ApiBase}{listingPath}/{ApiKey}", new StringContent(listingUpload, Encoding.UTF8, "application/json")); + + // ==================================================================================== + + var historyUploadObject = new UniversalisHistoryUploadRequest { - var clientState = Service.Get(); + WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = uploader.ToString(), + ItemId = request.CatalogId, + Entries = new List(), + }; - // ==================================================================================== - - var taxUploadObject = new UniversalisTaxUploadRequest - { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = clientState.LocalContentId.ToString(), - TaxData = new UniversalisTaxData - { - LimsaLominsa = taxRates.LimsaLominsaTax, - Gridania = taxRates.GridaniaTax, - Uldah = taxRates.UldahTax, - Ishgard = taxRates.IshgardTax, - Kugane = taxRates.KuganeTax, - Crystarium = taxRates.CrystariumTax, - }, - }; - - var taxPath = "/upload"; - var taxUpload = JsonConvert.SerializeObject(taxUploadObject); - Log.Verbose($"{taxPath}: {taxUpload}"); - - await Util.HttpClient.PostAsync($"{ApiBase}{taxPath}/{ApiKey}", new StringContent(taxUpload, Encoding.UTF8, "application/json")); - - // ==================================================================================== - - Log.Verbose("Universalis tax upload completed."); - } - - /// - /// - /// It may seem backwards that an upload only performs a delete request, however this is not trying - /// to track the available listings, that is done via the listings packet. All this does is remove - /// a listing, or delete it, when a purchase has been made. - /// - public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) + foreach (var marketBoardHistoryListing in request.History) { - var clientState = Service.Get(); - - var itemId = purchaseHandler.CatalogId; - var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0; - - // ==================================================================================== - - var deleteListingObject = new UniversalisItemListingDeleteRequest + historyUploadObject.Entries.Add(new UniversalisHistoryEntry { - PricePerUnit = purchaseHandler.PricePerUnit, - Quantity = purchaseHandler.ItemQuantity, - ListingId = purchaseHandler.ListingId.ToString(), - RetainerId = purchaseHandler.RetainerId.ToString(), - UploaderId = clientState.LocalContentId.ToString(), - }; - - var deletePath = $"/api/{worldId}/{itemId}/delete"; - var deleteListing = JsonConvert.SerializeObject(deleteListingObject); - Log.Verbose($"{deletePath}: {deleteListing}"); - - var content = new StringContent(deleteListing, Encoding.UTF8, "application/json"); - var message = new HttpRequestMessage(HttpMethod.Post, $"{ApiBase}{deletePath}"); - message.Headers.Add("Authorization", ApiKey); - message.Content = content; - - await Util.HttpClient.SendAsync(message); - - // ==================================================================================== - - Log.Verbose("Universalis purchase upload completed."); + BuyerName = marketBoardHistoryListing.BuyerName, + Hq = marketBoardHistoryListing.IsHq, + OnMannequin = marketBoardHistoryListing.OnMannequin, + PricePerUnit = marketBoardHistoryListing.SalePrice, + Quantity = marketBoardHistoryListing.Quantity, + Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(), + }); } + + var historyPath = "/upload"; + var historyUpload = JsonConvert.SerializeObject(historyUploadObject); + Log.Verbose($"{historyPath}: {historyUpload}"); + await Util.HttpClient.PostAsync($"{ApiBase}{historyPath}/{ApiKey}", new StringContent(historyUpload, Encoding.UTF8, "application/json")); + + // ==================================================================================== + + Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId); + } + + /// + public async Task UploadTax(MarketTaxRates taxRates) + { + var clientState = Service.Get(); + + // ==================================================================================== + + var taxUploadObject = new UniversalisTaxUploadRequest + { + WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = clientState.LocalContentId.ToString(), + TaxData = new UniversalisTaxData + { + LimsaLominsa = taxRates.LimsaLominsaTax, + Gridania = taxRates.GridaniaTax, + Uldah = taxRates.UldahTax, + Ishgard = taxRates.IshgardTax, + Kugane = taxRates.KuganeTax, + Crystarium = taxRates.CrystariumTax, + }, + }; + + var taxPath = "/upload"; + var taxUpload = JsonConvert.SerializeObject(taxUploadObject); + Log.Verbose($"{taxPath}: {taxUpload}"); + + await Util.HttpClient.PostAsync($"{ApiBase}{taxPath}/{ApiKey}", new StringContent(taxUpload, Encoding.UTF8, "application/json")); + + // ==================================================================================== + + Log.Verbose("Universalis tax upload completed."); + } + + /// + /// + /// It may seem backwards that an upload only performs a delete request, however this is not trying + /// to track the available listings, that is done via the listings packet. All this does is remove + /// a listing, or delete it, when a purchase has been made. + /// + public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) + { + var clientState = Service.Get(); + + var itemId = purchaseHandler.CatalogId; + var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0; + + // ==================================================================================== + + var deleteListingObject = new UniversalisItemListingDeleteRequest + { + PricePerUnit = purchaseHandler.PricePerUnit, + Quantity = purchaseHandler.ItemQuantity, + ListingId = purchaseHandler.ListingId.ToString(), + RetainerId = purchaseHandler.RetainerId.ToString(), + UploaderId = clientState.LocalContentId.ToString(), + }; + + var deletePath = $"/api/{worldId}/{itemId}/delete"; + var deleteListing = JsonConvert.SerializeObject(deleteListingObject); + Log.Verbose($"{deletePath}: {deleteListing}"); + + var content = new StringContent(deleteListing, Encoding.UTF8, "application/json"); + var message = new HttpRequestMessage(HttpMethod.Post, $"{ApiBase}{deletePath}"); + message.Headers.Add("Authorization", ApiKey); + message.Content = content; + + await Util.HttpClient.SendAsync(message); + + // ==================================================================================== + + Log.Verbose("Universalis purchase upload completed."); } } diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 4d7b7145e..352c0462a 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -16,278 +16,277 @@ using Dalamud.Utility; using Lumina.Excel.GeneratedSheets; using Serilog; -namespace Dalamud.Game.Network.Internal +namespace Dalamud.Game.Network.Internal; + +/// +/// This class handles network notifications and uploading market board data. +/// +internal class NetworkHandlers { + private readonly List marketBoardRequests = new(); + + private readonly bool optOutMbUploads; + private readonly IMarketBoardUploader uploader; + + private MarketBoardPurchaseHandler marketBoardPurchaseHandler; + /// - /// This class handles network notifications and uploading market board data. + /// Initializes a new instance of the class. /// - internal class NetworkHandlers + public NetworkHandlers() { - private readonly List marketBoardRequests = new(); + this.optOutMbUploads = Service.Get().OptOutMbCollection; - private readonly bool optOutMbUploads; - private readonly IMarketBoardUploader uploader; + this.uploader = new UniversalisMarketBoardUploader(); - private MarketBoardPurchaseHandler marketBoardPurchaseHandler; + Service.Get().NetworkMessage += this.OnNetworkMessage; + } - /// - /// Initializes a new instance of the class. - /// - public NetworkHandlers() + /// + /// Event which gets fired when a duty is ready. + /// + public event EventHandler CfPop; + + private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) + { + var dataManager = Service.GetNullable(); + + if (dataManager?.IsDataReady == false) + return; + + var configuration = Service.Get(); + + if (direction == NetworkMessageDirection.ZoneUp) { - this.optOutMbUploads = Service.Get().OptOutMbCollection; - - this.uploader = new UniversalisMarketBoardUploader(); - - Service.Get().NetworkMessage += this.OnNetworkMessage; - } - - /// - /// Event which gets fired when a duty is ready. - /// - public event EventHandler CfPop; - - private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) - { - var dataManager = Service.GetNullable(); - - if (dataManager?.IsDataReady == false) - return; - - var configuration = Service.Get(); - - if (direction == NetworkMessageDirection.ZoneUp) - { - if (!this.optOutMbUploads) - { - if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"]) - { - this.marketBoardPurchaseHandler = MarketBoardPurchaseHandler.Read(dataPtr); - return; - } - } - - return; - } - - if (opCode == dataManager.ServerOpCodes["CfNotifyPop"]) - { - this.HandleCfPop(dataPtr); - return; - } - if (!this.optOutMbUploads) { - if (opCode == dataManager.ServerOpCodes["MarketBoardItemRequestStart"]) + if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"]) { - var data = MarketBoardItemRequest.Read(dataPtr); - this.marketBoardRequests.Add(data); - - Log.Verbose($"NEW MB REQUEST START: item#{data.CatalogId} amount#{data.AmountToArrive}"); - return; - } - - if (opCode == dataManager.ServerOpCodes["MarketBoardOfferings"]) - { - var listing = MarketBoardCurrentOfferings.Read(dataPtr); - - var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone); - - if (request == default) - { - Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}"); - return; - } - - if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) - { - Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}"); - return; - } - - if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) - { - Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}"); - return; - } - - if (request.ListingsRequestId == -1 && request.Listings.Count > 0) - { - Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); - return; - } - - if (request.ListingsRequestId == -1) - { - request.ListingsRequestId = listing.RequestId; - Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}"); - } - - request.Listings.AddRange(listing.ItemListings); - - Log.Verbose( - "Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}", - listing.ItemListings.Count, - request.ListingsRequestId, - request.Listings.Count, - request.AmountToArrive, - request.CatalogId); - - if (request.IsDone) - { - Log.Verbose( - "Market Board request finished, starting upload: request#{0} item#{1} amount#{2}", - request.ListingsRequestId, - request.CatalogId, - request.AmountToArrive); - - Task.Run(() => this.uploader.Upload(request)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board offerings data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - } - - return; - } - - if (opCode == dataManager.ServerOpCodes["MarketBoardHistory"]) - { - var listing = MarketBoardHistory.Read(dataPtr); - - var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId); - - if (request == default) - { - Log.Error($"Market Board data arrived without a corresponding request: item#{listing.CatalogId}"); - return; - } - - if (request.ListingsRequestId != -1) - { - Log.Error($"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); - return; - } - - request.History.AddRange(listing.HistoryListings); - - Log.Verbose("Added history for item#{0}", listing.CatalogId); - - if (request.AmountToArrive == 0) - { - Log.Verbose("Request had 0 amount, uploading now"); - - Task.Run(() => this.uploader.Upload(request)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board history data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - } - } - - if (opCode == dataManager.ServerOpCodes["MarketTaxRates"]) - { - var category = (uint)Marshal.ReadInt32(dataPtr); - - // Result dialog packet does not contain market tax rates - if (category != 720905) - { - return; - } - - var taxes = MarketTaxRates.Read(dataPtr); - - Log.Verbose( - "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}", - taxes.LimsaLominsaTax, - taxes.GridaniaTax, - taxes.UldahTax, - taxes.IshgardTax, - taxes.KuganeTax, - taxes.CrystariumTax); - - Task.Run(() => this.uploader.UploadTax(taxes)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board tax data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - - return; - } - - if (opCode == dataManager.ServerOpCodes["MarketBoardPurchase"]) - { - if (this.marketBoardPurchaseHandler == null) - return; - - var purchase = MarketBoardPurchase.Read(dataPtr); - - var sameQty = purchase.ItemQuantity == this.marketBoardPurchaseHandler.ItemQuantity; - var itemMatch = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId; - var itemMatchHq = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId + 1_000_000; - - // Transaction succeeded - if (sameQty && (itemMatch || itemMatchHq)) - { - Log.Verbose($"Bought {purchase.ItemQuantity}x {this.marketBoardPurchaseHandler.CatalogId} for {this.marketBoardPurchaseHandler.PricePerUnit * purchase.ItemQuantity} gils, listing id is {this.marketBoardPurchaseHandler.ListingId}"); - - var handler = this.marketBoardPurchaseHandler; // Capture the object so that we don't pass in a null one when the task starts. - - Task.Run(() => this.uploader.UploadPurchase(handler)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board purchase data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - } - - this.marketBoardPurchaseHandler = null; + this.marketBoardPurchaseHandler = MarketBoardPurchaseHandler.Read(dataPtr); return; } } + + return; } - private unsafe void HandleCfPop(IntPtr dataPtr) + if (opCode == dataManager.ServerOpCodes["CfNotifyPop"]) { - var dataManager = Service.Get(); - var configuration = Service.Get(); + this.HandleCfPop(dataPtr); + return; + } - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64); - using var reader = new BinaryReader(stream); - - var notifyType = reader.ReadByte(); - stream.Position += 0x13; - var conditionId = reader.ReadUInt16(); - - if (notifyType != 3) - return; - - var cfConditionSheet = dataManager.GetExcelSheet()!; - var cfCondition = cfConditionSheet.GetRow(conditionId); - - if (cfCondition == null) + if (!this.optOutMbUploads) + { + if (opCode == dataManager.ServerOpCodes["MarketBoardItemRequestStart"]) { - Log.Error($"CFC key {conditionId} not in Lumina data."); + var data = MarketBoardItemRequest.Read(dataPtr); + this.marketBoardRequests.Add(data); + + Log.Verbose($"NEW MB REQUEST START: item#{data.CatalogId} amount#{data.AmountToArrive}"); return; } - var cfcName = cfCondition.Name.ToString(); - if (cfcName.IsNullOrEmpty()) + if (opCode == dataManager.ServerOpCodes["MarketBoardOfferings"]) { - cfcName = "Duty Roulette"; - cfCondition.Image = 112324; - } + var listing = MarketBoardCurrentOfferings.Read(dataPtr); - // Flash window - if (configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) - { - var flashInfo = new NativeFunctions.FlashWindowInfo - { - Size = (uint)Marshal.SizeOf(), - Count = uint.MaxValue, - Timeout = 0, - Flags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG, - Hwnd = Process.GetCurrentProcess().MainWindowHandle, - }; - NativeFunctions.FlashWindowEx(ref flashInfo); - } + var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone); - Task.Run(() => - { - if (configuration.DutyFinderChatMessage) + if (request == default) { - Service.Get().Print($"Duty pop: {cfcName}"); + Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}"); + return; } - this.CfPop?.Invoke(this, cfCondition); - }).ContinueWith((task) => Log.Error(task.Exception, "CfPop.Invoke failed."), TaskContinuationOptions.OnlyOnFaulted); + if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) + { + Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}"); + return; + } + + if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) + { + Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}"); + return; + } + + if (request.ListingsRequestId == -1 && request.Listings.Count > 0) + { + Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); + return; + } + + if (request.ListingsRequestId == -1) + { + request.ListingsRequestId = listing.RequestId; + Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}"); + } + + request.Listings.AddRange(listing.ItemListings); + + Log.Verbose( + "Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}", + listing.ItemListings.Count, + request.ListingsRequestId, + request.Listings.Count, + request.AmountToArrive, + request.CatalogId); + + if (request.IsDone) + { + Log.Verbose( + "Market Board request finished, starting upload: request#{0} item#{1} amount#{2}", + request.ListingsRequestId, + request.CatalogId, + request.AmountToArrive); + + Task.Run(() => this.uploader.Upload(request)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board offerings data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + } + + return; + } + + if (opCode == dataManager.ServerOpCodes["MarketBoardHistory"]) + { + var listing = MarketBoardHistory.Read(dataPtr); + + var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId); + + if (request == default) + { + Log.Error($"Market Board data arrived without a corresponding request: item#{listing.CatalogId}"); + return; + } + + if (request.ListingsRequestId != -1) + { + Log.Error($"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); + return; + } + + request.History.AddRange(listing.HistoryListings); + + Log.Verbose("Added history for item#{0}", listing.CatalogId); + + if (request.AmountToArrive == 0) + { + Log.Verbose("Request had 0 amount, uploading now"); + + Task.Run(() => this.uploader.Upload(request)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board history data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + } + } + + if (opCode == dataManager.ServerOpCodes["MarketTaxRates"]) + { + var category = (uint)Marshal.ReadInt32(dataPtr); + + // Result dialog packet does not contain market tax rates + if (category != 720905) + { + return; + } + + var taxes = MarketTaxRates.Read(dataPtr); + + Log.Verbose( + "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}", + taxes.LimsaLominsaTax, + taxes.GridaniaTax, + taxes.UldahTax, + taxes.IshgardTax, + taxes.KuganeTax, + taxes.CrystariumTax); + + Task.Run(() => this.uploader.UploadTax(taxes)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board tax data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + + return; + } + + if (opCode == dataManager.ServerOpCodes["MarketBoardPurchase"]) + { + if (this.marketBoardPurchaseHandler == null) + return; + + var purchase = MarketBoardPurchase.Read(dataPtr); + + var sameQty = purchase.ItemQuantity == this.marketBoardPurchaseHandler.ItemQuantity; + var itemMatch = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId; + var itemMatchHq = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId + 1_000_000; + + // Transaction succeeded + if (sameQty && (itemMatch || itemMatchHq)) + { + Log.Verbose($"Bought {purchase.ItemQuantity}x {this.marketBoardPurchaseHandler.CatalogId} for {this.marketBoardPurchaseHandler.PricePerUnit * purchase.ItemQuantity} gils, listing id is {this.marketBoardPurchaseHandler.ListingId}"); + + var handler = this.marketBoardPurchaseHandler; // Capture the object so that we don't pass in a null one when the task starts. + + Task.Run(() => this.uploader.UploadPurchase(handler)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board purchase data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + } + + this.marketBoardPurchaseHandler = null; + return; + } } } + + private unsafe void HandleCfPop(IntPtr dataPtr) + { + var dataManager = Service.Get(); + var configuration = Service.Get(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64); + using var reader = new BinaryReader(stream); + + var notifyType = reader.ReadByte(); + stream.Position += 0x13; + var conditionId = reader.ReadUInt16(); + + if (notifyType != 3) + return; + + var cfConditionSheet = dataManager.GetExcelSheet()!; + var cfCondition = cfConditionSheet.GetRow(conditionId); + + if (cfCondition == null) + { + Log.Error($"CFC key {conditionId} not in Lumina data."); + return; + } + + var cfcName = cfCondition.Name.ToString(); + if (cfcName.IsNullOrEmpty()) + { + cfcName = "Duty Roulette"; + cfCondition.Image = 112324; + } + + // Flash window + if (configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) + { + var flashInfo = new NativeFunctions.FlashWindowInfo + { + Size = (uint)Marshal.SizeOf(), + Count = uint.MaxValue, + Timeout = 0, + Flags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG, + Hwnd = Process.GetCurrentProcess().MainWindowHandle, + }; + NativeFunctions.FlashWindowEx(ref flashInfo); + } + + Task.Run(() => + { + if (configuration.DutyFinderChatMessage) + { + Service.Get().Print($"Duty pop: {cfcName}"); + } + + this.CfPop?.Invoke(this, cfCondition); + }).ContinueWith((task) => Log.Error(task.Exception, "CfPop.Invoke failed."), TaskContinuationOptions.OnlyOnFaulted); + } } diff --git a/Dalamud/Game/Network/Internal/WinSockHandlers.cs b/Dalamud/Game/Network/Internal/WinSockHandlers.cs index 82f2f0d91..792e80d8e 100644 --- a/Dalamud/Game/Network/Internal/WinSockHandlers.cs +++ b/Dalamud/Game/Network/Internal/WinSockHandlers.cs @@ -5,58 +5,57 @@ using System.Runtime.InteropServices; using Dalamud.Hooking; using Dalamud.Hooking.Internal; -namespace Dalamud.Game.Network.Internal +namespace Dalamud.Game.Network.Internal; + +/// +/// This class enables TCP optimizations in the game socket for better performance. +/// +internal sealed class WinSockHandlers : IDisposable { + private Hook ws2SocketHook; + /// - /// This class enables TCP optimizations in the game socket for better performance. + /// Initializes a new instance of the class. /// - internal sealed class WinSockHandlers : IDisposable + public WinSockHandlers() { - private Hook ws2SocketHook; + this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true); + this.ws2SocketHook?.Enable(); + } - /// - /// Initializes a new instance of the class. - /// - public WinSockHandlers() + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate IntPtr SocketDelegate(int af, int type, int protocol); + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.ws2SocketHook?.Dispose(); + } + + private IntPtr OnSocket(int af, int type, int protocol) + { + var socket = this.ws2SocketHook.Original(af, type, protocol); + + // IPPROTO_TCP + if (type == 1) { - this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true); - this.ws2SocketHook?.Enable(); - } - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate IntPtr SocketDelegate(int af, int type, int protocol); - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.ws2SocketHook?.Dispose(); - } - - private IntPtr OnSocket(int af, int type, int protocol) - { - var socket = this.ws2SocketHook.Original(af, type, protocol); - - // IPPROTO_TCP - if (type == 1) + // INVALID_SOCKET + if (socket != new IntPtr(-1)) { - // INVALID_SOCKET - if (socket != new IntPtr(-1)) - { - // In case you're not aware of it: (albeit you should) - // https://linux.die.net/man/7/tcp - // https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf - var value = new IntPtr(1); - _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4); + // In case you're not aware of it: (albeit you should) + // https://linux.die.net/man/7/tcp + // https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf + var value = new IntPtr(1); + _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4); - // Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards. - value = new IntPtr(1); - _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4); - } + // Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards. + value = new IntPtr(1); + _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4); } - - return socket; } + + return socket; } } diff --git a/Dalamud/Game/Network/NetworkMessageDirection.cs b/Dalamud/Game/Network/NetworkMessageDirection.cs index 79d9d2df2..87cce5173 100644 --- a/Dalamud/Game/Network/NetworkMessageDirection.cs +++ b/Dalamud/Game/Network/NetworkMessageDirection.cs @@ -1,18 +1,17 @@ -namespace Dalamud.Game.Network +namespace Dalamud.Game.Network; + +/// +/// This represents the direction of a network message. +/// +public enum NetworkMessageDirection { /// - /// This represents the direction of a network message. + /// A zone down message. /// - public enum NetworkMessageDirection - { - /// - /// A zone down message. - /// - ZoneDown, + ZoneDown, - /// - /// A zone up message. - /// - ZoneUp, - } + /// + /// A zone up message. + /// + ZoneUp, } diff --git a/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs b/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs index 364a21454..4d57a0fbd 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs @@ -3,225 +3,224 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Dalamud.Game.Network.Structures +namespace Dalamud.Game.Network.Structures; + +/// +/// This class represents the current market board offerings from a game network packet. +/// +public class MarketBoardCurrentOfferings { - /// - /// This class represents the current market board offerings from a game network packet. - /// - public class MarketBoardCurrentOfferings + private MarketBoardCurrentOfferings() { - private MarketBoardCurrentOfferings() + } + + /// + /// Gets the list of individual item listings. + /// + public List ItemListings { get; } = new(); + + /// + /// Gets the listing end index. + /// + public int ListingIndexEnd { get; internal set; } + + /// + /// Gets the listing start index. + /// + public int ListingIndexStart { get; internal set; } + + /// + /// Gets the request ID. + /// + public int RequestId { get; internal set; } + + /// + /// Read a object from memory. + /// + /// Address to read. + /// A new object. + public static unsafe MarketBoardCurrentOfferings Read(IntPtr dataPtr) + { + var output = new MarketBoardCurrentOfferings(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + + for (var i = 0; i < 10; i++) + { + var listingEntry = new MarketBoardItemListing(); + + listingEntry.ListingId = reader.ReadUInt64(); + listingEntry.RetainerId = reader.ReadUInt64(); + listingEntry.RetainerOwnerId = reader.ReadUInt64(); + listingEntry.ArtisanId = reader.ReadUInt64(); + listingEntry.PricePerUnit = reader.ReadUInt32(); + listingEntry.TotalTax = reader.ReadUInt32(); + listingEntry.ItemQuantity = reader.ReadUInt32(); + listingEntry.CatalogId = reader.ReadUInt32(); + listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime; + + reader.ReadUInt16(); // container + reader.ReadUInt32(); // slot + reader.ReadUInt16(); // durability + reader.ReadUInt16(); // spiritbond + + for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++) + { + var materiaVal = reader.ReadUInt16(); + var materiaEntry = new MarketBoardItemListing.ItemMateria() + { + MateriaId = (materiaVal & 0xFF0) >> 4, + Index = materiaVal & 0xF, + }; + + if (materiaEntry.MateriaId != 0) + listingEntry.Materia.Add(materiaEntry); + } + + reader.ReadUInt16(); + reader.ReadUInt32(); + + listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); + listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); + listingEntry.IsHq = reader.ReadBoolean(); + listingEntry.MateriaCount = reader.ReadByte(); + listingEntry.OnMannequin = reader.ReadBoolean(); + listingEntry.RetainerCityId = reader.ReadByte(); + listingEntry.StainId = reader.ReadUInt16(); + + reader.ReadUInt16(); + reader.ReadUInt32(); + + if (listingEntry.CatalogId != 0) + output.ItemListings.Add(listingEntry); + } + + output.ListingIndexEnd = reader.ReadByte(); + output.ListingIndexStart = reader.ReadByte(); + output.RequestId = reader.ReadUInt16(); + + return output; + } + + /// + /// This class represents the current market board offering of a single item from the network packet. + /// + public class MarketBoardItemListing + { + /// + /// Initializes a new instance of the class. + /// + internal MarketBoardItemListing() { } /// - /// Gets the list of individual item listings. + /// Gets the artisan ID. /// - public List ItemListings { get; } = new(); + public ulong ArtisanId { get; internal set; } /// - /// Gets the listing end index. + /// Gets the catalog ID. /// - public int ListingIndexEnd { get; internal set; } + public uint CatalogId { get; internal set; } /// - /// Gets the listing start index. + /// Gets a value indicating whether the item is HQ. /// - public int ListingIndexStart { get; internal set; } + public bool IsHq { get; internal set; } /// - /// Gets the request ID. + /// Gets the item quantity. /// - public int RequestId { get; internal set; } + public uint ItemQuantity { get; internal set; } /// - /// Read a object from memory. + /// Gets the time this offering was last reviewed. /// - /// Address to read. - /// A new object. - public static unsafe MarketBoardCurrentOfferings Read(IntPtr dataPtr) - { - var output = new MarketBoardCurrentOfferings(); - - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); - - for (var i = 0; i < 10; i++) - { - var listingEntry = new MarketBoardItemListing(); - - listingEntry.ListingId = reader.ReadUInt64(); - listingEntry.RetainerId = reader.ReadUInt64(); - listingEntry.RetainerOwnerId = reader.ReadUInt64(); - listingEntry.ArtisanId = reader.ReadUInt64(); - listingEntry.PricePerUnit = reader.ReadUInt32(); - listingEntry.TotalTax = reader.ReadUInt32(); - listingEntry.ItemQuantity = reader.ReadUInt32(); - listingEntry.CatalogId = reader.ReadUInt32(); - listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime; - - reader.ReadUInt16(); // container - reader.ReadUInt32(); // slot - reader.ReadUInt16(); // durability - reader.ReadUInt16(); // spiritbond - - for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++) - { - var materiaVal = reader.ReadUInt16(); - var materiaEntry = new MarketBoardItemListing.ItemMateria() - { - MateriaId = (materiaVal & 0xFF0) >> 4, - Index = materiaVal & 0xF, - }; - - if (materiaEntry.MateriaId != 0) - listingEntry.Materia.Add(materiaEntry); - } - - reader.ReadUInt16(); - reader.ReadUInt32(); - - listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); - listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); - listingEntry.IsHq = reader.ReadBoolean(); - listingEntry.MateriaCount = reader.ReadByte(); - listingEntry.OnMannequin = reader.ReadBoolean(); - listingEntry.RetainerCityId = reader.ReadByte(); - listingEntry.StainId = reader.ReadUInt16(); - - reader.ReadUInt16(); - reader.ReadUInt32(); - - if (listingEntry.CatalogId != 0) - output.ItemListings.Add(listingEntry); - } - - output.ListingIndexEnd = reader.ReadByte(); - output.ListingIndexStart = reader.ReadByte(); - output.RequestId = reader.ReadUInt16(); - - return output; - } + public DateTime LastReviewTime { get; internal set; } /// - /// This class represents the current market board offering of a single item from the network packet. + /// Gets the listing ID. /// - public class MarketBoardItemListing + public ulong ListingId { get; internal set; } + + /// + /// Gets the list of materia attached to this item. + /// + public List Materia { get; } = new(); + + /// + /// Gets the amount of attached materia. + /// + public int MateriaCount { get; internal set; } + + /// + /// Gets a value indicating whether this item is on a mannequin. + /// + public bool OnMannequin { get; internal set; } + + /// + /// Gets the player name. + /// + public string PlayerName { get; internal set; } + + /// + /// Gets the price per unit. + /// + public uint PricePerUnit { get; internal set; } + + /// + /// Gets the city ID of the retainer selling the item. + /// + public int RetainerCityId { get; internal set; } + + /// + /// Gets the ID of the retainer selling the item. + /// + public ulong RetainerId { get; internal set; } + + /// + /// Gets the name of the retainer. + /// + public string RetainerName { get; internal set; } + + /// + /// Gets the ID of the retainer's owner. + /// + public ulong RetainerOwnerId { get; internal set; } + + /// + /// Gets the stain or applied dye of the item. + /// + public int StainId { get; internal set; } + + /// + /// Gets the total tax. + /// + public uint TotalTax { get; internal set; } + + /// + /// This represents the materia slotted to an . + /// + public class ItemMateria { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - internal MarketBoardItemListing() + internal ItemMateria() { } /// - /// Gets the artisan ID. + /// Gets the materia index. /// - public ulong ArtisanId { get; internal set; } + public int Index { get; internal set; } /// - /// Gets the catalog ID. + /// Gets the materia ID. /// - public uint CatalogId { get; internal set; } - - /// - /// Gets a value indicating whether the item is HQ. - /// - public bool IsHq { get; internal set; } - - /// - /// Gets the item quantity. - /// - public uint ItemQuantity { get; internal set; } - - /// - /// Gets the time this offering was last reviewed. - /// - public DateTime LastReviewTime { get; internal set; } - - /// - /// Gets the listing ID. - /// - public ulong ListingId { get; internal set; } - - /// - /// Gets the list of materia attached to this item. - /// - public List Materia { get; } = new(); - - /// - /// Gets the amount of attached materia. - /// - public int MateriaCount { get; internal set; } - - /// - /// Gets a value indicating whether this item is on a mannequin. - /// - public bool OnMannequin { get; internal set; } - - /// - /// Gets the player name. - /// - public string PlayerName { get; internal set; } - - /// - /// Gets the price per unit. - /// - public uint PricePerUnit { get; internal set; } - - /// - /// Gets the city ID of the retainer selling the item. - /// - public int RetainerCityId { get; internal set; } - - /// - /// Gets the ID of the retainer selling the item. - /// - public ulong RetainerId { get; internal set; } - - /// - /// Gets the name of the retainer. - /// - public string RetainerName { get; internal set; } - - /// - /// Gets the ID of the retainer's owner. - /// - public ulong RetainerOwnerId { get; internal set; } - - /// - /// Gets the stain or applied dye of the item. - /// - public int StainId { get; internal set; } - - /// - /// Gets the total tax. - /// - public uint TotalTax { get; internal set; } - - /// - /// This represents the materia slotted to an . - /// - public class ItemMateria - { - /// - /// Initializes a new instance of the class. - /// - internal ItemMateria() - { - } - - /// - /// Gets the materia index. - /// - public int Index { get; internal set; } - - /// - /// Gets the materia ID. - /// - public int MateriaId { get; internal set; } - } + public int MateriaId { get; internal set; } } } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardHistory.cs b/Dalamud/Game/Network/Structures/MarketBoardHistory.cs index d753b0498..69532afd6 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardHistory.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardHistory.cs @@ -3,121 +3,120 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Dalamud.Game.Network.Structures +namespace Dalamud.Game.Network.Structures; + +/// +/// This class represents the market board history from a game network packet. +/// +public class MarketBoardHistory { /// - /// This class represents the market board history from a game network packet. + /// Initializes a new instance of the class. /// - public class MarketBoardHistory + internal MarketBoardHistory() + { + } + + /// + /// Gets the catalog ID. + /// + public uint CatalogId { get; private set; } + + /// + /// Gets the second catalog ID. + /// + public uint CatalogId2 { get; private set; } + + /// + /// Gets the list of individual item history listings. + /// + public List HistoryListings { get; } = new(); + + /// + /// Read a object from memory. + /// + /// Address to read. + /// A new object. + public static unsafe MarketBoardHistory Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + + var output = new MarketBoardHistory(); + + output.CatalogId = reader.ReadUInt32(); + output.CatalogId2 = reader.ReadUInt32(); + + for (var i = 0; i < 20; i++) + { + var listingEntry = new MarketBoardHistoryListing + { + SalePrice = reader.ReadUInt32(), + PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime, + Quantity = reader.ReadUInt32(), + IsHq = reader.ReadBoolean(), + }; + + reader.ReadBoolean(); + + listingEntry.OnMannequin = reader.ReadBoolean(); + listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000'); + listingEntry.NextCatalogId = reader.ReadUInt32(); + + output.HistoryListings.Add(listingEntry); + + if (listingEntry.NextCatalogId == 0) + break; + } + + return output; + } + + /// + /// This class represents the market board history of a single item from the network packet. + /// + public class MarketBoardHistoryListing { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - internal MarketBoardHistory() + internal MarketBoardHistoryListing() { } /// - /// Gets the catalog ID. + /// Gets the buyer's name. /// - public uint CatalogId { get; private set; } + public string BuyerName { get; internal set; } /// - /// Gets the second catalog ID. + /// Gets the next entry's catalog ID. /// - public uint CatalogId2 { get; private set; } + public uint NextCatalogId { get; internal set; } /// - /// Gets the list of individual item history listings. + /// Gets a value indicating whether the item is HQ. /// - public List HistoryListings { get; } = new(); + public bool IsHq { get; internal set; } /// - /// Read a object from memory. + /// Gets a value indicating whether the item is on a mannequin. /// - /// Address to read. - /// A new object. - public static unsafe MarketBoardHistory Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); - - var output = new MarketBoardHistory(); - - output.CatalogId = reader.ReadUInt32(); - output.CatalogId2 = reader.ReadUInt32(); - - for (var i = 0; i < 20; i++) - { - var listingEntry = new MarketBoardHistoryListing - { - SalePrice = reader.ReadUInt32(), - PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime, - Quantity = reader.ReadUInt32(), - IsHq = reader.ReadBoolean(), - }; - - reader.ReadBoolean(); - - listingEntry.OnMannequin = reader.ReadBoolean(); - listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000'); - listingEntry.NextCatalogId = reader.ReadUInt32(); - - output.HistoryListings.Add(listingEntry); - - if (listingEntry.NextCatalogId == 0) - break; - } - - return output; - } + public bool OnMannequin { get; internal set; } /// - /// This class represents the market board history of a single item from the network packet. + /// Gets the time of purchase. /// - public class MarketBoardHistoryListing - { - /// - /// Initializes a new instance of the class. - /// - internal MarketBoardHistoryListing() - { - } + public DateTime PurchaseTime { get; internal set; } - /// - /// Gets the buyer's name. - /// - public string BuyerName { get; internal set; } + /// + /// Gets the quantity. + /// + public uint Quantity { get; internal set; } - /// - /// Gets the next entry's catalog ID. - /// - public uint NextCatalogId { get; internal set; } - - /// - /// Gets a value indicating whether the item is HQ. - /// - public bool IsHq { get; internal set; } - - /// - /// Gets a value indicating whether the item is on a mannequin. - /// - public bool OnMannequin { get; internal set; } - - /// - /// Gets the time of purchase. - /// - public DateTime PurchaseTime { get; internal set; } - - /// - /// Gets the quantity. - /// - public uint Quantity { get; internal set; } - - /// - /// Gets the sale price. - /// - public uint SalePrice { get; internal set; } - } + /// + /// Gets the sale price. + /// + public uint SalePrice { get; internal set; } } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs b/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs index 5fc0de786..e3530aea7 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs @@ -1,45 +1,44 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures +namespace Dalamud.Game.Network.Structures; + +/// +/// Represents market board purchase information. This message is received from the +/// server when a purchase is made at a market board. +/// +internal class MarketBoardPurchase { - /// - /// Represents market board purchase information. This message is received from the - /// server when a purchase is made at a market board. - /// - internal class MarketBoardPurchase + private MarketBoardPurchase() { - private MarketBoardPurchase() - { - } + } - /// - /// Gets the item ID of the item that was purchased. - /// - public uint CatalogId { get; private set; } + /// + /// Gets the item ID of the item that was purchased. + /// + public uint CatalogId { get; private set; } - /// - /// Gets the quantity of the item that was purchased. - /// - public uint ItemQuantity { get; private set; } + /// + /// Gets the quantity of the item that was purchased. + /// + public uint ItemQuantity { get; private set; } - /// - /// Reads market board purchase information from the struct at the provided pointer. - /// - /// A pointer to a struct containing market board purchase information from the server. - /// An object representing the data read. - public static unsafe MarketBoardPurchase Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Reads market board purchase information from the struct at the provided pointer. + /// + /// A pointer to a struct containing market board purchase information from the server. + /// An object representing the data read. + public static unsafe MarketBoardPurchase Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketBoardPurchase(); + var output = new MarketBoardPurchase(); - output.CatalogId = reader.ReadUInt32(); - stream.Position += 4; - output.ItemQuantity = reader.ReadUInt32(); + output.CatalogId = reader.ReadUInt32(); + stream.Position += 4; + output.ItemQuantity = reader.ReadUInt32(); - return output; - } + return output; } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs b/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs index 0557b0e0d..8d1717acb 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs @@ -1,62 +1,61 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures +namespace Dalamud.Game.Network.Structures; + +/// +/// Represents market board purchase information. This message is sent from the +/// client when a purchase is made at a market board. +/// +internal class MarketBoardPurchaseHandler { - /// - /// Represents market board purchase information. This message is sent from the - /// client when a purchase is made at a market board. - /// - internal class MarketBoardPurchaseHandler + private MarketBoardPurchaseHandler() { - private MarketBoardPurchaseHandler() - { - } + } - /// - /// Gets the object ID of the retainer associated with the sale. - /// - public ulong RetainerId { get; private set; } + /// + /// Gets the object ID of the retainer associated with the sale. + /// + public ulong RetainerId { get; private set; } - /// - /// Gets the object ID of the item listing. - /// - public ulong ListingId { get; private set; } + /// + /// Gets the object ID of the item listing. + /// + public ulong ListingId { get; private set; } - /// - /// Gets the item ID of the item that was purchased. - /// - public uint CatalogId { get; private set; } + /// + /// Gets the item ID of the item that was purchased. + /// + public uint CatalogId { get; private set; } - /// - /// Gets the quantity of the item that was purchased. - /// - public uint ItemQuantity { get; private set; } + /// + /// Gets the quantity of the item that was purchased. + /// + public uint ItemQuantity { get; private set; } - /// - /// Gets the unit price of the item. - /// - public uint PricePerUnit { get; private set; } + /// + /// Gets the unit price of the item. + /// + public uint PricePerUnit { get; private set; } - /// - /// Reads market board purchase information from the struct at the provided pointer. - /// - /// A pointer to a struct containing market board purchase information from the client. - /// An object representing the data read. - public static unsafe MarketBoardPurchaseHandler Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Reads market board purchase information from the struct at the provided pointer. + /// + /// A pointer to a struct containing market board purchase information from the client. + /// An object representing the data read. + public static unsafe MarketBoardPurchaseHandler Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketBoardPurchaseHandler(); + var output = new MarketBoardPurchaseHandler(); - output.RetainerId = reader.ReadUInt64(); - output.ListingId = reader.ReadUInt64(); - output.CatalogId = reader.ReadUInt32(); - output.ItemQuantity = reader.ReadUInt32(); - output.PricePerUnit = reader.ReadUInt32(); + output.RetainerId = reader.ReadUInt64(); + output.ListingId = reader.ReadUInt64(); + output.CatalogId = reader.ReadUInt32(); + output.ItemQuantity = reader.ReadUInt32(); + output.PricePerUnit = reader.ReadUInt32(); - return output; - } + return output; } } diff --git a/Dalamud/Game/Network/Structures/MarketTaxRates.cs b/Dalamud/Game/Network/Structures/MarketTaxRates.cs index 4fb6a2b13..654bdd1a3 100644 --- a/Dalamud/Game/Network/Structures/MarketTaxRates.cs +++ b/Dalamud/Game/Network/Structures/MarketTaxRates.cs @@ -1,68 +1,67 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures +namespace Dalamud.Game.Network.Structures; + +/// +/// This class represents the market tax rates from a game network packet. +/// +public class MarketTaxRates { - /// - /// This class represents the market tax rates from a game network packet. - /// - public class MarketTaxRates + private MarketTaxRates() { - private MarketTaxRates() - { - } + } - /// - /// Gets the tax rate in Limsa Lominsa. - /// - public uint LimsaLominsaTax { get; private set; } + /// + /// Gets the tax rate in Limsa Lominsa. + /// + public uint LimsaLominsaTax { get; private set; } - /// - /// Gets the tax rate in Gridania. - /// - public uint GridaniaTax { get; private set; } + /// + /// Gets the tax rate in Gridania. + /// + public uint GridaniaTax { get; private set; } - /// - /// Gets the tax rate in Ul'dah. - /// - public uint UldahTax { get; private set; } + /// + /// Gets the tax rate in Ul'dah. + /// + public uint UldahTax { get; private set; } - /// - /// Gets the tax rate in Ishgard. - /// - public uint IshgardTax { get; private set; } + /// + /// Gets the tax rate in Ishgard. + /// + public uint IshgardTax { get; private set; } - /// - /// Gets the tax rate in Kugane. - /// - public uint KuganeTax { get; private set; } + /// + /// Gets the tax rate in Kugane. + /// + public uint KuganeTax { get; private set; } - /// - /// Gets the tax rate in the Crystarium. - /// - public uint CrystariumTax { get; private set; } + /// + /// Gets the tax rate in the Crystarium. + /// + public uint CrystariumTax { get; private set; } - /// - /// Read a object from memory. - /// - /// Address to read. - /// A new object. - public static unsafe MarketTaxRates Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Read a object from memory. + /// + /// Address to read. + /// A new object. + public static unsafe MarketTaxRates Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketTaxRates(); + var output = new MarketTaxRates(); - stream.Position += 8; - output.LimsaLominsaTax = reader.ReadUInt32(); - output.GridaniaTax = reader.ReadUInt32(); - output.UldahTax = reader.ReadUInt32(); - output.IshgardTax = reader.ReadUInt32(); - output.KuganeTax = reader.ReadUInt32(); - output.CrystariumTax = reader.ReadUInt32(); + stream.Position += 8; + output.LimsaLominsaTax = reader.ReadUInt32(); + output.GridaniaTax = reader.ReadUInt32(); + output.UldahTax = reader.ReadUInt32(); + output.IshgardTax = reader.ReadUInt32(); + output.KuganeTax = reader.ReadUInt32(); + output.CrystariumTax = reader.ReadUInt32(); - return output; - } + return output; } } diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index e35b17cc4..70a09c5b1 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -9,475 +9,474 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game +namespace Dalamud.Game; + +/// +/// A SigScanner facilitates searching for memory signatures in a given ProcessModule. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +public sealed class SigScanner : IDisposable { + private IntPtr moduleCopyPtr; + private long moduleCopyOffset; + /// - /// A SigScanner facilitates searching for memory signatures in a given ProcessModule. + /// Initializes a new instance of the class using the main module of the current process. /// - [PluginInterface] - [InterfaceVersion("1.0")] - public sealed class SigScanner : IDisposable + /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. + public SigScanner(bool doCopy = false) + : this(Process.GetCurrentProcess().MainModule!, doCopy) { - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; + } - /// - /// Initializes a new instance of the class using the main module of the current process. - /// - /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. - public SigScanner(bool doCopy = false) - : this(Process.GetCurrentProcess().MainModule!, doCopy) + /// + /// Initializes a new instance of the class. + /// + /// The ProcessModule to be used for scanning. + /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. + public SigScanner(ProcessModule module, bool doCopy = false) + { + this.Module = module; + this.Is32BitProcess = !Environment.Is64BitProcess; + this.IsCopy = doCopy; + + // 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}"); + } + + /// + /// Gets a value indicating whether or not the search on this module is performed on a copy. + /// + public bool IsCopy { get; } + + /// + /// Gets a value indicating whether or not the ProcessModule is 32-bit. + /// + public bool Is32BitProcess { get; } + + /// + /// Gets the base address of the search area. When copied, this will be the address of the copy. + /// + public IntPtr SearchBase => this.IsCopy ? this.moduleCopyPtr : this.Module.BaseAddress; + + /// + /// Gets the base address of the .text section search area. + /// + public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset); + + /// + /// Gets the offset of the .text section from the base of the module. + /// + public long TextSectionOffset { get; private set; } + + /// + /// Gets the size of the text section. + /// + public int TextSectionSize { get; private set; } + + /// + /// Gets the base address of the .data section search area. + /// + public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset); + + /// + /// Gets the offset of the .data section from the base of the module. + /// + public long DataSectionOffset { get; private set; } + + /// + /// Gets the size of the .data section. + /// + public int DataSectionSize { get; private set; } + + /// + /// Gets the base address of the .rdata section search area. + /// + public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset); + + /// + /// Gets the offset of the .rdata section from the base of the module. + /// + public long RDataSectionOffset { get; private set; } + + /// + /// Gets the size of the .rdata section. + /// + public int RDataSectionSize { get; private set; } + + /// + /// Gets the ProcessModule on which the search is performed. + /// + public ProcessModule Module { get; } + + private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; + + /// + /// Scan memory for a signature. + /// + /// The base address to scan from. + /// The amount of bytes to scan. + /// The signature to search for. + /// The found offset. + public static IntPtr Scan(IntPtr baseAddress, int size, string signature) + { + var (needle, mask) = ParseSignature(signature); + var index = IndexOf(baseAddress, size, needle, mask); + if (index < 0) + throw new KeyNotFoundException($"Can't find a signature of {signature}"); + return baseAddress + index; + } + + /// + /// Try scanning memory for a signature. + /// + /// The base address to scan from. + /// The amount of bytes to scan. + /// The signature to search for. + /// The offset, if found. + /// true if the signature was found. + public static bool TryScan(IntPtr baseAddress, int size, string signature, out IntPtr result) + { + try { + result = Scan(baseAddress, size, signature); + return true; } - - /// - /// Initializes a new instance of the class. - /// - /// The ProcessModule to be used for scanning. - /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. - public SigScanner(ProcessModule module, bool doCopy = false) + catch (KeyNotFoundException) { - this.Module = module; - this.Is32BitProcess = !Environment.Is64BitProcess; - this.IsCopy = doCopy; - - // 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}"); - } - - /// - /// Gets a value indicating whether or not the search on this module is performed on a copy. - /// - public bool IsCopy { get; } - - /// - /// Gets a value indicating whether or not the ProcessModule is 32-bit. - /// - public bool Is32BitProcess { get; } - - /// - /// Gets the base address of the search area. When copied, this will be the address of the copy. - /// - public IntPtr SearchBase => this.IsCopy ? this.moduleCopyPtr : this.Module.BaseAddress; - - /// - /// Gets the base address of the .text section search area. - /// - public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset); - - /// - /// Gets the offset of the .text section from the base of the module. - /// - public long TextSectionOffset { get; private set; } - - /// - /// Gets the size of the text section. - /// - public int TextSectionSize { get; private set; } - - /// - /// Gets the base address of the .data section search area. - /// - public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset); - - /// - /// Gets the offset of the .data section from the base of the module. - /// - public long DataSectionOffset { get; private set; } - - /// - /// Gets the size of the .data section. - /// - public int DataSectionSize { get; private set; } - - /// - /// Gets the base address of the .rdata section search area. - /// - public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset); - - /// - /// Gets the offset of the .rdata section from the base of the module. - /// - public long RDataSectionOffset { get; private set; } - - /// - /// Gets the size of the .rdata section. - /// - public int RDataSectionSize { get; private set; } - - /// - /// Gets the ProcessModule on which the search is performed. - /// - public ProcessModule Module { get; } - - private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; - - /// - /// Scan memory for a signature. - /// - /// The base address to scan from. - /// The amount of bytes to scan. - /// The signature to search for. - /// The found offset. - public static IntPtr Scan(IntPtr baseAddress, int size, string signature) - { - var (needle, mask) = ParseSignature(signature); - var index = IndexOf(baseAddress, size, needle, mask); - if (index < 0) - throw new KeyNotFoundException($"Can't find a signature of {signature}"); - return baseAddress + index; - } - - /// - /// Try scanning memory for a signature. - /// - /// The base address to scan from. - /// The amount of bytes to scan. - /// The signature to search for. - /// The offset, if found. - /// true if the signature was found. - public static bool TryScan(IntPtr baseAddress, int size, string signature, out IntPtr result) - { - try - { - result = Scan(baseAddress, size, signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Scan for a .data address using a .text function. - /// This is intended to be used with IDA sigs. - /// Place your cursor on the line calling a static address, and create and IDA sig. - /// - /// The signature of the function using the data. - /// The offset from function start of the instruction using the data. - /// An IntPtr to the static memory location. - public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) - { - var instrAddr = this.ScanText(signature); - instrAddr = IntPtr.Add(instrAddr, offset); - var bAddr = (long)this.Module.BaseAddress; - long num; - - do - { - instrAddr = IntPtr.Add(instrAddr, 1); - num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; - } - while (!(num >= this.DataSectionOffset && num <= this.DataSectionOffset + this.DataSectionSize) - && !(num >= this.RDataSectionOffset && num <= this.RDataSectionOffset + this.RDataSectionSize)); - - return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); - } - - /// - /// Try scanning for a .data address using a .text function. - /// This is intended to be used with IDA sigs. - /// Place your cursor on the line calling a static address, and create and IDA sig. - /// - /// The signature of the function using the data. - /// An IntPtr to the static memory location, if found. - /// The offset from function start of the instruction using the data. - /// true if the signature was found. - public bool TryGetStaticAddressFromSig(string signature, out IntPtr result, int offset = 0) - { - try - { - result = this.GetStaticAddressFromSig(signature, offset); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Scan for a byte signature in the .data section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanData(string signature) - { - var scanRet = Scan(this.DataSectionBase, this.DataSectionSize, signature); - - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - return scanRet; - } - - /// - /// Try scanning for a byte signature in the .data section. - /// - /// The signature. - /// The real offset of the signature, if found. - /// true if the signature was found. - public bool TryScanData(string signature, out IntPtr result) - { - try - { - result = this.ScanData(signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Scan for a byte signature in the whole module search area. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanModule(string signature) - { - var scanRet = Scan(this.SearchBase, this.Module.ModuleMemorySize, signature); - - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - return scanRet; - } - - /// - /// Try scanning for a byte signature in the whole module search area. - /// - /// The signature. - /// The real offset of the signature, if found. - /// true if the signature was found. - public bool TryScanModule(string signature, out IntPtr result) - { - try - { - result = this.ScanModule(signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Resolve a RVA address. - /// - /// The address of the next instruction. - /// The relative offset. - /// The calculated offset. - public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) - { - if (this.Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); - return nextInstAddr + relOffset; - } - - /// - /// Scan for a byte signature in the .text section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanText(string signature) - { - var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; - - var scanRet = Scan(mBase, this.TextSectionSize, signature); - - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - var insnByte = Marshal.ReadByte(scanRet); - - if (insnByte == 0xE8 || insnByte == 0xE9) - return ReadJmpCallSig(scanRet); - - return scanRet; - } - - /// - /// Try scanning for a byte signature in the .text section. - /// - /// The signature. - /// The real offset of the signature, if found. - /// true if the signature was found. - public bool TryScanText(string signature, out IntPtr result) - { - try - { - result = this.ScanText(signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Free the memory of the copied module search area on object disposal, if applicable. - /// - public void Dispose() - { - Marshal.FreeHGlobal(this.moduleCopyPtr); - } - - /// - /// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location. - /// - /// The address the JMP or CALL sig resolved to. - /// The real offset of the signature. - private static IntPtr ReadJmpCallSig(IntPtr sigLocation) - { - var jumpOffset = Marshal.ReadInt32(sigLocation, 1); - return IntPtr.Add(sigLocation, 5 + jumpOffset); - } - - private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) - { - signature = signature.Replace(" ", string.Empty); - if (signature.Length % 2 != 0) - throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); - - var needleLength = signature.Length / 2; - var needle = new byte[needleLength]; - var mask = new bool[needleLength]; - for (var i = 0; i < needleLength; i++) - { - var hexString = signature.Substring(i * 2, 2); - if (hexString == "??" || hexString == "**") - { - needle[i] = 0; - mask[i] = true; - continue; - } - - needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); - mask[i] = false; - } - - return (needle, mask); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) - { - if (needle.Length > bufferLength) return -1; - var badShift = BuildBadCharTable(needle, mask); - var last = needle.Length - 1; - var offset = 0; - var maxoffset = bufferLength - needle.Length; - var buffer = (byte*)bufferPtr; - - while (offset <= maxoffset) - { - int position; - for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) - { - if (position == 0) - return offset; - } - - offset += badShift[*(buffer + offset + last)]; - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int[] BuildBadCharTable(byte[] needle, bool[] mask) - { - int idx; - var last = needle.Length - 1; - var badShift = new int[256]; - for (idx = last; idx > 0 && !mask[idx]; --idx) - { - } - - var diff = last - idx; - if (diff == 0) diff = 1; - - for (idx = 0; idx <= 255; ++idx) - badShift[idx] = diff; - for (idx = last - diff; idx < last; ++idx) - badShift[needle[idx]] = last - idx; - return badShift; - } - - private void SetupSearchSpace(ProcessModule module) - { - var baseAddress = module.BaseAddress; - - // We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here. - var ntNewOffset = Marshal.ReadInt32(baseAddress, 0x3C); - var ntHeader = baseAddress + ntNewOffset; - - // IMAGE_NT_HEADER - var fileHeader = ntHeader + 4; - var numSections = Marshal.ReadInt16(ntHeader, 6); - - // IMAGE_OPTIONAL_HEADER - var optionalHeader = fileHeader + 20; - - IntPtr sectionHeader; - if (this.Is32BitProcess) // IMAGE_OPTIONAL_HEADER32 - sectionHeader = optionalHeader + 224; - else // IMAGE_OPTIONAL_HEADER64 - sectionHeader = optionalHeader + 240; - - // IMAGE_SECTION_HEADER - var sectionCursor = sectionHeader; - for (var i = 0; i < numSections; i++) - { - var sectionName = Marshal.ReadInt64(sectionCursor); - - // .text - switch (sectionName) - { - case 0x747865742E: // .text - this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8); - break; - case 0x617461642E: // .data - this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - this.DataSectionSize = Marshal.ReadInt32(sectionCursor, 8); - break; - case 0x61746164722E: // .rdata - this.RDataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - this.RDataSectionSize = Marshal.ReadInt32(sectionCursor, 8); - break; - } - - sectionCursor += 40; - } - } - - private unsafe void SetupCopiedSegments() - { - // .text - this.moduleCopyPtr = Marshal.AllocHGlobal(this.Module.ModuleMemorySize); - Buffer.MemoryCopy( - this.Module.BaseAddress.ToPointer(), - this.moduleCopyPtr.ToPointer(), - this.Module.ModuleMemorySize, - this.Module.ModuleMemorySize); - - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); + result = IntPtr.Zero; + return false; } } + + /// + /// Scan for a .data address using a .text function. + /// This is intended to be used with IDA sigs. + /// Place your cursor on the line calling a static address, and create and IDA sig. + /// + /// The signature of the function using the data. + /// The offset from function start of the instruction using the data. + /// An IntPtr to the static memory location. + public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) + { + var instrAddr = this.ScanText(signature); + instrAddr = IntPtr.Add(instrAddr, offset); + var bAddr = (long)this.Module.BaseAddress; + long num; + + do + { + instrAddr = IntPtr.Add(instrAddr, 1); + num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; + } + while (!(num >= this.DataSectionOffset && num <= this.DataSectionOffset + this.DataSectionSize) + && !(num >= this.RDataSectionOffset && num <= this.RDataSectionOffset + this.RDataSectionSize)); + + return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); + } + + /// + /// Try scanning for a .data address using a .text function. + /// This is intended to be used with IDA sigs. + /// Place your cursor on the line calling a static address, and create and IDA sig. + /// + /// The signature of the function using the data. + /// An IntPtr to the static memory location, if found. + /// The offset from function start of the instruction using the data. + /// true if the signature was found. + public bool TryGetStaticAddressFromSig(string signature, out IntPtr result, int offset = 0) + { + try + { + result = this.GetStaticAddressFromSig(signature, offset); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Scan for a byte signature in the .data section. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanData(string signature) + { + var scanRet = Scan(this.DataSectionBase, this.DataSectionSize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + return scanRet; + } + + /// + /// Try scanning for a byte signature in the .data section. + /// + /// The signature. + /// The real offset of the signature, if found. + /// true if the signature was found. + public bool TryScanData(string signature, out IntPtr result) + { + try + { + result = this.ScanData(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Scan for a byte signature in the whole module search area. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanModule(string signature) + { + var scanRet = Scan(this.SearchBase, this.Module.ModuleMemorySize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + return scanRet; + } + + /// + /// Try scanning for a byte signature in the whole module search area. + /// + /// The signature. + /// The real offset of the signature, if found. + /// true if the signature was found. + public bool TryScanModule(string signature, out IntPtr result) + { + try + { + result = this.ScanModule(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Resolve a RVA address. + /// + /// The address of the next instruction. + /// The relative offset. + /// The calculated offset. + public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) + { + if (this.Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); + return nextInstAddr + relOffset; + } + + /// + /// Scan for a byte signature in the .text section. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanText(string signature) + { + var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; + + var scanRet = Scan(mBase, this.TextSectionSize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + var insnByte = Marshal.ReadByte(scanRet); + + if (insnByte == 0xE8 || insnByte == 0xE9) + return ReadJmpCallSig(scanRet); + + return scanRet; + } + + /// + /// Try scanning for a byte signature in the .text section. + /// + /// The signature. + /// The real offset of the signature, if found. + /// true if the signature was found. + public bool TryScanText(string signature, out IntPtr result) + { + try + { + result = this.ScanText(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Free the memory of the copied module search area on object disposal, if applicable. + /// + public void Dispose() + { + Marshal.FreeHGlobal(this.moduleCopyPtr); + } + + /// + /// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location. + /// + /// The address the JMP or CALL sig resolved to. + /// The real offset of the signature. + private static IntPtr ReadJmpCallSig(IntPtr sigLocation) + { + var jumpOffset = Marshal.ReadInt32(sigLocation, 1); + return IntPtr.Add(sigLocation, 5 + jumpOffset); + } + + private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) + { + signature = signature.Replace(" ", string.Empty); + if (signature.Length % 2 != 0) + throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); + + var needleLength = signature.Length / 2; + var needle = new byte[needleLength]; + var mask = new bool[needleLength]; + for (var i = 0; i < needleLength; i++) + { + var hexString = signature.Substring(i * 2, 2); + if (hexString == "??" || hexString == "**") + { + needle[i] = 0; + mask[i] = true; + continue; + } + + needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); + mask[i] = false; + } + + return (needle, mask); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) + { + if (needle.Length > bufferLength) return -1; + var badShift = BuildBadCharTable(needle, mask); + var last = needle.Length - 1; + var offset = 0; + var maxoffset = bufferLength - needle.Length; + var buffer = (byte*)bufferPtr; + + while (offset <= maxoffset) + { + int position; + for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) + { + if (position == 0) + return offset; + } + + offset += badShift[*(buffer + offset + last)]; + } + + return -1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int[] BuildBadCharTable(byte[] needle, bool[] mask) + { + int idx; + var last = needle.Length - 1; + var badShift = new int[256]; + for (idx = last; idx > 0 && !mask[idx]; --idx) + { + } + + var diff = last - idx; + if (diff == 0) diff = 1; + + for (idx = 0; idx <= 255; ++idx) + badShift[idx] = diff; + for (idx = last - diff; idx < last; ++idx) + badShift[needle[idx]] = last - idx; + return badShift; + } + + private void SetupSearchSpace(ProcessModule module) + { + var baseAddress = module.BaseAddress; + + // We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here. + var ntNewOffset = Marshal.ReadInt32(baseAddress, 0x3C); + var ntHeader = baseAddress + ntNewOffset; + + // IMAGE_NT_HEADER + var fileHeader = ntHeader + 4; + var numSections = Marshal.ReadInt16(ntHeader, 6); + + // IMAGE_OPTIONAL_HEADER + var optionalHeader = fileHeader + 20; + + IntPtr sectionHeader; + if (this.Is32BitProcess) // IMAGE_OPTIONAL_HEADER32 + sectionHeader = optionalHeader + 224; + else // IMAGE_OPTIONAL_HEADER64 + sectionHeader = optionalHeader + 240; + + // IMAGE_SECTION_HEADER + var sectionCursor = sectionHeader; + for (var i = 0; i < numSections; i++) + { + var sectionName = Marshal.ReadInt64(sectionCursor); + + // .text + switch (sectionName) + { + case 0x747865742E: // .text + this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8); + break; + case 0x617461642E: // .data + this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.DataSectionSize = Marshal.ReadInt32(sectionCursor, 8); + break; + case 0x61746164722E: // .rdata + this.RDataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.RDataSectionSize = Marshal.ReadInt32(sectionCursor, 8); + break; + } + + sectionCursor += 40; + } + } + + private unsafe void SetupCopiedSegments() + { + // .text + this.moduleCopyPtr = Marshal.AllocHGlobal(this.Module.ModuleMemorySize); + Buffer.MemoryCopy( + this.Module.BaseAddress.ToPointer(), + this.moduleCopyPtr.ToPointer(), + this.Module.ModuleMemorySize, + this.Module.ModuleMemorySize); + + this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); + } } diff --git a/Dalamud/Game/Text/Sanitizer/ISanitizer.cs b/Dalamud/Game/Text/Sanitizer/ISanitizer.cs index ffaa9cc0a..65603951a 100644 --- a/Dalamud/Game/Text/Sanitizer/ISanitizer.cs +++ b/Dalamud/Game/Text/Sanitizer/ISanitizer.cs @@ -1,40 +1,39 @@ using System.Collections.Generic; -namespace Dalamud.Game.Text.Sanitizer +namespace Dalamud.Game.Text.Sanitizer; + +/// +/// Sanitize strings to remove soft hyphens and other special characters. +/// +public interface ISanitizer { /// - /// Sanitize strings to remove soft hyphens and other special characters. + /// Creates a sanitized string using current clientLanguage. /// - public interface ISanitizer - { - /// - /// Creates a sanitized string using current clientLanguage. - /// - /// An unsanitized string to sanitize. - /// A sanitized string. - string Sanitize(string unsanitizedString); + /// An unsanitized string to sanitize. + /// A sanitized string. + string Sanitize(string unsanitizedString); - /// - /// Creates a sanitized string using request clientLanguage. - /// - /// An unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A sanitized string. - string Sanitize(string unsanitizedString, ClientLanguage clientLanguage); + /// + /// Creates a sanitized string using request clientLanguage. + /// + /// An unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A sanitized string. + string Sanitize(string unsanitizedString, ClientLanguage clientLanguage); - /// - /// Creates a list of sanitized strings using current clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// A list of sanitized strings. - IEnumerable Sanitize(IEnumerable unsanitizedStrings); + /// + /// Creates a list of sanitized strings using current clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// A list of sanitized strings. + IEnumerable Sanitize(IEnumerable unsanitizedStrings); - /// - /// Creates a list of sanitized strings using requested clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A list of sanitized strings. - IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage); - } + /// + /// Creates a list of sanitized strings using requested clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A list of sanitized strings. + IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage); } diff --git a/Dalamud/Game/Text/Sanitizer/Sanitizer.cs b/Dalamud/Game/Text/Sanitizer/Sanitizer.cs index 0cf1f1ea6..0647f5d28 100644 --- a/Dalamud/Game/Text/Sanitizer/Sanitizer.cs +++ b/Dalamud/Game/Text/Sanitizer/Sanitizer.cs @@ -2,110 +2,109 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Dalamud.Game.Text.Sanitizer +namespace Dalamud.Game.Text.Sanitizer; + +/// +/// Sanitize strings to remove soft hyphens and other special characters. +/// +public class Sanitizer : ISanitizer { - /// - /// Sanitize strings to remove soft hyphens and other special characters. - /// - public class Sanitizer : ISanitizer + private static readonly Dictionary DESanitizationDict = new() { - private static readonly Dictionary DESanitizationDict = new() + { "\u0020\u2020", string.Empty }, // dagger + }; + + private static readonly Dictionary FRSanitizationDict = new() + { + { "\u0153", "\u006F\u0065" }, // ligature oe + }; + + private readonly ClientLanguage defaultClientLanguage; + + /// + /// Initializes a new instance of the class. + /// + /// Default clientLanguage for sanitizing strings. + public Sanitizer(ClientLanguage defaultClientLanguage) + { + this.defaultClientLanguage = defaultClientLanguage; + } + + /// + /// Creates a sanitized string using current clientLanguage. + /// + /// An unsanitized string to sanitize. + /// A sanitized string. + public string Sanitize(string unsanitizedString) + { + return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage); + } + + /// + /// Creates a sanitized string using request clientLanguage. + /// + /// An unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A sanitized string. + public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage) + { + return SanitizeByLanguage(unsanitizedString, clientLanguage); + } + + /// + /// Creates a list of sanitized strings using current clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// A list of sanitized strings. + public IEnumerable Sanitize(IEnumerable unsanitizedStrings) + { + return SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage); + } + + /// + /// Creates a list of sanitized strings using requested clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A list of sanitized strings. + public IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) + { + return SanitizeByLanguage(unsanitizedStrings, clientLanguage); + } + + private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage) + { + var sanitizedString = FilterUnprintableCharacters(unsanitizedString); + return clientLanguage switch { - { "\u0020\u2020", string.Empty }, // dagger + ClientLanguage.Japanese or ClientLanguage.English => sanitizedString, + ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict), + ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict), + _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), }; + } - private static readonly Dictionary FRSanitizationDict = new() + private static IEnumerable SanitizeByLanguage(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) + { + return clientLanguage switch { - { "\u0153", "\u006F\u0065" }, // ligature oe + ClientLanguage.Japanese => unsanitizedStrings.Select(FilterUnprintableCharacters), + ClientLanguage.English => unsanitizedStrings.Select(FilterUnprintableCharacters), + ClientLanguage.German => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), DESanitizationDict)), + ClientLanguage.French => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), FRSanitizationDict)), + _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), }; + } - private readonly ClientLanguage defaultClientLanguage; + private static string FilterUnprintableCharacters(string str) + { + return new string(str?.Where(ch => ch >= 0x20).ToArray()); + } - /// - /// Initializes a new instance of the class. - /// - /// Default clientLanguage for sanitizing strings. - public Sanitizer(ClientLanguage defaultClientLanguage) - { - this.defaultClientLanguage = defaultClientLanguage; - } - - /// - /// Creates a sanitized string using current clientLanguage. - /// - /// An unsanitized string to sanitize. - /// A sanitized string. - public string Sanitize(string unsanitizedString) - { - return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage); - } - - /// - /// Creates a sanitized string using request clientLanguage. - /// - /// An unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A sanitized string. - public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage) - { - return SanitizeByLanguage(unsanitizedString, clientLanguage); - } - - /// - /// Creates a list of sanitized strings using current clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// A list of sanitized strings. - public IEnumerable Sanitize(IEnumerable unsanitizedStrings) - { - return SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage); - } - - /// - /// Creates a list of sanitized strings using requested clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A list of sanitized strings. - public IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) - { - return SanitizeByLanguage(unsanitizedStrings, clientLanguage); - } - - private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage) - { - var sanitizedString = FilterUnprintableCharacters(unsanitizedString); - return clientLanguage switch - { - ClientLanguage.Japanese or ClientLanguage.English => sanitizedString, - ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict), - ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict), - _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), - }; - } - - private static IEnumerable SanitizeByLanguage(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) - { - return clientLanguage switch - { - ClientLanguage.Japanese => unsanitizedStrings.Select(FilterUnprintableCharacters), - ClientLanguage.English => unsanitizedStrings.Select(FilterUnprintableCharacters), - ClientLanguage.German => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), DESanitizationDict)), - ClientLanguage.French => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), FRSanitizationDict)), - _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), - }; - } - - private static string FilterUnprintableCharacters(string str) - { - return new string(str?.Where(ch => ch >= 0x20).ToArray()); - } - - private static string FilterByDict(string str, Dictionary dict) - { - return dict.Aggregate( - str, (current, kvp) => - current.Replace(kvp.Key, kvp.Value)); - } + private static string FilterByDict(string str, Dictionary dict) + { + return dict.Aggregate( + str, (current, kvp) => + current.Replace(kvp.Key, kvp.Value)); } } diff --git a/Dalamud/Game/Text/SeIconChar.cs b/Dalamud/Game/Text/SeIconChar.cs index 539868135..cf162ff63 100644 --- a/Dalamud/Game/Text/SeIconChar.cs +++ b/Dalamud/Game/Text/SeIconChar.cs @@ -1,743 +1,742 @@ -namespace Dalamud.Game.Text +namespace Dalamud.Game.Text; + +/// +/// Special unicode characters with game-related symbols that work both in-game and in any dalamud window. +/// +public enum SeIconChar { /// - /// Special unicode characters with game-related symbols that work both in-game and in any dalamud window. + /// The sprout icon unicode character. /// - public enum SeIconChar - { - /// - /// The sprout icon unicode character. - /// - BotanistSprout = 0xE034, - - /// - /// The item level icon unicode character. - /// - ItemLevel = 0xE033, - - /// - /// The auto translate open icon unicode character. - /// - AutoTranslateOpen = 0xE040, - - /// - /// The auto translate close icon unicode character. - /// - AutoTranslateClose = 0xE041, - - /// - /// The high quality icon unicode character. - /// - HighQuality = 0xE03C, - - /// - /// The clock icon unicode character. - /// - Clock = 0xE031, - - /// - /// The gil icon unicode character. - /// - Gil = 0xE049, - - /// - /// The Hydaelyn icon unicode character. - /// - Hyadelyn = 0xE048, - - /// - /// The no mouse click icon unicode character. - /// - MouseNoClick = 0xE050, - - /// - /// The left mouse click icon unicode character. - /// - MouseLeftClick = 0xE051, - - /// - /// The right mouse click icon unicode character. - /// - MouseRightClick = 0xE052, - - /// - /// The left/right mouse click icon unicode character. - /// - MouseBothClick = 0xE053, - - /// - /// The mouse wheel icon unicode character. - /// - MouseWheel = 0xE054, - - /// - /// The mouse with a 1 icon unicode character. - /// - Mouse1 = 0xE055, - - /// - /// The mouse with a 2 icon unicode character. - /// - Mouse2 = 0xE056, - - /// - /// The mouse with a 3 icon unicode character. - /// - Mouse3 = 0xE057, - - /// - /// The mouse with a 4 icon unicode character. - /// - Mouse4 = 0xE058, - - /// - /// The mouse with a 5 icon unicode character. - /// - Mouse5 = 0xE059, - - /// - /// The level English icon unicode character. - /// - LevelEn = 0xE06A, - - /// - /// The level German icon unicode character. - /// - LevelDe = 0xE06B, - - /// - /// The level French icon unicode character. - /// - LevelFr = 0xE06C, - - /// - /// The experience icon unicode character. - /// - Experience = 0xE0BC, - - /// - /// The experience filled icon unicode character. - /// - ExperienceFilled = 0xE0BD, - - /// - /// The A.M. time icon unicode character. - /// - TimeAm = 0xE06D, - - /// - /// The P.M. time icon unicode character. - /// - TimePm = 0xE06E, - - /// - /// The right arrow icon unicode character. - /// - ArrowRight = 0xE06F, - - /// - /// The down arrow icon unicode character. - /// - ArrowDown = 0xE035, - - /// - /// The number 0 icon unicode character. - /// - Number0 = 0xE060, - - /// - /// The number 1 icon unicode character. - /// - Number1 = 0xE061, - - /// - /// The number 2 icon unicode character. - /// - Number2 = 0xE062, - - /// - /// The number 3 icon unicode character. - /// - Number3 = 0xE063, - - /// - /// The number 4 icon unicode character. - /// - Number4 = 0xE064, - - /// - /// The number 5 icon unicode character. - /// - Number5 = 0xE065, - - /// - /// The number 6 icon unicode character. - /// - Number6 = 0xE066, - - /// - /// The number 7 icon unicode character. - /// - Number7 = 0xE067, - - /// - /// The number 8 icon unicode character. - /// - Number8 = 0xE068, - - /// - /// The number 9 icon unicode character. - /// - Number9 = 0xE069, - - /// - /// The boxed number 0 icon unicode character. - /// - BoxedNumber0 = 0xE08F, - - /// - /// The boxed number 1 icon unicode character. - /// - BoxedNumber1 = 0xE090, - - /// - /// The boxed number 2 icon unicode character. - /// - BoxedNumber2 = 0xE091, - - /// - /// The boxed number 3 icon unicode character. - /// - BoxedNumber3 = 0xE092, - - /// - /// The boxed number 4 icon unicode character. - /// - BoxedNumber4 = 0xE093, - - /// - /// The boxed number 5 icon unicode character. - /// - BoxedNumber5 = 0xE094, - - /// - /// The boxed number 6 icon unicode character. - /// - BoxedNumber6 = 0xE095, - - /// - /// The boxed number 7 icon unicode character. - /// - BoxedNumber7 = 0xE096, - - /// - /// The boxed number 8 icon unicode character. - /// - BoxedNumber8 = 0xE097, - - /// - /// The boxed number 9 icon unicode character. - /// - BoxedNumber9 = 0xE098, - - /// - /// The boxed number 10 icon unicode character. - /// - BoxedNumber10 = 0xE099, - - /// - /// The boxed number 11 icon unicode character. - /// - BoxedNumber11 = 0xE09A, - - /// - /// The boxed number 12 icon unicode character. - /// - BoxedNumber12 = 0xE09B, - - /// - /// The boxed number 13 icon unicode character. - /// - BoxedNumber13 = 0xE09C, - - /// - /// The boxed number 14 icon unicode character. - /// - BoxedNumber14 = 0xE09D, - - /// - /// The boxed number 15 icon unicode character. - /// - BoxedNumber15 = 0xE09E, - - /// - /// The boxed number 16 icon unicode character. - /// - BoxedNumber16 = 0xE09F, - - /// - /// The boxed number 17 icon unicode character. - /// - BoxedNumber17 = 0xE0A0, - - /// - /// The boxed number 18 icon unicode character. - /// - BoxedNumber18 = 0xE0A1, - - /// - /// The boxed number 19 icon unicode character. - /// - BoxedNumber19 = 0xE0A2, - - /// - /// The boxed number 20 icon unicode character. - /// - BoxedNumber20 = 0xE0A3, - - /// - /// The boxed number 21 icon unicode character. - /// - BoxedNumber21 = 0xE0A4, - - /// - /// The boxed number 22 icon unicode character. - /// - BoxedNumber22 = 0xE0A5, - - /// - /// The boxed number 23 icon unicode character. - /// - BoxedNumber23 = 0xE0A6, - - /// - /// The boxed number 24 icon unicode character. - /// - BoxedNumber24 = 0xE0A7, - - /// - /// The boxed number 25 icon unicode character. - /// - BoxedNumber25 = 0xE0A8, - - /// - /// The boxed number 26 icon unicode character. - /// - BoxedNumber26 = 0xE0A9, - - /// - /// The boxed number 27 icon unicode character. - /// - BoxedNumber27 = 0xE0AA, - - /// - /// The boxed number 28 icon unicode character. - /// - BoxedNumber28 = 0xE0AB, - - /// - /// The boxed number 29 icon unicode character. - /// - BoxedNumber29 = 0xE0AC, - - /// - /// The boxed number 30 icon unicode character. - /// - BoxedNumber30 = 0xE0AD, - - /// - /// The boxed number 31 icon unicode character. - /// - BoxedNumber31 = 0xE0AE, - - /// - /// The boxed plus icon unicode character. - /// - BoxedPlus = 0xE0AF, - - /// - /// The bosed question mark icon unicode character. - /// - BoxedQuestionMark = 0xE070, - - /// - /// The boxed star icon unicode character. - /// - BoxedStar = 0xE0C0, - - /// - /// The boxed Roman numeral 1 (I) icon unicode character. - /// - BoxedRoman1 = 0xE0C1, - - /// - /// The boxed Roman numeral 2 (II) icon unicode character. - /// - BoxedRoman2 = 0xE0C2, - - /// - /// The boxed Roman numeral 3 (III) icon unicode character. - /// - BoxedRoman3 = 0xE0C3, - - /// - /// The boxed Roman numeral 4 (IV) icon unicode character. - /// - BoxedRoman4 = 0xE0C4, - - /// - /// The boxed Roman numeral 5 (V) icon unicode character. - /// - BoxedRoman5 = 0xE0C5, - - /// - /// The boxed Roman numeral 6 (VI) icon unicode character. - /// - BoxedRoman6 = 0xE0C6, - - /// - /// The boxed letter A icon unicode character. - /// - BoxedLetterA = 0xE071, - - /// - /// The boxed letter B icon unicode character. - /// - BoxedLetterB = 0xE072, - - /// - /// The boxed letter C icon unicode character. - /// - BoxedLetterC = 0xE073, - - /// - /// The boxed letter D icon unicode character. - /// - BoxedLetterD = 0xE074, - - /// - /// The boxed letter E icon unicode character. - /// - BoxedLetterE = 0xE075, - - /// - /// The boxed letter F icon unicode character. - /// - BoxedLetterF = 0xE076, - - /// - /// The boxed letter G icon unicode character. - /// - BoxedLetterG = 0xE077, - - /// - /// The boxed letter H icon unicode character. - /// - BoxedLetterH = 0xE078, - - /// - /// The boxed letter I icon unicode character. - /// - BoxedLetterI = 0xE079, - - /// - /// The boxed letter J icon unicode character. - /// - BoxedLetterJ = 0xE07A, - - /// - /// The boxed letter K icon unicode character. - /// - BoxedLetterK = 0xE07B, - - /// - /// The boxed letter L icon unicode character. - /// - BoxedLetterL = 0xE07C, - - /// - /// The boxed letter M icon unicode character. - /// - BoxedLetterM = 0xE07D, - - /// - /// The boxed letter N icon unicode character. - /// - BoxedLetterN = 0xE07E, - - /// - /// The boxed letter O icon unicode character. - /// - BoxedLetterO = 0xE07F, - - /// - /// The boxed letter P icon unicode character. - /// - BoxedLetterP = 0xE080, - - /// - /// The boxed letter Q icon unicode character. - /// - BoxedLetterQ = 0xE081, - - /// - /// The boxed letter R icon unicode character. - /// - BoxedLetterR = 0xE082, - - /// - /// The boxed letter S icon unicode character. - /// - BoxedLetterS = 0xE083, - - /// - /// The boxed letter T icon unicode character. - /// - BoxedLetterT = 0xE084, - - /// - /// The boxed letter U icon unicode character. - /// - BoxedLetterU = 0xE085, - - /// - /// The boxed letter V icon unicode character. - /// - BoxedLetterV = 0xE086, - - /// - /// The boxed letter W icon unicode character. - /// - BoxedLetterW = 0xE087, - - /// - /// The boxed letter X icon unicode character. - /// - BoxedLetterX = 0xE088, - - /// - /// The boxed letter Y icon unicode character. - /// - BoxedLetterY = 0xE089, - - /// - /// The boxed letter Z icon unicode character. - /// - BoxedLetterZ = 0xE08A, - - /// - /// The circle icon unicode character. - /// - Circle = 0xE04A, - - /// - /// The square icon unicode character. - /// - Square = 0xE04B, - - /// - /// The cross icon unicode character. - /// - Cross = 0xE04C, - - /// - /// The triangle icon unicode character. - /// - Triangle = 0xE04D, - - /// - /// The hexagon icon unicode character. - /// - Hexagon = 0xE042, - - /// - /// The no-circle/prohobited icon unicode character. - /// - Prohibited = 0xE043, - - /// - /// The dice icon unicode character. - /// - Dice = 0xE03E, - - /// - /// The debuff icon unicode character. - /// - Debuff = 0xE05B, - - /// - /// The buff icon unicode character. - /// - Buff = 0xE05C, - - /// - /// The cross-world icon unicode character. - /// - CrossWorld = 0xE05D, - - /// - /// The Eureka level icon unicode character. - /// - EurekaLevel = 0xE03A, - - /// - /// The link marker icon unicode character. - /// - LinkMarker = 0xE0BB, - - /// - /// The glamoured icon unicode character. - /// - Glamoured = 0xE03B, - - /// - /// The glamoured and dyed icon unicode character. - /// - GlamouredDyed = 0xE04E, - - /// - /// The synced quest icon unicode character. - /// - QuestSync = 0xE0BE, - - /// - /// The repeatable quest icon unicode character. - /// - QuestRepeatable = 0xE0BF, - - /// - /// The IME hiragana icon unicode character. - /// - ImeHiragana = 0xE020, - - /// - /// The IME katakana icon unicode character. - /// - ImeKatakana = 0xE021, - - /// - /// The IME alphanumeric icon unicode character. - /// - ImeAlphanumeric = 0xE022, - - /// - /// The IME katakana half-width icon unicode character. - /// - ImeKatakanaHalfWidth = 0xE023, - - /// - /// The IME alphanumeric half-width icon unicode character. - /// - ImeAlphanumericHalfWidth = 0xE024, - - /// - /// The instance (1) icon unicode character. - /// - Instance1 = 0xE0B1, - - /// - /// The instance (2) icon unicode character. - /// - Instance2 = 0xE0B2, - - /// - /// The instance (3) icon unicode character. - /// - Instance3 = 0xE0B3, - - /// - /// The instance (4) icon unicode character. - /// - Instance4 = 0xE0B4, - - /// - /// The instance (5) icon unicode character. - /// - Instance5 = 0xE0B5, - - /// - /// The instance (6) icon unicode character. - /// - Instance6 = 0xE0B6, - - /// - /// The instance (7) icon unicode character. - /// - Instance7 = 0xE0B7, - - /// - /// The instance (8) icon unicode character. - /// - Instance8 = 0xE0B8, - - /// - /// The instance (9) icon unicode character. - /// - Instance9 = 0xE0B9, - - /// - /// The instance merged icon unicode character. - /// - InstanceMerged = 0xE0BA, - - /// - /// The English local time icon unicode character. - /// - LocalTimeEn = 0xE0D0, - - /// - /// The English server time icon unicode character. - /// - ServerTimeEn = 0xE0D1, - - /// - /// The English Eorzea time icon unicode character. - /// - EorzeaTimeEn = 0xE0D2, - - /// - /// The German local time icon unicode character. - /// - LocalTimeDe = 0xE0D3, - - /// - /// The German server time icon unicode character. - /// - ServerTimeDe = 0xE0D4, - - /// - /// The German Eorzea time icon unicode character. - /// - EorzeaTimeDe = 0xE0D5, - - /// - /// The French local time icon unicode character. - /// - LocalTimeFr = 0xE0D6, - - /// - /// The French server time icon unicode character. - /// - ServerTimeFr = 0xE0D7, - - /// - /// The French Eorzea time icon unicode character. - /// - EorzeaTimeFr = 0xE0D8, - - /// - /// The Japanese local time icon unicode character. - /// - LocalTimeJa = 0xE0D9, - - /// - /// The Japanese server time icon unicode character. - /// - ServerTimeJa = 0xE0DA, - - /// - /// The Japanese Eorzea time icon unicode character. - /// - EorzeaTimeJa = 0xE0DB, - } + BotanistSprout = 0xE034, + + /// + /// The item level icon unicode character. + /// + ItemLevel = 0xE033, + + /// + /// The auto translate open icon unicode character. + /// + AutoTranslateOpen = 0xE040, + + /// + /// The auto translate close icon unicode character. + /// + AutoTranslateClose = 0xE041, + + /// + /// The high quality icon unicode character. + /// + HighQuality = 0xE03C, + + /// + /// The clock icon unicode character. + /// + Clock = 0xE031, + + /// + /// The gil icon unicode character. + /// + Gil = 0xE049, + + /// + /// The Hydaelyn icon unicode character. + /// + Hyadelyn = 0xE048, + + /// + /// The no mouse click icon unicode character. + /// + MouseNoClick = 0xE050, + + /// + /// The left mouse click icon unicode character. + /// + MouseLeftClick = 0xE051, + + /// + /// The right mouse click icon unicode character. + /// + MouseRightClick = 0xE052, + + /// + /// The left/right mouse click icon unicode character. + /// + MouseBothClick = 0xE053, + + /// + /// The mouse wheel icon unicode character. + /// + MouseWheel = 0xE054, + + /// + /// The mouse with a 1 icon unicode character. + /// + Mouse1 = 0xE055, + + /// + /// The mouse with a 2 icon unicode character. + /// + Mouse2 = 0xE056, + + /// + /// The mouse with a 3 icon unicode character. + /// + Mouse3 = 0xE057, + + /// + /// The mouse with a 4 icon unicode character. + /// + Mouse4 = 0xE058, + + /// + /// The mouse with a 5 icon unicode character. + /// + Mouse5 = 0xE059, + + /// + /// The level English icon unicode character. + /// + LevelEn = 0xE06A, + + /// + /// The level German icon unicode character. + /// + LevelDe = 0xE06B, + + /// + /// The level French icon unicode character. + /// + LevelFr = 0xE06C, + + /// + /// The experience icon unicode character. + /// + Experience = 0xE0BC, + + /// + /// The experience filled icon unicode character. + /// + ExperienceFilled = 0xE0BD, + + /// + /// The A.M. time icon unicode character. + /// + TimeAm = 0xE06D, + + /// + /// The P.M. time icon unicode character. + /// + TimePm = 0xE06E, + + /// + /// The right arrow icon unicode character. + /// + ArrowRight = 0xE06F, + + /// + /// The down arrow icon unicode character. + /// + ArrowDown = 0xE035, + + /// + /// The number 0 icon unicode character. + /// + Number0 = 0xE060, + + /// + /// The number 1 icon unicode character. + /// + Number1 = 0xE061, + + /// + /// The number 2 icon unicode character. + /// + Number2 = 0xE062, + + /// + /// The number 3 icon unicode character. + /// + Number3 = 0xE063, + + /// + /// The number 4 icon unicode character. + /// + Number4 = 0xE064, + + /// + /// The number 5 icon unicode character. + /// + Number5 = 0xE065, + + /// + /// The number 6 icon unicode character. + /// + Number6 = 0xE066, + + /// + /// The number 7 icon unicode character. + /// + Number7 = 0xE067, + + /// + /// The number 8 icon unicode character. + /// + Number8 = 0xE068, + + /// + /// The number 9 icon unicode character. + /// + Number9 = 0xE069, + + /// + /// The boxed number 0 icon unicode character. + /// + BoxedNumber0 = 0xE08F, + + /// + /// The boxed number 1 icon unicode character. + /// + BoxedNumber1 = 0xE090, + + /// + /// The boxed number 2 icon unicode character. + /// + BoxedNumber2 = 0xE091, + + /// + /// The boxed number 3 icon unicode character. + /// + BoxedNumber3 = 0xE092, + + /// + /// The boxed number 4 icon unicode character. + /// + BoxedNumber4 = 0xE093, + + /// + /// The boxed number 5 icon unicode character. + /// + BoxedNumber5 = 0xE094, + + /// + /// The boxed number 6 icon unicode character. + /// + BoxedNumber6 = 0xE095, + + /// + /// The boxed number 7 icon unicode character. + /// + BoxedNumber7 = 0xE096, + + /// + /// The boxed number 8 icon unicode character. + /// + BoxedNumber8 = 0xE097, + + /// + /// The boxed number 9 icon unicode character. + /// + BoxedNumber9 = 0xE098, + + /// + /// The boxed number 10 icon unicode character. + /// + BoxedNumber10 = 0xE099, + + /// + /// The boxed number 11 icon unicode character. + /// + BoxedNumber11 = 0xE09A, + + /// + /// The boxed number 12 icon unicode character. + /// + BoxedNumber12 = 0xE09B, + + /// + /// The boxed number 13 icon unicode character. + /// + BoxedNumber13 = 0xE09C, + + /// + /// The boxed number 14 icon unicode character. + /// + BoxedNumber14 = 0xE09D, + + /// + /// The boxed number 15 icon unicode character. + /// + BoxedNumber15 = 0xE09E, + + /// + /// The boxed number 16 icon unicode character. + /// + BoxedNumber16 = 0xE09F, + + /// + /// The boxed number 17 icon unicode character. + /// + BoxedNumber17 = 0xE0A0, + + /// + /// The boxed number 18 icon unicode character. + /// + BoxedNumber18 = 0xE0A1, + + /// + /// The boxed number 19 icon unicode character. + /// + BoxedNumber19 = 0xE0A2, + + /// + /// The boxed number 20 icon unicode character. + /// + BoxedNumber20 = 0xE0A3, + + /// + /// The boxed number 21 icon unicode character. + /// + BoxedNumber21 = 0xE0A4, + + /// + /// The boxed number 22 icon unicode character. + /// + BoxedNumber22 = 0xE0A5, + + /// + /// The boxed number 23 icon unicode character. + /// + BoxedNumber23 = 0xE0A6, + + /// + /// The boxed number 24 icon unicode character. + /// + BoxedNumber24 = 0xE0A7, + + /// + /// The boxed number 25 icon unicode character. + /// + BoxedNumber25 = 0xE0A8, + + /// + /// The boxed number 26 icon unicode character. + /// + BoxedNumber26 = 0xE0A9, + + /// + /// The boxed number 27 icon unicode character. + /// + BoxedNumber27 = 0xE0AA, + + /// + /// The boxed number 28 icon unicode character. + /// + BoxedNumber28 = 0xE0AB, + + /// + /// The boxed number 29 icon unicode character. + /// + BoxedNumber29 = 0xE0AC, + + /// + /// The boxed number 30 icon unicode character. + /// + BoxedNumber30 = 0xE0AD, + + /// + /// The boxed number 31 icon unicode character. + /// + BoxedNumber31 = 0xE0AE, + + /// + /// The boxed plus icon unicode character. + /// + BoxedPlus = 0xE0AF, + + /// + /// The bosed question mark icon unicode character. + /// + BoxedQuestionMark = 0xE070, + + /// + /// The boxed star icon unicode character. + /// + BoxedStar = 0xE0C0, + + /// + /// The boxed Roman numeral 1 (I) icon unicode character. + /// + BoxedRoman1 = 0xE0C1, + + /// + /// The boxed Roman numeral 2 (II) icon unicode character. + /// + BoxedRoman2 = 0xE0C2, + + /// + /// The boxed Roman numeral 3 (III) icon unicode character. + /// + BoxedRoman3 = 0xE0C3, + + /// + /// The boxed Roman numeral 4 (IV) icon unicode character. + /// + BoxedRoman4 = 0xE0C4, + + /// + /// The boxed Roman numeral 5 (V) icon unicode character. + /// + BoxedRoman5 = 0xE0C5, + + /// + /// The boxed Roman numeral 6 (VI) icon unicode character. + /// + BoxedRoman6 = 0xE0C6, + + /// + /// The boxed letter A icon unicode character. + /// + BoxedLetterA = 0xE071, + + /// + /// The boxed letter B icon unicode character. + /// + BoxedLetterB = 0xE072, + + /// + /// The boxed letter C icon unicode character. + /// + BoxedLetterC = 0xE073, + + /// + /// The boxed letter D icon unicode character. + /// + BoxedLetterD = 0xE074, + + /// + /// The boxed letter E icon unicode character. + /// + BoxedLetterE = 0xE075, + + /// + /// The boxed letter F icon unicode character. + /// + BoxedLetterF = 0xE076, + + /// + /// The boxed letter G icon unicode character. + /// + BoxedLetterG = 0xE077, + + /// + /// The boxed letter H icon unicode character. + /// + BoxedLetterH = 0xE078, + + /// + /// The boxed letter I icon unicode character. + /// + BoxedLetterI = 0xE079, + + /// + /// The boxed letter J icon unicode character. + /// + BoxedLetterJ = 0xE07A, + + /// + /// The boxed letter K icon unicode character. + /// + BoxedLetterK = 0xE07B, + + /// + /// The boxed letter L icon unicode character. + /// + BoxedLetterL = 0xE07C, + + /// + /// The boxed letter M icon unicode character. + /// + BoxedLetterM = 0xE07D, + + /// + /// The boxed letter N icon unicode character. + /// + BoxedLetterN = 0xE07E, + + /// + /// The boxed letter O icon unicode character. + /// + BoxedLetterO = 0xE07F, + + /// + /// The boxed letter P icon unicode character. + /// + BoxedLetterP = 0xE080, + + /// + /// The boxed letter Q icon unicode character. + /// + BoxedLetterQ = 0xE081, + + /// + /// The boxed letter R icon unicode character. + /// + BoxedLetterR = 0xE082, + + /// + /// The boxed letter S icon unicode character. + /// + BoxedLetterS = 0xE083, + + /// + /// The boxed letter T icon unicode character. + /// + BoxedLetterT = 0xE084, + + /// + /// The boxed letter U icon unicode character. + /// + BoxedLetterU = 0xE085, + + /// + /// The boxed letter V icon unicode character. + /// + BoxedLetterV = 0xE086, + + /// + /// The boxed letter W icon unicode character. + /// + BoxedLetterW = 0xE087, + + /// + /// The boxed letter X icon unicode character. + /// + BoxedLetterX = 0xE088, + + /// + /// The boxed letter Y icon unicode character. + /// + BoxedLetterY = 0xE089, + + /// + /// The boxed letter Z icon unicode character. + /// + BoxedLetterZ = 0xE08A, + + /// + /// The circle icon unicode character. + /// + Circle = 0xE04A, + + /// + /// The square icon unicode character. + /// + Square = 0xE04B, + + /// + /// The cross icon unicode character. + /// + Cross = 0xE04C, + + /// + /// The triangle icon unicode character. + /// + Triangle = 0xE04D, + + /// + /// The hexagon icon unicode character. + /// + Hexagon = 0xE042, + + /// + /// The no-circle/prohobited icon unicode character. + /// + Prohibited = 0xE043, + + /// + /// The dice icon unicode character. + /// + Dice = 0xE03E, + + /// + /// The debuff icon unicode character. + /// + Debuff = 0xE05B, + + /// + /// The buff icon unicode character. + /// + Buff = 0xE05C, + + /// + /// The cross-world icon unicode character. + /// + CrossWorld = 0xE05D, + + /// + /// The Eureka level icon unicode character. + /// + EurekaLevel = 0xE03A, + + /// + /// The link marker icon unicode character. + /// + LinkMarker = 0xE0BB, + + /// + /// The glamoured icon unicode character. + /// + Glamoured = 0xE03B, + + /// + /// The glamoured and dyed icon unicode character. + /// + GlamouredDyed = 0xE04E, + + /// + /// The synced quest icon unicode character. + /// + QuestSync = 0xE0BE, + + /// + /// The repeatable quest icon unicode character. + /// + QuestRepeatable = 0xE0BF, + + /// + /// The IME hiragana icon unicode character. + /// + ImeHiragana = 0xE020, + + /// + /// The IME katakana icon unicode character. + /// + ImeKatakana = 0xE021, + + /// + /// The IME alphanumeric icon unicode character. + /// + ImeAlphanumeric = 0xE022, + + /// + /// The IME katakana half-width icon unicode character. + /// + ImeKatakanaHalfWidth = 0xE023, + + /// + /// The IME alphanumeric half-width icon unicode character. + /// + ImeAlphanumericHalfWidth = 0xE024, + + /// + /// The instance (1) icon unicode character. + /// + Instance1 = 0xE0B1, + + /// + /// The instance (2) icon unicode character. + /// + Instance2 = 0xE0B2, + + /// + /// The instance (3) icon unicode character. + /// + Instance3 = 0xE0B3, + + /// + /// The instance (4) icon unicode character. + /// + Instance4 = 0xE0B4, + + /// + /// The instance (5) icon unicode character. + /// + Instance5 = 0xE0B5, + + /// + /// The instance (6) icon unicode character. + /// + Instance6 = 0xE0B6, + + /// + /// The instance (7) icon unicode character. + /// + Instance7 = 0xE0B7, + + /// + /// The instance (8) icon unicode character. + /// + Instance8 = 0xE0B8, + + /// + /// The instance (9) icon unicode character. + /// + Instance9 = 0xE0B9, + + /// + /// The instance merged icon unicode character. + /// + InstanceMerged = 0xE0BA, + + /// + /// The English local time icon unicode character. + /// + LocalTimeEn = 0xE0D0, + + /// + /// The English server time icon unicode character. + /// + ServerTimeEn = 0xE0D1, + + /// + /// The English Eorzea time icon unicode character. + /// + EorzeaTimeEn = 0xE0D2, + + /// + /// The German local time icon unicode character. + /// + LocalTimeDe = 0xE0D3, + + /// + /// The German server time icon unicode character. + /// + ServerTimeDe = 0xE0D4, + + /// + /// The German Eorzea time icon unicode character. + /// + EorzeaTimeDe = 0xE0D5, + + /// + /// The French local time icon unicode character. + /// + LocalTimeFr = 0xE0D6, + + /// + /// The French server time icon unicode character. + /// + ServerTimeFr = 0xE0D7, + + /// + /// The French Eorzea time icon unicode character. + /// + EorzeaTimeFr = 0xE0D8, + + /// + /// The Japanese local time icon unicode character. + /// + LocalTimeJa = 0xE0D9, + + /// + /// The Japanese server time icon unicode character. + /// + ServerTimeJa = 0xE0DA, + + /// + /// The Japanese Eorzea time icon unicode character. + /// + EorzeaTimeJa = 0xE0DB, } diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index 6b82100e2..32acc0ad5 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -1,438 +1,437 @@ -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// This class represents special icons that can appear in chat naturally or as IconPayloads. +/// +public enum BitmapFontIcon : uint { /// - /// This class represents special icons that can appear in chat naturally or as IconPayloads. + /// No icon. /// - public enum BitmapFontIcon : uint - { - /// - /// No icon. - /// - None = 0, - - /// - /// The controller D-pad up icon. - /// - ControllerDPadUp = 1, - - /// - /// The controller D-pad down icon. - /// - ControllerDPadDown = 2, - - /// - /// The controller D-pad left icon. - /// - ControllerDPadLeft = 3, - - /// - /// The controller D-pad right icon. - /// - ControllerDPadRight = 4, - - /// - /// The controller D-pad up/down icon. - /// - ControllerDPadUpDown = 5, - - /// - /// The controller D-pad left/right icon. - /// - ControllerDPadLeftRight = 6, - - /// - /// The controller D-pad all directions icon. - /// - ControllerDPadAll = 7, - - /// - /// The controller button 0 icon (Xbox: B, PlayStation: Circle). - /// - ControllerButton0 = 8, - - /// - /// The controller button 1 icon (XBox: A, PlayStation: Cross). - /// - ControllerButton1 = 9, - - /// - /// The controller button 2 icon (XBox: X, PlayStation: Square). - /// - ControllerButton2 = 10, - - /// - /// The controller button 3 icon (BBox: Y, PlayStation: Triangle). - /// - ControllerButton3 = 11, - - /// - /// The controller left shoulder button icon. - /// - ControllerShoulderLeft = 12, - - /// - /// The controller right shoulder button icon. - /// - ControllerShoulderRight = 13, - - /// - /// The controller left trigger button icon. - /// - ControllerTriggerLeft = 14, - - /// - /// The controller right trigger button icon. - /// - ControllerTriggerRight = 15, - - /// - /// The controller left analog stick in icon. - /// - ControllerAnalogLeftStickIn = 16, - - /// - /// The controller right analog stick in icon. - /// - ControllerAnalogRightStickIn = 17, - - /// - /// The controller start button icon. - /// - ControllerStart = 18, - - /// - /// The controller back button icon. - /// - ControllerBack = 19, - - /// - /// The controller left analog stick icon. - /// - ControllerAnalogLeftStick = 20, - - /// - /// The controller left analog stick up/down icon. - /// - ControllerAnalogLeftStickUpDown = 21, - - /// - /// The controller left analog stick left/right icon. - /// - ControllerAnalogLeftStickLeftRight = 22, - - /// - /// The controller right analog stick icon. - /// - ControllerAnalogRightStick = 23, - - /// - /// The controller right analog stick up/down icon. - /// - ControllerAnalogRightStickUpDown = 24, - - /// - /// The controller right analog stick left/right icon. - /// - ControllerAnalogRightStickLeftRight = 25, - - /// - /// The La Noscea region icon. - /// - LaNoscea = 51, - - /// - /// The Black Shroud region icon. - /// - BlackShroud = 52, - - /// - /// The Thanalan region icon. - /// - Thanalan = 53, - - /// - /// The auto translate begin icon. - /// - AutoTranslateBegin = 54, - - /// - /// The auto translate end icon. - /// - AutoTranslateEnd = 55, - - /// - /// The fire element icon. - /// - ElementFire = 56, - - /// - /// The ice element icon. - /// - ElementIce = 57, - - /// - /// The wind element icon. - /// - ElementWind = 58, - - /// - /// The earth element icon. - /// - ElementEarth = 59, - - /// - /// The lightning element icon. - /// - ElementLightning = 60, - - /// - /// The water element icon. - /// - ElementWater = 61, - - /// - /// The level sync icon. - /// - LevelSync = 62, - - /// - /// The warning icon. - /// - Warning = 63, - - /// - /// The Ishgard region icon. - /// - Ishgard = 64, - - /// - /// The Aetheryte icon. - /// - Aetheryte = 65, - - /// - /// The Aethernet icon. - /// - Aethernet = 66, - - /// - /// The gold star icon. - /// - GoldStar = 67, - - /// - /// The silver star icon. - /// - SilverStar = 68, - - /// - /// The green dot icon. - /// - GreenDot = 70, - - /// - /// The unsheathed sword icon. - /// - SwordUnsheathed = 71, - - /// - /// The sheathed sword icon. - /// - SwordSheathed = 72, - - /// - /// The dice icon. - /// - Dice = 73, - - /// - /// The flyable zone icon. - /// - FlyZone = 74, - - /// - /// The no-flying zone icon. - /// - FlyZoneLocked = 75, - - /// - /// The no-circle/prohibited icon. - /// - NoCircle = 76, - - /// - /// The sprout icon. - /// - NewAdventurer = 77, - - /// - /// The mentor icon. - /// - Mentor = 78, - - /// - /// The PvE mentor icon. - /// - MentorPvE = 79, - - /// - /// The crafting mentor icon. - /// - MentorCrafting = 80, - - /// - /// The PvP mentor icon. - /// - MentorPvP = 81, - - /// - /// The tank role icon. - /// - Tank = 82, - - /// - /// The healer role icon. - /// - Healer = 83, - - /// - /// The DPS role icon. - /// - DPS = 84, - - /// - /// The crafter role icon. - /// - Crafter = 85, - - /// - /// The gatherer role icon. - /// - Gatherer = 86, - - /// - /// The "any" role icon. - /// - AnyClass = 87, - - /// - /// The cross-world icon. - /// - CrossWorld = 88, - - /// - /// The slay type Fate icon. - /// - FateSlay = 89, - - /// - /// The boss type Fate icon. - /// - FateBoss = 90, - - /// - /// The gather type Fate icon. - /// - FateGather = 91, - - /// - /// The defend type Fate icon. - /// - FateDefend = 92, - - /// - /// The escort type Fate icon. - /// - FateEscort = 93, - - /// - /// The special type 1 Fate icon. - /// - FateSpecial1 = 94, - - /// - /// The returner icon. - /// - Returner = 95, - - /// - /// The Far-East region icon. - /// - FarEast = 96, - - /// - /// The Gyr Albania region icon. - /// - GyrAbania = 97, - - /// - /// The special type 2 Fate icon. - /// - FateSpecial2 = 98, - - /// - /// The priority world icon. - /// - PriorityWorld = 99, - - /// - /// The elemental level icon. - /// - ElementalLevel = 100, - - /// - /// The exclamation rectangle icon. - /// - ExclamationRectangle = 101, - - /// - /// The notorious monster icon. - /// - NotoriousMonster = 102, - - /// - /// The recording icon. - /// - Recording = 103, - - /// - /// The alarm icon. - /// - Alarm = 104, - - /// - /// The arrow up icon. - /// - ArrowUp = 105, - - /// - /// The arrow down icon. - /// - ArrowDown = 106, - - /// - /// The Crystarium region icon. - /// - Crystarium = 107, - - /// - /// The mentor problem icon. - /// - MentorProblem = 108, - - /// - /// The unknown gold type Fate icon. - /// - FateUnknownGold = 109, - - /// - /// The orange diamond icon. - /// - OrangeDiamond = 110, - - /// - /// The crafting type Fate icon. - /// - FateCrafting = 111, - } + None = 0, + + /// + /// The controller D-pad up icon. + /// + ControllerDPadUp = 1, + + /// + /// The controller D-pad down icon. + /// + ControllerDPadDown = 2, + + /// + /// The controller D-pad left icon. + /// + ControllerDPadLeft = 3, + + /// + /// The controller D-pad right icon. + /// + ControllerDPadRight = 4, + + /// + /// The controller D-pad up/down icon. + /// + ControllerDPadUpDown = 5, + + /// + /// The controller D-pad left/right icon. + /// + ControllerDPadLeftRight = 6, + + /// + /// The controller D-pad all directions icon. + /// + ControllerDPadAll = 7, + + /// + /// The controller button 0 icon (Xbox: B, PlayStation: Circle). + /// + ControllerButton0 = 8, + + /// + /// The controller button 1 icon (XBox: A, PlayStation: Cross). + /// + ControllerButton1 = 9, + + /// + /// The controller button 2 icon (XBox: X, PlayStation: Square). + /// + ControllerButton2 = 10, + + /// + /// The controller button 3 icon (BBox: Y, PlayStation: Triangle). + /// + ControllerButton3 = 11, + + /// + /// The controller left shoulder button icon. + /// + ControllerShoulderLeft = 12, + + /// + /// The controller right shoulder button icon. + /// + ControllerShoulderRight = 13, + + /// + /// The controller left trigger button icon. + /// + ControllerTriggerLeft = 14, + + /// + /// The controller right trigger button icon. + /// + ControllerTriggerRight = 15, + + /// + /// The controller left analog stick in icon. + /// + ControllerAnalogLeftStickIn = 16, + + /// + /// The controller right analog stick in icon. + /// + ControllerAnalogRightStickIn = 17, + + /// + /// The controller start button icon. + /// + ControllerStart = 18, + + /// + /// The controller back button icon. + /// + ControllerBack = 19, + + /// + /// The controller left analog stick icon. + /// + ControllerAnalogLeftStick = 20, + + /// + /// The controller left analog stick up/down icon. + /// + ControllerAnalogLeftStickUpDown = 21, + + /// + /// The controller left analog stick left/right icon. + /// + ControllerAnalogLeftStickLeftRight = 22, + + /// + /// The controller right analog stick icon. + /// + ControllerAnalogRightStick = 23, + + /// + /// The controller right analog stick up/down icon. + /// + ControllerAnalogRightStickUpDown = 24, + + /// + /// The controller right analog stick left/right icon. + /// + ControllerAnalogRightStickLeftRight = 25, + + /// + /// The La Noscea region icon. + /// + LaNoscea = 51, + + /// + /// The Black Shroud region icon. + /// + BlackShroud = 52, + + /// + /// The Thanalan region icon. + /// + Thanalan = 53, + + /// + /// The auto translate begin icon. + /// + AutoTranslateBegin = 54, + + /// + /// The auto translate end icon. + /// + AutoTranslateEnd = 55, + + /// + /// The fire element icon. + /// + ElementFire = 56, + + /// + /// The ice element icon. + /// + ElementIce = 57, + + /// + /// The wind element icon. + /// + ElementWind = 58, + + /// + /// The earth element icon. + /// + ElementEarth = 59, + + /// + /// The lightning element icon. + /// + ElementLightning = 60, + + /// + /// The water element icon. + /// + ElementWater = 61, + + /// + /// The level sync icon. + /// + LevelSync = 62, + + /// + /// The warning icon. + /// + Warning = 63, + + /// + /// The Ishgard region icon. + /// + Ishgard = 64, + + /// + /// The Aetheryte icon. + /// + Aetheryte = 65, + + /// + /// The Aethernet icon. + /// + Aethernet = 66, + + /// + /// The gold star icon. + /// + GoldStar = 67, + + /// + /// The silver star icon. + /// + SilverStar = 68, + + /// + /// The green dot icon. + /// + GreenDot = 70, + + /// + /// The unsheathed sword icon. + /// + SwordUnsheathed = 71, + + /// + /// The sheathed sword icon. + /// + SwordSheathed = 72, + + /// + /// The dice icon. + /// + Dice = 73, + + /// + /// The flyable zone icon. + /// + FlyZone = 74, + + /// + /// The no-flying zone icon. + /// + FlyZoneLocked = 75, + + /// + /// The no-circle/prohibited icon. + /// + NoCircle = 76, + + /// + /// The sprout icon. + /// + NewAdventurer = 77, + + /// + /// The mentor icon. + /// + Mentor = 78, + + /// + /// The PvE mentor icon. + /// + MentorPvE = 79, + + /// + /// The crafting mentor icon. + /// + MentorCrafting = 80, + + /// + /// The PvP mentor icon. + /// + MentorPvP = 81, + + /// + /// The tank role icon. + /// + Tank = 82, + + /// + /// The healer role icon. + /// + Healer = 83, + + /// + /// The DPS role icon. + /// + DPS = 84, + + /// + /// The crafter role icon. + /// + Crafter = 85, + + /// + /// The gatherer role icon. + /// + Gatherer = 86, + + /// + /// The "any" role icon. + /// + AnyClass = 87, + + /// + /// The cross-world icon. + /// + CrossWorld = 88, + + /// + /// The slay type Fate icon. + /// + FateSlay = 89, + + /// + /// The boss type Fate icon. + /// + FateBoss = 90, + + /// + /// The gather type Fate icon. + /// + FateGather = 91, + + /// + /// The defend type Fate icon. + /// + FateDefend = 92, + + /// + /// The escort type Fate icon. + /// + FateEscort = 93, + + /// + /// The special type 1 Fate icon. + /// + FateSpecial1 = 94, + + /// + /// The returner icon. + /// + Returner = 95, + + /// + /// The Far-East region icon. + /// + FarEast = 96, + + /// + /// The Gyr Albania region icon. + /// + GyrAbania = 97, + + /// + /// The special type 2 Fate icon. + /// + FateSpecial2 = 98, + + /// + /// The priority world icon. + /// + PriorityWorld = 99, + + /// + /// The elemental level icon. + /// + ElementalLevel = 100, + + /// + /// The exclamation rectangle icon. + /// + ExclamationRectangle = 101, + + /// + /// The notorious monster icon. + /// + NotoriousMonster = 102, + + /// + /// The recording icon. + /// + Recording = 103, + + /// + /// The alarm icon. + /// + Alarm = 104, + + /// + /// The arrow up icon. + /// + ArrowUp = 105, + + /// + /// The arrow down icon. + /// + ArrowDown = 106, + + /// + /// The Crystarium region icon. + /// + Crystarium = 107, + + /// + /// The mentor problem icon. + /// + MentorProblem = 108, + + /// + /// The unknown gold type Fate icon. + /// + FateUnknownGold = 109, + + /// + /// The orange diamond icon. + /// + OrangeDiamond = 110, + + /// + /// The crafting type Fate icon. + /// + FateCrafting = 111, } diff --git a/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs b/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs index 5bcb2deb0..7be809ba2 100644 --- a/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs +++ b/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs @@ -1,13 +1,12 @@ -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// An interface binding for a payload that can provide readable Text. +/// +public interface ITextProvider { /// - /// An interface binding for a payload that can provide readable Text. + /// Gets the readable text. /// - public interface ITextProvider - { - /// - /// Gets the readable text. - /// - string Text { get; } - } + string Text { get; } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index 8a1e03cb1..b1fe708b3 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -15,405 +15,404 @@ using Serilog; // - [SeString] some way to add surrounding formatting information as flags/data to text (or other?) payloads? // eg, if a text payload is surrounded by italics payloads, strip them out and mark the text payload as italicized -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// This class represents a parsed SeString payload. +/// +public abstract partial class Payload { + // private for now, since subclasses shouldn't interact with this. + // To force-invalidate it, Dirty can be set to true + private byte[] encodedData; + /// - /// This class represents a parsed SeString payload. + /// Gets the Lumina instance to use for any necessary data lookups. /// - public abstract partial class Payload + public DataManager DataResolver => Service.Get(); + + /// + /// Gets the type of this payload. + /// + public abstract PayloadType Type { get; } + + /// + /// Gets or sets a value indicating whether whether this payload has been modified since the last Encode(). + /// + public bool Dirty { get; protected set; } = true; + + /// + /// Decodes a binary representation of a payload into its corresponding nice object payload. + /// + /// A reader positioned at the start of the payload, and containing at least one entire payload. + /// The constructed Payload-derived object that was decoded from the binary data. + public static Payload Decode(BinaryReader reader) { - // private for now, since subclasses shouldn't interact with this. - // To force-invalidate it, Dirty can be set to true - private byte[] encodedData; + var payloadStartPos = reader.BaseStream.Position; - /// - /// Gets the Lumina instance to use for any necessary data lookups. - /// - public DataManager DataResolver => Service.Get(); + Payload payload; - /// - /// Gets the type of this payload. - /// - public abstract PayloadType Type { get; } - - /// - /// Gets or sets a value indicating whether whether this payload has been modified since the last Encode(). - /// - public bool Dirty { get; protected set; } = true; - - /// - /// Decodes a binary representation of a payload into its corresponding nice object payload. - /// - /// A reader positioned at the start of the payload, and containing at least one entire payload. - /// The constructed Payload-derived object that was decoded from the binary data. - public static Payload Decode(BinaryReader reader) + var initialByte = reader.ReadByte(); + reader.BaseStream.Position--; + if (initialByte != START_BYTE) { - var payloadStartPos = reader.BaseStream.Position; - - Payload payload; - - var initialByte = reader.ReadByte(); - reader.BaseStream.Position--; - if (initialByte != START_BYTE) - { - payload = DecodeText(reader); - } - else - { - payload = DecodeChunk(reader); - } - - // for now, cache off the actual binary data for this payload, so we don't have to - // regenerate it if the payload isn't modified - // TODO: probably better ways to handle this - var payloadEndPos = reader.BaseStream.Position; - - reader.BaseStream.Position = payloadStartPos; - payload.encodedData = reader.ReadBytes((int)(payloadEndPos - payloadStartPos)); - payload.Dirty = false; - - // Log.Verbose($"got payload bytes {BitConverter.ToString(payload.encodedData).Replace("-", " ")}"); - - reader.BaseStream.Position = payloadEndPos; - - return payload; + payload = DecodeText(reader); + } + else + { + payload = DecodeChunk(reader); } - /// - /// Encode this payload object into a byte[] useable in-game for things like the chat log. - /// - /// If true, ignores any cached value and forcibly reencodes the payload from its internal representation. - /// A byte[] suitable for use with in-game handlers such as the chat log. - public byte[] Encode(bool force = false) - { - if (this.Dirty || force) - { - this.encodedData = this.EncodeImpl(); - this.Dirty = false; - } + // for now, cache off the actual binary data for this payload, so we don't have to + // regenerate it if the payload isn't modified + // TODO: probably better ways to handle this + var payloadEndPos = reader.BaseStream.Position; - return this.encodedData; - } + reader.BaseStream.Position = payloadStartPos; + payload.encodedData = reader.ReadBytes((int)(payloadEndPos - payloadStartPos)); + payload.Dirty = false; - /// - /// Encodes the internal state of this payload into a byte[] suitable for sending to in-game - /// handlers such as the chat log. - /// - /// Encoded binary payload data suitable for use with in-game handlers. - protected abstract byte[] EncodeImpl(); + // Log.Verbose($"got payload bytes {BitConverter.ToString(payload.encodedData).Replace("-", " ")}"); - /// - /// Decodes a byte stream from the game into a payload object. - /// - /// A BinaryReader containing at least all the data for this payload. - /// The location holding the end of the data for this payload. - // TODO: endOfStream is somewhat legacy now that payload length is always handled correctly. - // This could be changed to just take a straight byte[], but that would complicate reading - // but we could probably at least remove the end param - protected abstract void DecodeImpl(BinaryReader reader, long endOfStream); + reader.BaseStream.Position = payloadEndPos; - private static Payload DecodeChunk(BinaryReader reader) - { - Payload payload = null; - - reader.ReadByte(); // START_BYTE - var chunkType = (SeStringChunkType)reader.ReadByte(); - var chunkLen = GetInteger(reader); - - var packetStart = reader.BaseStream.Position; - - // any unhandled payload types will be turned into a RawPayload with the exact same binary data - switch (chunkType) - { - case SeStringChunkType.EmphasisItalic: - payload = new EmphasisItalicPayload(); - break; - - case SeStringChunkType.NewLine: - payload = NewLinePayload.Payload; - break; - - case SeStringChunkType.SeHyphen: - payload = SeHyphenPayload.Payload; - break; - - case SeStringChunkType.Interactable: - { - var subType = (EmbeddedInfoType)reader.ReadByte(); - switch (subType) - { - case EmbeddedInfoType.PlayerName: - payload = new PlayerPayload(); - break; - - case EmbeddedInfoType.ItemLink: - payload = new ItemPayload(); - break; - - case EmbeddedInfoType.MapPositionLink: - payload = new MapLinkPayload(); - break; - - case EmbeddedInfoType.Status: - payload = new StatusPayload(); - break; - - case EmbeddedInfoType.QuestLink: - payload = new QuestPayload(); - break; - - case EmbeddedInfoType.DalamudLink: - payload = new DalamudLinkPayload(); - break; - - case EmbeddedInfoType.LinkTerminator: - // this has no custom handling and so needs to fallthrough to ensure it is captured - default: - // but I'm also tired of this log - if (subType != EmbeddedInfoType.LinkTerminator) - { - Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType); - } - - // rewind so we capture the Interactable byte in the raw data - reader.BaseStream.Seek(-1, SeekOrigin.Current); - break; - } - } - - break; - - case SeStringChunkType.AutoTranslateKey: - payload = new AutoTranslatePayload(); - break; - - case SeStringChunkType.UIForeground: - payload = new UIForegroundPayload(); - break; - - case SeStringChunkType.UIGlow: - payload = new UIGlowPayload(); - break; - - case SeStringChunkType.Icon: - payload = new IconPayload(); - break; - - default: - Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); - break; - } - - payload ??= new RawPayload((byte)chunkType); - payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1); - - // read through the rest of the packet - var readBytes = (uint)(reader.BaseStream.Position - packetStart); - reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker - - return payload; - } - - private static Payload DecodeText(BinaryReader reader) - { - var payload = new TextPayload(); - payload.DecodeImpl(reader, reader.BaseStream.Length); - - return payload; - } + return payload; } /// - /// Parsing helpers. + /// Encode this payload object into a byte[] useable in-game for things like the chat log. /// - public abstract partial class Payload + /// If true, ignores any cached value and forcibly reencodes the payload from its internal representation. + /// A byte[] suitable for use with in-game handlers such as the chat log. + public byte[] Encode(bool force = false) { - /// - /// The start byte of a payload. - /// - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] - protected const byte START_BYTE = 0x02; - - /// - /// The end byte of a payload. - /// - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] - protected const byte END_BYTE = 0x03; - - /// - /// This represents the type of embedded info in a payload. - /// - public enum EmbeddedInfoType + if (this.Dirty || force) { - /// - /// A player's name. - /// - PlayerName = 0x01, - - /// - /// The link to an iteme. - /// - ItemLink = 0x03, - - /// - /// The link to a map position. - /// - MapPositionLink = 0x04, - - /// - /// The link to a quest. - /// - QuestLink = 0x05, - - /// - /// A status effect. - /// - Status = 0x09, - - /// - /// A custom Dalamud link. - /// - DalamudLink = 0x0F, - - /// - /// A link terminator. - /// - /// - /// It is not exactly clear what this is, but seems to always follow a link. - /// - LinkTerminator = 0xCF, + this.encodedData = this.EncodeImpl(); + this.Dirty = false; } - /// - /// This represents the type of payload and how it should be encoded. - /// - protected enum SeStringChunkType + return this.encodedData; + } + + /// + /// Encodes the internal state of this payload into a byte[] suitable for sending to in-game + /// handlers such as the chat log. + /// + /// Encoded binary payload data suitable for use with in-game handlers. + protected abstract byte[] EncodeImpl(); + + /// + /// Decodes a byte stream from the game into a payload object. + /// + /// A BinaryReader containing at least all the data for this payload. + /// The location holding the end of the data for this payload. + // TODO: endOfStream is somewhat legacy now that payload length is always handled correctly. + // This could be changed to just take a straight byte[], but that would complicate reading + // but we could probably at least remove the end param + protected abstract void DecodeImpl(BinaryReader reader, long endOfStream); + + private static Payload DecodeChunk(BinaryReader reader) + { + Payload payload = null; + + reader.ReadByte(); // START_BYTE + var chunkType = (SeStringChunkType)reader.ReadByte(); + var chunkLen = GetInteger(reader); + + var packetStart = reader.BaseStream.Position; + + // any unhandled payload types will be turned into a RawPayload with the exact same binary data + switch (chunkType) { - /// - /// See the class. - /// - Icon = 0x12, + case SeStringChunkType.EmphasisItalic: + payload = new EmphasisItalicPayload(); + break; - /// - /// See the class. - /// - EmphasisItalic = 0x1A, + case SeStringChunkType.NewLine: + payload = NewLinePayload.Payload; + break; - /// - /// See the . - /// - NewLine = 0x10, + case SeStringChunkType.SeHyphen: + payload = SeHyphenPayload.Payload; + break; - /// - /// See the class. - /// - SeHyphen = 0x1F, - - /// - /// See any of the link-type classes: - /// , - /// , - /// , - /// , - /// , - /// . - /// - Interactable = 0x27, - - /// - /// See the class. - /// - AutoTranslateKey = 0x2E, - - /// - /// See the class. - /// - UIForeground = 0x48, - - /// - /// See the class. - /// - UIGlow = 0x49, - } - - /// - /// Retrieve the packed integer from SE's native data format. - /// - /// The BinaryReader instance. - /// An integer. - // made protected, unless we actually want to use it externally - // in which case it should probably go live somewhere else - protected static uint GetInteger(BinaryReader input) - { - uint marker = input.ReadByte(); - if (marker < 0xD0) - return marker - 1; - - // the game adds 0xF0 marker for values >= 0xCF - // uasge of 0xD0-0xEF is unknown, should we throw here? - // if (marker < 0xF0) throw new NotSupportedException(); - - marker = (marker + 1) & 0b1111; - - var ret = new byte[4]; - for (var i = 3; i >= 0; i--) - { - ret[i] = (marker & (1 << i)) == 0 ? (byte)0 : input.ReadByte(); - } - - return BitConverter.ToUInt32(ret, 0); - } - - /// - /// Create a packed integer in Se's native data format. - /// - /// The value to pack. - /// A packed integer. - protected static byte[] MakeInteger(uint value) - { - if (value < 0xCF) - { - return new byte[] { (byte)(value + 1) }; - } - - var bytes = BitConverter.GetBytes(value); - - var ret = new List() { 0xF0 }; - for (var i = 3; i >= 0; i--) - { - if (bytes[i] != 0) + case SeStringChunkType.Interactable: { - ret.Add(bytes[i]); - ret[0] |= (byte)(1 << i); + var subType = (EmbeddedInfoType)reader.ReadByte(); + switch (subType) + { + case EmbeddedInfoType.PlayerName: + payload = new PlayerPayload(); + break; + + case EmbeddedInfoType.ItemLink: + payload = new ItemPayload(); + break; + + case EmbeddedInfoType.MapPositionLink: + payload = new MapLinkPayload(); + break; + + case EmbeddedInfoType.Status: + payload = new StatusPayload(); + break; + + case EmbeddedInfoType.QuestLink: + payload = new QuestPayload(); + break; + + case EmbeddedInfoType.DalamudLink: + payload = new DalamudLinkPayload(); + break; + + case EmbeddedInfoType.LinkTerminator: + // this has no custom handling and so needs to fallthrough to ensure it is captured + default: + // but I'm also tired of this log + if (subType != EmbeddedInfoType.LinkTerminator) + { + Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType); + } + + // rewind so we capture the Interactable byte in the raw data + reader.BaseStream.Seek(-1, SeekOrigin.Current); + break; + } } - } - ret[0] -= 1; + break; - return ret.ToArray(); + case SeStringChunkType.AutoTranslateKey: + payload = new AutoTranslatePayload(); + break; + + case SeStringChunkType.UIForeground: + payload = new UIForegroundPayload(); + break; + + case SeStringChunkType.UIGlow: + payload = new UIGlowPayload(); + break; + + case SeStringChunkType.Icon: + payload = new IconPayload(); + break; + + default: + Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); + break; } - /// - /// From a binary packed integer, get the high and low bytes. - /// - /// The BinaryReader instance. - /// The high and low bytes. - protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input) - { - var value = GetInteger(input); - return (value >> 16, value & 0xFFFF); - } + payload ??= new RawPayload((byte)chunkType); + payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1); - /// - /// Create a packed integer from the given high and low bytes. - /// - /// The high order bytes. - /// The low order bytes. - /// A packed integer. - protected static byte[] MakePackedInteger(uint high, uint low) - { - var value = (high << 16) | (low & 0xFFFF); - return MakeInteger(value); - } + // read through the rest of the packet + var readBytes = (uint)(reader.BaseStream.Position - packetStart); + reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker + + return payload; + } + + private static Payload DecodeText(BinaryReader reader) + { + var payload = new TextPayload(); + payload.DecodeImpl(reader, reader.BaseStream.Length); + + return payload; + } +} + +/// +/// Parsing helpers. +/// +public abstract partial class Payload +{ + /// + /// The start byte of a payload. + /// + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] + protected const byte START_BYTE = 0x02; + + /// + /// The end byte of a payload. + /// + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] + protected const byte END_BYTE = 0x03; + + /// + /// This represents the type of embedded info in a payload. + /// + public enum EmbeddedInfoType + { + /// + /// A player's name. + /// + PlayerName = 0x01, + + /// + /// The link to an iteme. + /// + ItemLink = 0x03, + + /// + /// The link to a map position. + /// + MapPositionLink = 0x04, + + /// + /// The link to a quest. + /// + QuestLink = 0x05, + + /// + /// A status effect. + /// + Status = 0x09, + + /// + /// A custom Dalamud link. + /// + DalamudLink = 0x0F, + + /// + /// A link terminator. + /// + /// + /// It is not exactly clear what this is, but seems to always follow a link. + /// + LinkTerminator = 0xCF, + } + + /// + /// This represents the type of payload and how it should be encoded. + /// + protected enum SeStringChunkType + { + /// + /// See the class. + /// + Icon = 0x12, + + /// + /// See the class. + /// + EmphasisItalic = 0x1A, + + /// + /// See the . + /// + NewLine = 0x10, + + /// + /// See the class. + /// + SeHyphen = 0x1F, + + /// + /// See any of the link-type classes: + /// , + /// , + /// , + /// , + /// , + /// . + /// + Interactable = 0x27, + + /// + /// See the class. + /// + AutoTranslateKey = 0x2E, + + /// + /// See the class. + /// + UIForeground = 0x48, + + /// + /// See the class. + /// + UIGlow = 0x49, + } + + /// + /// Retrieve the packed integer from SE's native data format. + /// + /// The BinaryReader instance. + /// An integer. + // made protected, unless we actually want to use it externally + // in which case it should probably go live somewhere else + protected static uint GetInteger(BinaryReader input) + { + uint marker = input.ReadByte(); + if (marker < 0xD0) + return marker - 1; + + // the game adds 0xF0 marker for values >= 0xCF + // uasge of 0xD0-0xEF is unknown, should we throw here? + // if (marker < 0xF0) throw new NotSupportedException(); + + marker = (marker + 1) & 0b1111; + + var ret = new byte[4]; + for (var i = 3; i >= 0; i--) + { + ret[i] = (marker & (1 << i)) == 0 ? (byte)0 : input.ReadByte(); + } + + return BitConverter.ToUInt32(ret, 0); + } + + /// + /// Create a packed integer in Se's native data format. + /// + /// The value to pack. + /// A packed integer. + protected static byte[] MakeInteger(uint value) + { + if (value < 0xCF) + { + return new byte[] { (byte)(value + 1) }; + } + + var bytes = BitConverter.GetBytes(value); + + var ret = new List() { 0xF0 }; + for (var i = 3; i >= 0; i--) + { + if (bytes[i] != 0) + { + ret.Add(bytes[i]); + ret[0] |= (byte)(1 << i); + } + } + + ret[0] -= 1; + + return ret.ToArray(); + } + + /// + /// From a binary packed integer, get the high and low bytes. + /// + /// The BinaryReader instance. + /// The high and low bytes. + protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input) + { + var value = GetInteger(input); + return (value >> 16, value & 0xFFFF); + } + + /// + /// Create a packed integer from the given high and low bytes. + /// + /// The high order bytes. + /// The low order bytes. + /// A packed integer. + protected static byte[] MakePackedInteger(uint high, uint low) + { + var value = (high << 16) | (low & 0xFFFF); + return MakeInteger(value); } } diff --git a/Dalamud/Game/Text/SeStringHandling/PayloadType.cs b/Dalamud/Game/Text/SeStringHandling/PayloadType.cs index 93bcc7e3e..c58f2954d 100644 --- a/Dalamud/Game/Text/SeStringHandling/PayloadType.cs +++ b/Dalamud/Game/Text/SeStringHandling/PayloadType.cs @@ -1,83 +1,82 @@ -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// All parsed types of SeString payloads. +/// +public enum PayloadType { /// - /// All parsed types of SeString payloads. + /// An unknown SeString. /// - public enum PayloadType - { - /// - /// An unknown SeString. - /// - Unknown, + Unknown, - /// - /// An SeString payload representing a player link. - /// - Player, + /// + /// An SeString payload representing a player link. + /// + Player, - /// - /// An SeString payload representing an Item link. - /// - Item, + /// + /// An SeString payload representing an Item link. + /// + Item, - /// - /// An SeString payload representing an Status Effect link. - /// - Status, + /// + /// An SeString payload representing an Status Effect link. + /// + Status, - /// - /// An SeString payload representing raw, typed text. - /// - RawText, + /// + /// An SeString payload representing raw, typed text. + /// + RawText, - /// - /// An SeString payload representing a text foreground color. - /// - UIForeground, + /// + /// An SeString payload representing a text foreground color. + /// + UIForeground, - /// - /// An SeString payload representing a text glow color. - /// - UIGlow, + /// + /// An SeString payload representing a text glow color. + /// + UIGlow, - /// - /// An SeString payload representing a map position link, such as from <flag> or <pos>. - /// - MapLink, + /// + /// An SeString payload representing a map position link, such as from <flag> or <pos>. + /// + MapLink, - /// - /// An SeString payload representing an auto-translate dictionary entry. - /// - AutoTranslateText, + /// + /// An SeString payload representing an auto-translate dictionary entry. + /// + AutoTranslateText, - /// - /// An SeString payload representing italic emphasis formatting on text. - /// - EmphasisItalic, + /// + /// An SeString payload representing italic emphasis formatting on text. + /// + EmphasisItalic, - /// - /// An SeString payload representing a bitmap icon. - /// - Icon, + /// + /// An SeString payload representing a bitmap icon. + /// + Icon, - /// - /// A SeString payload representing a quest link. - /// - Quest, + /// + /// A SeString payload representing a quest link. + /// + Quest, - /// - /// A SeString payload representing a custom clickable link for dalamud plugins. - /// - DalamudLink, + /// + /// A SeString payload representing a custom clickable link for dalamud plugins. + /// + DalamudLink, - /// - /// An SeString payload representing a newline character. - /// - NewLine, + /// + /// An SeString payload representing a newline character. + /// + NewLine, - /// - /// An SeString payload representing a doublewide SE hypen. - /// - SeHyphen, - } + /// + /// An SeString payload representing a doublewide SE hypen. + /// + SeHyphen, } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index 8e9eb5b65..e239037f6 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -7,165 +7,164 @@ using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload containing an auto-translation/completion chat message. +/// +public class AutoTranslatePayload : Payload, ITextProvider { + private string text; + + [JsonProperty] + private uint group; + + [JsonProperty] + private uint key; + /// - /// An SeString Payload containing an auto-translation/completion chat message. + /// Initializes a new instance of the class. + /// Creates a new auto-translate payload. /// - public class AutoTranslatePayload : Payload, ITextProvider + /// The group id for this message. + /// The key/row id for this message. Which table this is in depends on the group id and details the Completion table. + /// + /// This table is somewhat complicated in structure, and so using this constructor may not be very nice. + /// There is probably little use to create one of these, however. + /// + public AutoTranslatePayload(uint group, uint key) { - private string text; + // TODO: friendlier ctor? not sure how to handle that given how weird the tables are + this.group = group; + this.key = key; + } - [JsonProperty] - private uint group; + /// + /// Initializes a new instance of the class. + /// + internal AutoTranslatePayload() + { + } - [JsonProperty] - private uint key; + /// + public override PayloadType Type => PayloadType.AutoTranslateText; - /// - /// Initializes a new instance of the class. - /// Creates a new auto-translate payload. - /// - /// The group id for this message. - /// The key/row id for this message. Which table this is in depends on the group id and details the Completion table. - /// - /// This table is somewhat complicated in structure, and so using this constructor may not be very nice. - /// There is probably little use to create one of these, however. - /// - public AutoTranslatePayload(uint group, uint key) + /// + /// Gets the actual text displayed in-game for this payload. + /// + /// + /// Value is evaluated lazily and cached. + /// + public string Text + { + get { - // TODO: friendlier ctor? not sure how to handle that given how weird the tables are - this.group = group; - this.key = key; + // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads + return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}"; } + } - /// - /// Initializes a new instance of the class. - /// - internal AutoTranslatePayload() - { - } + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}"; + } - /// - public override PayloadType Type => PayloadType.AutoTranslateText; + /// + protected override byte[] EncodeImpl() + { + var keyBytes = MakeInteger(this.key); - /// - /// Gets the actual text displayed in-game for this payload. - /// - /// - /// Value is evaluated lazily and cached. - /// - public string Text - { - get - { - // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads - return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}"; - } - } - - /// - /// Returns a string that represents the current object. - /// - /// A string that represents the current object. - public override string ToString() - { - return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}"; - } - - /// - protected override byte[] EncodeImpl() - { - var keyBytes = MakeInteger(this.key); - - var chunkLen = keyBytes.Length + 2; - var bytes = new List() + var chunkLen = keyBytes.Length + 2; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, (byte)this.group, }; - bytes.AddRange(keyBytes); - bytes.Add(END_BYTE); + bytes.AddRange(keyBytes); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // this seems to always be a bare byte, and not following normal integer encoding + // the values in the table are all <70 so this is presumably ok + this.group = reader.ReadByte(); + + this.key = GetInteger(reader); + } + + private string Resolve() + { + string value = null; + + var sheet = this.DataResolver.GetExcelSheet(); + + Completion row = null; + try { - // this seems to always be a bare byte, and not following normal integer encoding - // the values in the table are all <70 so this is presumably ok - this.group = reader.ReadByte(); - - this.key = GetInteger(reader); + // try to get the row in the Completion table itself, because this is 'easiest' + // The row may not exist at all (if the Key is for another table), or it could be the wrong row + // (again, if it's meant for another table) + row = sheet.GetRow(this.key); } - - private string Resolve() + catch { - string value = null; + } // don't care, row will be null - var sheet = this.DataResolver.GetExcelSheet(); - - Completion row = null; + if (row?.Group == this.group) + { + // if the row exists in this table and the group matches, this is actually the correct data + value = row.Text; + } + else + { try { - // try to get the row in the Completion table itself, because this is 'easiest' - // The row may not exist at all (if the Key is for another table), or it could be the wrong row - // (again, if it's meant for another table) - row = sheet.GetRow(this.key); - } - catch - { - } // don't care, row will be null + // we need to get the linked table and do the lookup there instead + // in this case, there will only be one entry for this group id + row = sheet.First(r => r.Group == this.group); + // many of the names contain valid id ranges after the table name, but we don't need those + var actualTableName = row.LookupTable.RawString.Split('[')[0]; - if (row?.Group == this.group) - { - // if the row exists in this table and the group matches, this is actually the correct data - value = row.Text; - } - else - { - try + var name = actualTableName switch { - // we need to get the linked table and do the lookup there instead - // in this case, there will only be one entry for this group id - row = sheet.First(r => r.Group == this.group); - // many of the names contain valid id ranges after the table name, but we don't need those - var actualTableName = row.LookupTable.RawString.Split('[')[0]; + "Action" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "ActionComboRoute" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "BuddyAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "ClassJob" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "Companion" => this.DataResolver.GetExcelSheet().GetRow(this.key).Singular, + "CraftAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "GeneralAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "GuardianDeity" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "MainCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "Mount" => this.DataResolver.GetExcelSheet().GetRow(this.key).Singular, + "Pet" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "PetAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "PetMirage" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "PlaceName" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "Race" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, + "TextCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Command, + "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, + "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + _ => throw new Exception(actualTableName), + }; - var name = actualTableName switch - { - "Action" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "ActionComboRoute" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "BuddyAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "ClassJob" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "Companion" => this.DataResolver.GetExcelSheet().GetRow(this.key).Singular, - "CraftAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "GeneralAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "GuardianDeity" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "MainCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "Mount" => this.DataResolver.GetExcelSheet().GetRow(this.key).Singular, - "Pet" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "PetAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "PetMirage" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "PlaceName" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "Race" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, - "TextCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Command, - "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, - "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - _ => throw new Exception(actualTableName), - }; - - value = name; - } - catch (Exception e) - { - Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}"); - } + value = name; + } + catch (Exception e) + { + Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}"); } - - return value; } + + return value; } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index 6aedf04ed..7a1ed417c 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -3,57 +3,56 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// This class represents a custom Dalamud clickable chat link. +/// +public class DalamudLinkPayload : Payload { + /// + public override PayloadType Type => PayloadType.DalamudLink; + /// - /// This class represents a custom Dalamud clickable chat link. + /// Gets the plugin command ID to be linked. /// - public class DalamudLinkPayload : Payload + public uint CommandId { get; internal set; } = 0; + + /// + /// Gets the plugin name to be linked. + /// + public string Plugin { get; internal set; } = string.Empty; + + /// + public override string ToString() { - /// - public override PayloadType Type => PayloadType.DalamudLink; + return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; + } - /// - /// Gets the plugin command ID to be linked. - /// - public uint CommandId { get; internal set; } = 0; + /// + protected override byte[] EncodeImpl() + { + var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin); + var commandBytes = MakeInteger(this.CommandId); + var chunkLen = 3 + pluginBytes.Length + commandBytes.Length; - /// - /// Gets the plugin name to be linked. - /// - public string Plugin { get; internal set; } = string.Empty; - - /// - public override string ToString() + if (chunkLen > 255) { - return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; + throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload"); } - /// - protected override byte[] EncodeImpl() - { - var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin); - var commandBytes = MakeInteger(this.CommandId); - var chunkLen = 3 + pluginBytes.Length + commandBytes.Length; + var bytes = new List { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink }; + bytes.Add((byte)pluginBytes.Length); + bytes.AddRange(pluginBytes); + bytes.AddRange(commandBytes); + bytes.Add(END_BYTE); + return bytes.ToArray(); + } - if (chunkLen > 255) - { - throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload"); - } - - var bytes = new List { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink }; - bytes.Add((byte)pluginBytes.Length); - bytes.AddRange(pluginBytes); - bytes.AddRange(commandBytes); - bytes.Add(END_BYTE); - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte())); - this.CommandId = GetInteger(reader); - } + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte())); + this.CommandId = GetInteger(reader); } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs index b6c3bbd76..ff3d67d06 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs @@ -2,81 +2,80 @@ using System; using System.Collections.Generic; using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload containing information about enabling or disabling italics formatting on following text. +/// +/// +/// As with other formatting payloads, this is only useful in a payload block, where it affects any subsequent +/// text payloads. +/// +public class EmphasisItalicPayload : Payload { /// - /// An SeString Payload containing information about enabling or disabling italics formatting on following text. + /// Initializes a new instance of the class. + /// Creates an EmphasisItalicPayload. /// - /// - /// As with other formatting payloads, this is only useful in a payload block, where it affects any subsequent - /// text payloads. - /// - public class EmphasisItalicPayload : Payload + /// Whether italics formatting should be enabled or disabled for following text. + public EmphasisItalicPayload(bool enabled) { - /// - /// Initializes a new instance of the class. - /// Creates an EmphasisItalicPayload. - /// - /// Whether italics formatting should be enabled or disabled for following text. - public EmphasisItalicPayload(bool enabled) - { - this.IsEnabled = enabled; - } + this.IsEnabled = enabled; + } - /// - /// Initializes a new instance of the class. - /// Creates an EmphasisItalicPayload. - /// - internal EmphasisItalicPayload() - { - } + /// + /// Initializes a new instance of the class. + /// Creates an EmphasisItalicPayload. + /// + internal EmphasisItalicPayload() + { + } - /// - /// Gets a payload representing enabling italics on following text. - /// - public static EmphasisItalicPayload ItalicsOn => new(true); + /// + /// Gets a payload representing enabling italics on following text. + /// + public static EmphasisItalicPayload ItalicsOn => new(true); - /// - /// Gets a payload representing disabling italics on following text. - /// - public static EmphasisItalicPayload ItalicsOff => new(false); + /// + /// Gets a payload representing disabling italics on following text. + /// + public static EmphasisItalicPayload ItalicsOff => new(false); - /// - /// Gets a value indicating whether this payload enables italics formatting for following text. - /// - public bool IsEnabled { get; private set; } + /// + /// Gets a value indicating whether this payload enables italics formatting for following text. + /// + public bool IsEnabled { get; private set; } - /// - public override PayloadType Type => PayloadType.EmphasisItalic; + /// + public override PayloadType Type => PayloadType.EmphasisItalic; - /// - public override string ToString() - { - return $"{this.Type} - Enabled: {this.IsEnabled}"; - } + /// + public override string ToString() + { + return $"{this.Type} - Enabled: {this.IsEnabled}"; + } - /// - protected override byte[] EncodeImpl() - { - // realistically this will always be a single byte of value 1 or 2 - // but we'll treat it normally anyway - var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0); + /// + protected override byte[] EncodeImpl() + { + // realistically this will always be a single byte of value 1 or 2 + // but we'll treat it normally anyway + var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0); - var chunkLen = enabledBytes.Length + 1; - var bytes = new List() + var chunkLen = enabledBytes.Length + 1; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen, }; - bytes.AddRange(enabledBytes); - bytes.Add(END_BYTE); + bytes.AddRange(enabledBytes); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.IsEnabled = GetInteger(reader) == 1; - } + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.IsEnabled = GetInteger(reader) == 1; } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs index 69588f085..5336d2242 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs @@ -1,63 +1,62 @@ using System.Collections.Generic; using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// SeString payload representing a bitmap icon from fontIcon. +/// +public class IconPayload : Payload { /// - /// SeString payload representing a bitmap icon from fontIcon. + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. /// - public class IconPayload : Payload + /// The Icon. + public IconPayload(BitmapFontIcon icon) { - /// - /// Initializes a new instance of the class. - /// Create a Icon payload for the specified icon. - /// - /// The Icon. - public IconPayload(BitmapFontIcon icon) + this.Icon = icon; + } + + /// + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. + /// + internal IconPayload() + { + } + + /// + public override PayloadType Type => PayloadType.Icon; + + /// + /// Gets or sets the icon the payload represents. + /// + public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None; + + /// + public override string ToString() + { + return $"{this.Type} - {this.Icon}"; + } + + /// + protected override byte[] EncodeImpl() + { + var indexBytes = MakeInteger((uint)this.Icon); + var chunkLen = indexBytes.Length + 1; + var bytes = new List(new byte[] { - this.Icon = icon; - } - - /// - /// Initializes a new instance of the class. - /// Create a Icon payload for the specified icon. - /// - internal IconPayload() - { - } - - /// - public override PayloadType Type => PayloadType.Icon; - - /// - /// Gets or sets the icon the payload represents. - /// - public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None; - - /// - public override string ToString() - { - return $"{this.Type} - {this.Icon}"; - } - - /// - protected override byte[] EncodeImpl() - { - var indexBytes = MakeInteger((uint)this.Icon); - var chunkLen = indexBytes.Length + 1; - var bytes = new List(new byte[] - { START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen, - }); - bytes.AddRange(indexBytes); - bytes.Add(END_BYTE); - return bytes.ToArray(); - } + }); + bytes.AddRange(indexBytes); + bytes.Add(END_BYTE); + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.Icon = (BitmapFontIcon)GetInteger(reader); - } + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.Icon = (BitmapFontIcon)GetInteger(reader); } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index 9714852e9..7cce11709 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -6,184 +6,183 @@ using System.Text; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing an interactable item link. +/// +public class ItemPayload : Payload { + private Item item; + + // mainly to allow overriding the name (for things like owo) + // TODO: even though this is present in some item links, it may not really have a use at all + // For things like owo, changing the text payload is probably correct, whereas changing the + // actual embedded name might not work properly. + private string displayName = null; + + [JsonProperty] + private uint itemId; + /// - /// An SeString Payload representing an interactable item link. + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. /// - public class ItemPayload : Payload + /// The id of the item. + /// Whether or not the link should be for the high-quality variant of the item. + /// An optional name to include in the item link. Typically this should + /// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent + /// TextPayload that is a part of a full item link in chat. + public ItemPayload(uint itemId, bool isHQ, string displayNameOverride = null) { - private Item item; + this.itemId = itemId; + this.IsHQ = isHQ; + this.displayName = displayNameOverride; + } - // mainly to allow overriding the name (for things like owo) - // TODO: even though this is present in some item links, it may not really have a use at all - // For things like owo, changing the text payload is probably correct, whereas changing the - // actual embedded name might not work properly. - private string displayName = null; + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + internal ItemPayload() + { + } - [JsonProperty] - private uint itemId; + /// + public override PayloadType Type => PayloadType.Item; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. - /// - /// The id of the item. - /// Whether or not the link should be for the high-quality variant of the item. - /// An optional name to include in the item link. Typically this should - /// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent - /// TextPayload that is a part of a full item link in chat. - public ItemPayload(uint itemId, bool isHQ, string displayNameOverride = null) + /// + /// Gets or sets the displayed name for this item link. Note that incoming links only sometimes have names embedded, + /// often the name is only present in a following text payload. + /// + public string DisplayName + { + get { - this.itemId = itemId; - this.IsHQ = isHQ; - this.displayName = displayNameOverride; + return this.displayName; } - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. - /// - internal ItemPayload() + set { + this.displayName = value; + this.Dirty = true; } + } - /// - public override PayloadType Type => PayloadType.Item; + /// + /// Gets the raw item ID of this payload. + /// + [JsonIgnore] + public uint ItemId => this.itemId; - /// - /// Gets or sets the displayed name for this item link. Note that incoming links only sometimes have names embedded, - /// often the name is only present in a following text payload. - /// - public string DisplayName + /// + /// Gets the underlying Lumina Item represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Item Item => this.item ??= this.DataResolver.GetExcelSheet().GetRow(this.itemId); + + /// + /// Gets a value indicating whether or not this item link is for a high-quality version of the item. + /// + [JsonProperty] + public bool IsHQ { get; private set; } = false; + + /// + public override string ToString() + { + return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}"; + } + + /// + protected override byte[] EncodeImpl() + { + var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId; + var idBytes = MakeInteger(actualItemId); + var hasName = !string.IsNullOrEmpty(this.displayName); + + var chunkLen = idBytes.Length + 4; + if (hasName) { - get + // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself + chunkLen += 1 + 1 + this.displayName.Length; + if (this.IsHQ) { - return this.displayName; - } - - set - { - this.displayName = value; - this.Dirty = true; + chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space } } - /// - /// Gets the raw item ID of this payload. - /// - [JsonIgnore] - public uint ItemId => this.itemId; - - /// - /// Gets the underlying Lumina Item represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Item Item => this.item ??= this.DataResolver.GetExcelSheet().GetRow(this.itemId); - - /// - /// Gets a value indicating whether or not this item link is for a high-quality version of the item. - /// - [JsonProperty] - public bool IsHQ { get; private set; } = false; - - /// - public override string ToString() - { - return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}"; - } - - /// - protected override byte[] EncodeImpl() - { - var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId; - var idBytes = MakeInteger(actualItemId); - var hasName = !string.IsNullOrEmpty(this.displayName); - - var chunkLen = idBytes.Length + 4; - if (hasName) - { - // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself - chunkLen += 1 + 1 + this.displayName.Length; - if (this.IsHQ) - { - chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space - } - } - - var bytes = new List() + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink, }; - bytes.AddRange(idBytes); - // unk - bytes.AddRange(new byte[] { 0x02, 0x01 }); + bytes.AddRange(idBytes); + // unk + bytes.AddRange(new byte[] { 0x02, 0x01 }); - // Links don't have to include the name, but if they do, it requires additional work - if (hasName) + // Links don't have to include the name, but if they do, it requires additional work + if (hasName) + { + var nameLen = this.displayName.Length + 1; + if (this.IsHQ) { - var nameLen = this.displayName.Length + 1; - if (this.IsHQ) - { - nameLen += 4; // space plus 3 bytes for HQ symbol - } + nameLen += 4; // space plus 3 bytes for HQ symbol + } - bytes.AddRange(new byte[] - { + bytes.AddRange(new byte[] + { 0xFF, // unk (byte)nameLen, - }); - bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); + }); + bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); - if (this.IsHQ) - { - // space and HQ symbol - bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC }); - } + if (this.IsHQ) + { + // space and HQ symbol + bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC }); } - - bytes.Add(END_BYTE); - - return bytes.ToArray(); } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.itemId = GetInteger(reader); + + if (this.itemId > 1000000) { - this.itemId = GetInteger(reader); + this.itemId -= 1000000; + this.IsHQ = true; + } - if (this.itemId > 1000000) + if (reader.BaseStream.Position + 3 < endOfStream) + { + // unk + reader.ReadBytes(3); + + var itemNameLen = (int)GetInteger(reader); + var itemNameBytes = reader.ReadBytes(itemNameLen); + + // it probably isn't necessary to store this, as we now get the lumina Item + // on demand from the id, which will have the name + // For incoming links, the name "should?" always match + // but we'll store it for use in encode just in case it doesn't + + // HQ items have the HQ symbol as part of the name, but since we already recorded + // the HQ flag, we want just the bare name + if (this.IsHQ) { - this.itemId -= 1000000; - this.IsHQ = true; + itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray(); } - if (reader.BaseStream.Position + 3 < endOfStream) - { - // unk - reader.ReadBytes(3); - - var itemNameLen = (int)GetInteger(reader); - var itemNameBytes = reader.ReadBytes(itemNameLen); - - // it probably isn't necessary to store this, as we now get the lumina Item - // on demand from the id, which will have the name - // For incoming links, the name "should?" always match - // but we'll store it for use in encode just in case it doesn't - - // HQ items have the HQ symbol as part of the name, but since we already recorded - // the HQ flag, we want just the bare name - if (this.IsHQ) - { - itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray(); - } - - this.displayName = Encoding.UTF8.GetString(itemNameBytes); - } + this.displayName = Encoding.UTF8.GetString(itemNameBytes); } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs index 8f7418b48..51c2f0e61 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs @@ -5,232 +5,231 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing an interactable map position link. +/// +public class MapLinkPayload : Payload { + private Map map; + private TerritoryType territoryType; + private string placeNameRegion; + private string placeName; + + [JsonProperty] + private uint territoryTypeId; + + [JsonProperty] + private uint mapId; + /// - /// An SeString Payload representing an interactable map position link. + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a human-readable position. /// - public class MapLinkPayload : Payload + /// The id of the TerritoryType entry for this link. + /// The id of the Map entry for this link. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + public MapLinkPayload(uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) { - private Map map; - private TerritoryType territoryType; - private string placeNameRegion; - private string placeName; + this.territoryTypeId = territoryTypeId; + this.mapId = mapId; + // this fudge is necessary basically to ensure we don't shift down a full tenth + // because essentially values are truncated instead of rounded, so 3.09999f will become + // 3.0f and not 3.1f + this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor); + this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor); + } - [JsonProperty] - private uint territoryTypeId; + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a raw position. + /// + /// The id of the TerritoryType entry for this link. + /// The id of the Map entry for this link. + /// The internal raw x-coordinate for this link. + /// The internal raw y-coordinate for this link. + public MapLinkPayload(uint territoryTypeId, uint mapId, int rawX, int rawY) + { + this.territoryTypeId = territoryTypeId; + this.mapId = mapId; + this.RawX = rawX; + this.RawY = rawY; + } - [JsonProperty] - private uint mapId; + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a human-readable position. + /// + internal MapLinkPayload() + { + } - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a human-readable position. - /// - /// The id of the TerritoryType entry for this link. - /// The id of the Map entry for this link. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - public MapLinkPayload(uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) + /// + public override PayloadType Type => PayloadType.MapLink; + + /// + /// Gets the Map specified for this map link. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); + + /// + /// Gets the TerritoryType specified for this map link. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); + + /// + /// Gets the internal x-coordinate for this map position. + /// + public int RawX { get; private set; } + + /// + /// Gets the internal y-coordinate for this map position. + /// + public int RawY { get; private set; } + + // these could be cached, but this isn't really too egregious + + /// + /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. + /// + public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor); + + /// + /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. + /// + [JsonIgnore] + public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor); + + // there is no Z; it's purely in the text payload where applicable + + /// + /// Gets the printable map coordinates for this link. This value tries to match the in-game printable text as closely + /// as possible but is an approximation and may be slightly off for some positions. + /// + [JsonIgnore] + public string CoordinateString + { + get { - this.territoryTypeId = territoryTypeId; - this.mapId = mapId; - // this fudge is necessary basically to ensure we don't shift down a full tenth - // because essentially values are truncated instead of rounded, so 3.09999f will become - // 3.0f and not 3.1f - this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor); - this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor); + // this truncates the values to one decimal without rounding, which is what the game does + // the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues + // TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable + const float fudge = 0.02f; + var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f; + var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f; + + // the formatting and spacing the game uses + return $"( {x:0.0} , {y:0.0} )"; } + } - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a raw position. - /// - /// The id of the TerritoryType entry for this link. - /// The id of the Map entry for this link. - /// The internal raw x-coordinate for this link. - /// The internal raw y-coordinate for this link. - public MapLinkPayload(uint territoryTypeId, uint mapId, int rawX, int rawY) - { - this.territoryTypeId = territoryTypeId; - this.mapId = mapId; - this.RawX = rawX; - this.RawY = rawY; - } + /// + /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". + /// + [JsonIgnore] + public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a human-readable position. - /// - internal MapLinkPayload() - { - } + /// + /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". + /// + [JsonIgnore] + public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; - /// - public override PayloadType Type => PayloadType.MapLink; + /// + /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. + /// + public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; - /// - /// Gets the Map specified for this map link. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); + /// + public override string ToString() + { + return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}"; + } - /// - /// Gets the TerritoryType specified for this map link. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); + /// + protected override byte[] EncodeImpl() + { + var packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId); + var xBytes = MakeInteger(unchecked((uint)this.RawX)); + var yBytes = MakeInteger(unchecked((uint)this.RawY)); - /// - /// Gets the internal x-coordinate for this map position. - /// - public int RawX { get; private set; } + var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length; - /// - /// Gets the internal y-coordinate for this map position. - /// - public int RawY { get; private set; } - - // these could be cached, but this isn't really too egregious - - /// - /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. - /// - public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor); - - /// - /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. - /// - [JsonIgnore] - public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor); - - // there is no Z; it's purely in the text payload where applicable - - /// - /// Gets the printable map coordinates for this link. This value tries to match the in-game printable text as closely - /// as possible but is an approximation and may be slightly off for some positions. - /// - [JsonIgnore] - public string CoordinateString - { - get - { - // this truncates the values to one decimal without rounding, which is what the game does - // the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues - // TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable - const float fudge = 0.02f; - var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f; - var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f; - - // the formatting and spacing the game uses - return $"( {x:0.0} , {y:0.0} )"; - } - } - - /// - /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". - /// - [JsonIgnore] - public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; - - /// - /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". - /// - [JsonIgnore] - public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; - - /// - /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. - /// - public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; - - /// - public override string ToString() - { - return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}"; - } - - /// - protected override byte[] EncodeImpl() - { - var packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId); - var xBytes = MakeInteger(unchecked((uint)this.RawX)); - var yBytes = MakeInteger(unchecked((uint)this.RawY)); - - var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length; - - var bytes = new List() + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink, }; - bytes.AddRange(packedTerritoryAndMapBytes); - bytes.AddRange(xBytes); - bytes.AddRange(yBytes); + bytes.AddRange(packedTerritoryAndMapBytes); + bytes.AddRange(xBytes); + bytes.AddRange(yBytes); - // unk - bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE }); + // unk + bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE }); - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // for debugging for now - var oldPos = reader.BaseStream.Position; - var bytes = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); - reader.BaseStream.Position = oldPos; - - try - { - (this.territoryTypeId, this.mapId) = GetPackedIntegers(reader); - this.RawX = unchecked((int)GetInteger(reader)); - this.RawY = unchecked((int)GetInteger(reader)); - // the Z coordinate is never in this chunk, just the text (if applicable) - - // seems to always be FF 01 - reader.ReadBytes(2); - } - catch (NotSupportedException) - { - Serilog.Log.Information($"Unsupported map bytes {BitConverter.ToString(bytes).Replace("-", " ")}"); - // we still want to break here for now, or we'd just throw again later - throw; - } - } - - #region ugliness - - // from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md - // extra 1/1000 because that is how the network ints are done - private float ConvertRawPositionToMapCoordinate(int pos, float scale) - { - var c = scale / 100.0f; - var scaledPos = pos * c / 1000.0f; - - return (41.0f / c * ((scaledPos + 1024.0f) / 2048.0f)) + 1.0f; - } - - // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that - private int ConvertMapCoordinateToRawPosition(float pos, float scale) - { - var c = scale / 100.0f; - - var scaledPos = (((pos - 1.0f) * c / 41.0f * 2048.0f) - 1024.0f) / c; - scaledPos *= 1000.0f; - - return (int)scaledPos; - } - - #endregion + return bytes.ToArray(); } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // for debugging for now + var oldPos = reader.BaseStream.Position; + var bytes = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); + reader.BaseStream.Position = oldPos; + + try + { + (this.territoryTypeId, this.mapId) = GetPackedIntegers(reader); + this.RawX = unchecked((int)GetInteger(reader)); + this.RawY = unchecked((int)GetInteger(reader)); + // the Z coordinate is never in this chunk, just the text (if applicable) + + // seems to always be FF 01 + reader.ReadBytes(2); + } + catch (NotSupportedException) + { + Serilog.Log.Information($"Unsupported map bytes {BitConverter.ToString(bytes).Replace("-", " ")}"); + // we still want to break here for now, or we'd just throw again later + throw; + } + } + + #region ugliness + + // from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md + // extra 1/1000 because that is how the network ints are done + private float ConvertRawPositionToMapCoordinate(int pos, float scale) + { + var c = scale / 100.0f; + var scaledPos = pos * c / 1000.0f; + + return (41.0f / c * ((scaledPos + 1024.0f) / 2048.0f)) + 1.0f; + } + + // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that + private int ConvertMapCoordinateToRawPosition(float pos, float scale) + { + var c = scale / 100.0f; + + var scaledPos = (((pos - 1.0f) * c / 41.0f * 2048.0f) - 1024.0f) / c; + scaledPos *= 1000.0f; + + return (int)scaledPos; + } + + #endregion } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs index 13aba8077..3df724e75 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs @@ -1,34 +1,33 @@ using System; using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// A wrapped newline character. +/// +public class NewLinePayload : Payload, ITextProvider { + private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE }; + /// - /// A wrapped newline character. + /// Gets an instance of NewLinePayload. /// - public class NewLinePayload : Payload, ITextProvider + public static NewLinePayload Payload => new(); + + /// + /// Gets the text of this payload, evaluates to Environment.NewLine. + /// + public string Text => Environment.NewLine; + + /// + public override PayloadType Type => PayloadType.NewLine; + + /// + protected override byte[] EncodeImpl() => this.bytes; + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE }; - - /// - /// Gets an instance of NewLinePayload. - /// - public static NewLinePayload Payload => new(); - - /// - /// Gets the text of this payload, evaluates to Environment.NewLine. - /// - public string Text => Environment.NewLine; - - /// - public override PayloadType Type => PayloadType.NewLine; - - /// - protected override byte[] EncodeImpl() => this.bytes; - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index 01d8331e1..ea4bab53d 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -5,89 +5,89 @@ using System.Text; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing a player link. +/// +public class PlayerPayload : Payload { + private World world; + + [JsonProperty] + private uint serverId; + + [JsonProperty] + private string playerName; + /// - /// An SeString Payload representing a player link. + /// Initializes a new instance of the class. + /// Create a PlayerPayload link for the specified player. /// - public class PlayerPayload : Payload + /// The player's displayed name. + /// The player's home server id. + public PlayerPayload(string playerName, uint serverId) { - private World world; + this.playerName = playerName; + this.serverId = serverId; + } - [JsonProperty] - private uint serverId; + /// + /// Initializes a new instance of the class. + /// Create a PlayerPayload link for the specified player. + /// + internal PlayerPayload() + { + } - [JsonProperty] - private string playerName; + /// + /// Gets the Lumina object representing the player's home server. + /// + /// + /// Value is evaluated lazily and cached. + /// + [JsonIgnore] + public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); - /// - /// Initializes a new instance of the class. - /// Create a PlayerPayload link for the specified player. - /// - /// The player's displayed name. - /// The player's home server id. - public PlayerPayload(string playerName, uint serverId) + /// + /// Gets or sets the player's displayed name. This does not contain the server name. + /// + [JsonIgnore] + public string PlayerName + { + get { - this.playerName = playerName; - this.serverId = serverId; + return this.playerName; } - /// - /// Initializes a new instance of the class. - /// Create a PlayerPayload link for the specified player. - /// - internal PlayerPayload() + set { + this.playerName = value; + this.Dirty = true; } + } - /// - /// Gets the Lumina object representing the player's home server. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); + /// + /// Gets the text representation of this player link matching how it might appear in-game. + /// The world name will always be present. + /// + [JsonIgnore] + public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; - /// - /// Gets or sets the player's displayed name. This does not contain the server name. - /// - [JsonIgnore] - public string PlayerName - { - get - { - return this.playerName; - } + /// + public override PayloadType Type => PayloadType.Player; - set - { - this.playerName = value; - this.Dirty = true; - } - } + /// + public override string ToString() + { + return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; + } - /// - /// Gets the text representation of this player link matching how it might appear in-game. - /// The world name will always be present. - /// - [JsonIgnore] - public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; - - /// - public override PayloadType Type => PayloadType.Player; - - /// - public override string ToString() - { - return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; - } - - /// - protected override byte[] EncodeImpl() - { - var chunkLen = this.playerName.Length + 7; - var bytes = new List() + /// + protected override byte[] EncodeImpl() + { + var chunkLen = this.playerName.Length + 7; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, @@ -98,39 +98,38 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads (byte)(this.playerName.Length + 1), }; - bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); - bytes.Add(END_BYTE); + bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); + bytes.Add(END_BYTE); - // TODO: should these really be here? additional payloads should come in separately already... + // TODO: should these really be here? additional payloads should come in separately already... - // encoded names are followed by the name in plain text again - // use the payload parsing for consistency, as this is technically a new chunk - bytes.AddRange(new TextPayload(this.playerName).Encode()); + // encoded names are followed by the name in plain text again + // use the payload parsing for consistency, as this is technically a new chunk + bytes.AddRange(new TextPayload(this.playerName).Encode()); - // unsure about this entire packet, but it seems to always follow a name - bytes.AddRange(new byte[] - { + // unsure about this entire packet, but it seems to always follow a name + bytes.AddRange(new byte[] + { START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator, 0x01, 0x01, 0x01, 0xFF, 0x01, END_BYTE, - }); + }); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // unk - reader.ReadByte(); + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // unk + reader.ReadByte(); - this.serverId = GetInteger(reader); + this.serverId = GetInteger(reader); - // unk - reader.ReadBytes(2); + // unk + reader.ReadBytes(2); - var nameLen = (int)GetInteger(reader); - this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); - } + var nameLen = (int)GetInteger(reader); + this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs index d9dca651b..f53819b87 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs @@ -4,75 +4,74 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing an interactable quest link. +/// +public class QuestPayload : Payload { + private Quest quest; + + [JsonProperty] + private uint questId; + /// - /// An SeString Payload representing an interactable quest link. + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable quest link for the specified quest. /// - public class QuestPayload : Payload + /// The id of the quest. + public QuestPayload(uint questId) { - private Quest quest; + this.questId = questId; + } - [JsonProperty] - private uint questId; + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable quest link for the specified quest. + /// + internal QuestPayload() + { + } - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable quest link for the specified quest. - /// - /// The id of the quest. - public QuestPayload(uint questId) - { - this.questId = questId; - } + /// + public override PayloadType Type => PayloadType.Quest; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable quest link for the specified quest. - /// - internal QuestPayload() - { - } + /// + /// Gets the underlying Lumina Quest represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); - /// - public override PayloadType Type => PayloadType.Quest; + /// + public override string ToString() + { + return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; + } - /// - /// Gets the underlying Lumina Quest represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); + /// + protected override byte[] EncodeImpl() + { + var idBytes = MakeInteger((ushort)this.questId); + var chunkLen = idBytes.Length + 4; - /// - public override string ToString() - { - return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; - } - - /// - protected override byte[] EncodeImpl() - { - var idBytes = MakeInteger((ushort)this.questId); - var chunkLen = idBytes.Length + 4; - - var bytes = new List() + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.QuestLink, }; - bytes.AddRange(idBytes); - bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE }); - return bytes.ToArray(); - } + bytes.AddRange(idBytes); + bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE }); + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // Game uses int16, Luimina uses int32 - this.questId = GetInteger(reader) + 65536; - } + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // Game uses int16, Luimina uses int32 + this.questId = GetInteger(reader) + 65536; } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs index 0d80f015d..d83026e87 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs @@ -5,116 +5,115 @@ using System.Linq; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing unhandled raw payload data. +/// Mainly useful for constructing unhandled hardcoded payloads, or forwarding any unknown +/// payloads without modification. +/// +public class RawPayload : Payload { + [JsonProperty] + private byte chunkType; + + [JsonProperty] + private byte[] data; + /// - /// An SeString Payload representing unhandled raw payload data. - /// Mainly useful for constructing unhandled hardcoded payloads, or forwarding any unknown - /// payloads without modification. + /// Initializes a new instance of the class. /// - public class RawPayload : Payload + /// The payload data. + public RawPayload(byte[] data) { - [JsonProperty] - private byte chunkType; + // this payload is 'special' in that we require the entire chunk to be passed in + // and not just the data after the header + // This sets data to hold the chunk data fter the header, excluding the END_BYTE + this.chunkType = data[1]; + this.data = data.Skip(3).Take(data.Length - 4).ToArray(); + } - [JsonProperty] - private byte[] data; + /// + /// Initializes a new instance of the class. + /// + /// The chunk type. + [JsonConstructor] + internal RawPayload(byte chunkType) + { + this.chunkType = chunkType; + } - /// - /// Initializes a new instance of the class. - /// - /// The payload data. - public RawPayload(byte[] data) + /// + /// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains. + /// + public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 }); + + /// + public override PayloadType Type => PayloadType.Unknown; + + /// + /// Gets the entire payload byte sequence for this payload. + /// The returned data is a clone and modifications will not be persisted. + /// + [JsonIgnore] + public byte[] Data + { + // this is a bit different from the underlying data + // We need to store just the chunk data for decode to behave nicely, but when reading data out + // it makes more sense to get the entire payload + get { - // this payload is 'special' in that we require the entire chunk to be passed in - // and not just the data after the header - // This sets data to hold the chunk data fter the header, excluding the END_BYTE - this.chunkType = data[1]; - this.data = data.Skip(3).Take(data.Length - 4).ToArray(); + // for now don't allow modifying the contents + // because we don't really have a way to track Dirty + return (byte[])this.Encode().Clone(); + } + } + + /// + public override bool Equals(object obj) + { + if (obj is RawPayload rp) + { + if (rp.Data.Length != this.Data.Length) return false; + return !this.Data.Where((t, i) => rp.Data[i] != t).Any(); } - /// - /// Initializes a new instance of the class. - /// - /// The chunk type. - [JsonConstructor] - internal RawPayload(byte chunkType) - { - this.chunkType = chunkType; - } + return false; + } - /// - /// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains. - /// - public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 }); + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Type, this.chunkType, this.data); + } - /// - public override PayloadType Type => PayloadType.Unknown; + /// + public override string ToString() + { + return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}"; + } - /// - /// Gets the entire payload byte sequence for this payload. - /// The returned data is a clone and modifications will not be persisted. - /// - [JsonIgnore] - public byte[] Data - { - // this is a bit different from the underlying data - // We need to store just the chunk data for decode to behave nicely, but when reading data out - // it makes more sense to get the entire payload - get - { - // for now don't allow modifying the contents - // because we don't really have a way to track Dirty - return (byte[])this.Encode().Clone(); - } - } + /// + protected override byte[] EncodeImpl() + { + var chunkLen = this.data.Length + 1; - /// - public override bool Equals(object obj) - { - if (obj is RawPayload rp) - { - if (rp.Data.Length != this.Data.Length) return false; - return !this.Data.Where((t, i) => rp.Data[i] != t).Any(); - } - - return false; - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Type, this.chunkType, this.data); - } - - /// - public override string ToString() - { - return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}"; - } - - /// - protected override byte[] EncodeImpl() - { - var chunkLen = this.data.Length + 1; - - var bytes = new List() + var bytes = new List() { START_BYTE, this.chunkType, (byte)chunkLen, }; - bytes.AddRange(this.data); + bytes.AddRange(this.data); - bytes.Add(END_BYTE); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); - } + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs index bb50f6a02..1739b9cda 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs @@ -1,33 +1,32 @@ using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// A wrapped '–'. +/// +public class SeHyphenPayload : Payload, ITextProvider { + private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE }; + /// - /// A wrapped '–'. + /// Gets an instance of SeHyphenPayload. /// - public class SeHyphenPayload : Payload, ITextProvider + public static SeHyphenPayload Payload => new(); + + /// + /// Gets the text, just a '–'. + /// + public string Text => "–"; + + /// + public override PayloadType Type => PayloadType.SeHyphen; + + /// + protected override byte[] EncodeImpl() => this.bytes; + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE }; - - /// - /// Gets an instance of SeHyphenPayload. - /// - public static SeHyphenPayload Payload => new(); - - /// - /// Gets the text, just a '–'. - /// - public string Text => "–"; - - /// - public override PayloadType Type => PayloadType.SeHyphen; - - /// - protected override byte[] EncodeImpl() => this.bytes; - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs index e7df98ae8..f6408d279 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs @@ -4,76 +4,75 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing an interactable status link. +/// +public class StatusPayload : Payload { + private Status status; + + [JsonProperty] + private uint statusId; + /// - /// An SeString Payload representing an interactable status link. + /// Initializes a new instance of the class. + /// Creates a new StatusPayload for the given status id. /// - public class StatusPayload : Payload + /// The id of the Status for this link. + public StatusPayload(uint statusId) { - private Status status; + this.statusId = statusId; + } - [JsonProperty] - private uint statusId; + /// + /// Initializes a new instance of the class. + /// Creates a new StatusPayload for the given status id. + /// + internal StatusPayload() + { + } - /// - /// Initializes a new instance of the class. - /// Creates a new StatusPayload for the given status id. - /// - /// The id of the Status for this link. - public StatusPayload(uint statusId) - { - this.statusId = statusId; - } + /// + public override PayloadType Type => PayloadType.Status; - /// - /// Initializes a new instance of the class. - /// Creates a new StatusPayload for the given status id. - /// - internal StatusPayload() - { - } + /// + /// Gets the Lumina Status object represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); - /// - public override PayloadType Type => PayloadType.Status; + /// + public override string ToString() + { + return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; + } - /// - /// Gets the Lumina Status object represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); + /// + protected override byte[] EncodeImpl() + { + var idBytes = MakeInteger(this.statusId); - /// - public override string ToString() - { - return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; - } - - /// - protected override byte[] EncodeImpl() - { - var idBytes = MakeInteger(this.statusId); - - var chunkLen = idBytes.Length + 7; - var bytes = new List() + var chunkLen = idBytes.Length + 7; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status, }; - bytes.AddRange(idBytes); - // unk - bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE }); + bytes.AddRange(idBytes); + // unk + bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE }); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.statusId = GetInteger(reader); - } + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.statusId = GetInteger(reader); } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs index d12bdd3a7..9d8e64fa6 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs @@ -5,98 +5,97 @@ using System.Text; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing a plain text string. +/// +public class TextPayload : Payload, ITextProvider { + [JsonProperty] + private string text; + /// - /// An SeString Payload representing a plain text string. + /// Initializes a new instance of the class. + /// Creates a new TextPayload for the given text. /// - public class TextPayload : Payload, ITextProvider + /// The text to include for this payload. + public TextPayload(string text) { - [JsonProperty] - private string text; + this.text = text; + } - /// - /// Initializes a new instance of the class. - /// Creates a new TextPayload for the given text. - /// - /// The text to include for this payload. - public TextPayload(string text) + /// + /// Initializes a new instance of the class. + /// Creates a new TextPayload for the given text. + /// + internal TextPayload() + { + } + + /// + public override PayloadType Type => PayloadType.RawText; + + /// + /// Gets or sets the text contained in this payload. + /// This may contain SE's special unicode characters. + /// + [JsonIgnore] + public string Text + { + get { - this.text = text; + return this.text; } - /// - /// Initializes a new instance of the class. - /// Creates a new TextPayload for the given text. - /// - internal TextPayload() + set { + this.text = value; + this.Dirty = true; + } + } + + /// + public override string ToString() + { + return $"{this.Type} - Text: {this.Text}"; + } + + /// + protected override byte[] EncodeImpl() + { + // special case to allow for empty text payloads, so users don't have to check + // this may change or go away + if (string.IsNullOrEmpty(this.text)) + { + return Array.Empty(); } - /// - public override PayloadType Type => PayloadType.RawText; + return Encoding.UTF8.GetBytes(this.text); + } - /// - /// Gets or sets the text contained in this payload. - /// This may contain SE's special unicode characters. - /// - [JsonIgnore] - public string Text + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + var textBytes = new List(); + + while (reader.BaseStream.Position < endOfStream) { - get + var nextByte = reader.ReadByte(); + if (nextByte == START_BYTE) { - return this.text; + // rewind since this byte isn't part of this payload + reader.BaseStream.Position--; + break; } - set - { - this.text = value; - this.Dirty = true; - } + textBytes.Add(nextByte); } - /// - public override string ToString() + if (textBytes.Count > 0) { - return $"{this.Type} - Text: {this.Text}"; - } - - /// - protected override byte[] EncodeImpl() - { - // special case to allow for empty text payloads, so users don't have to check - // this may change or go away - if (string.IsNullOrEmpty(this.text)) - { - return Array.Empty(); - } - - return Encoding.UTF8.GetBytes(this.text); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - var textBytes = new List(); - - while (reader.BaseStream.Position < endOfStream) - { - var nextByte = reader.ReadByte(); - if (nextByte == START_BYTE) - { - // rewind since this byte isn't part of this payload - reader.BaseStream.Position--; - break; - } - - textBytes.Add(nextByte); - } - - if (textBytes.Count > 0) - { - // TODO: handling of the game's assorted special unicode characters - this.text = Encoding.UTF8.GetString(textBytes.ToArray()); - } + // TODO: handling of the game's assorted special unicode characters + this.text = Encoding.UTF8.GetString(textBytes.ToArray()); } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index 4934004bf..ff6d783fb 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -4,111 +4,110 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing a UI foreground color applied to following text payloads. +/// +public class UIForegroundPayload : Payload { + private UIColor color; + + [JsonProperty] + private ushort colorKey; + /// - /// An SeString Payload representing a UI foreground color applied to following text payloads. + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. /// - public class UIForegroundPayload : Payload + /// A UIColor key. + public UIForegroundPayload(ushort colorKey) { - private UIColor color; + this.colorKey = colorKey; + } - [JsonProperty] - private ushort colorKey; + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + internal UIForegroundPayload() + { + } - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. - /// - /// A UIColor key. - public UIForegroundPayload(ushort colorKey) + /// + /// Gets a payload representing disabling foreground color on following text. + /// + // TODO Make this work with DI + public static UIForegroundPayload UIForegroundOff => new(0); + + /// + public override PayloadType Type => PayloadType.UIForeground; + + /// + /// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one. + /// + public bool IsEnabled => this.ColorKey != 0; + + /// + /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + + /// + /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. + /// + [JsonIgnore] + public ushort ColorKey + { + get { - this.colorKey = colorKey; + return this.colorKey; } - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. - /// - internal UIForegroundPayload() + set { - } - - /// - /// Gets a payload representing disabling foreground color on following text. - /// - // TODO Make this work with DI - public static UIForegroundPayload UIForegroundOff => new(0); - - /// - public override PayloadType Type => PayloadType.UIForeground; - - /// - /// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one. - /// - public bool IsEnabled => this.ColorKey != 0; - - /// - /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); - - /// - /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. - /// - [JsonIgnore] - public ushort ColorKey - { - get - { - return this.colorKey; - } - - set - { - this.colorKey = value; - this.color = null; - this.Dirty = true; - } - } - - /// - /// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color. - /// - [JsonIgnore] - public uint RGB => this.UIColor.UIForeground & 0xFFFFFF; - - /// - public override string ToString() - { - return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; - } - - /// - protected override byte[] EncodeImpl() - { - var colorBytes = MakeInteger(this.colorKey); - var chunkLen = colorBytes.Length + 1; - - var bytes = new List(new byte[] - { - START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen, - }); - - bytes.AddRange(colorBytes); - bytes.Add(END_BYTE); - - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.colorKey = (ushort)GetInteger(reader); + this.colorKey = value; + this.color = null; + this.Dirty = true; } } + + /// + /// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color. + /// + [JsonIgnore] + public uint RGB => this.UIColor.UIForeground & 0xFFFFFF; + + /// + public override string ToString() + { + return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; + } + + /// + protected override byte[] EncodeImpl() + { + var colorBytes = MakeInteger(this.colorKey); + var chunkLen = colorBytes.Length + 1; + + var bytes = new List(new byte[] + { + START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen, + }); + + bytes.AddRange(colorBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.colorKey = (ushort)GetInteger(reader); + } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index 480aae24a..ad5dfd7d3 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -4,111 +4,110 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads +namespace Dalamud.Game.Text.SeStringHandling.Payloads; + +/// +/// An SeString Payload representing a UI glow color applied to following text payloads. +/// +public class UIGlowPayload : Payload { + private UIColor color; + + [JsonProperty] + private ushort colorKey; + /// - /// An SeString Payload representing a UI glow color applied to following text payloads. + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. /// - public class UIGlowPayload : Payload + /// A UIColor key. + public UIGlowPayload(ushort colorKey) { - private UIColor color; + this.colorKey = colorKey; + } - [JsonProperty] - private ushort colorKey; + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + internal UIGlowPayload() + { + } - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. - /// - /// A UIColor key. - public UIGlowPayload(ushort colorKey) + /// + /// Gets a payload representing disabling glow color on following text. + /// + // TODO Make this work with DI + public static UIGlowPayload UIGlowOff => new(0); + + /// + public override PayloadType Type => PayloadType.UIGlow; + + /// + /// Gets or sets the color key used as a lookup in the UIColor table for this glow color. + /// + [JsonIgnore] + public ushort ColorKey + { + get { - this.colorKey = colorKey; + return this.colorKey; } - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. - /// - internal UIGlowPayload() + set { - } - - /// - /// Gets a payload representing disabling glow color on following text. - /// - // TODO Make this work with DI - public static UIGlowPayload UIGlowOff => new(0); - - /// - public override PayloadType Type => PayloadType.UIGlow; - - /// - /// Gets or sets the color key used as a lookup in the UIColor table for this glow color. - /// - [JsonIgnore] - public ushort ColorKey - { - get - { - return this.colorKey; - } - - set - { - this.colorKey = value; - this.color = null; - this.Dirty = true; - } - } - - /// - /// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one. - /// - public bool IsEnabled => this.ColorKey != 0; - - /// - /// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color. - /// - [JsonIgnore] - public uint RGB => this.UIColor.UIGlow & 0xFFFFFF; - - /// - /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); - - /// - public override string ToString() - { - return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; - } - - /// - protected override byte[] EncodeImpl() - { - var colorBytes = MakeInteger(this.colorKey); - var chunkLen = colorBytes.Length + 1; - - var bytes = new List(new byte[] - { - START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen, - }); - - bytes.AddRange(colorBytes); - bytes.Add(END_BYTE); - - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.colorKey = (ushort)GetInteger(reader); + this.colorKey = value; + this.color = null; + this.Dirty = true; } } + + /// + /// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one. + /// + public bool IsEnabled => this.ColorKey != 0; + + /// + /// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color. + /// + [JsonIgnore] + public uint RGB => this.UIColor.UIGlow & 0xFFFFFF; + + /// + /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + + /// + public override string ToString() + { + return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; + } + + /// + protected override byte[] EncodeImpl() + { + var colorBytes = MakeInteger(this.colorKey); + var chunkLen = colorBytes.Length + 1; + + var bytes = new List(new byte[] + { + START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen, + }); + + bytes.AddRange(colorBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.colorKey = (ushort)GetInteger(reader); + } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index c7c3cff34..8f08cacf0 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -10,361 +10,360 @@ using Dalamud.Utility; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// This class represents a parsed SeString. +/// +public class SeString { /// - /// This class represents a parsed SeString. + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. /// - public class SeString + public SeString() { - /// - /// Initializes a new instance of the class. - /// Creates a new SeString from an ordered list of payloads. - /// - public SeString() - { - this.Payloads = new List(); - } + this.Payloads = new List(); + } - /// - /// Initializes a new instance of the class. - /// Creates a new SeString from an ordered list of payloads. - /// - /// The Payload objects to make up this string. - [JsonConstructor] - public SeString(List payloads) - { - this.Payloads = payloads; - } + /// + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + /// The Payload objects to make up this string. + [JsonConstructor] + public SeString(List payloads) + { + this.Payloads = payloads; + } - /// - /// Initializes a new instance of the class. - /// Creates a new SeString from an ordered list of payloads. - /// - /// The Payload objects to make up this string. - public SeString(params Payload[] payloads) - { - this.Payloads = new List(payloads); - } + /// + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + /// The Payload objects to make up this string. + public SeString(params Payload[] payloads) + { + this.Payloads = new List(payloads); + } - /// - /// Gets a list of Payloads necessary to display the arrow link marker icon in chat - /// with the appropriate glow and coloring. - /// - /// A list of all the payloads required to insert the link marker. - public static IEnumerable TextArrowPayloads => new List(new Payload[] - { + /// + /// Gets a list of Payloads necessary to display the arrow link marker icon in chat + /// with the appropriate glow and coloring. + /// + /// A list of all the payloads required to insert the link marker. + public static IEnumerable TextArrowPayloads => new List(new Payload[] + { new UIForegroundPayload(0x01F4), new UIGlowPayload(0x01F5), new TextPayload($"{(char)SeIconChar.LinkMarker}"), UIGlowPayload.UIGlowOff, UIForegroundPayload.UIForegroundOff, - }); + }); - /// - /// Gets an empty SeString. - /// - public static SeString Empty => new(); + /// + /// Gets an empty SeString. + /// + public static SeString Empty => new(); - /// - /// Gets the ordered list of payloads included in this SeString. - /// - public List Payloads { get; } + /// + /// Gets the ordered list of payloads included in this SeString. + /// + public List Payloads { get; } - /// - /// Gets all of the raw text from a message as a single joined string. - /// - /// - /// All the raw text from the contained payloads, joined into a single string. - /// - public string TextValue + /// + /// Gets all of the raw text from a message as a single joined string. + /// + /// + /// All the raw text from the contained payloads, joined into a single string. + /// + public string TextValue + { + get { - get + return this.Payloads + .Where(p => p is ITextProvider) + .Cast() + .Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString()); + } + } + + /// + /// Implicitly convert a string into a SeString containing a . + /// + /// string to convert. + /// Equivalent SeString. + public static implicit operator SeString(string str) => new(new TextPayload(str)); + + /// + /// Implicitly convert a string into a SeString containing a . + /// + /// string to convert. + /// Equivalent SeString. + public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString(); + + /// + /// Parse a binary game message into an SeString. + /// + /// Pointer to the string's data in memory. + /// Length of the string's data in memory. + /// An SeString containing parsed Payload objects for each payload in the data. + public static unsafe SeString Parse(byte* ptr, int len) + { + if (ptr == null) + return Empty; + + var payloads = new List(); + + using (var stream = new UnmanagedMemoryStream(ptr, len)) + using (var reader = new BinaryReader(stream)) + { + while (stream.Position < len) { - return this.Payloads - .Where(p => p is ITextProvider) - .Cast() - .Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString()); + var payload = Payload.Decode(reader); + if (payload != null) + payloads.Add(payload); } } - /// - /// Implicitly convert a string into a SeString containing a . - /// - /// string to convert. - /// Equivalent SeString. - public static implicit operator SeString(string str) => new(new TextPayload(str)); + return new SeString(payloads); + } - /// - /// Implicitly convert a string into a SeString containing a . - /// - /// string to convert. - /// Equivalent SeString. - public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString(); - - /// - /// Parse a binary game message into an SeString. - /// - /// Pointer to the string's data in memory. - /// Length of the string's data in memory. - /// An SeString containing parsed Payload objects for each payload in the data. - public static unsafe SeString Parse(byte* ptr, int len) + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + public static unsafe SeString Parse(ReadOnlySpan data) + { + fixed (byte* ptr = data) { - if (ptr == null) - return Empty; + return Parse(ptr, data.Length); + } + } - var payloads = new List(); + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + public static SeString Parse(byte[] bytes) => Parse(new ReadOnlySpan(bytes)); - using (var stream = new UnmanagedMemoryStream(ptr, len)) - using (var reader = new BinaryReader(stream)) - { - while (stream.Position < len) - { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); - } - } + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The id of the item to link. + /// Whether to link the high-quality variant of the item. + /// 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) + { + var data = Service.Get(); - return new SeString(payloads); + var displayName = displayNameOverride ?? data.GetExcelSheet()?.GetRow(itemId)?.Name; + if (isHq) + { + displayName += $" {(char)SeIconChar.HighQuality}"; } - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - public static unsafe SeString Parse(ReadOnlySpan data) + // TODO: probably a cleaner way to build these than doing the bulk+insert + var payloads = new List(new Payload[] { - fixed (byte* ptr = data) - { - return Parse(ptr, data.Length); - } - } - - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - public static SeString Parse(byte[] bytes) => Parse(new ReadOnlySpan(bytes)); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The id of the item to link. - /// Whether to link the high-quality variant of the item. - /// 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) - { - var data = Service.Get(); - - var displayName = displayNameOverride ?? data.GetExcelSheet()?.GetRow(itemId)?.Name; - if (isHq) - { - displayName += $" {(char)SeIconChar.HighQuality}"; - } - - // TODO: probably a cleaner way to build these than doing the bulk+insert - var payloads = new List(new Payload[] - { new UIForegroundPayload(0x0225), new UIGlowPayload(0x0226), new ItemPayload(itemId, isHq), // arrow goes here new TextPayload(displayName), RawPayload.LinkTerminator, - // sometimes there is another set of uiglow/foreground off payloads here - // might be necessary when including additional text after the item name - }); - payloads.InsertRange(3, TextArrowPayloads); + // sometimes there is another set of uiglow/foreground off payloads here + // might be necessary when including additional text after the item name + }); + payloads.InsertRange(3, TextArrowPayloads); - return new SeString(payloads); - } + return new SeString(payloads); + } - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The Lumina Item to link. - /// Whether to link the high-quality variant of the item. - /// 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(Item item, bool isHq, string? displayNameOverride = null) + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The Lumina Item to link. + /// Whether to link the high-quality variant of the item. + /// 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(Item item, bool isHq, string? displayNameOverride = null) + { + return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name); + } + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The raw x-coordinate for this link. + /// The raw y-coordinate for this link.. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + public static SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) + { + var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY); + var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; + + var payloads = new List(new Payload[] { - return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name); - } - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The raw x-coordinate for this link. - /// The raw y-coordinate for this link.. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - public static SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) - { - var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY); - var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; - - var payloads = new List(new Payload[] - { mapPayload, // arrow goes here new TextPayload(nameString), RawPayload.LinkTerminator, - }); - payloads.InsertRange(1, TextArrowPayloads); + }); + payloads.InsertRange(1, TextArrowPayloads); - return new SeString(payloads); - } + return new SeString(payloads); + } - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - public static SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + public static SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) + { + var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor); + var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; + + var payloads = new List(new Payload[] { - var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor); - var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; - - var payloads = new List(new Payload[] - { mapPayload, // arrow goes here new TextPayload(nameString), RawPayload.LinkTerminator, - }); - payloads.InsertRange(1, TextArrowPayloads); + }); + payloads.InsertRange(1, TextArrowPayloads); - return new SeString(payloads); - } + return new SeString(payloads); + } - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. - /// Returns null if no corresponding PlaceName was found. - /// - /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. + /// Returns null if no corresponding PlaceName was found. + /// + /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) + { + var data = Service.Get(); + + var mapSheet = data.GetExcelSheet(); + + var matches = data.GetExcelSheet() + .Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant()) + .ToArray(); + + foreach (var place in matches) { - var data = Service.Get(); - - var mapSheet = data.GetExcelSheet(); - - var matches = data.GetExcelSheet() - .Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant()) - .ToArray(); - - foreach (var place in matches) + var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); + if (map != null) { - var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); - if (map != null) - { - return CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor); - } + return CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor); } - - // TODO: empty? throw? - return null; } - /// - /// Creates a SeString from a json. (For testing - not recommended for production use.) - /// - /// A serialized SeString produced by ToJson() . - /// A SeString initialized with values from the json. - public static SeString? FromJson(string json) + // TODO: empty? throw? + return null; + } + + /// + /// Creates a SeString from a json. (For testing - not recommended for production use.) + /// + /// A serialized SeString produced by ToJson() . + /// A SeString initialized with values from the json. + public static SeString? FromJson(string json) + { + var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings { - var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - TypeNameHandling = TypeNameHandling.Auto, - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, - }); + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + TypeNameHandling = TypeNameHandling.Auto, + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, + }); - return s; - } + return s; + } - /// - /// Serializes the SeString to json. - /// - /// An json representation of this object. - public string ToJson() + /// + /// Serializes the SeString to json. + /// + /// An json representation of this object. + public string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() { - return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - TypeNameHandling = TypeNameHandling.Auto, - }); - } + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + TypeNameHandling = TypeNameHandling.Auto, + }); + } - /// - /// Appends the contents of one SeString to this one. - /// - /// The SeString to append to this one. - /// This object. - public SeString Append(SeString other) + /// + /// Appends the contents of one SeString to this one. + /// + /// The SeString to append to this one. + /// This object. + public SeString Append(SeString other) + { + this.Payloads.AddRange(other.Payloads); + return this; + } + + /// + /// Appends a list of payloads to this SeString. + /// + /// The Payloads to append. + /// This object. + public SeString Append(List payloads) + { + this.Payloads.AddRange(payloads); + return this; + } + + /// + /// Appends a single payload to this SeString. + /// + /// The payload to append. + /// This object. + public SeString Append(Payload payload) + { + this.Payloads.Add(payload); + return this; + } + + /// + /// Encodes the Payloads in this SeString into a binary representation + /// suitable for use by in-game handlers, such as the chat log. + /// + /// The binary encoded payload data. + public byte[] Encode() + { + var messageBytes = new List(); + foreach (var p in this.Payloads) { - this.Payloads.AddRange(other.Payloads); - return this; + messageBytes.AddRange(p.Encode()); } - /// - /// Appends a list of payloads to this SeString. - /// - /// The Payloads to append. - /// This object. - public SeString Append(List payloads) - { - this.Payloads.AddRange(payloads); - return this; - } + return messageBytes.ToArray(); + } - /// - /// Appends a single payload to this SeString. - /// - /// The payload to append. - /// This object. - public SeString Append(Payload payload) - { - this.Payloads.Add(payload); - return this; - } - - /// - /// Encodes the Payloads in this SeString into a binary representation - /// suitable for use by in-game handlers, such as the chat log. - /// - /// The binary encoded payload data. - public byte[] Encode() - { - var messageBytes = new List(); - foreach (var p in this.Payloads) - { - messageBytes.AddRange(p.Encode()); - } - - return messageBytes.ToArray(); - } - - /// - /// Get the text value of this SeString. - /// - /// The TextValue property. - public override string ToString() - { - return this.TextValue; - } + /// + /// Get the text value of this SeString. + /// + /// The TextValue property. + public override string ToString() + { + return this.TextValue; } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs index 16f2b4209..10c3b6347 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs @@ -9,109 +9,108 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Game.Text.SeStringHandling +namespace Dalamud.Game.Text.SeStringHandling; + +/// +/// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components. +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] +public sealed class SeStringManager { /// - /// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components. + /// Initializes a new instance of the class. /// - [PluginInterface] - [InterfaceVersion("1.0")] - [Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] - public sealed class SeStringManager + internal SeStringManager() { - /// - /// Initializes a new instance of the class. - /// - internal SeStringManager() - { - } - - /// - /// Parse a binary game message into an SeString. - /// - /// Pointer to the string's data in memory. - /// Length of the string's data in memory. - /// An SeString containing parsed Payload objects for each payload in the data. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public unsafe SeString Parse(byte* ptr, int len) => SeString.Parse(ptr, len); - - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public unsafe SeString Parse(ReadOnlySpan data) => SeString.Parse(data); - - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan(bytes)); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The id of the item to link. - /// Whether to link the high-quality variant of the item. - /// 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. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(itemId, isHQ, displayNameOverride); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The Lumina Item to link. - /// Whether to link the high-quality variant of the item. - /// 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. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(item, isHQ, displayNameOverride); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The raw x-coordinate for this link. - /// The raw y-coordinate for this link.. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) => - SeString.CreateMapLink(territoryId, mapId, rawX, rawY); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(territoryId, mapId, xCoord, yCoord, fudgeFactor); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. - /// - /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(placeName, xCoord, yCoord, fudgeFactor); - - /// - /// Creates a list of Payloads necessary to display the arrow link marker icon in chat - /// with the appropriate glow and coloring. - /// - /// A list of all the payloads required to insert the link marker. - [Obsolete("This data is obsolete. Please use the static version on SeString instead.", true)] - public List TextArrowPayloads() => new(SeString.TextArrowPayloads); } + + /// + /// Parse a binary game message into an SeString. + /// + /// Pointer to the string's data in memory. + /// Length of the string's data in memory. + /// An SeString containing parsed Payload objects for each payload in the data. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public unsafe SeString Parse(byte* ptr, int len) => SeString.Parse(ptr, len); + + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public unsafe SeString Parse(ReadOnlySpan data) => SeString.Parse(data); + + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan(bytes)); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The id of the item to link. + /// Whether to link the high-quality variant of the item. + /// 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. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(itemId, isHQ, displayNameOverride); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The Lumina Item to link. + /// Whether to link the high-quality variant of the item. + /// 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. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(item, isHQ, displayNameOverride); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The raw x-coordinate for this link. + /// The raw y-coordinate for this link.. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) => + SeString.CreateMapLink(territoryId, mapId, rawX, rawY); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(territoryId, mapId, xCoord, yCoord, fudgeFactor); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. + /// + /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(placeName, xCoord, yCoord, fudgeFactor); + + /// + /// Creates a list of Payloads necessary to display the arrow link marker icon in chat + /// with the appropriate glow and coloring. + /// + /// A list of all the payloads required to insert the link marker. + [Obsolete("This data is obsolete. Please use the static version on SeString instead.", true)] + public List TextArrowPayloads() => new(SeString.TextArrowPayloads); } diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index a5a11766e..afc89b906 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -2,36 +2,35 @@ using System; using Dalamud.Game.Text.SeStringHandling; -namespace Dalamud.Game.Text +namespace Dalamud.Game.Text; + +/// +/// This class represents a single chat log entry. +/// +public sealed class XivChatEntry { /// - /// This class represents a single chat log entry. + /// Gets or sets the type of entry. /// - public sealed class XivChatEntry - { - /// - /// Gets or sets the type of entry. - /// - public XivChatType Type { get; set; } = XivChatType.Debug; + public XivChatType Type { get; set; } = XivChatType.Debug; - /// - /// Gets or sets the sender ID. - /// - public uint SenderId { get; set; } + /// + /// Gets or sets the sender ID. + /// + public uint SenderId { get; set; } - /// - /// Gets or sets the sender name. - /// - public SeString Name { get; set; } = string.Empty; + /// + /// Gets or sets the sender name. + /// + public SeString Name { get; set; } = string.Empty; - /// - /// Gets or sets the message. - /// - public SeString Message { get; set; } = string.Empty; + /// + /// Gets or sets the message. + /// + public SeString Message { get; set; } = string.Empty; - /// - /// Gets or sets the message parameters. - /// - public IntPtr Parameters { get; set; } - } + /// + /// Gets or sets the message parameters. + /// + public IntPtr Parameters { get; set; } } diff --git a/Dalamud/Game/Text/XivChatType.cs b/Dalamud/Game/Text/XivChatType.cs index d89fc5f0c..b5d7bf2b3 100644 --- a/Dalamud/Game/Text/XivChatType.cs +++ b/Dalamud/Game/Text/XivChatType.cs @@ -1,237 +1,236 @@ -namespace Dalamud.Game.Text +namespace Dalamud.Game.Text; + +/// +/// The FFXIV chat types as seen in the LogKind ex table. +/// +public enum XivChatType : ushort // FIXME: this is a single byte { /// - /// The FFXIV chat types as seen in the LogKind ex table. + /// No chat type. /// - public enum XivChatType : ushort // FIXME: this is a single byte - { - /// - /// No chat type. - /// - None = 0, + None = 0, - /// - /// The debug chat type. - /// - Debug = 1, + /// + /// The debug chat type. + /// + Debug = 1, - /// - /// The urgent chat type. - /// - [XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)] - Urgent = 2, + /// + /// The urgent chat type. + /// + [XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)] + Urgent = 2, - /// - /// The notice chat type. - /// - [XivChatTypeInfo("Notice", "notice", 0xFF9400D3)] - Notice = 3, + /// + /// The notice chat type. + /// + [XivChatTypeInfo("Notice", "notice", 0xFF9400D3)] + Notice = 3, - /// - /// The say chat type. - /// - [XivChatTypeInfo("Say", "say", 0xFFFFFFFF)] - Say = 10, + /// + /// The say chat type. + /// + [XivChatTypeInfo("Say", "say", 0xFFFFFFFF)] + Say = 10, - /// - /// The shout chat type. - /// - [XivChatTypeInfo("Shout", "shout", 0xFFFF4500)] - Shout = 11, + /// + /// The shout chat type. + /// + [XivChatTypeInfo("Shout", "shout", 0xFFFF4500)] + Shout = 11, - /// - /// The outgoing tell chat type. - /// - TellOutgoing = 12, + /// + /// The outgoing tell chat type. + /// + TellOutgoing = 12, - /// - /// The incoming tell chat type. - /// - [XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)] - TellIncoming = 13, + /// + /// The incoming tell chat type. + /// + [XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)] + TellIncoming = 13, - /// - /// The party chat type. - /// - [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] - Party = 14, + /// + /// The party chat type. + /// + [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] + Party = 14, - /// - /// The alliance chat type. - /// - [XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)] - Alliance = 15, + /// + /// The alliance chat type. + /// + [XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)] + Alliance = 15, - /// - /// The linkshell 1 chat type. - /// - [XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)] - Ls1 = 16, + /// + /// The linkshell 1 chat type. + /// + [XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)] + Ls1 = 16, - /// - /// The linkshell 2 chat type. - /// - [XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)] - Ls2 = 17, + /// + /// The linkshell 2 chat type. + /// + [XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)] + Ls2 = 17, - /// - /// The linkshell 3 chat type. - /// - [XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)] - Ls3 = 18, + /// + /// The linkshell 3 chat type. + /// + [XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)] + Ls3 = 18, - /// - /// The linkshell 4 chat type. - /// - [XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)] - Ls4 = 19, + /// + /// The linkshell 4 chat type. + /// + [XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)] + Ls4 = 19, - /// - /// The linkshell 5 chat type. - /// - [XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)] - Ls5 = 20, + /// + /// The linkshell 5 chat type. + /// + [XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)] + Ls5 = 20, - /// - /// The linkshell 6 chat type. - /// - [XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)] - Ls6 = 21, + /// + /// The linkshell 6 chat type. + /// + [XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)] + Ls6 = 21, - /// - /// The linkshell 7 chat type. - /// - [XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)] - Ls7 = 22, + /// + /// The linkshell 7 chat type. + /// + [XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)] + Ls7 = 22, - /// - /// The linkshell 8 chat type. - /// - [XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)] - Ls8 = 23, + /// + /// The linkshell 8 chat type. + /// + [XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)] + Ls8 = 23, - /// - /// The free company chat type. - /// - [XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)] - FreeCompany = 24, + /// + /// The free company chat type. + /// + [XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)] + FreeCompany = 24, - /// - /// The novice network chat type. - /// - [XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)] - NoviceNetwork = 27, + /// + /// The novice network chat type. + /// + [XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)] + NoviceNetwork = 27, - /// - /// The custom emotes chat type. - /// - [XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)] - CustomEmote = 28, + /// + /// The custom emotes chat type. + /// + [XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)] + CustomEmote = 28, - /// - /// The standard emotes chat type. - /// - [XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)] - StandardEmote = 29, + /// + /// The standard emotes chat type. + /// + [XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)] + StandardEmote = 29, - /// - /// The yell chat type. - /// - [XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)] - Yell = 30, + /// + /// The yell chat type. + /// + [XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)] + Yell = 30, - /// - /// The cross-world party chat type. - /// - [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] - CrossParty = 32, + /// + /// The cross-world party chat type. + /// + [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] + CrossParty = 32, - /// - /// The PvP team chat type. - /// - [XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)] - PvPTeam = 36, + /// + /// The PvP team chat type. + /// + [XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)] + PvPTeam = 36, - /// - /// The cross-world linkshell chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)] - CrossLinkShell1 = 37, + /// + /// The cross-world linkshell chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)] + CrossLinkShell1 = 37, - /// - /// The echo chat type. - /// - [XivChatTypeInfo("Echo", "echo", 0xFF808080)] - Echo = 56, + /// + /// The echo chat type. + /// + [XivChatTypeInfo("Echo", "echo", 0xFF808080)] + Echo = 56, - /// - /// The system error chat type. - /// - SystemError = 58, + /// + /// The system error chat type. + /// + SystemError = 58, - /// - /// The system message chat type. - /// - SystemMessage = 57, + /// + /// The system message chat type. + /// + SystemMessage = 57, - /// - /// The system message (gathering) chat type. - /// - GatheringSystemMessage = 59, + /// + /// The system message (gathering) chat type. + /// + GatheringSystemMessage = 59, - /// - /// The error message chat type. - /// - ErrorMessage = 60, + /// + /// The error message chat type. + /// + ErrorMessage = 60, - /// - /// The retainer sale chat type. - /// - /// - /// This might be used for other purposes. - /// - RetainerSale = 71, + /// + /// The retainer sale chat type. + /// + /// + /// This might be used for other purposes. + /// + RetainerSale = 71, - /// - /// The cross-world linkshell 2 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)] - CrossLinkShell2 = 101, + /// + /// The cross-world linkshell 2 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)] + CrossLinkShell2 = 101, - /// - /// The cross-world linkshell 3 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)] - CrossLinkShell3 = 102, + /// + /// The cross-world linkshell 3 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)] + CrossLinkShell3 = 102, - /// - /// The cross-world linkshell 4 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)] - CrossLinkShell4 = 103, + /// + /// The cross-world linkshell 4 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)] + CrossLinkShell4 = 103, - /// - /// The cross-world linkshell 5 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)] - CrossLinkShell5 = 104, + /// + /// The cross-world linkshell 5 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)] + CrossLinkShell5 = 104, - /// - /// The cross-world linkshell 6 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)] - CrossLinkShell6 = 105, + /// + /// The cross-world linkshell 6 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)] + CrossLinkShell6 = 105, - /// - /// The cross-world linkshell 7 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)] - CrossLinkShell7 = 106, + /// + /// The cross-world linkshell 7 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)] + CrossLinkShell7 = 106, - /// - /// The cross-world linkshell 8 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)] - CrossLinkShell8 = 107, - } + /// + /// The cross-world linkshell 8 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)] + CrossLinkShell8 = 107, } diff --git a/Dalamud/Game/Text/XivChatTypeExtensions.cs b/Dalamud/Game/Text/XivChatTypeExtensions.cs index a26687c47..3bdeb1525 100644 --- a/Dalamud/Game/Text/XivChatTypeExtensions.cs +++ b/Dalamud/Game/Text/XivChatTypeExtensions.cs @@ -1,20 +1,19 @@ using Dalamud.Utility; -namespace Dalamud.Game.Text +namespace Dalamud.Game.Text; + +/// +/// Extension methods for the type. +/// +public static class XivChatTypeExtensions { /// - /// Extension methods for the type. + /// Get the InfoAttribute associated with this chat type. /// - public static class XivChatTypeExtensions + /// The chat type. + /// The info attribute. + public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) { - /// - /// Get the InfoAttribute associated with this chat type. - /// - /// The chat type. - /// The info attribute. - public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) - { - return chatType.GetAttribute(); - } + return chatType.GetAttribute(); } } diff --git a/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs b/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs index e549ac761..91bdeef40 100644 --- a/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs +++ b/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs @@ -1,39 +1,38 @@ using System; -namespace Dalamud.Game.Text +namespace Dalamud.Game.Text; + +/// +/// Storage for relevant information associated with the chat type. +/// +[AttributeUsage(AttributeTargets.Field)] +public class XivChatTypeInfoAttribute : Attribute { /// - /// Storage for relevant information associated with the chat type. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Field)] - public class XivChatTypeInfoAttribute : Attribute + /// The fancy name. + /// The name slug. + /// The default color. + internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) { - /// - /// Initializes a new instance of the class. - /// - /// The fancy name. - /// The name slug. - /// The default color. - internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) - { - this.FancyName = fancyName; - this.Slug = slug; - this.DefaultColor = defaultColor; - } - - /// - /// Gets the "fancy" name of the type. - /// - public string FancyName { get; } - - /// - /// Gets the type name slug or short-form. - /// - public string Slug { get; } - - /// - /// Gets the type default color. - /// - public uint DefaultColor { get; } + this.FancyName = fancyName; + this.Slug = slug; + this.DefaultColor = defaultColor; } + + /// + /// Gets the "fancy" name of the type. + /// + public string FancyName { get; } + + /// + /// Gets the type name slug or short-form. + /// + public string Slug { get; } + + /// + /// Gets the type default color. + /// + public uint DefaultColor { get; } } diff --git a/Dalamud/Hooking/AsmHook.cs b/Dalamud/Hooking/AsmHook.cs index 35eac4efd..043b02b9e 100644 --- a/Dalamud/Hooking/AsmHook.cs +++ b/Dalamud/Hooking/AsmHook.cs @@ -6,178 +6,177 @@ using Dalamud.Hooking.Internal; using Dalamud.Memory; using Reloaded.Hooks; -namespace Dalamud.Hooking +namespace Dalamud.Hooking; + +/// +/// Manages a hook which can be used to intercept a call to native function. +/// This class is basically a thin wrapper around the LocalHook type to provide helper functions. +/// +public sealed class AsmHook : IDisposable, IDalamudHook { + private readonly IntPtr address; + private readonly Reloaded.Hooks.Definitions.IAsmHook hookImpl; + + private bool isActivated = false; + private bool isEnabled = false; + + private DynamicMethod statsMethod; + /// - /// Manages a hook which can be used to intercept a call to native function. - /// This class is basically a thin wrapper around the LocalHook type to provide helper functions. + /// Initializes a new instance of the class. + /// This is an assembly hook and should not be used for except under unique circumstances. + /// Hook is not activated until Enable() method is called. /// - public sealed class AsmHook : IDisposable, IDalamudHook + /// A memory address to install a hook. + /// Assembly code representing your hook. + /// The name of what you are hooking, since a delegate is not required. + /// How the hook is inserted into the execution flow. + public AsmHook(IntPtr address, byte[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) { - private readonly IntPtr address; - private readonly Reloaded.Hooks.Definitions.IAsmHook hookImpl; + address = HookManager.FollowJmp(address); - private bool isActivated = false; - private bool isEnabled = false; - - private DynamicMethod statsMethod; - - /// - /// Initializes a new instance of the class. - /// This is an assembly hook and should not be used for except under unique circumstances. - /// Hook is not activated until Enable() method is called. - /// - /// A memory address to install a hook. - /// Assembly code representing your hook. - /// The name of what you are hooking, since a delegate is not required. - /// How the hook is inserted into the execution flow. - public AsmHook(IntPtr address, byte[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (!hasOtherHooks) { - address = HookManager.FollowJmp(address); - - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (!hasOtherHooks) - { - MemoryHelper.ReadRaw(address, 0x32, out var original); - HookManager.Originals[address] = original; - } - - this.address = address; - this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); - - this.statsMethod = new DynamicMethod(name, null, null); - this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); - var dele = this.statsMethod.CreateDelegate(typeof(Action)); - - HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); + MemoryHelper.ReadRaw(address, 0x32, out var original); + HookManager.Originals[address] = original; } - /// - /// Initializes a new instance of the class. - /// This is an assembly hook and should not be used for except under unique circumstances. - /// Hook is not activated until Enable() method is called. - /// - /// A memory address to install a hook. - /// FASM syntax assembly code representing your hook. The first line should be use64. - /// The name of what you are hooking, since a delegate is not required. - /// How the hook is inserted into the execution flow. - public AsmHook(IntPtr address, string[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) + this.address = address; + this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); + + this.statsMethod = new DynamicMethod(name, null, null); + this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); + var dele = this.statsMethod.CreateDelegate(typeof(Action)); + + HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); + } + + /// + /// Initializes a new instance of the class. + /// This is an assembly hook and should not be used for except under unique circumstances. + /// Hook is not activated until Enable() method is called. + /// + /// A memory address to install a hook. + /// FASM syntax assembly code representing your hook. The first line should be use64. + /// The name of what you are hooking, since a delegate is not required. + /// How the hook is inserted into the execution flow. + public AsmHook(IntPtr address, string[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) + { + address = HookManager.FollowJmp(address); + + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (!hasOtherHooks) { - address = HookManager.FollowJmp(address); - - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (!hasOtherHooks) - { - MemoryHelper.ReadRaw(address, 0x32, out var original); - HookManager.Originals[address] = original; - } - - this.address = address; - this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); - - this.statsMethod = new DynamicMethod(name, null, null); - this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); - var dele = this.statsMethod.CreateDelegate(typeof(Action)); - - HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); + MemoryHelper.ReadRaw(address, 0x32, out var original); + HookManager.Originals[address] = original; } - /// - /// Gets a memory address of the target function. - /// - /// Hook is already disposed. - public IntPtr Address - { - get - { - this.CheckDisposed(); - return this.address; - } - } + this.address = address; + this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public bool IsEnabled - { - get - { - this.CheckDisposed(); - return this.isEnabled; - } - } + this.statsMethod = new DynamicMethod(name, null, null); + this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); + var dele = this.statsMethod.CreateDelegate(typeof(Action)); - /// - /// Gets a value indicating whether or not the hook has been disposed. - /// - public bool IsDisposed { get; private set; } + HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); + } - /// - public string BackendName => "Reloaded/Asm"; - - /// - /// Remove a hook from the current process. - /// - public void Dispose() - { - if (this.IsDisposed) - return; - - this.IsDisposed = true; - - if (this.isEnabled) - { - this.isEnabled = false; - this.hookImpl.Disable(); - } - } - - /// - /// Starts intercepting a call to the function. - /// - public void Enable() + /// + /// Gets a memory address of the target function. + /// + /// Hook is already disposed. + public IntPtr Address + { + get { this.CheckDisposed(); - - if (!this.isActivated) - { - this.isActivated = true; - this.hookImpl.Activate(); - } - - if (!this.isEnabled) - { - this.isEnabled = true; - this.hookImpl.Enable(); - } + return this.address; } + } - /// - /// Stops intercepting a call to the function. - /// - public void Disable() + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled + { + get { this.CheckDisposed(); + return this.isEnabled; + } + } - if (!this.isEnabled) - return; + /// + /// Gets a value indicating whether or not the hook has been disposed. + /// + public bool IsDisposed { get; private set; } - if (this.isEnabled) - { - this.isEnabled = false; - this.hookImpl.Disable(); - } + /// + public string BackendName => "Reloaded/Asm"; + + /// + /// Remove a hook from the current process. + /// + public void Dispose() + { + if (this.IsDisposed) + return; + + this.IsDisposed = true; + + if (this.isEnabled) + { + this.isEnabled = false; + this.hookImpl.Disable(); + } + } + + /// + /// Starts intercepting a call to the function. + /// + public void Enable() + { + this.CheckDisposed(); + + if (!this.isActivated) + { + this.isActivated = true; + this.hookImpl.Activate(); } - /// - /// Check if this object has been disposed already. - /// - private void CheckDisposed() + if (!this.isEnabled) { - if (this.IsDisposed) - { - throw new ObjectDisposedException(message: "Hook is already disposed", null); - } + this.isEnabled = true; + this.hookImpl.Enable(); + } + } + + /// + /// Stops intercepting a call to the function. + /// + public void Disable() + { + this.CheckDisposed(); + + if (!this.isEnabled) + return; + + if (this.isEnabled) + { + this.isEnabled = false; + this.hookImpl.Disable(); + } + } + + /// + /// Check if this object has been disposed already. + /// + private void CheckDisposed() + { + if (this.IsDisposed) + { + throw new ObjectDisposedException(message: "Hook is already disposed", null); } } } diff --git a/Dalamud/Hooking/AsmHookBehaviour.cs b/Dalamud/Hooking/AsmHookBehaviour.cs index 9f856ae67..6676ee670 100644 --- a/Dalamud/Hooking/AsmHookBehaviour.cs +++ b/Dalamud/Hooking/AsmHookBehaviour.cs @@ -1,24 +1,23 @@ -namespace Dalamud.Hooking +namespace Dalamud.Hooking; + +/// +/// Defines the behaviour used by the Dalamud.Hooking.AsmHook. +/// This is equivalent to the same enumeration in Reloaded and is included so you do not have to reference the assembly. +/// +public enum AsmHookBehaviour { /// - /// Defines the behaviour used by the Dalamud.Hooking.AsmHook. - /// This is equivalent to the same enumeration in Reloaded and is included so you do not have to reference the assembly. + /// Executes your assembly code before the original. /// - public enum AsmHookBehaviour - { - /// - /// Executes your assembly code before the original. - /// - ExecuteFirst = 0, + ExecuteFirst = 0, - /// - /// Executes your assembly code after the original. - /// - ExecuteAfter = 1, + /// + /// Executes your assembly code after the original. + /// + ExecuteAfter = 1, - /// - /// Do not execute original replaced code (Dangerous!). - /// - DoNotExecuteOriginal = 2, - } + /// + /// Do not execute original replaced code (Dangerous!). + /// + DoNotExecuteOriginal = 2, } diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index 430527ed0..3a8c34656 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -7,260 +7,259 @@ using Dalamud.Hooking.Internal; using Dalamud.Memory; using Reloaded.Hooks; -namespace Dalamud.Hooking +namespace Dalamud.Hooking; + +/// +/// Manages a hook which can be used to intercept a call to native function. +/// This class is basically a thin wrapper around the LocalHook type to provide helper functions. +/// +/// Delegate type to represents a function prototype. This must be the same prototype as original function do. +public sealed class Hook : IDisposable, IDalamudHook where T : Delegate { + private readonly IntPtr address; + private readonly Reloaded.Hooks.Definitions.IHook hookImpl; + private readonly MinSharp.Hook minHookImpl; + private readonly bool isMinHook; + /// - /// Manages a hook which can be used to intercept a call to native function. - /// This class is basically a thin wrapper around the LocalHook type to provide helper functions. + /// Initializes a new instance of the class. + /// Hook is not activated until Enable() method is called. /// - /// Delegate type to represents a function prototype. This must be the same prototype as original function do. - public sealed class Hook : IDisposable, IDalamudHook where T : Delegate + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + public Hook(IntPtr address, T detour) + : this(address, detour, false, Assembly.GetCallingAssembly()) { - private readonly IntPtr address; - private readonly Reloaded.Hooks.Definitions.IHook hookImpl; - private readonly MinSharp.Hook minHookImpl; - private readonly bool isMinHook; + } - /// - /// Initializes a new instance of the class. - /// Hook is not activated until Enable() method is called. - /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - public Hook(IntPtr address, T detour) - : this(address, detour, false, Assembly.GetCallingAssembly()) + /// + /// Initializes a new instance of the class. + /// Hook is not activated until Enable() method is called. + /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. + /// + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// Use the MinHook hooking library instead of Reloaded. + public Hook(IntPtr address, T detour, bool useMinHook) + : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) + { + } + + private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) + { + address = HookManager.FollowJmp(address); + this.isMinHook = !EnvironmentConfiguration.DalamudForceReloaded && (EnvironmentConfiguration.DalamudForceMinHook || useMinHook); + + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (!hasOtherHooks) { + MemoryHelper.ReadRaw(address, 0x32, out var original); + HookManager.Originals[address] = original; } - /// - /// Initializes a new instance of the class. - /// Hook is not activated until Enable() method is called. - /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. - /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - /// Use the MinHook hooking library instead of Reloaded. - public Hook(IntPtr address, T detour, bool useMinHook) - : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) + this.address = address; + if (this.isMinHook) { + if (!HookManager.MultiHookTracker.TryGetValue(address, out var indexList)) + indexList = HookManager.MultiHookTracker[address] = new(); + + var index = (ulong)indexList.Count; + + this.minHookImpl = new MinSharp.Hook(address, detour, index); + + // Add afterwards, so the hookIdent starts at 0. + indexList.Add(this); + } + else + { + this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); } - private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) - { - address = HookManager.FollowJmp(address); - this.isMinHook = !EnvironmentConfiguration.DalamudForceReloaded && (EnvironmentConfiguration.DalamudForceMinHook || useMinHook); + HookManager.TrackedHooks.Add(new HookInfo(this, detour, callingAssembly)); + } - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (!hasOtherHooks) - { - MemoryHelper.ReadRaw(address, 0x32, out var original); - HookManager.Originals[address] = original; - } - - this.address = address; - if (this.isMinHook) - { - if (!HookManager.MultiHookTracker.TryGetValue(address, out var indexList)) - indexList = HookManager.MultiHookTracker[address] = new(); - - var index = (ulong)indexList.Count; - - this.minHookImpl = new MinSharp.Hook(address, detour, index); - - // Add afterwards, so the hookIdent starts at 0. - indexList.Add(this); - } - else - { - this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); - } - - HookManager.TrackedHooks.Add(new HookInfo(this, detour, callingAssembly)); - } - - /// - /// Gets a memory address of the target function. - /// - /// Hook is already disposed. - public IntPtr Address - { - get - { - this.CheckDisposed(); - return this.address; - } - } - - /// - /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. - /// - /// Hook is already disposed. - public T Original - { - get - { - this.CheckDisposed(); - if (this.isMinHook) - { - return this.minHookImpl.Original; - } - else - { - return this.hookImpl.OriginalFunction; - } - } - } - - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public bool IsEnabled - { - get - { - this.CheckDisposed(); - if (this.isMinHook) - { - return this.minHookImpl.Enabled; - } - else - { - return this.hookImpl.IsHookEnabled; - } - } - } - - /// - /// Gets a value indicating whether or not the hook has been disposed. - /// - public bool IsDisposed { get; private set; } - - /// - public string BackendName - { - get - { - if (this.isMinHook) - return "MinHook"; - - return "Reloaded"; - } - } - - /// - /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. - /// The hook is not activated until Enable() method is called. - /// - /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). - /// A name of the exported function name (e.g. send). - /// Callback function. Delegate must have a same original function prototype. - /// The hook with the supplied parameters. - public static Hook FromSymbol(string moduleName, string exportName, T detour) - => FromSymbol(moduleName, exportName, detour, false); - - /// - /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. - /// The hook is not activated until Enable() method is called. - /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. - /// - /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). - /// A name of the exported function name (e.g. send). - /// Callback function. Delegate must have a same original function prototype. - /// Use the MinHook hooking library instead of Reloaded. - /// The hook with the supplied parameters. - public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) - { - var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); - if (moduleHandle == IntPtr.Zero) - throw new Exception($"Could not get a handle to module {moduleName}"); - - var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName); - if (procAddress == IntPtr.Zero) - throw new Exception($"Could not get the address of {moduleName}::{exportName}"); - - return new Hook(procAddress, detour, useMinHook); - } - - /// - /// Remove a hook from the current process. - /// - public void Dispose() - { - if (this.IsDisposed) - return; - - if (this.isMinHook) - { - this.minHookImpl.Dispose(); - - var index = HookManager.MultiHookTracker[this.address].IndexOf(this); - HookManager.MultiHookTracker[this.address][index] = null; - } - else - { - this.Disable(); - } - - this.IsDisposed = true; - } - - /// - /// Starts intercepting a call to the function. - /// - public void Enable() + /// + /// Gets a memory address of the target function. + /// + /// Hook is already disposed. + public IntPtr Address + { + get { this.CheckDisposed(); - - if (this.isMinHook) - { - if (!this.minHookImpl.Enabled) - { - this.minHookImpl.Enable(); - } - } - else - { - if (!this.hookImpl.IsHookActivated) - this.hookImpl.Activate(); - - if (!this.hookImpl.IsHookEnabled) - this.hookImpl.Enable(); - } + return this.address; } + } - /// - /// Stops intercepting a call to the function. - /// - public void Disable() + /// + /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. + /// + /// Hook is already disposed. + public T Original + { + get { this.CheckDisposed(); - if (this.isMinHook) { - if (this.minHookImpl.Enabled) - { - this.minHookImpl.Disable(); - } + return this.minHookImpl.Original; } else { - if (!this.hookImpl.IsHookActivated) - return; - - if (this.hookImpl.IsHookEnabled) - this.hookImpl.Disable(); - } - } - - /// - /// Check if this object has been disposed already. - /// - private void CheckDisposed() - { - if (this.IsDisposed) - { - throw new ObjectDisposedException(message: "Hook is already disposed", null); + return this.hookImpl.OriginalFunction; } } } + + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled + { + get + { + this.CheckDisposed(); + if (this.isMinHook) + { + return this.minHookImpl.Enabled; + } + else + { + return this.hookImpl.IsHookEnabled; + } + } + } + + /// + /// Gets a value indicating whether or not the hook has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + public string BackendName + { + get + { + if (this.isMinHook) + return "MinHook"; + + return "Reloaded"; + } + } + + /// + /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. + /// The hook is not activated until Enable() method is called. + /// + /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). + /// A name of the exported function name (e.g. send). + /// Callback function. Delegate must have a same original function prototype. + /// The hook with the supplied parameters. + public static Hook FromSymbol(string moduleName, string exportName, T detour) + => FromSymbol(moduleName, exportName, detour, false); + + /// + /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. + /// The hook is not activated until Enable() method is called. + /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. + /// + /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). + /// A name of the exported function name (e.g. send). + /// Callback function. Delegate must have a same original function prototype. + /// Use the MinHook hooking library instead of Reloaded. + /// The hook with the supplied parameters. + public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) + { + var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); + if (moduleHandle == IntPtr.Zero) + throw new Exception($"Could not get a handle to module {moduleName}"); + + var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName); + if (procAddress == IntPtr.Zero) + throw new Exception($"Could not get the address of {moduleName}::{exportName}"); + + return new Hook(procAddress, detour, useMinHook); + } + + /// + /// Remove a hook from the current process. + /// + public void Dispose() + { + if (this.IsDisposed) + return; + + if (this.isMinHook) + { + this.minHookImpl.Dispose(); + + var index = HookManager.MultiHookTracker[this.address].IndexOf(this); + HookManager.MultiHookTracker[this.address][index] = null; + } + else + { + this.Disable(); + } + + this.IsDisposed = true; + } + + /// + /// Starts intercepting a call to the function. + /// + public void Enable() + { + this.CheckDisposed(); + + if (this.isMinHook) + { + if (!this.minHookImpl.Enabled) + { + this.minHookImpl.Enable(); + } + } + else + { + if (!this.hookImpl.IsHookActivated) + this.hookImpl.Activate(); + + if (!this.hookImpl.IsHookEnabled) + this.hookImpl.Enable(); + } + } + + /// + /// Stops intercepting a call to the function. + /// + public void Disable() + { + this.CheckDisposed(); + + if (this.isMinHook) + { + if (this.minHookImpl.Enabled) + { + this.minHookImpl.Disable(); + } + } + else + { + if (!this.hookImpl.IsHookActivated) + return; + + if (this.hookImpl.IsHookEnabled) + this.hookImpl.Disable(); + } + } + + /// + /// Check if this object has been disposed already. + /// + private void CheckDisposed() + { + if (this.IsDisposed) + { + throw new ObjectDisposedException(message: "Hook is already disposed", null); + } + } } diff --git a/Dalamud/Hooking/IDalamudHook.cs b/Dalamud/Hooking/IDalamudHook.cs index 5a9ae2716..1104597a1 100644 --- a/Dalamud/Hooking/IDalamudHook.cs +++ b/Dalamud/Hooking/IDalamudHook.cs @@ -1,30 +1,29 @@ using System; -namespace Dalamud.Hooking +namespace Dalamud.Hooking; + +/// +/// Interface describing a generic hook. +/// +public interface IDalamudHook { /// - /// Interface describing a generic hook. + /// Gets the address to hook. /// - public interface IDalamudHook - { - /// - /// Gets the address to hook. - /// - public IntPtr Address { get; } + public IntPtr Address { get; } - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public bool IsEnabled { get; } + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled { get; } - /// - /// Gets a value indicating whether or not the hook is disposed. - /// - public bool IsDisposed { get; } + /// + /// Gets a value indicating whether or not the hook is disposed. + /// + public bool IsDisposed { get; } - /// - /// Gets the name of the hooking backend used for the hook. - /// - public string BackendName { get; } - } + /// + /// Gets the name of the hooking backend used for the hook. + /// + public string BackendName { get; } } diff --git a/Dalamud/Hooking/Internal/HookInfo.cs b/Dalamud/Hooking/Internal/HookInfo.cs index 73db6864b..0ab05c314 100644 --- a/Dalamud/Hooking/Internal/HookInfo.cs +++ b/Dalamud/Hooking/Internal/HookInfo.cs @@ -2,73 +2,72 @@ using System; using System.Diagnostics; using System.Reflection; -namespace Dalamud.Hooking.Internal +namespace Dalamud.Hooking.Internal; + +/// +/// Class containing information about registered hooks. +/// +internal class HookInfo { + private ulong? inProcessMemory = 0; + /// - /// Class containing information about registered hooks. + /// Initializes a new instance of the class. /// - internal class HookInfo + /// The tracked hook. + /// The hook delegate. + /// The assembly implementing the hook. + public HookInfo(IDalamudHook hook, Delegate hookDelegate, Assembly assembly) { - private ulong? inProcessMemory = 0; + this.Hook = hook; + this.Delegate = hookDelegate; + this.Assembly = assembly; + } - /// - /// Initializes a new instance of the class. - /// - /// The tracked hook. - /// The hook delegate. - /// The assembly implementing the hook. - public HookInfo(IDalamudHook hook, Delegate hookDelegate, Assembly assembly) + /// + /// Gets the RVA of the hook. + /// + internal ulong? InProcessMemory + { + get { - this.Hook = hook; - this.Delegate = hookDelegate; - this.Assembly = assembly; - } + if (this.Hook.IsDisposed) + return 0; - /// - /// Gets the RVA of the hook. - /// - internal ulong? InProcessMemory - { - get + if (this.inProcessMemory == null) + return null; + + if (this.inProcessMemory.Value > 0) + return this.inProcessMemory.Value; + + var p = Process.GetCurrentProcess().MainModule; + var begin = (ulong)p.BaseAddress.ToInt64(); + var end = begin + (ulong)p.ModuleMemorySize; + var hookAddr = (ulong)this.Hook.Address.ToInt64(); + + if (hookAddr >= begin && hookAddr <= end) { - if (this.Hook.IsDisposed) - return 0; - - if (this.inProcessMemory == null) - return null; - - if (this.inProcessMemory.Value > 0) - return this.inProcessMemory.Value; - - var p = Process.GetCurrentProcess().MainModule; - var begin = (ulong)p.BaseAddress.ToInt64(); - var end = begin + (ulong)p.ModuleMemorySize; - var hookAddr = (ulong)this.Hook.Address.ToInt64(); - - if (hookAddr >= begin && hookAddr <= end) - { - return this.inProcessMemory = hookAddr - begin; - } - else - { - return this.inProcessMemory = null; - } + return this.inProcessMemory = hookAddr - begin; + } + else + { + return this.inProcessMemory = null; } } - - /// - /// Gets the tracked hook. - /// - internal IDalamudHook Hook { get; } - - /// - /// Gets the tracked delegate. - /// - internal Delegate Delegate { get; } - - /// - /// Gets the assembly implementing the hook. - /// - internal Assembly Assembly { get; } } + + /// + /// Gets the tracked hook. + /// + internal IDalamudHook Hook { get; } + + /// + /// Gets the tracked delegate. + /// + internal Delegate Delegate { get; } + + /// + /// Gets the assembly implementing the hook. + /// + internal Assembly Assembly { get; } } diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index fde7ba3ba..2b9f94b51 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -3,143 +3,140 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using Dalamud.Configuration.Internal; using Dalamud.Logging.Internal; using Dalamud.Memory; using Iced.Intel; -using Microsoft.Win32; -namespace Dalamud.Hooking.Internal +namespace Dalamud.Hooking.Internal; + +/// +/// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. +/// +internal class HookManager : IDisposable { + private static readonly ModuleLog Log = new("HM"); + /// - /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. + /// Initializes a new instance of the class. /// - internal class HookManager : IDisposable + public HookManager() { - private static readonly ModuleLog Log = new("HM"); + } - /// - /// Initializes a new instance of the class. - /// - public HookManager() + /// + /// Gets a static list of tracked and registered hooks. + /// + internal static List TrackedHooks { get; } = new(); + + /// + /// Gets a static dictionary of original code for a hooked address. + /// + internal static Dictionary Originals { get; } = new(); + + /// + /// Gets a static dictionary of the number of hooks on a given address. + /// + internal static Dictionary> MultiHookTracker { get; } = new(); + + /// + public void Dispose() + { + RevertHooks(); + TrackedHooks.Clear(); + Originals.Clear(); + } + + /// + /// Follow a JMP or Jcc instruction to the next logical location. + /// + /// Address of the instruction. + /// The address referenced by the jmp. + internal static IntPtr FollowJmp(IntPtr address) + { + while (true) { - } - - /// - /// Gets a static list of tracked and registered hooks. - /// - internal static List TrackedHooks { get; } = new(); - - /// - /// Gets a static dictionary of original code for a hooked address. - /// - internal static Dictionary Originals { get; } = new(); - - /// - /// Gets a static dictionary of the number of hooks on a given address. - /// - internal static Dictionary> MultiHookTracker { get; } = new(); - - /// - public void Dispose() - { - RevertHooks(); - TrackedHooks.Clear(); - Originals.Clear(); - } - - /// - /// Follow a JMP or Jcc instruction to the next logical location. - /// - /// Address of the instruction. - /// The address referenced by the jmp. - internal static IntPtr FollowJmp(IntPtr address) - { - while (true) + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (hasOtherHooks) { - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (hasOtherHooks) - { - // This address has been hooked already. Do not follow a jmp into a trampoline of our own making. - Log.Verbose($"Detected hook trampoline at {address.ToInt64():X}, stopping jump resolution."); - return address; - } - - var bytes = MemoryHelper.ReadRaw(address, 8); - - var codeReader = new ByteArrayCodeReader(bytes); - var decoder = Decoder.Create(64, codeReader); - decoder.IP = (ulong)address.ToInt64(); - decoder.Decode(out var inst); - - if (inst.Mnemonic == Mnemonic.Jmp) - { - var kind = inst.Op0Kind; - - IntPtr newAddress; - switch (inst.Op0Kind) - { - case OpKind.NearBranch64: - case OpKind.NearBranch32: - case OpKind.NearBranch16: - newAddress = (IntPtr)inst.NearBranchTarget; - break; - case OpKind.Immediate16: - case OpKind.Immediate8to16: - case OpKind.Immediate8to32: - case OpKind.Immediate8to64: - case OpKind.Immediate32to64: - case OpKind.Immediate32 when IntPtr.Size == 4: - case OpKind.Immediate64: - newAddress = (IntPtr)inst.GetImmediate(0); - break; - case OpKind.Memory when inst.IsIPRelativeMemoryOperand: - newAddress = (IntPtr)inst.IPRelativeMemoryAddress; - newAddress = Marshal.ReadIntPtr(newAddress); - break; - case OpKind.Memory: - newAddress = (IntPtr)inst.MemoryDisplacement64; - newAddress = Marshal.ReadIntPtr(newAddress); - break; - default: - var debugBytes = string.Join(" ", bytes.Take(inst.Length).Select(b => $"{b:X2}")); - throw new Exception($"Unknown OpKind {inst.Op0Kind} from {debugBytes}"); - } - - Log.Verbose($"Resolving assembly jump ({kind}) from {address.ToInt64():X} to {newAddress.ToInt64():X}"); - address = newAddress; - } - else - { - break; - } + // This address has been hooked already. Do not follow a jmp into a trampoline of our own making. + Log.Verbose($"Detected hook trampoline at {address.ToInt64():X}, stopping jump resolution."); + return address; } - return address; + var bytes = MemoryHelper.ReadRaw(address, 8); + + var codeReader = new ByteArrayCodeReader(bytes); + var decoder = Decoder.Create(64, codeReader); + decoder.IP = (ulong)address.ToInt64(); + decoder.Decode(out var inst); + + if (inst.Mnemonic == Mnemonic.Jmp) + { + var kind = inst.Op0Kind; + + IntPtr newAddress; + switch (inst.Op0Kind) + { + case OpKind.NearBranch64: + case OpKind.NearBranch32: + case OpKind.NearBranch16: + newAddress = (IntPtr)inst.NearBranchTarget; + break; + case OpKind.Immediate16: + case OpKind.Immediate8to16: + case OpKind.Immediate8to32: + case OpKind.Immediate8to64: + case OpKind.Immediate32to64: + case OpKind.Immediate32 when IntPtr.Size == 4: + case OpKind.Immediate64: + newAddress = (IntPtr)inst.GetImmediate(0); + break; + case OpKind.Memory when inst.IsIPRelativeMemoryOperand: + newAddress = (IntPtr)inst.IPRelativeMemoryAddress; + newAddress = Marshal.ReadIntPtr(newAddress); + break; + case OpKind.Memory: + newAddress = (IntPtr)inst.MemoryDisplacement64; + newAddress = Marshal.ReadIntPtr(newAddress); + break; + default: + var debugBytes = string.Join(" ", bytes.Take(inst.Length).Select(b => $"{b:X2}")); + throw new Exception($"Unknown OpKind {inst.Op0Kind} from {debugBytes}"); + } + + Log.Verbose($"Resolving assembly jump ({kind}) from {address.ToInt64():X} to {newAddress.ToInt64():X}"); + address = newAddress; + } + else + { + break; + } } - private static unsafe void RevertHooks() + return address; + } + + private static unsafe void RevertHooks() + { + foreach (var (address, originalBytes) in Originals) { - foreach (var (address, originalBytes) in Originals) + var i = 0; + var current = (byte*)address; + // Find how many bytes have been modified by comparing to the saved original + for (; i < originalBytes.Length; i++) { - var i = 0; - var current = (byte*)address; - // Find how many bytes have been modified by comparing to the saved original - for (; i < originalBytes.Length; i++) - { - if (current[i] == originalBytes[i]) - break; - } + if (current[i] == originalBytes[i]) + break; + } - var snippet = originalBytes[0..i]; + var snippet = originalBytes[0..i]; - if (i > 0) - { - Log.Verbose($"Reverting hook at 0x{address.ToInt64():X} ({snippet.Length} bytes)"); - MemoryHelper.ChangePermission(address, i, MemoryProtection.ExecuteReadWrite, out var oldPermissions); - MemoryHelper.WriteRaw(address, snippet); - MemoryHelper.ChangePermission(address, i, oldPermissions); - } + if (i > 0) + { + Log.Verbose($"Reverting hook at 0x{address.ToInt64():X} ({snippet.Length} bytes)"); + MemoryHelper.ChangePermission(address, i, MemoryProtection.ExecuteReadWrite, out var oldPermissions); + MemoryHelper.WriteRaw(address, snippet); + MemoryHelper.ChangePermission(address, i, oldPermissions); } } } diff --git a/Dalamud/Interface/Animation/AnimUtil.cs b/Dalamud/Interface/Animation/AnimUtil.cs index cff36b21c..ba870b9cf 100644 --- a/Dalamud/Interface/Animation/AnimUtil.cs +++ b/Dalamud/Interface/Animation/AnimUtil.cs @@ -1,36 +1,35 @@ -using System.Numerics; +using System.Numerics; -namespace Dalamud.Interface.Animation +namespace Dalamud.Interface.Animation; + +/// +/// Class providing helper functions when facilitating animations. +/// +public static class AnimUtil { /// - /// Class providing helper functions when facilitating animations. + /// Lerp between two floats. /// - public static class AnimUtil + /// The first float. + /// The second float. + /// The point to lerp to. + /// The lerped value. + public static float Lerp(float firstFloat, float secondFloat, float by) { - /// - /// Lerp between two floats. - /// - /// The first float. - /// The second float. - /// The point to lerp to. - /// The lerped value. - public static float Lerp(float firstFloat, float secondFloat, float by) - { - return (firstFloat * (1 - @by)) + (secondFloat * by); - } + return (firstFloat * (1 - @by)) + (secondFloat * by); + } - /// - /// Lerp between two vectors. - /// - /// The first vector. - /// The second float. - /// The point to lerp to. - /// The lerped vector. - public static Vector2 Lerp(Vector2 firstVector, Vector2 secondVector, float by) - { - var retX = Lerp(firstVector.X, secondVector.X, by); - var retY = Lerp(firstVector.Y, secondVector.Y, by); - return new Vector2(retX, retY); - } + /// + /// Lerp between two vectors. + /// + /// The first vector. + /// The second float. + /// The point to lerp to. + /// The lerped vector. + public static Vector2 Lerp(Vector2 firstVector, Vector2 secondVector, float by) + { + var retX = Lerp(firstVector.X, secondVector.X, by); + var retY = Lerp(firstVector.Y, secondVector.Y, by); + return new Vector2(retX, retY); } } diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index 00509169f..e8f3deb34 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -1,117 +1,116 @@ -using System; +using System; using System.Diagnostics; using System.Numerics; -namespace Dalamud.Interface.Animation +namespace Dalamud.Interface.Animation; + +/// +/// Base class facilitating the implementation of easing functions. +/// +public abstract class Easing { + // TODO: Use game delta time here instead + private readonly Stopwatch animationTimer = new(); + + private double valueInternal; + /// - /// Base class facilitating the implementation of easing functions. + /// Initializes a new instance of the class with the specified duration. /// - public abstract class Easing + /// The animation duration. + protected Easing(TimeSpan duration) { - // TODO: Use game delta time here instead - private readonly Stopwatch animationTimer = new(); - - private double valueInternal; - - /// - /// Initializes a new instance of the class with the specified duration. - /// - /// The animation duration. - protected Easing(TimeSpan duration) - { - this.Duration = duration; - } - - /// - /// Gets or sets the origin point of the animation. - /// - public Vector2? Point1 { get; set; } - - /// - /// Gets or sets the destination point of the animation. - /// - public Vector2? Point2 { get; set; } - - /// - /// Gets the resulting point at the current timestep. - /// - public Vector2 EasedPoint { get; private set; } - - /// - /// Gets or sets a value indicating whether the result of the easing should be inversed. - /// - public bool IsInverse { get; set; } - - /// - /// Gets or sets the current value of the animation, from 0 to 1. - /// - public double Value - { - get - { - if (this.IsInverse) - return 1 - this.valueInternal; - - return this.valueInternal; - } - - protected set - { - this.valueInternal = value; - - if (this.Point1.HasValue && this.Point2.HasValue) - this.EasedPoint = AnimUtil.Lerp(this.Point1.Value, this.Point2.Value, (float)this.valueInternal); - } - } - - /// - /// Gets or sets the duration of the animation. - /// - public TimeSpan Duration { get; set; } - - /// - /// Gets a value indicating whether or not the animation is running. - /// - public bool IsRunning => this.animationTimer.IsRunning; - - /// - /// Gets a value indicating whether or not the animation is done. - /// - public bool IsDone => this.animationTimer.ElapsedMilliseconds > this.Duration.TotalMilliseconds; - - /// - /// Gets the progress of the animation, from 0 to 1. - /// - protected double Progress => this.animationTimer.ElapsedMilliseconds / this.Duration.TotalMilliseconds; - - /// - /// Starts the animation from where it was last stopped, or from the start if it was never started before. - /// - public void Start() - { - this.animationTimer.Start(); - } - - /// - /// Stops the animation at the current point. - /// - public void Stop() - { - this.animationTimer.Stop(); - } - - /// - /// Restarts the animation. - /// - public void Restart() - { - this.animationTimer.Restart(); - } - - /// - /// Updates the animation. - /// - public abstract void Update(); + this.Duration = duration; } + + /// + /// Gets or sets the origin point of the animation. + /// + public Vector2? Point1 { get; set; } + + /// + /// Gets or sets the destination point of the animation. + /// + public Vector2? Point2 { get; set; } + + /// + /// Gets the resulting point at the current timestep. + /// + public Vector2 EasedPoint { get; private set; } + + /// + /// Gets or sets a value indicating whether the result of the easing should be inversed. + /// + public bool IsInverse { get; set; } + + /// + /// Gets or sets the current value of the animation, from 0 to 1. + /// + public double Value + { + get + { + if (this.IsInverse) + return 1 - this.valueInternal; + + return this.valueInternal; + } + + protected set + { + this.valueInternal = value; + + if (this.Point1.HasValue && this.Point2.HasValue) + this.EasedPoint = AnimUtil.Lerp(this.Point1.Value, this.Point2.Value, (float)this.valueInternal); + } + } + + /// + /// Gets or sets the duration of the animation. + /// + public TimeSpan Duration { get; set; } + + /// + /// Gets a value indicating whether or not the animation is running. + /// + public bool IsRunning => this.animationTimer.IsRunning; + + /// + /// Gets a value indicating whether or not the animation is done. + /// + public bool IsDone => this.animationTimer.ElapsedMilliseconds > this.Duration.TotalMilliseconds; + + /// + /// Gets the progress of the animation, from 0 to 1. + /// + protected double Progress => this.animationTimer.ElapsedMilliseconds / this.Duration.TotalMilliseconds; + + /// + /// Starts the animation from where it was last stopped, or from the start if it was never started before. + /// + public void Start() + { + this.animationTimer.Start(); + } + + /// + /// Stops the animation at the current point. + /// + public void Stop() + { + this.animationTimer.Stop(); + } + + /// + /// Restarts the animation. + /// + public void Restart() + { + this.animationTimer.Restart(); + } + + /// + /// Updates the animation. + /// + public abstract void Update(); } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs index 952e3539d..92ad1d863 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InCirc" easing animation. +/// +public class InCirc : Easing { /// - /// Class providing an "InCirc" easing animation. + /// Initializes a new instance of the class. /// - public class InCirc : Easing + /// The duration of the animation. + public InCirc(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InCirc(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs index ebca3203d..52b56e2c3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InCubic" easing animation. +/// +public class InCubic : Easing { /// - /// Class providing an "InCubic" easing animation. + /// Initializes a new instance of the class. /// - public class InCubic : Easing + /// The duration of the animation. + public InCubic(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InCubic(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p * p * p; - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs index 97159df47..e1be03c1c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -1,33 +1,32 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InElastic" easing animation. +/// +public class InElastic : Easing { + private const double Constant = (2 * Math.PI) / 3; + /// - /// Class providing an "InElastic" easing animation. + /// Initializes a new instance of the class. /// - public class InElastic : Easing + /// The duration of the animation. + public InElastic(TimeSpan duration) + : base(duration) { - private const double Constant = (2 * Math.PI) / 3; + // ignored + } - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InElastic(TimeSpan duration) - : base(duration) - { - // ignored - } - - /// - public override void Update() - { - var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs index 97fd1a2d2..6766921d9 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -1,29 +1,28 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InOutCirc" easing animation. +/// +public class InOutCirc : Easing { /// - /// Class providing an "InOutCirc" easing animation. + /// Initializes a new instance of the class. /// - public class InOutCirc : Easing + /// The duration of the animation. + public InOutCirc(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InOutCirc(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p < 0.5 - ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 - : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p < 0.5 + ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 + : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs index c075da538..298cc98de 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InOutCubic" easing animation. +/// +public class InOutCubic : Easing { /// - /// Class providing an "InOutCubic" easing animation. + /// Initializes a new instance of the class. /// - public class InOutCubic : Easing + /// The duration of the animation. + public InOutCubic(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InOutCubic(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs index 7bb36dd74..3da2e2c58 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -1,35 +1,34 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InOutCirc" easing animation. +/// +public class InOutElastic : Easing { + private const double Constant = (2 * Math.PI) / 4.5; + /// - /// Class providing an "InOutCirc" easing animation. + /// Initializes a new instance of the class. /// - public class InOutElastic : Easing + /// The duration of the animation. + public InOutElastic(TimeSpan duration) + : base(duration) { - private const double Constant = (2 * Math.PI) / 4.5; + // ignored + } - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InOutElastic(TimeSpan duration) - : base(duration) - { - // ignored - } - - /// - public override void Update() - { - var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : p < 0.5 - ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 - : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : p < 0.5 + ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 + : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs index 70f3123aa..4140e946e 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InOutQuint" easing animation. +/// +public class InOutQuint : Easing { /// - /// Class providing an "InOutQuint" easing animation. + /// Initializes a new instance of the class. /// - public class InOutQuint : Easing + /// The duration of the animation. + public InOutQuint(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InOutQuint(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs index 4808079d3..118910205 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InOutSine" easing animation. +/// +public class InOutSine : Easing { /// - /// Class providing an "InOutSine" easing animation. + /// Initializes a new instance of the class. /// - public class InOutSine : Easing + /// The duration of the animation. + public InOutSine(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InOutSine(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs index 42604f136..b9df11728 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InQuint" easing animation. +/// +public class InQuint : Easing { /// - /// Class providing an "InQuint" easing animation. + /// Initializes a new instance of the class. /// - public class InQuint : Easing + /// The duration of the animation. + public InQuint(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InQuint(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p * p * p * p * p; - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p * p * p * p * p; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs index aaa19aa40..35f891d54 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "InSine" easing animation. +/// +public class InSine : Easing { /// - /// Class providing an "InSine" easing animation. + /// Initializes a new instance of the class. /// - public class InSine : Easing + /// The duration of the animation. + public InSine(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public InSine(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Cos((p * Math.PI) / 2); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Cos((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs index da7b0029a..29f2562e7 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "OutCirc" easing animation. +/// +public class OutCirc : Easing { /// - /// Class providing an "OutCirc" easing animation. + /// Initializes a new instance of the class. /// - public class OutCirc : Easing + /// The duration of the animation. + public OutCirc(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public OutCirc(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs index e527b228f..6804bcf05 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "OutCubic" easing animation. +/// +public class OutCubic : Easing { /// - /// Class providing an "OutCubic" easing animation. + /// Initializes a new instance of the class. /// - public class OutCubic : Easing + /// The duration of the animation. + public OutCubic(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public OutCubic(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 3); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Pow(1 - p, 3); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs index 3475c4a72..6aced4403 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -1,33 +1,32 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "OutElastic" easing animation. +/// +public class OutElastic : Easing { + private const double Constant = (2 * Math.PI) / 3; + /// - /// Class providing an "OutElastic" easing animation. + /// Initializes a new instance of the class. /// - public class OutElastic : Easing + /// The duration of the animation. + public OutElastic(TimeSpan duration) + : base(duration) { - private const double Constant = (2 * Math.PI) / 3; + // ignored + } - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public OutElastic(TimeSpan duration) - : base(duration) - { - // ignored - } - - /// - public override void Update() - { - var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs index c99c77a57..566a22ffa 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "OutQuint" easing animation. +/// +public class OutQuint : Easing { /// - /// Class providing an "OutQuint" easing animation. + /// Initializes a new instance of the class. /// - public class OutQuint : Easing + /// The duration of the animation. + public OutQuint(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public OutQuint(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 5); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Pow(1 - p, 5); } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs index c1becf81c..da9faf5e1 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -1,27 +1,26 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions +namespace Dalamud.Interface.Animation.EasingFunctions; + +/// +/// Class providing an "OutSine" easing animation. +/// +public class OutSine : Easing { /// - /// Class providing an "OutSine" easing animation. + /// Initializes a new instance of the class. /// - public class OutSine : Easing + /// The duration of the animation. + public OutSine(TimeSpan duration) + : base(duration) { - /// - /// Initializes a new instance of the class. - /// - /// The duration of the animation. - public OutSine(TimeSpan duration) - : base(duration) - { - // ignored - } + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = Math.Sin((p * Math.PI) / 2); - } + /// + public override void Update() + { + var p = this.Progress; + this.Value = Math.Sin((p * Math.PI) / 2); } } diff --git a/Dalamud/Interface/Colors/ImGuiColors.cs b/Dalamud/Interface/Colors/ImGuiColors.cs index a8cb2f6fb..866211d8e 100644 --- a/Dalamud/Interface/Colors/ImGuiColors.cs +++ b/Dalamud/Interface/Colors/ImGuiColors.cs @@ -1,105 +1,104 @@ using System.Numerics; -namespace Dalamud.Interface.Colors +namespace Dalamud.Interface.Colors; + +/// +/// Class containing frequently used colors for easier reference. +/// +public static class ImGuiColors { /// - /// Class containing frequently used colors for easier reference. + /// Gets red used in dalamud. /// - public static class ImGuiColors - { - /// - /// Gets red used in dalamud. - /// - public static Vector4 DalamudRed { get; internal set; } = new(1f, 0f, 0f, 1f); + public static Vector4 DalamudRed { get; internal set; } = new(1f, 0f, 0f, 1f); - /// - /// Gets grey used in dalamud. - /// - public static Vector4 DalamudGrey { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); + /// + /// Gets grey used in dalamud. + /// + public static Vector4 DalamudGrey { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); - /// - /// Gets grey used in dalamud. - /// - public static Vector4 DalamudGrey2 { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); + /// + /// Gets grey used in dalamud. + /// + public static Vector4 DalamudGrey2 { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); - /// - /// Gets grey used in dalamud. - /// - public static Vector4 DalamudGrey3 { get; internal set; } = new(0.5f, 0.5f, 0.5f, 1f); + /// + /// Gets grey used in dalamud. + /// + public static Vector4 DalamudGrey3 { get; internal set; } = new(0.5f, 0.5f, 0.5f, 1f); - /// - /// Gets white used in dalamud. - /// - public static Vector4 DalamudWhite { get; internal set; } = new(1f, 1f, 1f, 1f); + /// + /// Gets white used in dalamud. + /// + public static Vector4 DalamudWhite { get; internal set; } = new(1f, 1f, 1f, 1f); - /// - /// Gets white used in dalamud. - /// - public static Vector4 DalamudWhite2 { get; internal set; } = new(0.878f, 0.878f, 0.878f, 1f); + /// + /// Gets white used in dalamud. + /// + public static Vector4 DalamudWhite2 { get; internal set; } = new(0.878f, 0.878f, 0.878f, 1f); - /// - /// Gets orange used in dalamud. - /// - public static Vector4 DalamudOrange { get; internal set; } = new(1f, 0.709f, 0f, 1f); + /// + /// Gets orange used in dalamud. + /// + public static Vector4 DalamudOrange { get; internal set; } = new(1f, 0.709f, 0f, 1f); - /// - /// Gets yellow used in dalamud. - /// - public static Vector4 DalamudYellow { get; } = new(1f, 1f, .4f, 1f); + /// + /// Gets yellow used in dalamud. + /// + public static Vector4 DalamudYellow { get; } = new(1f, 1f, .4f, 1f); - /// - /// Gets violet used in dalamud. - /// - public static Vector4 DalamudViolet { get; } = new(0.770f, 0.700f, 0.965f, 1.000f); + /// + /// Gets violet used in dalamud. + /// + public static Vector4 DalamudViolet { get; } = new(0.770f, 0.700f, 0.965f, 1.000f); - /// - /// Gets tank blue (UIColor37). - /// - public static Vector4 TankBlue { get; internal set; } = new(0f, 0.6f, 1f, 1f); + /// + /// Gets tank blue (UIColor37). + /// + public static Vector4 TankBlue { get; internal set; } = new(0f, 0.6f, 1f, 1f); - /// - /// Gets healer green (UIColor504). - /// - public static Vector4 HealerGreen { get; internal set; } = new(0f, 0.8f, 0.1333333f, 1f); + /// + /// Gets healer green (UIColor504). + /// + public static Vector4 HealerGreen { get; internal set; } = new(0f, 0.8f, 0.1333333f, 1f); - /// - /// Gets dps red (UIColor545). - /// - public static Vector4 DPSRed { get; internal set; } = new(0.7058824f, 0f, 0f, 1f); + /// + /// Gets dps red (UIColor545). + /// + public static Vector4 DPSRed { get; internal set; } = new(0.7058824f, 0f, 0f, 1f); - /// - /// Gets parsed grey. - /// - public static Vector4 ParsedGrey { get; } = new(0.4f, 0.4f, 0.4f, 1f); + /// + /// Gets parsed grey. + /// + public static Vector4 ParsedGrey { get; } = new(0.4f, 0.4f, 0.4f, 1f); - /// - /// Gets parsed green. - /// - public static Vector4 ParsedGreen { get; } = new(0.117f, 1f, 0f, 1f); + /// + /// Gets parsed green. + /// + public static Vector4 ParsedGreen { get; } = new(0.117f, 1f, 0f, 1f); - /// - /// Gets parsed blue. - /// - public static Vector4 ParsedBlue { get; } = new(0f, 0.439f, 1f, 1f); + /// + /// Gets parsed blue. + /// + public static Vector4 ParsedBlue { get; } = new(0f, 0.439f, 1f, 1f); - /// - /// Gets parsed purple. - /// - public static Vector4 ParsedPurple { get; } = new(0.639f, 0.207f, 0.933f, 1f); + /// + /// Gets parsed purple. + /// + public static Vector4 ParsedPurple { get; } = new(0.639f, 0.207f, 0.933f, 1f); - /// - /// Gets parsed orange. - /// - public static Vector4 ParsedOrange { get; } = new(1f, 0.501f, 0f, 1f); + /// + /// Gets parsed orange. + /// + public static Vector4 ParsedOrange { get; } = new(1f, 0.501f, 0f, 1f); - /// - /// Gets parsed pink. - /// - public static Vector4 ParsedPink { get; } = new(0.886f, 0.407f, 0.658f, 1f); + /// + /// Gets parsed pink. + /// + public static Vector4 ParsedPink { get; } = new(0.886f, 0.407f, 0.658f, 1f); - /// - /// Gets parsed gold. - /// - public static Vector4 ParsedGold { get; } = new(0.898f, 0.8f, 0.501f, 1f); - } + /// + /// Gets parsed gold. + /// + public static Vector4 ParsedGold { get; } = new(0.898f, 0.8f, 0.501f, 1f); } diff --git a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs index 447d0cf03..e9db345cb 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs @@ -2,69 +2,68 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { /// - /// Class containing various methods providing ImGui components. + /// ColorPicker with palette. /// - public static partial class ImGuiComponents + /// Id for the color picker. + /// The description of the color picker. + /// The current color. + /// Selected color. + public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor) { - /// - /// ColorPicker with palette. - /// - /// Id for the color picker. - /// The description of the color picker. - /// The current color. - /// Selected color. - public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor) + const ImGuiColorEditFlags flags = ImGuiColorEditFlags.NoSidePreview | ImGuiColorEditFlags.NoSmallPreview; + return ColorPickerWithPalette(id, description, originalColor, flags); + } + + /// + /// ColorPicker with palette with color picker options. + /// + /// Id for the color picker. + /// The description of the color picker. + /// The current color. + /// Flags to customize color picker. + /// Selected color. + public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor, ImGuiColorEditFlags flags) + { + var existingColor = originalColor; + var selectedColor = originalColor; + var colorPalette = ImGuiHelpers.DefaultColorPalette(36); + if (ImGui.ColorButton($"{description}###ColorPickerButton{id}", originalColor)) { - const ImGuiColorEditFlags flags = ImGuiColorEditFlags.NoSidePreview | ImGuiColorEditFlags.NoSmallPreview; - return ColorPickerWithPalette(id, description, originalColor, flags); + ImGui.OpenPopup($"###ColorPickerPopup{id}"); } - /// - /// ColorPicker with palette with color picker options. - /// - /// Id for the color picker. - /// The description of the color picker. - /// The current color. - /// Flags to customize color picker. - /// Selected color. - public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor, ImGuiColorEditFlags flags) + if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) { - var existingColor = originalColor; - var selectedColor = originalColor; - var colorPalette = ImGuiHelpers.DefaultColorPalette(36); - if (ImGui.ColorButton($"{description}###ColorPickerButton{id}", originalColor)) + if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) { - ImGui.OpenPopup($"###ColorPickerPopup{id}"); + selectedColor = existingColor; } - if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) + for (var i = 0; i < 4; i++) { - if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) + ImGui.Spacing(); + for (var j = i * 9; j < (i * 9) + 9; j++) { - selectedColor = existingColor; - } - - for (var i = 0; i < 4; i++) - { - ImGui.Spacing(); - for (var j = i * 9; j < (i * 9) + 9; j++) + if (ImGui.ColorButton($"###ColorPickerSwatch{id}{i}{j}", colorPalette[j])) { - if (ImGui.ColorButton($"###ColorPickerSwatch{id}{i}{j}", colorPalette[j])) - { - selectedColor = colorPalette[j]; - } - - ImGui.SameLine(); + selectedColor = colorPalette[j]; } - } - ImGui.EndPopup(); + ImGui.SameLine(); + } } - return selectedColor; + ImGui.EndPopup(); } + + return selectedColor; } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs index 225b171bb..181bbbfd7 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs @@ -2,75 +2,74 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { /// - /// Class containing various methods providing ImGui components. + /// Alpha modified IconButton component to use an icon as a button with alpha and color options. /// - public static partial class ImGuiComponents + /// The icon for the button. + /// The ID of the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// A multiplier for the current alpha levels. + /// Indicator if button is clicked. + public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) { - /// - /// Alpha modified IconButton component to use an icon as a button with alpha and color options. - /// - /// The icon for the button. - /// The ID of the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// A multiplier for the current alpha levels. - /// Indicator if button is clicked. - public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) - { - ImGui.PushFont(UiBuilder.IconFont); + ImGui.PushFont(UiBuilder.IconFont); - var text = icon.ToIconString(); - if (id.HasValue) - text = $"{text}{id}"; + var text = icon.ToIconString(); + if (id.HasValue) + text = $"{text}{id}"; - var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); + var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); - ImGui.PopFont(); + ImGui.PopFont(); - return button; - } + return button; + } - /// - /// Alpha modified Button component to use as a disabled button with alpha and color options. - /// - /// The button label with ID. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// A multiplier for the current alpha levels. - /// Indicator if button is clicked. - public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) - { - if (defaultColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + /// + /// Alpha modified Button component to use as a disabled button with alpha and color options. + /// + /// The button label with ID. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// A multiplier for the current alpha levels. + /// Indicator if button is clicked. + public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) + { + if (defaultColor.HasValue) + ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - if (activeColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + if (activeColor.HasValue) + ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - if (hoveredColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + if (hoveredColor.HasValue) + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - var style = ImGui.GetStyle(); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); + var style = ImGui.GetStyle(); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); - var button = ImGui.Button(labelWithId); + var button = ImGui.Button(labelWithId); - ImGui.PopStyleVar(); + ImGui.PopStyleVar(); - if (defaultColor.HasValue) - ImGui.PopStyleColor(); + if (defaultColor.HasValue) + ImGui.PopStyleColor(); - if (activeColor.HasValue) - ImGui.PopStyleColor(); + if (activeColor.HasValue) + ImGui.PopStyleColor(); - if (hoveredColor.HasValue) - ImGui.PopStyleColor(); + if (hoveredColor.HasValue) + ImGui.PopStyleColor(); - return button; - } + return button; } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index dfb86cf79..f57746eca 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -1,28 +1,27 @@ using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { /// - /// Class containing various methods providing ImGui components. + /// HelpMarker component to add a help icon with text on hover. /// - public static partial class ImGuiComponents + /// The text to display on hover. + public static void HelpMarker(string helpText) { - /// - /// HelpMarker component to add a help icon with text on hover. - /// - /// The text to display on hover. - public static void HelpMarker(string helpText) - { - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); - ImGui.PopFont(); - if (!ImGui.IsItemHovered()) return; - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); - } + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); + ImGui.PopFont(); + if (!ImGui.IsItemHovered()) return; + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); + ImGui.TextUnformatted(helpText); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index 3d65deb74..7ef3f56e5 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -2,101 +2,100 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { /// - /// Class containing various methods providing ImGui components. + /// IconButton component to use an icon as a button. /// - public static partial class ImGuiComponents + /// The icon for the button. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon) + => IconButton(icon, null, null, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon) + => IconButton(id, icon, null, null, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// Text already containing the icon string. + /// Indicator if button is clicked. + public static bool IconButton(string iconText) + => IconButton(iconText, null, null, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + => IconButton($"{icon.ToIconString()}{id}", defaultColor, activeColor, hoveredColor); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// Text already containing the icon string. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Indicator if button is clicked. + public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) { - /// - /// IconButton component to use an icon as a button. - /// - /// The icon for the button. - /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon) - => IconButton(icon, null, null, null); + var numColors = 0; - /// - /// IconButton component to use an icon as a button. - /// - /// The ID of the button. - /// The icon for the button. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); - - /// - /// IconButton component to use an icon as a button. - /// - /// Text already containing the icon string. - /// Indicator if button is clicked. - public static bool IconButton(string iconText) - => IconButton(iconText, null, null, null); - - /// - /// IconButton component to use an icon as a button. - /// - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}{id}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// Text already containing the icon string. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + if (defaultColor.HasValue) { - var numColors = 0; - - if (defaultColor.HasValue) - { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; - } - - if (activeColor.HasValue) - { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; - } - - if (hoveredColor.HasValue) - { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; - } - - ImGui.PushFont(UiBuilder.IconFont); - - var button = ImGui.Button(iconText); - - ImGui.PopFont(); - - if (numColors > 0) - ImGui.PopStyleColor(numColors); - - return button; + ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + numColors++; } + + if (activeColor.HasValue) + { + ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + numColors++; + } + + if (hoveredColor.HasValue) + { + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + numColors++; + } + + ImGui.PushFont(UiBuilder.IconFont); + + var button = ImGui.Button(iconText); + + ImGui.PopFont(); + + if (numColors > 0) + ImGui.PopStyleColor(numColors); + + return button; } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.Test.cs b/Dalamud/Interface/Components/ImGuiComponents.Test.cs index 54026b379..ddc083cd8 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.Test.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.Test.cs @@ -1,18 +1,17 @@ using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { /// - /// Class containing various methods providing ImGui components. + /// Test component to demonstrate how ImGui components work. /// - public static partial class ImGuiComponents + public static void Test() { - /// - /// Test component to demonstrate how ImGui components work. - /// - 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."); } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs index 991fefb3a..597b472c6 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs @@ -1,31 +1,30 @@ using ImGuiNET; -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { /// - /// Class containing various methods providing ImGui components. + /// TextWithLabel component to show labeled text. /// - public static partial class ImGuiComponents + /// The label for text. + /// The text value. + /// The hint to show on hover. + public static void TextWithLabel(string label, string value, string hint = "") { - /// - /// TextWithLabel component to show labeled text. - /// - /// The label for text. - /// The text value. - /// The hint to show on hover. - public static void TextWithLabel(string label, string value, string hint = "") + ImGui.Text(label + ": "); + ImGui.SameLine(); + if (string.IsNullOrEmpty(hint)) { - ImGui.Text(label + ": "); - ImGui.SameLine(); - if (string.IsNullOrEmpty(hint)) - { - ImGui.Text(value); - } - else - { - ImGui.Text(value + "*"); - if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); - } + ImGui.Text(value); + } + else + { + ImGui.Text(value + "*"); + if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.cs b/Dalamud/Interface/Components/ImGuiComponents.cs index 60d6a0e06..e63ebd890 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.cs @@ -1,9 +1,8 @@ -namespace Dalamud.Interface.Components +namespace Dalamud.Interface.Components; + +/// +/// Class containing various methods providing ImGui components. +/// +public static partial class ImGuiComponents { - /// - /// Class containing various methods providing ImGui components. - /// - public static partial class ImGuiComponents - { - } } diff --git a/Dalamud/Interface/FontAwesomeExtensions.cs b/Dalamud/Interface/FontAwesomeExtensions.cs index 734ec128d..d64eb09da 100644 --- a/Dalamud/Interface/FontAwesomeExtensions.cs +++ b/Dalamud/Interface/FontAwesomeExtensions.cs @@ -1,30 +1,29 @@ -// Font-Awesome - Version 5.0.9 +// Font-Awesome - Version 5.0.9 -namespace Dalamud.Interface +namespace Dalamud.Interface; + +/// +/// Extension methods for . +/// +public static class FontAwesomeExtensions { /// - /// Extension methods for . + /// Convert the FontAwesomeIcon to a type. /// - public static class FontAwesomeExtensions + /// The icon to convert. + /// The converted icon. + public static char ToIconChar(this FontAwesomeIcon icon) { - /// - /// Convert the FontAwesomeIcon to a type. - /// - /// The icon to convert. - /// The converted icon. - public static char ToIconChar(this FontAwesomeIcon icon) - { - return (char)icon; - } + return (char)icon; + } - /// - /// Conver the FontAwesomeIcon to a type. - /// - /// The icon to convert. - /// The converted icon. - public static string ToIconString(this FontAwesomeIcon icon) - { - return string.Empty + (char)icon; - } + /// + /// Conver the FontAwesomeIcon to a type. + /// + /// The icon to convert. + /// The converted icon. + public static string ToIconString(this FontAwesomeIcon icon) + { + return string.Empty + (char)icon; } } diff --git a/Dalamud/Interface/FontAwesomeIcon.cs b/Dalamud/Interface/FontAwesomeIcon.cs index c2267766c..eb87cf784 100644 --- a/Dalamud/Interface/FontAwesomeIcon.cs +++ b/Dalamud/Interface/FontAwesomeIcon.cs @@ -1,7050 +1,7049 @@ // Font-Awesome - Version 5.0.9 -namespace Dalamud.Interface +namespace Dalamud.Interface; + +/// +/// Font Awesome unicode characters for use with the font. +/// +public enum FontAwesomeIcon { /// - /// Font Awesome unicode characters for use with the font. + /// No icon. /// - public enum FontAwesomeIcon - { - /// - /// No icon. - /// - None = 0, - - /// - /// The Font Awesome "500px" icon unicode character. - /// - _500Px = 0xF26E, - - /// - /// The Font Awesome "accessible-icon" icon unicode character. - /// - AccessibleIcon = 0xF368, - - /// - /// The Font Awesome "accusoft" icon unicode character. - /// - Accusoft = 0xF369, - - /// - /// The Font Awesome "acquisitions-incorporated" icon unicode character. - /// - AcquisitionsIncorporated = 0xF6AF, - - /// - /// The Font Awesome "ad" icon unicode character. - /// - Ad = 0xF641, - - /// - /// The Font Awesome "address-book" icon unicode character. - /// - AddressBook = 0xF2B9, - - /// - /// The Font Awesome "address-card" icon unicode character. - /// - AddressCard = 0xF2BB, - - /// - /// The Font Awesome "adjust" icon unicode character. - /// - Adjust = 0xF042, - - /// - /// The Font Awesome "adn" icon unicode character. - /// - Adn = 0xF17, - - /// - /// The Font Awesome "adobe" icon unicode character. - /// - Adobe = 0xF778, - - /// - /// The Font Awesome "adversal" icon unicode character. - /// - Adversal = 0xF36A, - - /// - /// The Font Awesome "affiliatetheme" icon unicode character. - /// - Affiliatetheme = 0xF36B, - - /// - /// The Font Awesome "airbnb" icon unicode character. - /// - Airbnb = 0xF834, - - /// - /// The Font Awesome "air-freshener" icon unicode character. - /// - AirFreshener = 0xF5D, - - /// - /// The Font Awesome "algolia" icon unicode character. - /// - Algolia = 0xF36C, - - /// - /// The Font Awesome "align-center" icon unicode character. - /// - AlignCenter = 0xF037, - - /// - /// The Font Awesome "align-justify" icon unicode character. - /// - AlignJustify = 0xF039, - - /// - /// The Font Awesome "align-left" icon unicode character. - /// - AlignLeft = 0xF036, - - /// - /// The Font Awesome "align-right" icon unicode character. - /// - AlignRight = 0xF038, - - /// - /// The Font Awesome "alipay" icon unicode character. - /// - Alipay = 0xF642, - - /// - /// The Font Awesome "allergies" icon unicode character. - /// - Allergies = 0xF461, - - /// - /// The Font Awesome "amazon" icon unicode character. - /// - Amazon = 0xF27, - - /// - /// The Font Awesome "amazon-pay" icon unicode character. - /// - AmazonPay = 0xF42C, - - /// - /// The Font Awesome "ambulance" icon unicode character. - /// - Ambulance = 0xF0F9, - - /// - /// The Font Awesome "american-sign-language-interpreting" icon unicode character. - /// - AmericanSignLanguageInterpreting = 0xF2A3, - - /// - /// The Font Awesome "amilia" icon unicode character. - /// - Amilia = 0xF36D, - - /// - /// The Font Awesome "anchor" icon unicode character. - /// - Anchor = 0xF13D, - - /// - /// The Font Awesome "android" icon unicode character. - /// - Android = 0xF17B, - - /// - /// The Font Awesome "angellist" icon unicode character. - /// - Angellist = 0xF209, - - /// - /// The Font Awesome "angle-double-down" icon unicode character. - /// - AngleDoubleDown = 0xF103, - - /// - /// The Font Awesome "angle-double-left" icon unicode character. - /// - AngleDoubleLeft = 0xF1, - - /// - /// The Font Awesome "angle-double-right" icon unicode character. - /// - AngleDoubleRight = 0xF101, - - /// - /// The Font Awesome "angle-double-up" icon unicode character. - /// - AngleDoubleUp = 0xF102, - - /// - /// The Font Awesome "angle-down" icon unicode character. - /// - AngleDown = 0xF107, - - /// - /// The Font Awesome "angle-left" icon unicode character. - /// - AngleLeft = 0xF104, - - /// - /// The Font Awesome "angle-right" icon unicode character. - /// - AngleRight = 0xF105, - - /// - /// The Font Awesome "angle-up" icon unicode character. - /// - AngleUp = 0xF106, - - /// - /// The Font Awesome "angry" icon unicode character. - /// - Angry = 0xF556, - - /// - /// The Font Awesome "angrycreative" icon unicode character. - /// - Angrycreative = 0xF36E, - - /// - /// The Font Awesome "angular" icon unicode character. - /// - Angular = 0xF42, - - /// - /// The Font Awesome "ankh" icon unicode character. - /// - Ankh = 0xF644, - - /// - /// The Font Awesome "apper" icon unicode character. - /// - Apper = 0xF371, - - /// - /// The Font Awesome "apple" icon unicode character. - /// - Apple = 0xF179, - - /// - /// The Font Awesome "apple-alt" icon unicode character. - /// - AppleAlt = 0xF5D1, - - /// - /// The Font Awesome "apple-pay" icon unicode character. - /// - ApplePay = 0xF415, - - /// - /// The Font Awesome "app-store" icon unicode character. - /// - AppStore = 0xF36F, - - /// - /// The Font Awesome "app-store-ios" icon unicode character. - /// - AppStoreIos = 0xF37, - - /// - /// The Font Awesome "archive" icon unicode character. - /// - Archive = 0xF187, - - /// - /// The Font Awesome "archway" icon unicode character. - /// - Archway = 0xF557, - - /// - /// The Font Awesome "arrow-alt-circle-down" icon unicode character. - /// - ArrowAltCircleDown = 0xF358, - - /// - /// The Font Awesome "arrow-alt-circle-left" icon unicode character. - /// - ArrowAltCircleLeft = 0xF359, - - /// - /// The Font Awesome "arrow-alt-circle-right" icon unicode character. - /// - ArrowAltCircleRight = 0xF35A, - - /// - /// The Font Awesome "arrow-alt-circle-up" icon unicode character. - /// - ArrowAltCircleUp = 0xF35B, - - /// - /// The Font Awesome "arrow-circle-down" icon unicode character. - /// - ArrowCircleDown = 0xF0AB, - - /// - /// The Font Awesome "arrow-circle-left" icon unicode character. - /// - ArrowCircleLeft = 0xF0A8, - - /// - /// The Font Awesome "arrow-circle-right" icon unicode character. - /// - ArrowCircleRight = 0xF0A9, - - /// - /// The Font Awesome "arrow-circle-up" icon unicode character. - /// - ArrowCircleUp = 0xF0AA, - - /// - /// The Font Awesome "arrow-down" icon unicode character. - /// - ArrowDown = 0xF063, - - /// - /// The Font Awesome "arrow-left" icon unicode character. - /// - ArrowLeft = 0xF06, - - /// - /// The Font Awesome "arrow-right" icon unicode character. - /// - ArrowRight = 0xF061, - - /// - /// The Font Awesome "arrows-alt" icon unicode character. - /// - ArrowsAlt = 0xF0B2, - - /// - /// The Font Awesome "arrows-alt-h" icon unicode character. - /// - ArrowsAltH = 0xF337, - - /// - /// The Font Awesome "arrows-alt-v" icon unicode character. - /// - ArrowsAltV = 0xF338, - - /// - /// The Font Awesome "arrow-up" icon unicode character. - /// - ArrowUp = 0xF062, - - /// - /// The Font Awesome "artstation" icon unicode character. - /// - Artstation = 0xF77A, - - /// - /// The Font Awesome "assistive-listening-systems" icon unicode character. - /// - AssistiveListeningSystems = 0xF2A2, - - /// - /// The Font Awesome "asterisk" icon unicode character. - /// - Asterisk = 0xF069, - - /// - /// The Font Awesome "asymmetrik" icon unicode character. - /// - Asymmetrik = 0xF372, - - /// - /// The Font Awesome "at" icon unicode character. - /// - At = 0xF1FA, - - /// - /// The Font Awesome "atlas" icon unicode character. - /// - Atlas = 0xF558, - - /// - /// The Font Awesome "atlassian" icon unicode character. - /// - Atlassian = 0xF77B, - - /// - /// The Font Awesome "atom" icon unicode character. - /// - Atom = 0xF5D2, - - /// - /// The Font Awesome "audible" icon unicode character. - /// - Audible = 0xF373, - - /// - /// The Font Awesome "audio-description" icon unicode character. - /// - AudioDescription = 0xF29E, - - /// - /// The Font Awesome "autoprefixer" icon unicode character. - /// - Autoprefixer = 0xF41C, - - /// - /// The Font Awesome "avianex" icon unicode character. - /// - Avianex = 0xF374, - - /// - /// The Font Awesome "aviato" icon unicode character. - /// - Aviato = 0xF421, - - /// - /// The Font Awesome "award" icon unicode character. - /// - Award = 0xF559, - - /// - /// The Font Awesome "aws" icon unicode character. - /// - Aws = 0xF375, - - /// - /// The Font Awesome "baby" icon unicode character. - /// - Baby = 0xF77C, - - /// - /// The Font Awesome "baby-carriage" icon unicode character. - /// - BabyCarriage = 0xF77D, - - /// - /// The Font Awesome "backspace" icon unicode character. - /// - Backspace = 0xF55A, - - /// - /// The Font Awesome "backward" icon unicode character. - /// - Backward = 0xF04A, - - /// - /// The Font Awesome "bacon" icon unicode character. - /// - Bacon = 0xF7E5, - - /// - /// The Font Awesome "bahai" icon unicode character. - /// - Bahai = 0xF666, - - /// - /// The Font Awesome "balance-scale" icon unicode character. - /// - BalanceScale = 0xF24E, - - /// - /// The Font Awesome "balance-scale-left" icon unicode character. - /// - BalanceScaleLeft = 0xF515, - - /// - /// The Font Awesome "balance-scale-right" icon unicode character. - /// - BalanceScaleRight = 0xF516, - - /// - /// The Font Awesome "ban" icon unicode character. - /// - Ban = 0xF05E, - - /// - /// The Font Awesome "band-aid" icon unicode character. - /// - BandAid = 0xF462, - - /// - /// The Font Awesome "bandcamp" icon unicode character. - /// - Bandcamp = 0xF2D5, - - /// - /// The Font Awesome "barcode" icon unicode character. - /// - Barcode = 0xF02A, - - /// - /// The Font Awesome "bars" icon unicode character. - /// - Bars = 0xF0C9, - - /// - /// The Font Awesome "baseball-ball" icon unicode character. - /// - BaseballBall = 0xF433, - - /// - /// The Font Awesome "basketball-ball" icon unicode character. - /// - BasketballBall = 0xF434, - - /// - /// The Font Awesome "bath" icon unicode character. - /// - Bath = 0xF2CD, - - /// - /// The Font Awesome "battery-empty" icon unicode character. - /// - BatteryEmpty = 0xF244, - - /// - /// The Font Awesome "battery-full" icon unicode character. - /// - BatteryFull = 0xF24, - - /// - /// The Font Awesome "battery-half" icon unicode character. - /// - BatteryHalf = 0xF242, - - /// - /// The Font Awesome "battery-quarter" icon unicode character. - /// - BatteryQuarter = 0xF243, - - /// - /// The Font Awesome "battery-three-quarters" icon unicode character. - /// - BatteryThreeQuarters = 0xF241, - - /// - /// The Font Awesome "battle-net" icon unicode character. - /// - BattleNet = 0xF835, - - /// - /// The Font Awesome "bed" icon unicode character. - /// - Bed = 0xF236, - - /// - /// The Font Awesome "beer" icon unicode character. - /// - Beer = 0xF0FC, - - /// - /// The Font Awesome "behance" icon unicode character. - /// - Behance = 0xF1B4, - - /// - /// The Font Awesome "behance-square" icon unicode character. - /// - BehanceSquare = 0xF1B5, - - /// - /// The Font Awesome "bell" icon unicode character. - /// - Bell = 0xF0F3, - - /// - /// The Font Awesome "bell-slash" icon unicode character. - /// - BellSlash = 0xF1F6, - - /// - /// The Font Awesome "bezier-curve" icon unicode character. - /// - BezierCurve = 0xF55B, - - /// - /// The Font Awesome "bible" icon unicode character. - /// - Bible = 0xF647, - - /// - /// The Font Awesome "bicycle" icon unicode character. - /// - Bicycle = 0xF206, - - /// - /// The Font Awesome "biking" icon unicode character. - /// - Biking = 0xF84A, - - /// - /// The Font Awesome "bimobject" icon unicode character. - /// - Bimobject = 0xF378, - - /// - /// The Font Awesome "binoculars" icon unicode character. - /// - Binoculars = 0xF1E5, - - /// - /// The Font Awesome "biohazard" icon unicode character. - /// - Biohazard = 0xF78, - - /// - /// The Font Awesome "birthday-cake" icon unicode character. - /// - BirthdayCake = 0xF1FD, - - /// - /// The Font Awesome "bitbucket" icon unicode character. - /// - Bitbucket = 0xF171, - - /// - /// The Font Awesome "bitcoin" icon unicode character. - /// - Bitcoin = 0xF379, - - /// - /// The Font Awesome "bity" icon unicode character. - /// - Bity = 0xF37A, - - /// - /// The Font Awesome "blackberry" icon unicode character. - /// - Blackberry = 0xF37B, - - /// - /// The Font Awesome "black-tie" icon unicode character. - /// - BlackTie = 0xF27E, - - /// - /// The Font Awesome "blender" icon unicode character. - /// - Blender = 0xF517, - - /// - /// The Font Awesome "blender-phone" icon unicode character. - /// - BlenderPhone = 0xF6B6, - - /// - /// The Font Awesome "blind" icon unicode character. - /// - Blind = 0xF29D, - - /// - /// The Font Awesome "blog" icon unicode character. - /// - Blog = 0xF781, - - /// - /// The Font Awesome "blogger" icon unicode character. - /// - Blogger = 0xF37C, - - /// - /// The Font Awesome "blogger-b" icon unicode character. - /// - BloggerB = 0xF37D, - - /// - /// The Font Awesome "bluetooth" icon unicode character. - /// - Bluetooth = 0xF293, - - /// - /// The Font Awesome "bluetooth-b" icon unicode character. - /// - BluetoothB = 0xF294, - - /// - /// The Font Awesome "bold" icon unicode character. - /// - Bold = 0xF032, - - /// - /// The Font Awesome "bolt" icon unicode character. - /// - Bolt = 0xF0E7, - - /// - /// The Font Awesome "bomb" icon unicode character. - /// - Bomb = 0xF1E2, - - /// - /// The Font Awesome "bone" icon unicode character. - /// - Bone = 0xF5D7, - - /// - /// The Font Awesome "bong" icon unicode character. - /// - Bong = 0xF55C, - - /// - /// The Font Awesome "book" icon unicode character. - /// - Book = 0xF02D, - - /// - /// The Font Awesome "book-dead" icon unicode character. - /// - BookDead = 0xF6B7, - - /// - /// The Font Awesome "bookmark" icon unicode character. - /// - Bookmark = 0xF02E, - - /// - /// The Font Awesome "book-medical" icon unicode character. - /// - BookMedical = 0xF7E6, - - /// - /// The Font Awesome "book-open" icon unicode character. - /// - BookOpen = 0xF518, - - /// - /// The Font Awesome "book-reader" icon unicode character. - /// - BookReader = 0xF5DA, - - /// - /// The Font Awesome "bootstrap" icon unicode character. - /// - Bootstrap = 0xF836, - - /// - /// The Font Awesome "border-all" icon unicode character. - /// - BorderAll = 0xF84C, - - /// - /// The Font Awesome "border-none" icon unicode character. - /// - BorderNone = 0xF85, - - /// - /// The Font Awesome "border-style" icon unicode character. - /// - BorderStyle = 0xF853, - - /// - /// The Font Awesome "bowling-ball" icon unicode character. - /// - BowlingBall = 0xF436, - - /// - /// The Font Awesome "box" icon unicode character. - /// - Box = 0xF466, - - /// - /// The Font Awesome "boxes" icon unicode character. - /// - Boxes = 0xF468, - - /// - /// The Font Awesome "box-open" icon unicode character. - /// - BoxOpen = 0xF49E, - - /// - /// The Font Awesome "braille" icon unicode character. - /// - Braille = 0xF2A1, - - /// - /// The Font Awesome "brain" icon unicode character. - /// - Brain = 0xF5DC, - - /// - /// The Font Awesome "bread-slice" icon unicode character. - /// - BreadSlice = 0xF7EC, - - /// - /// The Font Awesome "briefcase" icon unicode character. - /// - Briefcase = 0xF0B1, - - /// - /// The Font Awesome "briefcase-medical" icon unicode character. - /// - BriefcaseMedical = 0xF469, - - /// - /// The Font Awesome "broadcast-tower" icon unicode character. - /// - BroadcastTower = 0xF519, - - /// - /// The Font Awesome "broom" icon unicode character. - /// - Broom = 0xF51A, - - /// - /// The Font Awesome "brush" icon unicode character. - /// - Brush = 0xF55D, - - /// - /// The Font Awesome "btc" icon unicode character. - /// - Btc = 0xF15A, - - /// - /// The Font Awesome "buffer" icon unicode character. - /// - Buffer = 0xF837, - - /// - /// The Font Awesome "bug" icon unicode character. - /// - Bug = 0xF188, - - /// - /// The Font Awesome "building" icon unicode character. - /// - Building = 0xF1AD, - - /// - /// The Font Awesome "bullhorn" icon unicode character. - /// - Bullhorn = 0xF0A1, - - /// - /// The Font Awesome "bullseye" icon unicode character. - /// - Bullseye = 0xF14, - - /// - /// The Font Awesome "burn" icon unicode character. - /// - Burn = 0xF46A, - - /// - /// The Font Awesome "buromobelexperte" icon unicode character. - /// - Buromobelexperte = 0xF37F, - - /// - /// The Font Awesome "bus" icon unicode character. - /// - Bus = 0xF207, - - /// - /// The Font Awesome "bus-alt" icon unicode character. - /// - BusAlt = 0xF55E, - - /// - /// The Font Awesome "business-time" icon unicode character. - /// - BusinessTime = 0xF64A, - - /// - /// The Font Awesome "buy-n-large" icon unicode character. - /// - BuyNLarge = 0xF8A6, - - /// - /// The Font Awesome "buysellads" icon unicode character. - /// - Buysellads = 0xF20D, - - /// - /// The Font Awesome "calculator" icon unicode character. - /// - Calculator = 0xF1EC, - - /// - /// The Font Awesome "calendar" icon unicode character. - /// - Calendar = 0xF133, - - /// - /// The Font Awesome "calendar-alt" icon unicode character. - /// - CalendarAlt = 0xF073, - - /// - /// The Font Awesome "calendar-check" icon unicode character. - /// - CalendarCheck = 0xF274, - - /// - /// The Font Awesome "calendar-day" icon unicode character. - /// - CalendarDay = 0xF783, - - /// - /// The Font Awesome "calendar-minus" icon unicode character. - /// - CalendarMinus = 0xF272, - - /// - /// The Font Awesome "calendar-plus" icon unicode character. - /// - CalendarPlus = 0xF271, - - /// - /// The Font Awesome "calendar-times" icon unicode character. - /// - CalendarTimes = 0xF273, - - /// - /// The Font Awesome "calendar-week" icon unicode character. - /// - CalendarWeek = 0xF784, - - /// - /// The Font Awesome "camera" icon unicode character. - /// - Camera = 0xF03, - - /// - /// The Font Awesome "camera-retro" icon unicode character. - /// - CameraRetro = 0xF083, - - /// - /// The Font Awesome "campground" icon unicode character. - /// - Campground = 0xF6BB, - - /// - /// The Font Awesome "canadian-maple-leaf" icon unicode character. - /// - CanadianMapleLeaf = 0xF785, - - /// - /// The Font Awesome "candy-cane" icon unicode character. - /// - CandyCane = 0xF786, - - /// - /// The Font Awesome "cannabis" icon unicode character. - /// - Cannabis = 0xF55F, - - /// - /// The Font Awesome "capsules" icon unicode character. - /// - Capsules = 0xF46B, - - /// - /// The Font Awesome "car" icon unicode character. - /// - Car = 0xF1B9, - - /// - /// The Font Awesome "car-alt" icon unicode character. - /// - CarAlt = 0xF5DE, - - /// - /// The Font Awesome "caravan" icon unicode character. - /// - Caravan = 0xF8FF, - - /// - /// The Font Awesome "car-battery" icon unicode character. - /// - CarBattery = 0xF5DF, - - /// - /// The Font Awesome "car-crash" icon unicode character. - /// - CarCrash = 0xF5E1, - - /// - /// The Font Awesome "caret-down" icon unicode character. - /// - CaretDown = 0xF0D7, - - /// - /// The Font Awesome "caret-left" icon unicode character. - /// - CaretLeft = 0xF0D9, - - /// - /// The Font Awesome "caret-right" icon unicode character. - /// - CaretRight = 0xF0DA, - - /// - /// The Font Awesome "caret-square-down" icon unicode character. - /// - CaretSquareDown = 0xF15, - - /// - /// The Font Awesome "caret-square-left" icon unicode character. - /// - CaretSquareLeft = 0xF191, - - /// - /// The Font Awesome "caret-square-right" icon unicode character. - /// - CaretSquareRight = 0xF152, - - /// - /// The Font Awesome "caret-square-up" icon unicode character. - /// - CaretSquareUp = 0xF151, - - /// - /// The Font Awesome "caret-up" icon unicode character. - /// - CaretUp = 0xF0D8, - - /// - /// The Font Awesome "carrot" icon unicode character. - /// - Carrot = 0xF787, - - /// - /// The Font Awesome "car-side" icon unicode character. - /// - CarSide = 0xF5E4, - - /// - /// The Font Awesome "cart-arrow-down" icon unicode character. - /// - CartArrowDown = 0xF218, - - /// - /// The Font Awesome "cart-plus" icon unicode character. - /// - CartPlus = 0xF217, - - /// - /// The Font Awesome "cash-register" icon unicode character. - /// - CashRegister = 0xF788, - - /// - /// The Font Awesome "cat" icon unicode character. - /// - Cat = 0xF6BE, - - /// - /// The Font Awesome "cc-amazon-pay" icon unicode character. - /// - CcAmazonPay = 0xF42D, - - /// - /// The Font Awesome "cc-amex" icon unicode character. - /// - CcAmex = 0xF1F3, - - /// - /// The Font Awesome "cc-apple-pay" icon unicode character. - /// - CcApplePay = 0xF416, - - /// - /// The Font Awesome "cc-diners-club" icon unicode character. - /// - CcDinersClub = 0xF24C, - - /// - /// The Font Awesome "cc-discover" icon unicode character. - /// - CcDiscover = 0xF1F2, - - /// - /// The Font Awesome "cc-jcb" icon unicode character. - /// - CcJcb = 0xF24B, - - /// - /// The Font Awesome "cc-mastercard" icon unicode character. - /// - CcMastercard = 0xF1F1, - - /// - /// The Font Awesome "cc-paypal" icon unicode character. - /// - CcPaypal = 0xF1F4, - - /// - /// The Font Awesome "cc-stripe" icon unicode character. - /// - CcStripe = 0xF1F5, - - /// - /// The Font Awesome "cc-visa" icon unicode character. - /// - CcVisa = 0xF1F, - - /// - /// The Font Awesome "centercode" icon unicode character. - /// - Centercode = 0xF38, - - /// - /// The Font Awesome "centos" icon unicode character. - /// - Centos = 0xF789, - - /// - /// The Font Awesome "certificate" icon unicode character. - /// - Certificate = 0xF0A3, - - /// - /// The Font Awesome "chair" icon unicode character. - /// - Chair = 0xF6C, - - /// - /// The Font Awesome "chalkboard" icon unicode character. - /// - Chalkboard = 0xF51B, - - /// - /// The Font Awesome "chalkboard-teacher" icon unicode character. - /// - ChalkboardTeacher = 0xF51C, - - /// - /// The Font Awesome "charging-station" icon unicode character. - /// - ChargingStation = 0xF5E7, - - /// - /// The Font Awesome "chart-area" icon unicode character. - /// - ChartArea = 0xF1FE, - - /// - /// The Font Awesome "chart-bar" icon unicode character. - /// - ChartBar = 0xF08, - - /// - /// The Font Awesome "chart-line" icon unicode character. - /// - ChartLine = 0xF201, - - /// - /// The Font Awesome "chart-pie" icon unicode character. - /// - ChartPie = 0xF2, - - /// - /// The Font Awesome "check" icon unicode character. - /// - Check = 0xF00C, - - /// - /// The Font Awesome "check-circle" icon unicode character. - /// - CheckCircle = 0xF058, - - /// - /// The Font Awesome "check-double" icon unicode character. - /// - CheckDouble = 0xF56, - - /// - /// The Font Awesome "check-square" icon unicode character. - /// - CheckSquare = 0xF14A, - - /// - /// The Font Awesome "cheese" icon unicode character. - /// - Cheese = 0xF7EF, - - /// - /// The Font Awesome "chess" icon unicode character. - /// - Chess = 0xF439, - - /// - /// The Font Awesome "chess-bishop" icon unicode character. - /// - ChessBishop = 0xF43A, - - /// - /// The Font Awesome "chess-board" icon unicode character. - /// - ChessBoard = 0xF43C, - - /// - /// The Font Awesome "chess-king" icon unicode character. - /// - ChessKing = 0xF43F, - - /// - /// The Font Awesome "chess-knight" icon unicode character. - /// - ChessKnight = 0xF441, - - /// - /// The Font Awesome "chess-pawn" icon unicode character. - /// - ChessPawn = 0xF443, - - /// - /// The Font Awesome "chess-queen" icon unicode character. - /// - ChessQueen = 0xF445, - - /// - /// The Font Awesome "chess-rook" icon unicode character. - /// - ChessRook = 0xF447, - - /// - /// The Font Awesome "chevron-circle-down" icon unicode character. - /// - ChevronCircleDown = 0xF13A, - - /// - /// The Font Awesome "chevron-circle-left" icon unicode character. - /// - ChevronCircleLeft = 0xF137, - - /// - /// The Font Awesome "chevron-circle-right" icon unicode character. - /// - ChevronCircleRight = 0xF138, - - /// - /// The Font Awesome "chevron-circle-up" icon unicode character. - /// - ChevronCircleUp = 0xF139, - - /// - /// The Font Awesome "chevron-down" icon unicode character. - /// - ChevronDown = 0xF078, - - /// - /// The Font Awesome "chevron-left" icon unicode character. - /// - ChevronLeft = 0xF053, - - /// - /// The Font Awesome "chevron-right" icon unicode character. - /// - ChevronRight = 0xF054, - - /// - /// The Font Awesome "chevron-up" icon unicode character. - /// - ChevronUp = 0xF077, - - /// - /// The Font Awesome "child" icon unicode character. - /// - Child = 0xF1AE, - - /// - /// The Font Awesome "chrome" icon unicode character. - /// - Chrome = 0xF268, - - /// - /// The Font Awesome "chromecast" icon unicode character. - /// - Chromecast = 0xF838, - - /// - /// The Font Awesome "church" icon unicode character. - /// - Church = 0xF51D, - - /// - /// The Font Awesome "circle" icon unicode character. - /// - Circle = 0xF111, - - /// - /// The Font Awesome "circle-notch" icon unicode character. - /// - CircleNotch = 0xF1CE, - - /// - /// The Font Awesome "city" icon unicode character. - /// - City = 0xF64F, - - /// - /// The Font Awesome "clinic-medical" icon unicode character. - /// - ClinicMedical = 0xF7F2, - - /// - /// The Font Awesome "clipboard" icon unicode character. - /// - Clipboard = 0xF328, - - /// - /// The Font Awesome "clipboard-check" icon unicode character. - /// - ClipboardCheck = 0xF46C, - - /// - /// The Font Awesome "clipboard-list" icon unicode character. - /// - ClipboardList = 0xF46D, - - /// - /// The Font Awesome "clock" icon unicode character. - /// - Clock = 0xF017, - - /// - /// The Font Awesome "clone" icon unicode character. - /// - Clone = 0xF24D, - - /// - /// The Font Awesome "closed-captioning" icon unicode character. - /// - ClosedCaptioning = 0xF20A, - - /// - /// The Font Awesome "cloud" icon unicode character. - /// - Cloud = 0xF0C2, - - /// - /// The Font Awesome "cloud-download-alt" icon unicode character. - /// - CloudDownloadAlt = 0xF381, - - /// - /// The Font Awesome "cloud-meatball" icon unicode character. - /// - CloudMeatball = 0xF73B, - - /// - /// The Font Awesome "cloud-moon" icon unicode character. - /// - CloudMoon = 0xF6C3, - - /// - /// The Font Awesome "cloud-moon-rain" icon unicode character. - /// - CloudMoonRain = 0xF73C, - - /// - /// The Font Awesome "cloud-rain" icon unicode character. - /// - CloudRain = 0xF73D, - - /// - /// The Font Awesome "cloudscale" icon unicode character. - /// - Cloudscale = 0xF383, - - /// - /// The Font Awesome "cloud-showers-heavy" icon unicode character. - /// - CloudShowersHeavy = 0xF74, - - /// - /// The Font Awesome "cloudsmith" icon unicode character. - /// - Cloudsmith = 0xF384, - - /// - /// The Font Awesome "cloud-sun" icon unicode character. - /// - CloudSun = 0xF6C4, - - /// - /// The Font Awesome "cloud-sun-rain" icon unicode character. - /// - CloudSunRain = 0xF743, - - /// - /// The Font Awesome "cloud-upload-alt" icon unicode character. - /// - CloudUploadAlt = 0xF382, - - /// - /// The Font Awesome "cloudversify" icon unicode character. - /// - Cloudversify = 0xF385, - - /// - /// The Font Awesome "cocktail" icon unicode character. - /// - Cocktail = 0xF561, - - /// - /// The Font Awesome "code" icon unicode character. - /// - Code = 0xF121, - - /// - /// The Font Awesome "code-branch" icon unicode character. - /// - CodeBranch = 0xF126, - - /// - /// The Font Awesome "codepen" icon unicode character. - /// - Codepen = 0xF1CB, - - /// - /// The Font Awesome "codiepie" icon unicode character. - /// - Codiepie = 0xF284, - - /// - /// The Font Awesome "coffee" icon unicode character. - /// - Coffee = 0xF0F4, - - /// - /// The Font Awesome "cog" icon unicode character. - /// - Cog = 0xF013, - - /// - /// The Font Awesome "cogs" icon unicode character. - /// - Cogs = 0xF085, - - /// - /// The Font Awesome "coins" icon unicode character. - /// - Coins = 0xF51E, - - /// - /// The Font Awesome "columns" icon unicode character. - /// - Columns = 0xF0DB, - - /// - /// The Font Awesome "comment" icon unicode character. - /// - Comment = 0xF075, - - /// - /// The Font Awesome "comment-alt" icon unicode character. - /// - CommentAlt = 0xF27A, - - /// - /// The Font Awesome "comment-dollar" icon unicode character. - /// - CommentDollar = 0xF651, - - /// - /// The Font Awesome "comment-dots" icon unicode character. - /// - CommentDots = 0xF4AD, - - /// - /// The Font Awesome "comment-medical" icon unicode character. - /// - CommentMedical = 0xF7F5, - - /// - /// The Font Awesome "comments" icon unicode character. - /// - Comments = 0xF086, - - /// - /// The Font Awesome "comments-dollar" icon unicode character. - /// - CommentsDollar = 0xF653, - - /// - /// The Font Awesome "comment-slash" icon unicode character. - /// - CommentSlash = 0xF4B3, - - /// - /// The Font Awesome "compact-disc" icon unicode character. - /// - CompactDisc = 0xF51F, - - /// - /// The Font Awesome "compass" icon unicode character. - /// - Compass = 0xF14E, - - /// - /// The Font Awesome "compress" icon unicode character. - /// - Compress = 0xF066, - - /// - /// The Font Awesome "compress-alt" icon unicode character. - /// - CompressAlt = 0xF422, - - /// - /// The Font Awesome "compress-arrows-alt" icon unicode character. - /// - CompressArrowsAlt = 0xF78C, - - /// - /// The Font Awesome "concierge-bell" icon unicode character. - /// - ConciergeBell = 0xF562, - - /// - /// The Font Awesome "confluence" icon unicode character. - /// - Confluence = 0xF78D, - - /// - /// The Font Awesome "connectdevelop" icon unicode character. - /// - Connectdevelop = 0xF20E, - - /// - /// The Font Awesome "contao" icon unicode character. - /// - Contao = 0xF26D, - - /// - /// The Font Awesome "cookie" icon unicode character. - /// - Cookie = 0xF563, - - /// - /// The Font Awesome "cookie-bite" icon unicode character. - /// - CookieBite = 0xF564, - - /// - /// The Font Awesome "copy" icon unicode character. - /// - Copy = 0xF0C5, - - /// - /// The Font Awesome "copyright" icon unicode character. - /// - Copyright = 0xF1F9, - - /// - /// The Font Awesome "cotton-bureau" icon unicode character. - /// - CottonBureau = 0xF89E, - - /// - /// The Font Awesome "couch" icon unicode character. - /// - Couch = 0xF4B8, - - /// - /// The Font Awesome "cpanel" icon unicode character. - /// - Cpanel = 0xF388, - - /// - /// The Font Awesome "creative-commons" icon unicode character. - /// - CreativeCommons = 0xF25E, - - /// - /// The Font Awesome "creative-commons-by" icon unicode character. - /// - CreativeCommonsBy = 0xF4E7, - - /// - /// The Font Awesome "creative-commons-nc" icon unicode character. - /// - CreativeCommonsNc = 0xF4E8, - - /// - /// The Font Awesome "creative-commons-nc-eu" icon unicode character. - /// - CreativeCommonsNcEu = 0xF4E9, - - /// - /// The Font Awesome "creative-commons-nc-jp" icon unicode character. - /// - CreativeCommonsNcJp = 0xF4EA, - - /// - /// The Font Awesome "creative-commons-nd" icon unicode character. - /// - CreativeCommonsNd = 0xF4EB, - - /// - /// The Font Awesome "creative-commons-pd" icon unicode character. - /// - CreativeCommonsPd = 0xF4EC, - - /// - /// The Font Awesome "creative-commons-pd-alt" icon unicode character. - /// - CreativeCommonsPdAlt = 0xF4ED, - - /// - /// The Font Awesome "creative-commons-remix" icon unicode character. - /// - CreativeCommonsRemix = 0xF4EE, - - /// - /// The Font Awesome "creative-commons-sa" icon unicode character. - /// - CreativeCommonsSa = 0xF4EF, - - /// - /// The Font Awesome "creative-commons-sampling" icon unicode character. - /// - CreativeCommonsSampling = 0xF4F, - - /// - /// The Font Awesome "creative-commons-sampling-plus" icon unicode character. - /// - CreativeCommonsSamplingPlus = 0xF4F1, - - /// - /// The Font Awesome "creative-commons-share" icon unicode character. - /// - CreativeCommonsShare = 0xF4F2, - - /// - /// The Font Awesome "creative-commons-zero" icon unicode character. - /// - CreativeCommonsZero = 0xF4F3, - - /// - /// The Font Awesome "credit-card" icon unicode character. - /// - CreditCard = 0xF09D, - - /// - /// The Font Awesome "critical-role" icon unicode character. - /// - CriticalRole = 0xF6C9, - - /// - /// The Font Awesome "crop" icon unicode character. - /// - Crop = 0xF125, - - /// - /// The Font Awesome "crop-alt" icon unicode character. - /// - CropAlt = 0xF565, - - /// - /// The Font Awesome "cross" icon unicode character. - /// - Cross = 0xF654, - - /// - /// The Font Awesome "crosshairs" icon unicode character. - /// - Crosshairs = 0xF05B, - - /// - /// The Font Awesome "crow" icon unicode character. - /// - Crow = 0xF52, - - /// - /// The Font Awesome "crown" icon unicode character. - /// - Crown = 0xF521, - - /// - /// The Font Awesome "crutch" icon unicode character. - /// - Crutch = 0xF7F7, - - /// - /// The Font Awesome "css3" icon unicode character. - /// - Css3 = 0xF13C, - - /// - /// The Font Awesome "css3-alt" icon unicode character. - /// - Css3Alt = 0xF38B, - - /// - /// The Font Awesome "cube" icon unicode character. - /// - Cube = 0xF1B2, - - /// - /// The Font Awesome "cubes" icon unicode character. - /// - Cubes = 0xF1B3, - - /// - /// The Font Awesome "cut" icon unicode character. - /// - Cut = 0xF0C4, - - /// - /// The Font Awesome "cuttlefish" icon unicode character. - /// - Cuttlefish = 0xF38C, - - /// - /// The Font Awesome "dailymotion" icon unicode character. - /// - Dailymotion = 0xF952, - - /// - /// The Font Awesome "d-and-d" icon unicode character. - /// - DAndD = 0xF38D, - - /// - /// The Font Awesome "d-and-d-beyond" icon unicode character. - /// - DAndDBeyond = 0xF6CA, - - /// - /// The Font Awesome "dashcube" icon unicode character. - /// - Dashcube = 0xF21, - - /// - /// The Font Awesome "database" icon unicode character. - /// - Database = 0xF1C, - - /// - /// The Font Awesome "deaf" icon unicode character. - /// - Deaf = 0xF2A4, - - /// - /// The Font Awesome "delicious" icon unicode character. - /// - Delicious = 0xF1A5, - - /// - /// The Font Awesome "democrat" icon unicode character. - /// - Democrat = 0xF747, - - /// - /// The Font Awesome "deploydog" icon unicode character. - /// - Deploydog = 0xF38E, - - /// - /// The Font Awesome "deskpro" icon unicode character. - /// - Deskpro = 0xF38F, - - /// - /// The Font Awesome "desktop" icon unicode character. - /// - Desktop = 0xF108, - - /// - /// The Font Awesome "dev" icon unicode character. - /// - Dev = 0xF6CC, - - /// - /// The Font Awesome "deviantart" icon unicode character. - /// - Deviantart = 0xF1BD, - - /// - /// The Font Awesome "dharmachakra" icon unicode character. - /// - Dharmachakra = 0xF655, - - /// - /// The Font Awesome "dhl" icon unicode character. - /// - Dhl = 0xF79, - - /// - /// The Font Awesome "diagnoses" icon unicode character. - /// - Diagnoses = 0xF47, - - /// - /// The Font Awesome "diaspora" icon unicode character. - /// - Diaspora = 0xF791, - - /// - /// The Font Awesome "dice" icon unicode character. - /// - Dice = 0xF522, - - /// - /// The Font Awesome "dice-d20" icon unicode character. - /// - DiceD20 = 0xF6CF, - - /// - /// The Font Awesome "dice-d6" icon unicode character. - /// - DiceD6 = 0xF6D1, - - /// - /// The Font Awesome "dice-five" icon unicode character. - /// - DiceFive = 0xF523, - - /// - /// The Font Awesome "dice-four" icon unicode character. - /// - DiceFour = 0xF524, - - /// - /// The Font Awesome "dice-one" icon unicode character. - /// - DiceOne = 0xF525, - - /// - /// The Font Awesome "dice-six" icon unicode character. - /// - DiceSix = 0xF526, - - /// - /// The Font Awesome "dice-three" icon unicode character. - /// - DiceThree = 0xF527, - - /// - /// The Font Awesome "dice-two" icon unicode character. - /// - DiceTwo = 0xF528, - - /// - /// The Font Awesome "digg" icon unicode character. - /// - Digg = 0xF1A6, - - /// - /// The Font Awesome "digital-ocean" icon unicode character. - /// - DigitalOcean = 0xF391, - - /// - /// The Font Awesome "digital-tachograph" icon unicode character. - /// - DigitalTachograph = 0xF566, - - /// - /// The Font Awesome "directions" icon unicode character. - /// - Directions = 0xF5EB, - - /// - /// The Font Awesome "discord" icon unicode character. - /// - Discord = 0xF392, - - /// - /// The Font Awesome "discourse" icon unicode character. - /// - Discourse = 0xF393, - - /// - /// The Font Awesome "divide" icon unicode character. - /// - Divide = 0xF529, - - /// - /// The Font Awesome "dizzy" icon unicode character. - /// - Dizzy = 0xF567, - - /// - /// The Font Awesome "dna" icon unicode character. - /// - Dna = 0xF471, - - /// - /// The Font Awesome "dochub" icon unicode character. - /// - Dochub = 0xF394, - - /// - /// The Font Awesome "docker" icon unicode character. - /// - Docker = 0xF395, - - /// - /// The Font Awesome "dog" icon unicode character. - /// - Dog = 0xF6D3, - - /// - /// The Font Awesome "dollar-sign" icon unicode character. - /// - DollarSign = 0xF155, - - /// - /// The Font Awesome "dolly" icon unicode character. - /// - Dolly = 0xF472, - - /// - /// The Font Awesome "dolly-flatbed" icon unicode character. - /// - DollyFlatbed = 0xF474, - - /// - /// The Font Awesome "donate" icon unicode character. - /// - Donate = 0xF4B9, - - /// - /// The Font Awesome "door-closed" icon unicode character. - /// - DoorClosed = 0xF52A, - - /// - /// The Font Awesome "door-open" icon unicode character. - /// - DoorOpen = 0xF52B, - - /// - /// The Font Awesome "dot-circle" icon unicode character. - /// - DotCircle = 0xF192, - - /// - /// The Font Awesome "dove" icon unicode character. - /// - Dove = 0xF4BA, - - /// - /// The Font Awesome "download" icon unicode character. - /// - Download = 0xF019, - - /// - /// The Font Awesome "draft2digital" icon unicode character. - /// - Draft2digital = 0xF396, - - /// - /// The Font Awesome "drafting-compass" icon unicode character. - /// - DraftingCompass = 0xF568, - - /// - /// The Font Awesome "dragon" icon unicode character. - /// - Dragon = 0xF6D5, - - /// - /// The Font Awesome "draw-polygon" icon unicode character. - /// - DrawPolygon = 0xF5EE, - - /// - /// The Font Awesome "dribbble" icon unicode character. - /// - Dribbble = 0xF17D, - - /// - /// The Font Awesome "dribbble-square" icon unicode character. - /// - DribbbleSquare = 0xF397, - - /// - /// The Font Awesome "dropbox" icon unicode character. - /// - Dropbox = 0xF16B, - - /// - /// The Font Awesome "drum" icon unicode character. - /// - Drum = 0xF569, - - /// - /// The Font Awesome "drum-steelpan" icon unicode character. - /// - DrumSteelpan = 0xF56A, - - /// - /// The Font Awesome "drumstick-bite" icon unicode character. - /// - DrumstickBite = 0xF6D7, - - /// - /// The Font Awesome "drupal" icon unicode character. - /// - Drupal = 0xF1A9, - - /// - /// The Font Awesome "dumbbell" icon unicode character. - /// - Dumbbell = 0xF44B, - - /// - /// The Font Awesome "dumpster" icon unicode character. - /// - Dumpster = 0xF793, - - /// - /// The Font Awesome "dumpster-fire" icon unicode character. - /// - DumpsterFire = 0xF794, - - /// - /// The Font Awesome "dungeon" icon unicode character. - /// - Dungeon = 0xF6D9, - - /// - /// The Font Awesome "dyalog" icon unicode character. - /// - Dyalog = 0xF399, - - /// - /// The Font Awesome "earlybirds" icon unicode character. - /// - Earlybirds = 0xF39A, - - /// - /// The Font Awesome "ebay" icon unicode character. - /// - Ebay = 0xF4F4, - - /// - /// The Font Awesome "edge" icon unicode character. - /// - Edge = 0xF282, - - /// - /// The Font Awesome "edit" icon unicode character. - /// - Edit = 0xF044, - - /// - /// The Font Awesome "egg" icon unicode character. - /// - Egg = 0xF7FB, - - /// - /// The Font Awesome "eject" icon unicode character. - /// - Eject = 0xF052, - - /// - /// The Font Awesome "elementor" icon unicode character. - /// - Elementor = 0xF43, - - /// - /// The Font Awesome "ellipsis-h" icon unicode character. - /// - EllipsisH = 0xF141, - - /// - /// The Font Awesome "ellipsis-v" icon unicode character. - /// - EllipsisV = 0xF142, - - /// - /// The Font Awesome "ello" icon unicode character. - /// - Ello = 0xF5F1, - - /// - /// The Font Awesome "ember" icon unicode character. - /// - Ember = 0xF423, - - /// - /// The Font Awesome "empire" icon unicode character. - /// - Empire = 0xF1D1, - - /// - /// The Font Awesome "envelope" icon unicode character. - /// - Envelope = 0xF0E, - - /// - /// The Font Awesome "envelope-open" icon unicode character. - /// - EnvelopeOpen = 0xF2B6, - - /// - /// The Font Awesome "envelope-open-text" icon unicode character. - /// - EnvelopeOpenText = 0xF658, - - /// - /// The Font Awesome "envelope-square" icon unicode character. - /// - EnvelopeSquare = 0xF199, - - /// - /// The Font Awesome "envira" icon unicode character. - /// - Envira = 0xF299, - - /// - /// The Font Awesome "equals" icon unicode character. - /// - Equals = 0xF52C, - - /// - /// The Font Awesome "eraser" icon unicode character. - /// - Eraser = 0xF12D, - - /// - /// The Font Awesome "erlang" icon unicode character. - /// - Erlang = 0xF39D, - - /// - /// The Font Awesome "ethereum" icon unicode character. - /// - Ethereum = 0xF42E, - - /// - /// The Font Awesome "ethernet" icon unicode character. - /// - Ethernet = 0xF796, - - /// - /// The Font Awesome "etsy" icon unicode character. - /// - Etsy = 0xF2D7, - - /// - /// The Font Awesome "euro-sign" icon unicode character. - /// - EuroSign = 0xF153, - - /// - /// The Font Awesome "evernote" icon unicode character. - /// - Evernote = 0xF839, - - /// - /// The Font Awesome "exchange-alt" icon unicode character. - /// - ExchangeAlt = 0xF362, - - /// - /// The Font Awesome "exclamation" icon unicode character. - /// - Exclamation = 0xF12A, - - /// - /// The Font Awesome "exclamation-circle" icon unicode character. - /// - ExclamationCircle = 0xF06A, - - /// - /// The Font Awesome "exclamation-triangle" icon unicode character. - /// - ExclamationTriangle = 0xF071, - - /// - /// The Font Awesome "expand" icon unicode character. - /// - Expand = 0xF065, - - /// - /// The Font Awesome "expand-alt" icon unicode character. - /// - ExpandAlt = 0xF424, - - /// - /// The Font Awesome "expand-arrows-alt" icon unicode character. - /// - ExpandArrowsAlt = 0xF31E, - - /// - /// The Font Awesome "expeditedssl" icon unicode character. - /// - Expeditedssl = 0xF23E, - - /// - /// The Font Awesome "external-link-alt" icon unicode character. - /// - ExternalLinkAlt = 0xF35D, - - /// - /// The Font Awesome "external-link-square-alt" icon unicode character. - /// - ExternalLinkSquareAlt = 0xF36, - - /// - /// The Font Awesome "eye" icon unicode character. - /// - Eye = 0xF06E, - - /// - /// The Font Awesome "eye-dropper" icon unicode character. - /// - EyeDropper = 0xF1FB, - - /// - /// The Font Awesome "eye-slash" icon unicode character. - /// - EyeSlash = 0xF07, - - /// - /// The Font Awesome "facebook" icon unicode character. - /// - Facebook = 0xF09A, - - /// - /// The Font Awesome "facebook-f" icon unicode character. - /// - FacebookF = 0xF39E, - - /// - /// The Font Awesome "facebook-messenger" icon unicode character. - /// - FacebookMessenger = 0xF39F, - - /// - /// The Font Awesome "facebook-square" icon unicode character. - /// - FacebookSquare = 0xF082, - - /// - /// The Font Awesome "fan" icon unicode character. - /// - Fan = 0xF863, - - /// - /// The Font Awesome "fantasy-flight-games" icon unicode character. - /// - FantasyFlightGames = 0xF6DC, - - /// - /// The Font Awesome "fast-backward" icon unicode character. - /// - FastBackward = 0xF049, - - /// - /// The Font Awesome "fast-forward" icon unicode character. - /// - FastForward = 0xF05, - - /// - /// The Font Awesome "fax" icon unicode character. - /// - Fax = 0xF1AC, - - /// - /// The Font Awesome "feather" icon unicode character. - /// - Feather = 0xF52D, - - /// - /// The Font Awesome "feather-alt" icon unicode character. - /// - FeatherAlt = 0xF56B, - - /// - /// The Font Awesome "fedex" icon unicode character. - /// - Fedex = 0xF797, - - /// - /// The Font Awesome "fedora" icon unicode character. - /// - Fedora = 0xF798, - - /// - /// The Font Awesome "female" icon unicode character. - /// - Female = 0xF182, - - /// - /// The Font Awesome "fighter-jet" icon unicode character. - /// - FighterJet = 0xF0FB, - - /// - /// The Font Awesome "figma" icon unicode character. - /// - Figma = 0xF799, - - /// - /// The Font Awesome "file" icon unicode character. - /// - File = 0xF15B, - - /// - /// The Font Awesome "file-alt" icon unicode character. - /// - FileAlt = 0xF15C, - - /// - /// The Font Awesome "file-archive" icon unicode character. - /// - FileArchive = 0xF1C6, - - /// - /// The Font Awesome "file-audio" icon unicode character. - /// - FileAudio = 0xF1C7, - - /// - /// The Font Awesome "file-code" icon unicode character. - /// - FileCode = 0xF1C9, - - /// - /// The Font Awesome "file-contract" icon unicode character. - /// - FileContract = 0xF56C, - - /// - /// The Font Awesome "file-csv" icon unicode character. - /// - FileCsv = 0xF6DD, - - /// - /// The Font Awesome "file-download" icon unicode character. - /// - FileDownload = 0xF56D, - - /// - /// The Font Awesome "file-excel" icon unicode character. - /// - FileExcel = 0xF1C3, - - /// - /// The Font Awesome "file-export" icon unicode character. - /// - FileExport = 0xF56E, - - /// - /// The Font Awesome "file-image" icon unicode character. - /// - FileImage = 0xF1C5, - - /// - /// The Font Awesome "file-import" icon unicode character. - /// - FileImport = 0xF56F, - - /// - /// The Font Awesome "file-invoice" icon unicode character. - /// - FileInvoice = 0xF57, - - /// - /// The Font Awesome "file-invoice-dollar" icon unicode character. - /// - FileInvoiceDollar = 0xF571, - - /// - /// The Font Awesome "file-medical" icon unicode character. - /// - FileMedical = 0xF477, - - /// - /// The Font Awesome "file-medical-alt" icon unicode character. - /// - FileMedicalAlt = 0xF478, - - /// - /// The Font Awesome "file-pdf" icon unicode character. - /// - FilePdf = 0xF1C1, - - /// - /// The Font Awesome "file-powerpoint" icon unicode character. - /// - FilePowerpoint = 0xF1C4, - - /// - /// The Font Awesome "file-prescription" icon unicode character. - /// - FilePrescription = 0xF572, - - /// - /// The Font Awesome "file-signature" icon unicode character. - /// - FileSignature = 0xF573, - - /// - /// The Font Awesome "file-upload" icon unicode character. - /// - FileUpload = 0xF574, - - /// - /// The Font Awesome "file-video" icon unicode character. - /// - FileVideo = 0xF1C8, - - /// - /// The Font Awesome "file-word" icon unicode character. - /// - FileWord = 0xF1C2, - - /// - /// The Font Awesome "fill" icon unicode character. - /// - Fill = 0xF575, - - /// - /// The Font Awesome "fill-drip" icon unicode character. - /// - FillDrip = 0xF576, - - /// - /// The Font Awesome "film" icon unicode character. - /// - Film = 0xF008, - - /// - /// The Font Awesome "filter" icon unicode character. - /// - Filter = 0xF0B, - - /// - /// The Font Awesome "fingerprint" icon unicode character. - /// - Fingerprint = 0xF577, - - /// - /// The Font Awesome "fire" icon unicode character. - /// - Fire = 0xF06D, - - /// - /// The Font Awesome "fire-alt" icon unicode character. - /// - FireAlt = 0xF7E4, - - /// - /// The Font Awesome "fire-extinguisher" icon unicode character. - /// - FireExtinguisher = 0xF134, - - /// - /// The Font Awesome "firefox" icon unicode character. - /// - Firefox = 0xF269, - - /// - /// The Font Awesome "firefox-browser" icon unicode character. - /// - FirefoxBrowser = 0xF907, - - /// - /// The Font Awesome "first-aid" icon unicode character. - /// - FirstAid = 0xF479, - - /// - /// The Font Awesome "firstdraft" icon unicode character. - /// - Firstdraft = 0xF3A1, - - /// - /// The Font Awesome "first-order" icon unicode character. - /// - FirstOrder = 0xF2B, - - /// - /// The Font Awesome "first-order-alt" icon unicode character. - /// - FirstOrderAlt = 0xF50A, - - /// - /// The Font Awesome "fish" icon unicode character. - /// - Fish = 0xF578, - - /// - /// The Font Awesome "fist-raised" icon unicode character. - /// - FistRaised = 0xF6DE, - - /// - /// The Font Awesome "flag" icon unicode character. - /// - Flag = 0xF024, - - /// - /// The Font Awesome "flag-checkered" icon unicode character. - /// - FlagCheckered = 0xF11E, - - /// - /// The Font Awesome "flag-usa" icon unicode character. - /// - FlagUsa = 0xF74D, - - /// - /// The Font Awesome "flask" icon unicode character. - /// - Flask = 0xF0C3, - - /// - /// The Font Awesome "flickr" icon unicode character. - /// - Flickr = 0xF16E, - - /// - /// The Font Awesome "flipboard" icon unicode character. - /// - Flipboard = 0xF44D, - - /// - /// The Font Awesome "flushed" icon unicode character. - /// - Flushed = 0xF579, - - /// - /// The Font Awesome "fly" icon unicode character. - /// - Fly = 0xF417, - - /// - /// The Font Awesome "folder" icon unicode character. - /// - Folder = 0xF07B, - - /// - /// The Font Awesome "folder-minus" icon unicode character. - /// - FolderMinus = 0xF65D, - - /// - /// The Font Awesome "folder-open" icon unicode character. - /// - FolderOpen = 0xF07C, - - /// - /// The Font Awesome "folder-plus" icon unicode character. - /// - FolderPlus = 0xF65E, - - /// - /// The Font Awesome "font" icon unicode character. - /// - Font = 0xF031, - - /// - /// The Font Awesome "font-awesome" icon unicode character. - /// - FontAwesome = 0xF2B4, - - /// - /// The Font Awesome "font-awesome-alt" icon unicode character. - /// - FontAwesomeAlt = 0xF35C, - - /// - /// The Font Awesome "font-awesome-flag" icon unicode character. - /// - FontAwesomeFlag = 0xF425, - - /// - /// The Font Awesome "font-awesome-logo-full" icon unicode character. - /// - FontAwesomeLogoFull = 0xF4E6, - - /// - /// The Font Awesome "fonticons" icon unicode character. - /// - Fonticons = 0xF28, - - /// - /// The Font Awesome "fonticons-fi" icon unicode character. - /// - FonticonsFi = 0xF3A2, - - /// - /// The Font Awesome "football-ball" icon unicode character. - /// - FootballBall = 0xF44E, - - /// - /// The Font Awesome "fort-awesome" icon unicode character. - /// - FortAwesome = 0xF286, - - /// - /// The Font Awesome "fort-awesome-alt" icon unicode character. - /// - FortAwesomeAlt = 0xF3A3, - - /// - /// The Font Awesome "forumbee" icon unicode character. - /// - Forumbee = 0xF211, - - /// - /// The Font Awesome "forward" icon unicode character. - /// - Forward = 0xF04E, - - /// - /// The Font Awesome "foursquare" icon unicode character. - /// - Foursquare = 0xF18, - - /// - /// The Font Awesome "freebsd" icon unicode character. - /// - Freebsd = 0xF3A4, - - /// - /// The Font Awesome "free-code-camp" icon unicode character. - /// - FreeCodeCamp = 0xF2C5, - - /// - /// The Font Awesome "frog" icon unicode character. - /// - Frog = 0xF52E, - - /// - /// The Font Awesome "frown" icon unicode character. - /// - Frown = 0xF119, - - /// - /// The Font Awesome "frown-open" icon unicode character. - /// - FrownOpen = 0xF57A, - - /// - /// The Font Awesome "fulcrum" icon unicode character. - /// - Fulcrum = 0xF50B, - - /// - /// The Font Awesome "funnel-dollar" icon unicode character. - /// - FunnelDollar = 0xF662, - - /// - /// The Font Awesome "futbol" icon unicode character. - /// - Futbol = 0xF1E3, - - /// - /// The Font Awesome "galactic-republic" icon unicode character. - /// - GalacticRepublic = 0xF50C, - - /// - /// The Font Awesome "galactic-senate" icon unicode character. - /// - GalacticSenate = 0xF50D, - - /// - /// The Font Awesome "gamepad" icon unicode character. - /// - Gamepad = 0xF11B, - - /// - /// The Font Awesome "gas-pump" icon unicode character. - /// - GasPump = 0xF52F, - - /// - /// The Font Awesome "gavel" icon unicode character. - /// - Gavel = 0xF0E3, - - /// - /// The Font Awesome "gem" icon unicode character. - /// - Gem = 0xF3A5, - - /// - /// The Font Awesome "genderless" icon unicode character. - /// - Genderless = 0xF22D, - - /// - /// The Font Awesome "get-pocket" icon unicode character. - /// - GetPocket = 0xF265, - - /// - /// The Font Awesome "gg" icon unicode character. - /// - Gg = 0xF26, - - /// - /// The Font Awesome "gg-circle" icon unicode character. - /// - GgCircle = 0xF261, - - /// - /// The Font Awesome "ghost" icon unicode character. - /// - Ghost = 0xF6E2, - - /// - /// The Font Awesome "gift" icon unicode character. - /// - Gift = 0xF06B, - - /// - /// The Font Awesome "gifts" icon unicode character. - /// - Gifts = 0xF79C, - - /// - /// The Font Awesome "git" icon unicode character. - /// - Git = 0xF1D3, - - /// - /// The Font Awesome "git-alt" icon unicode character. - /// - GitAlt = 0xF841, - - /// - /// The Font Awesome "github" icon unicode character. - /// - Github = 0xF09B, - - /// - /// The Font Awesome "github-alt" icon unicode character. - /// - GithubAlt = 0xF113, - - /// - /// The Font Awesome "github-square" icon unicode character. - /// - GithubSquare = 0xF092, - - /// - /// The Font Awesome "gitkraken" icon unicode character. - /// - Gitkraken = 0xF3A6, - - /// - /// The Font Awesome "gitlab" icon unicode character. - /// - Gitlab = 0xF296, - - /// - /// The Font Awesome "git-square" icon unicode character. - /// - GitSquare = 0xF1D2, - - /// - /// The Font Awesome "gitter" icon unicode character. - /// - Gitter = 0xF426, - - /// - /// The Font Awesome "glass-cheers" icon unicode character. - /// - GlassCheers = 0xF79F, - - /// - /// The Font Awesome "glasses" icon unicode character. - /// - Glasses = 0xF53, - - /// - /// The Font Awesome "glass-martini" icon unicode character. - /// - GlassMartini = 0xF, - - /// - /// The Font Awesome "glass-martini-alt" icon unicode character. - /// - GlassMartiniAlt = 0xF57B, - - /// - /// The Font Awesome "glass-whiskey" icon unicode character. - /// - GlassWhiskey = 0xF7A, - - /// - /// The Font Awesome "glide" icon unicode character. - /// - Glide = 0xF2A5, - - /// - /// The Font Awesome "glide-g" icon unicode character. - /// - GlideG = 0xF2A6, - - /// - /// The Font Awesome "globe" icon unicode character. - /// - Globe = 0xF0AC, - - /// - /// The Font Awesome "globe-africa" icon unicode character. - /// - GlobeAfrica = 0xF57C, - - /// - /// The Font Awesome "globe-americas" icon unicode character. - /// - GlobeAmericas = 0xF57D, - - /// - /// The Font Awesome "globe-asia" icon unicode character. - /// - GlobeAsia = 0xF57E, - - /// - /// The Font Awesome "globe-europe" icon unicode character. - /// - GlobeEurope = 0xF7A2, - - /// - /// The Font Awesome "gofore" icon unicode character. - /// - Gofore = 0xF3A7, - - /// - /// The Font Awesome "golf-ball" icon unicode character. - /// - GolfBall = 0xF45, - - /// - /// The Font Awesome "goodreads" icon unicode character. - /// - Goodreads = 0xF3A8, - - /// - /// The Font Awesome "goodreads-g" icon unicode character. - /// - GoodreadsG = 0xF3A9, - - /// - /// The Font Awesome "google" icon unicode character. - /// - Google = 0xF1A, - - /// - /// The Font Awesome "google-drive" icon unicode character. - /// - GoogleDrive = 0xF3AA, - - /// - /// The Font Awesome "google-play" icon unicode character. - /// - GooglePlay = 0xF3AB, - - /// - /// The Font Awesome "google-plus" icon unicode character. - /// - GooglePlus = 0xF2B3, - - /// - /// The Font Awesome "google-plus-g" icon unicode character. - /// - GooglePlusG = 0xF0D5, - - /// - /// The Font Awesome "google-plus-square" icon unicode character. - /// - GooglePlusSquare = 0xF0D4, - - /// - /// The Font Awesome "google-wallet" icon unicode character. - /// - GoogleWallet = 0xF1EE, - - /// - /// The Font Awesome "gopuram" icon unicode character. - /// - Gopuram = 0xF664, - - /// - /// The Font Awesome "graduation-cap" icon unicode character. - /// - GraduationCap = 0xF19D, - - /// - /// The Font Awesome "gratipay" icon unicode character. - /// - Gratipay = 0xF184, - - /// - /// The Font Awesome "grav" icon unicode character. - /// - Grav = 0xF2D6, - - /// - /// The Font Awesome "greater-than" icon unicode character. - /// - GreaterThan = 0xF531, - - /// - /// The Font Awesome "greater-than-equal" icon unicode character. - /// - GreaterThanEqual = 0xF532, - - /// - /// The Font Awesome "grimace" icon unicode character. - /// - Grimace = 0xF57F, - - /// - /// The Font Awesome "grin" icon unicode character. - /// - Grin = 0xF58, - - /// - /// The Font Awesome "grin-alt" icon unicode character. - /// - GrinAlt = 0xF581, - - /// - /// The Font Awesome "grin-beam" icon unicode character. - /// - GrinBeam = 0xF582, - - /// - /// The Font Awesome "grin-beam-sweat" icon unicode character. - /// - GrinBeamSweat = 0xF583, - - /// - /// The Font Awesome "grin-hearts" icon unicode character. - /// - GrinHearts = 0xF584, - - /// - /// The Font Awesome "grin-squint" icon unicode character. - /// - GrinSquint = 0xF585, - - /// - /// The Font Awesome "grin-squint-tears" icon unicode character. - /// - GrinSquintTears = 0xF586, - - /// - /// The Font Awesome "grin-stars" icon unicode character. - /// - GrinStars = 0xF587, - - /// - /// The Font Awesome "grin-tears" icon unicode character. - /// - GrinTears = 0xF588, - - /// - /// The Font Awesome "grin-tongue" icon unicode character. - /// - GrinTongue = 0xF589, - - /// - /// The Font Awesome "grin-tongue-squint" icon unicode character. - /// - GrinTongueSquint = 0xF58A, - - /// - /// The Font Awesome "grin-tongue-wink" icon unicode character. - /// - GrinTongueWink = 0xF58B, - - /// - /// The Font Awesome "grin-wink" icon unicode character. - /// - GrinWink = 0xF58C, - - /// - /// The Font Awesome "gripfire" icon unicode character. - /// - Gripfire = 0xF3AC, - - /// - /// The Font Awesome "grip-horizontal" icon unicode character. - /// - GripHorizontal = 0xF58D, - - /// - /// The Font Awesome "grip-lines" icon unicode character. - /// - GripLines = 0xF7A4, - - /// - /// The Font Awesome "grip-lines-vertical" icon unicode character. - /// - GripLinesVertical = 0xF7A5, - - /// - /// The Font Awesome "grip-vertical" icon unicode character. - /// - GripVertical = 0xF58E, - - /// - /// The Font Awesome "grunt" icon unicode character. - /// - Grunt = 0xF3AD, - - /// - /// The Font Awesome "guitar" icon unicode character. - /// - Guitar = 0xF7A6, - - /// - /// The Font Awesome "gulp" icon unicode character. - /// - Gulp = 0xF3AE, - - /// - /// The Font Awesome "hacker-news" icon unicode character. - /// - HackerNews = 0xF1D4, - - /// - /// The Font Awesome "hacker-news-square" icon unicode character. - /// - HackerNewsSquare = 0xF3AF, - - /// - /// The Font Awesome "hackerrank" icon unicode character. - /// - Hackerrank = 0xF5F7, - - /// - /// The Font Awesome "hamburger" icon unicode character. - /// - Hamburger = 0xF805, - - /// - /// The Font Awesome "hammer" icon unicode character. - /// - Hammer = 0xF6E3, - - /// - /// The Font Awesome "hamsa" icon unicode character. - /// - Hamsa = 0xF665, - - /// - /// The Font Awesome "hand-holding" icon unicode character. - /// - HandHolding = 0xF4BD, - - /// - /// The Font Awesome "hand-holding-heart" icon unicode character. - /// - HandHoldingHeart = 0xF4BE, - - /// - /// The Font Awesome "hand-holding-usd" icon unicode character. - /// - HandHoldingUsd = 0xF4C, - - /// - /// The Font Awesome "hand-lizard" icon unicode character. - /// - HandLizard = 0xF258, - - /// - /// The Font Awesome "hand-middle-finger" icon unicode character. - /// - HandMiddleFinger = 0xF806, - - /// - /// The Font Awesome "hand-paper" icon unicode character. - /// - HandPaper = 0xF256, - - /// - /// The Font Awesome "hand-peace" icon unicode character. - /// - HandPeace = 0xF25B, - - /// - /// The Font Awesome "hand-point-down" icon unicode character. - /// - HandPointDown = 0xF0A7, - - /// - /// The Font Awesome "hand-pointer" icon unicode character. - /// - HandPointer = 0xF25A, - - /// - /// The Font Awesome "hand-point-left" icon unicode character. - /// - HandPointLeft = 0xF0A5, - - /// - /// The Font Awesome "hand-point-right" icon unicode character. - /// - HandPointRight = 0xF0A4, - - /// - /// The Font Awesome "hand-point-up" icon unicode character. - /// - HandPointUp = 0xF0A6, - - /// - /// The Font Awesome "hand-rock" icon unicode character. - /// - HandRock = 0xF255, - - /// - /// The Font Awesome "hands" icon unicode character. - /// - Hands = 0xF4C2, - - /// - /// The Font Awesome "hand-scissors" icon unicode character. - /// - HandScissors = 0xF257, - - /// - /// The Font Awesome "handshake" icon unicode character. - /// - Handshake = 0xF2B5, - - /// - /// The Font Awesome "hands-helping" icon unicode character. - /// - HandsHelping = 0xF4C4, - - /// - /// The Font Awesome "hand-spock" icon unicode character. - /// - HandSpock = 0xF259, - - /// - /// The Font Awesome "hanukiah" icon unicode character. - /// - Hanukiah = 0xF6E6, - - /// - /// The Font Awesome "hard-hat" icon unicode character. - /// - HardHat = 0xF807, - - /// - /// The Font Awesome "hashtag" icon unicode character. - /// - Hashtag = 0xF292, - - /// - /// The Font Awesome "hat-cowboy" icon unicode character. - /// - HatCowboy = 0xF8C, - - /// - /// The Font Awesome "hat-cowboy-side" icon unicode character. - /// - HatCowboySide = 0xF8C1, - - /// - /// The Font Awesome "hat-wizard" icon unicode character. - /// - HatWizard = 0xF6E8, - - /// - /// The Font Awesome "hdd" icon unicode character. - /// - Hdd = 0xF0A, - - /// - /// The Font Awesome "heading" icon unicode character. - /// - Heading = 0xF1DC, - - /// - /// The Font Awesome "headphones" icon unicode character. - /// - Headphones = 0xF025, - - /// - /// The Font Awesome "headphones-alt" icon unicode character. - /// - HeadphonesAlt = 0xF58F, - - /// - /// The Font Awesome "headset" icon unicode character. - /// - Headset = 0xF59, - - /// - /// The Font Awesome "heart" icon unicode character. - /// - Heart = 0xF004, - - /// - /// The Font Awesome "heartbeat" icon unicode character. - /// - Heartbeat = 0xF21E, - - /// - /// The Font Awesome "heart-broken" icon unicode character. - /// - HeartBroken = 0xF7A9, - - /// - /// The Font Awesome "helicopter" icon unicode character. - /// - Helicopter = 0xF533, - - /// - /// The Font Awesome "highlighter" icon unicode character. - /// - Highlighter = 0xF591, - - /// - /// The Font Awesome "hiking" icon unicode character. - /// - Hiking = 0xF6EC, - - /// - /// The Font Awesome "hippo" icon unicode character. - /// - Hippo = 0xF6ED, - - /// - /// The Font Awesome "hips" icon unicode character. - /// - Hips = 0xF452, - - /// - /// The Font Awesome "hire-a-helper" icon unicode character. - /// - HireAHelper = 0xF3B, - - /// - /// The Font Awesome "history" icon unicode character. - /// - History = 0xF1DA, - - /// - /// The Font Awesome "hockey-puck" icon unicode character. - /// - HockeyPuck = 0xF453, - - /// - /// The Font Awesome "holly-berry" icon unicode character. - /// - HollyBerry = 0xF7AA, - - /// - /// The Font Awesome "home" icon unicode character. - /// - Home = 0xF015, - - /// - /// The Font Awesome "hooli" icon unicode character. - /// - Hooli = 0xF427, - - /// - /// The Font Awesome "hornbill" icon unicode character. - /// - Hornbill = 0xF592, - - /// - /// The Font Awesome "horse" icon unicode character. - /// - Horse = 0xF6F, - - /// - /// The Font Awesome "horse-head" icon unicode character. - /// - HorseHead = 0xF7AB, - - /// - /// The Font Awesome "hospital" icon unicode character. - /// - Hospital = 0xF0F8, - - /// - /// The Font Awesome "hospital-alt" icon unicode character. - /// - HospitalAlt = 0xF47D, - - /// - /// The Font Awesome "hospital-symbol" icon unicode character. - /// - HospitalSymbol = 0xF47E, - - /// - /// The Font Awesome "hotdog" icon unicode character. - /// - Hotdog = 0xF80F, - - /// - /// The Font Awesome "hotel" icon unicode character. - /// - Hotel = 0xF594, - - /// - /// The Font Awesome "hotjar" icon unicode character. - /// - Hotjar = 0xF3B1, - - /// - /// The Font Awesome "hot-tub" icon unicode character. - /// - HotTub = 0xF593, - - /// - /// The Font Awesome "hourglass" icon unicode character. - /// - Hourglass = 0xF254, - - /// - /// The Font Awesome "hourglass-end" icon unicode character. - /// - HourglassEnd = 0xF253, - - /// - /// The Font Awesome "hourglass-half" icon unicode character. - /// - HourglassHalf = 0xF252, - - /// - /// The Font Awesome "hourglass-start" icon unicode character. - /// - HourglassStart = 0xF251, - - /// - /// The Font Awesome "house-damage" icon unicode character. - /// - HouseDamage = 0xF6F1, - - /// - /// The Font Awesome "houzz" icon unicode character. - /// - Houzz = 0xF27C, - - /// - /// The Font Awesome "hryvnia" icon unicode character. - /// - Hryvnia = 0xF6F2, - - /// - /// The Font Awesome "h-square" icon unicode character. - /// - HSquare = 0xF0FD, - - /// - /// The Font Awesome "html5" icon unicode character. - /// - Html5 = 0xF13B, - - /// - /// The Font Awesome "hubspot" icon unicode character. - /// - Hubspot = 0xF3B2, - - /// - /// The Font Awesome "ice-cream" icon unicode character. - /// - IceCream = 0xF81, - - /// - /// The Font Awesome "icicles" icon unicode character. - /// - Icicles = 0xF7AD, - - /// - /// The Font Awesome "icons" icon unicode character. - /// - Icons = 0xF86D, - - /// - /// The Font Awesome "i-cursor" icon unicode character. - /// - ICursor = 0xF246, - - /// - /// The Font Awesome "id-badge" icon unicode character. - /// - IdBadge = 0xF2C1, - - /// - /// The Font Awesome "id-card" icon unicode character. - /// - IdCard = 0xF2C2, - - /// - /// The Font Awesome "id-card-alt" icon unicode character. - /// - IdCardAlt = 0xF47F, - - /// - /// The Font Awesome "ideal" icon unicode character. - /// - Ideal = 0xF913, - - /// - /// The Font Awesome "igloo" icon unicode character. - /// - Igloo = 0xF7AE, - - /// - /// The Font Awesome "image" icon unicode character. - /// - Image = 0xF03E, - - /// - /// The Font Awesome "images" icon unicode character. - /// - Images = 0xF302, - - /// - /// The Font Awesome "imdb" icon unicode character. - /// - Imdb = 0xF2D8, - - /// - /// The Font Awesome "inbox" icon unicode character. - /// - Inbox = 0xF01C, - - /// - /// The Font Awesome "indent" icon unicode character. - /// - Indent = 0xF03C, - - /// - /// The Font Awesome "industry" icon unicode character. - /// - Industry = 0xF275, - - /// - /// The Font Awesome "infinity" icon unicode character. - /// - Infinity = 0xF534, - - /// - /// The Font Awesome "info" icon unicode character. - /// - Info = 0xF129, - - /// - /// The Font Awesome "info-circle" icon unicode character. - /// - InfoCircle = 0xF05A, - - /// - /// The Font Awesome "instagram" icon unicode character. - /// - Instagram = 0xF16D, - - /// - /// The Font Awesome "instagram-square" icon unicode character. - /// - InstagramSquare = 0xF955, - - /// - /// The Font Awesome "intercom" icon unicode character. - /// - Intercom = 0xF7AF, - - /// - /// The Font Awesome "internet-explorer" icon unicode character. - /// - InternetExplorer = 0xF26B, - - /// - /// The Font Awesome "invision" icon unicode character. - /// - Invision = 0xF7B, - - /// - /// The Font Awesome "ioxhost" icon unicode character. - /// - Ioxhost = 0xF208, - - /// - /// The Font Awesome "italic" icon unicode character. - /// - Italic = 0xF033, - - /// - /// The Font Awesome "itch-io" icon unicode character. - /// - ItchIo = 0xF83A, - - /// - /// The Font Awesome "itunes" icon unicode character. - /// - Itunes = 0xF3B4, - - /// - /// The Font Awesome "itunes-note" icon unicode character. - /// - ItunesNote = 0xF3B5, - - /// - /// The Font Awesome "java" icon unicode character. - /// - Java = 0xF4E4, - - /// - /// The Font Awesome "jedi" icon unicode character. - /// - Jedi = 0xF669, - - /// - /// The Font Awesome "jedi-order" icon unicode character. - /// - JediOrder = 0xF50E, - - /// - /// The Font Awesome "jenkins" icon unicode character. - /// - Jenkins = 0xF3B6, - - /// - /// The Font Awesome "jira" icon unicode character. - /// - Jira = 0xF7B1, - - /// - /// The Font Awesome "joget" icon unicode character. - /// - Joget = 0xF3B7, - - /// - /// The Font Awesome "joint" icon unicode character. - /// - Joint = 0xF595, - - /// - /// The Font Awesome "joomla" icon unicode character. - /// - Joomla = 0xF1AA, - - /// - /// The Font Awesome "journal-whills" icon unicode character. - /// - JournalWhills = 0xF66A, - - /// - /// The Font Awesome "js" icon unicode character. - /// - Js = 0xF3B8, - - /// - /// The Font Awesome "jsfiddle" icon unicode character. - /// - Jsfiddle = 0xF1CC, - - /// - /// The Font Awesome "js-square" icon unicode character. - /// - JsSquare = 0xF3B9, - - /// - /// The Font Awesome "kaaba" icon unicode character. - /// - Kaaba = 0xF66B, - - /// - /// The Font Awesome "kaggle" icon unicode character. - /// - Kaggle = 0xF5FA, - - /// - /// The Font Awesome "key" icon unicode character. - /// - Key = 0xF084, - - /// - /// The Font Awesome "keybase" icon unicode character. - /// - Keybase = 0xF4F5, - - /// - /// The Font Awesome "keyboard" icon unicode character. - /// - Keyboard = 0xF11C, - - /// - /// The Font Awesome "keycdn" icon unicode character. - /// - Keycdn = 0xF3BA, - - /// - /// The Font Awesome "khanda" icon unicode character. - /// - Khanda = 0xF66D, - - /// - /// The Font Awesome "kickstarter" icon unicode character. - /// - Kickstarter = 0xF3BB, - - /// - /// The Font Awesome "kickstarter-k" icon unicode character. - /// - KickstarterK = 0xF3BC, - - /// - /// The Font Awesome "kiss" icon unicode character. - /// - Kiss = 0xF596, - - /// - /// The Font Awesome "kiss-beam" icon unicode character. - /// - KissBeam = 0xF597, - - /// - /// The Font Awesome "kiss-wink-heart" icon unicode character. - /// - KissWinkHeart = 0xF598, - - /// - /// The Font Awesome "kiwi-bird" icon unicode character. - /// - KiwiBird = 0xF535, - - /// - /// The Font Awesome "korvue" icon unicode character. - /// - Korvue = 0xF42F, - - /// - /// The Font Awesome "landmark" icon unicode character. - /// - Landmark = 0xF66F, - - /// - /// The Font Awesome "language" icon unicode character. - /// - Language = 0xF1AB, - - /// - /// The Font Awesome "laptop" icon unicode character. - /// - Laptop = 0xF109, - - /// - /// The Font Awesome "laptop-code" icon unicode character. - /// - LaptopCode = 0xF5FC, - - /// - /// The Font Awesome "laptop-medical" icon unicode character. - /// - LaptopMedical = 0xF812, - - /// - /// The Font Awesome "laravel" icon unicode character. - /// - Laravel = 0xF3BD, - - /// - /// The Font Awesome "lastfm" icon unicode character. - /// - Lastfm = 0xF202, - - /// - /// The Font Awesome "lastfm-square" icon unicode character. - /// - LastfmSquare = 0xF203, - - /// - /// The Font Awesome "laugh" icon unicode character. - /// - Laugh = 0xF599, - - /// - /// The Font Awesome "laugh-beam" icon unicode character. - /// - LaughBeam = 0xF59A, - - /// - /// The Font Awesome "laugh-squint" icon unicode character. - /// - LaughSquint = 0xF59B, - - /// - /// The Font Awesome "laugh-wink" icon unicode character. - /// - LaughWink = 0xF59C, - - /// - /// The Font Awesome "layer-group" icon unicode character. - /// - LayerGroup = 0xF5FD, - - /// - /// The Font Awesome "leaf" icon unicode character. - /// - Leaf = 0xF06C, - - /// - /// The Font Awesome "leanpub" icon unicode character. - /// - Leanpub = 0xF212, - - /// - /// The Font Awesome "lemon" icon unicode character. - /// - Lemon = 0xF094, - - /// - /// The Font Awesome "less" icon unicode character. - /// - Less = 0xF41D, - - /// - /// The Font Awesome "less-than" icon unicode character. - /// - LessThan = 0xF536, - - /// - /// The Font Awesome "less-than-equal" icon unicode character. - /// - LessThanEqual = 0xF537, - - /// - /// The Font Awesome "level-down-alt" icon unicode character. - /// - LevelDownAlt = 0xF3BE, - - /// - /// The Font Awesome "level-up-alt" icon unicode character. - /// - LevelUpAlt = 0xF3BF, - - /// - /// The Font Awesome "life-ring" icon unicode character. - /// - LifeRing = 0xF1CD, - - /// - /// The Font Awesome "lightbulb" icon unicode character. - /// - Lightbulb = 0xF0EB, - - /// - /// The Font Awesome "line" icon unicode character. - /// - Line = 0xF3C, - - /// - /// The Font Awesome "link" icon unicode character. - /// - Link = 0xF0C1, - - /// - /// The Font Awesome "linkedin" icon unicode character. - /// - Linkedin = 0xF08C, - - /// - /// The Font Awesome "linkedin-in" icon unicode character. - /// - LinkedinIn = 0xF0E1, - - /// - /// The Font Awesome "linode" icon unicode character. - /// - Linode = 0xF2B8, - - /// - /// The Font Awesome "linux" icon unicode character. - /// - Linux = 0xF17C, - - /// - /// The Font Awesome "lira-sign" icon unicode character. - /// - LiraSign = 0xF195, - - /// - /// The Font Awesome "list" icon unicode character. - /// - List = 0xF03A, - - /// - /// The Font Awesome "list-alt" icon unicode character. - /// - ListAlt = 0xF022, - - /// - /// The Font Awesome "list-ol" icon unicode character. - /// - ListOl = 0xF0CB, - - /// - /// The Font Awesome "list-ul" icon unicode character. - /// - ListUl = 0xF0CA, - - /// - /// The Font Awesome "location-arrow" icon unicode character. - /// - LocationArrow = 0xF124, - - /// - /// The Font Awesome "lock" icon unicode character. - /// - Lock = 0xF023, - - /// - /// The Font Awesome "lock-open" icon unicode character. - /// - LockOpen = 0xF3C1, - - /// - /// The Font Awesome "long-arrow-alt-down" icon unicode character. - /// - LongArrowAltDown = 0xF309, - - /// - /// The Font Awesome "long-arrow-alt-left" icon unicode character. - /// - LongArrowAltLeft = 0xF30A, - - /// - /// The Font Awesome "long-arrow-alt-right" icon unicode character. - /// - LongArrowAltRight = 0xF30B, - - /// - /// The Font Awesome "long-arrow-alt-up" icon unicode character. - /// - LongArrowAltUp = 0xF30C, - - /// - /// The Font Awesome "low-vision" icon unicode character. - /// - LowVision = 0xF2A8, - - /// - /// The Font Awesome "luggage-cart" icon unicode character. - /// - LuggageCart = 0xF59D, - - /// - /// The Font Awesome "lyft" icon unicode character. - /// - Lyft = 0xF3C3, - - /// - /// The Font Awesome "magento" icon unicode character. - /// - Magento = 0xF3C4, - - /// - /// The Font Awesome "magic" icon unicode character. - /// - Magic = 0xF0D, - - /// - /// The Font Awesome "magnet" icon unicode character. - /// - Magnet = 0xF076, - - /// - /// The Font Awesome "mail-bulk" icon unicode character. - /// - MailBulk = 0xF674, - - /// - /// The Font Awesome "mailchimp" icon unicode character. - /// - Mailchimp = 0xF59E, - - /// - /// The Font Awesome "male" icon unicode character. - /// - Male = 0xF183, - - /// - /// The Font Awesome "mandalorian" icon unicode character. - /// - Mandalorian = 0xF50F, - - /// - /// The Font Awesome "map" icon unicode character. - /// - Map = 0xF279, - - /// - /// The Font Awesome "map-marked" icon unicode character. - /// - MapMarked = 0xF59F, - - /// - /// The Font Awesome "map-marked-alt" icon unicode character. - /// - MapMarkedAlt = 0xF5A, - - /// - /// The Font Awesome "map-marker" icon unicode character. - /// - MapMarker = 0xF041, - - /// - /// The Font Awesome "map-marker-alt" icon unicode character. - /// - MapMarkerAlt = 0xF3C5, - - /// - /// The Font Awesome "map-pin" icon unicode character. - /// - MapPin = 0xF276, - - /// - /// The Font Awesome "map-signs" icon unicode character. - /// - MapSigns = 0xF277, - - /// - /// The Font Awesome "markdown" icon unicode character. - /// - Markdown = 0xF60F, - - /// - /// The Font Awesome "marker" icon unicode character. - /// - Marker = 0xF5A1, - - /// - /// The Font Awesome "mars" icon unicode character. - /// - Mars = 0xF222, - - /// - /// The Font Awesome "mars-double" icon unicode character. - /// - MarsDouble = 0xF227, - - /// - /// The Font Awesome "mars-stroke" icon unicode character. - /// - MarsStroke = 0xF229, - - /// - /// The Font Awesome "mars-stroke-h" icon unicode character. - /// - MarsStrokeH = 0xF22B, - - /// - /// The Font Awesome "mars-stroke-v" icon unicode character. - /// - MarsStrokeV = 0xF22A, - - /// - /// The Font Awesome "mask" icon unicode character. - /// - Mask = 0xF6FA, - - /// - /// The Font Awesome "mastodon" icon unicode character. - /// - Mastodon = 0xF4F6, - - /// - /// The Font Awesome "maxcdn" icon unicode character. - /// - Maxcdn = 0xF136, - - /// - /// The Font Awesome "mdb" icon unicode character. - /// - Mdb = 0xF8CA, - - /// - /// The Font Awesome "medal" icon unicode character. - /// - Medal = 0xF5A2, - - /// - /// The Font Awesome "medapps" icon unicode character. - /// - Medapps = 0xF3C6, - - /// - /// The Font Awesome "medium" icon unicode character. - /// - Medium = 0xF23A, - - /// - /// The Font Awesome "medium-m" icon unicode character. - /// - MediumM = 0xF3C7, - - /// - /// The Font Awesome "medkit" icon unicode character. - /// - Medkit = 0xF0FA, - - /// - /// The Font Awesome "medrt" icon unicode character. - /// - Medrt = 0xF3C8, - - /// - /// The Font Awesome "meetup" icon unicode character. - /// - Meetup = 0xF2E, - - /// - /// The Font Awesome "megaport" icon unicode character. - /// - Megaport = 0xF5A3, - - /// - /// The Font Awesome "meh" icon unicode character. - /// - Meh = 0xF11A, - - /// - /// The Font Awesome "meh-blank" icon unicode character. - /// - MehBlank = 0xF5A4, - - /// - /// The Font Awesome "meh-rolling-eyes" icon unicode character. - /// - MehRollingEyes = 0xF5A5, - - /// - /// The Font Awesome "memory" icon unicode character. - /// - Memory = 0xF538, - - /// - /// The Font Awesome "mendeley" icon unicode character. - /// - Mendeley = 0xF7B3, - - /// - /// The Font Awesome "menorah" icon unicode character. - /// - Menorah = 0xF676, - - /// - /// The Font Awesome "mercury" icon unicode character. - /// - Mercury = 0xF223, - - /// - /// The Font Awesome "meteor" icon unicode character. - /// - Meteor = 0xF753, - - /// - /// The Font Awesome "microblog" icon unicode character. - /// - Microblog = 0xF91A, - - /// - /// The Font Awesome "microchip" icon unicode character. - /// - Microchip = 0xF2DB, - - /// - /// The Font Awesome "microphone" icon unicode character. - /// - Microphone = 0xF13, - - /// - /// The Font Awesome "microphone-alt" icon unicode character. - /// - MicrophoneAlt = 0xF3C9, - - /// - /// The Font Awesome "microphone-alt-slash" icon unicode character. - /// - MicrophoneAltSlash = 0xF539, - - /// - /// The Font Awesome "microphone-slash" icon unicode character. - /// - MicrophoneSlash = 0xF131, - - /// - /// The Font Awesome "microscope" icon unicode character. - /// - Microscope = 0xF61, - - /// - /// The Font Awesome "microsoft" icon unicode character. - /// - Microsoft = 0xF3CA, - - /// - /// The Font Awesome "minus" icon unicode character. - /// - Minus = 0xF068, - - /// - /// The Font Awesome "minus-circle" icon unicode character. - /// - MinusCircle = 0xF056, - - /// - /// The Font Awesome "minus-square" icon unicode character. - /// - MinusSquare = 0xF146, - - /// - /// The Font Awesome "mitten" icon unicode character. - /// - Mitten = 0xF7B5, - - /// - /// The Font Awesome "mix" icon unicode character. - /// - Mix = 0xF3CB, - - /// - /// The Font Awesome "mixcloud" icon unicode character. - /// - Mixcloud = 0xF289, - - /// - /// The Font Awesome "mixer" icon unicode character. - /// - Mixer = 0xF956, - - /// - /// The Font Awesome "mizuni" icon unicode character. - /// - Mizuni = 0xF3CC, - - /// - /// The Font Awesome "mobile" icon unicode character. - /// - Mobile = 0xF10B, - - /// - /// The Font Awesome "mobile-alt" icon unicode character. - /// - MobileAlt = 0xF3CD, - - /// - /// The Font Awesome "modx" icon unicode character. - /// - Modx = 0xF285, - - /// - /// The Font Awesome "monero" icon unicode character. - /// - Monero = 0xF3D, - - /// - /// The Font Awesome "money-bill" icon unicode character. - /// - MoneyBill = 0xF0D6, - - /// - /// The Font Awesome "money-bill-alt" icon unicode character. - /// - MoneyBillAlt = 0xF3D1, - - /// - /// The Font Awesome "money-bill-wave" icon unicode character. - /// - MoneyBillWave = 0xF53A, - - /// - /// The Font Awesome "money-bill-wave-alt" icon unicode character. - /// - MoneyBillWaveAlt = 0xF53B, - - /// - /// The Font Awesome "money-check" icon unicode character. - /// - MoneyCheck = 0xF53C, - - /// - /// The Font Awesome "money-check-alt" icon unicode character. - /// - MoneyCheckAlt = 0xF53D, - - /// - /// The Font Awesome "monument" icon unicode character. - /// - Monument = 0xF5A6, - - /// - /// The Font Awesome "moon" icon unicode character. - /// - Moon = 0xF186, - - /// - /// The Font Awesome "mortar-pestle" icon unicode character. - /// - MortarPestle = 0xF5A7, - - /// - /// The Font Awesome "mosque" icon unicode character. - /// - Mosque = 0xF678, - - /// - /// The Font Awesome "motorcycle" icon unicode character. - /// - Motorcycle = 0xF21C, - - /// - /// The Font Awesome "mountain" icon unicode character. - /// - Mountain = 0xF6FC, - - /// - /// The Font Awesome "mouse" icon unicode character. - /// - Mouse = 0xF8CC, - - /// - /// The Font Awesome "mouse-pointer" icon unicode character. - /// - MousePointer = 0xF245, - - /// - /// The Font Awesome "mug-hot" icon unicode character. - /// - MugHot = 0xF7B6, - - /// - /// The Font Awesome "music" icon unicode character. - /// - Music = 0xF001, - - /// - /// The Font Awesome "napster" icon unicode character. - /// - Napster = 0xF3D2, - - /// - /// The Font Awesome "neos" icon unicode character. - /// - Neos = 0xF612, - - /// - /// The Font Awesome "network-wired" icon unicode character. - /// - NetworkWired = 0xF6FF, - - /// - /// The Font Awesome "neuter" icon unicode character. - /// - Neuter = 0xF22C, - - /// - /// The Font Awesome "newspaper" icon unicode character. - /// - Newspaper = 0xF1EA, - - /// - /// The Font Awesome "nimblr" icon unicode character. - /// - Nimblr = 0xF5A8, - - /// - /// The Font Awesome "node" icon unicode character. - /// - Node = 0xF419, - - /// - /// The Font Awesome "node-js" icon unicode character. - /// - NodeJs = 0xF3D3, - - /// - /// The Font Awesome "not-equal" icon unicode character. - /// - NotEqual = 0xF53E, - - /// - /// The Font Awesome "notes-medical" icon unicode character. - /// - NotesMedical = 0xF481, - - /// - /// The Font Awesome "npm" icon unicode character. - /// - Npm = 0xF3D4, - - /// - /// The Font Awesome "ns8" icon unicode character. - /// - Ns8 = 0xF3D5, - - /// - /// The Font Awesome "nutritionix" icon unicode character. - /// - Nutritionix = 0xF3D6, - - /// - /// The Font Awesome "object-group" icon unicode character. - /// - ObjectGroup = 0xF247, - - /// - /// The Font Awesome "object-ungroup" icon unicode character. - /// - ObjectUngroup = 0xF248, - - /// - /// The Font Awesome "odnoklassniki" icon unicode character. - /// - Odnoklassniki = 0xF263, - - /// - /// The Font Awesome "odnoklassniki-square" icon unicode character. - /// - OdnoklassnikiSquare = 0xF264, - - /// - /// The Font Awesome "oil-can" icon unicode character. - /// - OilCan = 0xF613, - - /// - /// The Font Awesome "old-republic" icon unicode character. - /// - OldRepublic = 0xF51, - - /// - /// The Font Awesome "om" icon unicode character. - /// - Om = 0xF679, - - /// - /// The Font Awesome "opencart" icon unicode character. - /// - Opencart = 0xF23D, - - /// - /// The Font Awesome "openid" icon unicode character. - /// - Openid = 0xF19B, - - /// - /// The Font Awesome "opera" icon unicode character. - /// - Opera = 0xF26A, - - /// - /// The Font Awesome "optin-monster" icon unicode character. - /// - OptinMonster = 0xF23C, - - /// - /// The Font Awesome "orcid" icon unicode character. - /// - Orcid = 0xF8D2, - - /// - /// The Font Awesome "osi" icon unicode character. - /// - Osi = 0xF41A, - - /// - /// The Font Awesome "otter" icon unicode character. - /// - Otter = 0xF7, - - /// - /// The Font Awesome "outdent" icon unicode character. - /// - Outdent = 0xF03B, - - /// - /// The Font Awesome "page4" icon unicode character. - /// - Page4 = 0xF3D7, - - /// - /// The Font Awesome "pagelines" icon unicode character. - /// - Pagelines = 0xF18C, - - /// - /// The Font Awesome "pager" icon unicode character. - /// - Pager = 0xF815, - - /// - /// The Font Awesome "paint-brush" icon unicode character. - /// - PaintBrush = 0xF1FC, - - /// - /// The Font Awesome "paint-roller" icon unicode character. - /// - PaintRoller = 0xF5AA, - - /// - /// The Font Awesome "palette" icon unicode character. - /// - Palette = 0xF53F, - - /// - /// The Font Awesome "palfed" icon unicode character. - /// - Palfed = 0xF3D8, - - /// - /// The Font Awesome "pallet" icon unicode character. - /// - Pallet = 0xF482, - - /// - /// The Font Awesome "paperclip" icon unicode character. - /// - Paperclip = 0xF0C6, - - /// - /// The Font Awesome "paper-plane" icon unicode character. - /// - PaperPlane = 0xF1D8, - - /// - /// The Font Awesome "parachute-box" icon unicode character. - /// - ParachuteBox = 0xF4CD, - - /// - /// The Font Awesome "paragraph" icon unicode character. - /// - Paragraph = 0xF1DD, - - /// - /// The Font Awesome "parking" icon unicode character. - /// - Parking = 0xF54, - - /// - /// The Font Awesome "passport" icon unicode character. - /// - Passport = 0xF5AB, - - /// - /// The Font Awesome "pastafarianism" icon unicode character. - /// - Pastafarianism = 0xF67B, - - /// - /// The Font Awesome "paste" icon unicode character. - /// - Paste = 0xF0EA, - - /// - /// The Font Awesome "patreon" icon unicode character. - /// - Patreon = 0xF3D9, - - /// - /// The Font Awesome "pause" icon unicode character. - /// - Pause = 0xF04C, - - /// - /// The Font Awesome "pause-circle" icon unicode character. - /// - PauseCircle = 0xF28B, - - /// - /// The Font Awesome "paw" icon unicode character. - /// - Paw = 0xF1B, - - /// - /// The Font Awesome "paypal" icon unicode character. - /// - Paypal = 0xF1ED, - - /// - /// The Font Awesome "peace" icon unicode character. - /// - Peace = 0xF67C, - - /// - /// The Font Awesome "pen" icon unicode character. - /// - Pen = 0xF304, - - /// - /// The Font Awesome "pen-alt" icon unicode character. - /// - PenAlt = 0xF305, - - /// - /// The Font Awesome "pencil-alt" icon unicode character. - /// - PencilAlt = 0xF303, - - /// - /// The Font Awesome "pencil-ruler" icon unicode character. - /// - PencilRuler = 0xF5AE, - - /// - /// The Font Awesome "pen-fancy" icon unicode character. - /// - PenFancy = 0xF5AC, - - /// - /// The Font Awesome "pen-nib" icon unicode character. - /// - PenNib = 0xF5AD, - - /// - /// The Font Awesome "penny-arcade" icon unicode character. - /// - PennyArcade = 0xF704, - - /// - /// The Font Awesome "pen-square" icon unicode character. - /// - PenSquare = 0xF14B, - - /// - /// The Font Awesome "people-carry" icon unicode character. - /// - PeopleCarry = 0xF4CE, - - /// - /// The Font Awesome "pepper-hot" icon unicode character. - /// - PepperHot = 0xF816, - - /// - /// The Font Awesome "percent" icon unicode character. - /// - Percent = 0xF295, - - /// - /// The Font Awesome "percentage" icon unicode character. - /// - Percentage = 0xF541, - - /// - /// The Font Awesome "periscope" icon unicode character. - /// - Periscope = 0xF3DA, - - /// - /// The Font Awesome "person-booth" icon unicode character. - /// - PersonBooth = 0xF756, - - /// - /// The Font Awesome "phabricator" icon unicode character. - /// - Phabricator = 0xF3DB, - - /// - /// The Font Awesome "phoenix-framework" icon unicode character. - /// - PhoenixFramework = 0xF3DC, - - /// - /// The Font Awesome "phoenix-squadron" icon unicode character. - /// - PhoenixSquadron = 0xF511, - - /// - /// The Font Awesome "phone" icon unicode character. - /// - Phone = 0xF095, - - /// - /// The Font Awesome "phone-alt" icon unicode character. - /// - PhoneAlt = 0xF879, - - /// - /// The Font Awesome "phone-slash" icon unicode character. - /// - PhoneSlash = 0xF3DD, - - /// - /// The Font Awesome "phone-square" icon unicode character. - /// - PhoneSquare = 0xF098, - - /// - /// The Font Awesome "phone-square-alt" icon unicode character. - /// - PhoneSquareAlt = 0xF87B, - - /// - /// The Font Awesome "phone-volume" icon unicode character. - /// - PhoneVolume = 0xF2A, - - /// - /// The Font Awesome "photo-video" icon unicode character. - /// - PhotoVideo = 0xF87C, - - /// - /// The Font Awesome "php" icon unicode character. - /// - Php = 0xF457, - - /// - /// The Font Awesome "pied-piper" icon unicode character. - /// - PiedPiper = 0xF2AE, - - /// - /// The Font Awesome "pied-piper-alt" icon unicode character. - /// - PiedPiperAlt = 0xF1A8, - - /// - /// The Font Awesome "pied-piper-hat" icon unicode character. - /// - PiedPiperHat = 0xF4E5, - - /// - /// The Font Awesome "pied-piper-pp" icon unicode character. - /// - PiedPiperPp = 0xF1A7, - - /// - /// The Font Awesome "pied-piper-square" icon unicode character. - /// - PiedPiperSquare = 0xF91E, - - /// - /// The Font Awesome "piggy-bank" icon unicode character. - /// - PiggyBank = 0xF4D3, - - /// - /// The Font Awesome "pills" icon unicode character. - /// - Pills = 0xF484, - - /// - /// The Font Awesome "pinterest" icon unicode character. - /// - Pinterest = 0xF0D2, - - /// - /// The Font Awesome "pinterest-p" icon unicode character. - /// - PinterestP = 0xF231, - - /// - /// The Font Awesome "pinterest-square" icon unicode character. - /// - PinterestSquare = 0xF0D3, - - /// - /// The Font Awesome "pizza-slice" icon unicode character. - /// - PizzaSlice = 0xF818, - - /// - /// The Font Awesome "place-of-worship" icon unicode character. - /// - PlaceOfWorship = 0xF67F, - - /// - /// The Font Awesome "plane" icon unicode character. - /// - Plane = 0xF072, - - /// - /// The Font Awesome "plane-arrival" icon unicode character. - /// - PlaneArrival = 0xF5AF, - - /// - /// The Font Awesome "plane-departure" icon unicode character. - /// - PlaneDeparture = 0xF5B, - - /// - /// The Font Awesome "play" icon unicode character. - /// - Play = 0xF04B, - - /// - /// The Font Awesome "play-circle" icon unicode character. - /// - PlayCircle = 0xF144, - - /// - /// The Font Awesome "playstation" icon unicode character. - /// - Playstation = 0xF3DF, - - /// - /// The Font Awesome "plug" icon unicode character. - /// - Plug = 0xF1E6, - - /// - /// The Font Awesome "plus" icon unicode character. - /// - Plus = 0xF067, - - /// - /// The Font Awesome "plus-circle" icon unicode character. - /// - PlusCircle = 0xF055, - - /// - /// The Font Awesome "plus-square" icon unicode character. - /// - PlusSquare = 0xF0FE, - - /// - /// The Font Awesome "podcast" icon unicode character. - /// - Podcast = 0xF2CE, - - /// - /// The Font Awesome "poll" icon unicode character. - /// - Poll = 0xF681, - - /// - /// The Font Awesome "poll-h" icon unicode character. - /// - PollH = 0xF682, - - /// - /// The Font Awesome "poo" icon unicode character. - /// - Poo = 0xF2FE, - - /// - /// The Font Awesome "poop" icon unicode character. - /// - Poop = 0xF619, - - /// - /// The Font Awesome "poo-storm" icon unicode character. - /// - PooStorm = 0xF75A, - - /// - /// The Font Awesome "portrait" icon unicode character. - /// - Portrait = 0xF3E, - - /// - /// The Font Awesome "pound-sign" icon unicode character. - /// - PoundSign = 0xF154, - - /// - /// The Font Awesome "power-off" icon unicode character. - /// - PowerOff = 0xF011, - - /// - /// The Font Awesome "pray" icon unicode character. - /// - Pray = 0xF683, - - /// - /// The Font Awesome "praying-hands" icon unicode character. - /// - PrayingHands = 0xF684, - - /// - /// The Font Awesome "prescription" icon unicode character. - /// - Prescription = 0xF5B1, - - /// - /// The Font Awesome "prescription-bottle" icon unicode character. - /// - PrescriptionBottle = 0xF485, - - /// - /// The Font Awesome "prescription-bottle-alt" icon unicode character. - /// - PrescriptionBottleAlt = 0xF486, - - /// - /// The Font Awesome "print" icon unicode character. - /// - Print = 0xF02F, - - /// - /// The Font Awesome "procedures" icon unicode character. - /// - Procedures = 0xF487, - - /// - /// The Font Awesome "product-hunt" icon unicode character. - /// - ProductHunt = 0xF288, - - /// - /// The Font Awesome "project-diagram" icon unicode character. - /// - ProjectDiagram = 0xF542, - - /// - /// The Font Awesome "pushed" icon unicode character. - /// - Pushed = 0xF3E1, - - /// - /// The Font Awesome "puzzle-piece" icon unicode character. - /// - PuzzlePiece = 0xF12E, - - /// - /// The Font Awesome "python" icon unicode character. - /// - Python = 0xF3E2, - - /// - /// The Font Awesome "qq" icon unicode character. - /// - Qq = 0xF1D6, - - /// - /// The Font Awesome "qrcode" icon unicode character. - /// - Qrcode = 0xF029, - - /// - /// The Font Awesome "question" icon unicode character. - /// - Question = 0xF128, - - /// - /// The Font Awesome "question-circle" icon unicode character. - /// - QuestionCircle = 0xF059, - - /// - /// The Font Awesome "quidditch" icon unicode character. - /// - Quidditch = 0xF458, - - /// - /// The Font Awesome "quinscape" icon unicode character. - /// - Quinscape = 0xF459, - - /// - /// The Font Awesome "quora" icon unicode character. - /// - Quora = 0xF2C4, - - /// - /// The Font Awesome "quote-left" icon unicode character. - /// - QuoteLeft = 0xF10D, - - /// - /// The Font Awesome "quote-right" icon unicode character. - /// - QuoteRight = 0xF10E, - - /// - /// The Font Awesome "quran" icon unicode character. - /// - Quran = 0xF687, - - /// - /// The Font Awesome "radiation" icon unicode character. - /// - Radiation = 0xF7B9, - - /// - /// The Font Awesome "radiation-alt" icon unicode character. - /// - RadiationAlt = 0xF7BA, - - /// - /// The Font Awesome "rainbow" icon unicode character. - /// - Rainbow = 0xF75B, - - /// - /// The Font Awesome "random" icon unicode character. - /// - Random = 0xF074, - - /// - /// The Font Awesome "raspberry-pi" icon unicode character. - /// - RaspberryPi = 0xF7BB, - - /// - /// The Font Awesome "ravelry" icon unicode character. - /// - Ravelry = 0xF2D9, - - /// - /// The Font Awesome "react" icon unicode character. - /// - React = 0xF41B, - - /// - /// The Font Awesome "reacteurope" icon unicode character. - /// - Reacteurope = 0xF75D, - - /// - /// The Font Awesome "readme" icon unicode character. - /// - Readme = 0xF4D5, - - /// - /// The Font Awesome "rebel" icon unicode character. - /// - Rebel = 0xF1D, - - /// - /// The Font Awesome "receipt" icon unicode character. - /// - Receipt = 0xF543, - - /// - /// The Font Awesome "record-vinyl" icon unicode character. - /// - RecordVinyl = 0xF8D9, - - /// - /// The Font Awesome "recycle" icon unicode character. - /// - Recycle = 0xF1B8, - - /// - /// The Font Awesome "reddit" icon unicode character. - /// - Reddit = 0xF1A1, - - /// - /// The Font Awesome "reddit-alien" icon unicode character. - /// - RedditAlien = 0xF281, - - /// - /// The Font Awesome "reddit-square" icon unicode character. - /// - RedditSquare = 0xF1A2, - - /// - /// The Font Awesome "redhat" icon unicode character. - /// - Redhat = 0xF7BC, - - /// - /// The Font Awesome "redo" icon unicode character. - /// - Redo = 0xF01E, - - /// - /// The Font Awesome "redo-alt" icon unicode character. - /// - RedoAlt = 0xF2F9, - - /// - /// The Font Awesome "red-river" icon unicode character. - /// - RedRiver = 0xF3E3, - - /// - /// The Font Awesome "registered" icon unicode character. - /// - Registered = 0xF25D, - - /// - /// The Font Awesome "remove-format" icon unicode character. - /// - RemoveFormat = 0xF87D, - - /// - /// The Font Awesome "renren" icon unicode character. - /// - Renren = 0xF18B, - - /// - /// The Font Awesome "reply" icon unicode character. - /// - Reply = 0xF3E5, - - /// - /// The Font Awesome "reply-all" icon unicode character. - /// - ReplyAll = 0xF122, - - /// - /// The Font Awesome "replyd" icon unicode character. - /// - Replyd = 0xF3E6, - - /// - /// The Font Awesome "republican" icon unicode character. - /// - Republican = 0xF75E, - - /// - /// The Font Awesome "researchgate" icon unicode character. - /// - Researchgate = 0xF4F8, - - /// - /// The Font Awesome "resolving" icon unicode character. - /// - Resolving = 0xF3E7, - - /// - /// The Font Awesome "restroom" icon unicode character. - /// - Restroom = 0xF7BD, - - /// - /// The Font Awesome "retweet" icon unicode character. - /// - Retweet = 0xF079, - - /// - /// The Font Awesome "rev" icon unicode character. - /// - Rev = 0xF5B2, - - /// - /// The Font Awesome "ribbon" icon unicode character. - /// - Ribbon = 0xF4D6, - - /// - /// The Font Awesome "ring" icon unicode character. - /// - Ring = 0xF70B, - - /// - /// The Font Awesome "road" icon unicode character. - /// - Road = 0xF018, - - /// - /// The Font Awesome "robot" icon unicode character. - /// - Robot = 0xF544, - - /// - /// The Font Awesome "rocket" icon unicode character. - /// - Rocket = 0xF135, - - /// - /// The Font Awesome "rocketchat" icon unicode character. - /// - Rocketchat = 0xF3E8, - - /// - /// The Font Awesome "rockrms" icon unicode character. - /// - Rockrms = 0xF3E9, - - /// - /// The Font Awesome "route" icon unicode character. - /// - Route = 0xF4D7, - - /// - /// The Font Awesome "r-project" icon unicode character. - /// - RProject = 0xF4F7, - - /// - /// The Font Awesome "rss" icon unicode character. - /// - Rss = 0xF09E, - - /// - /// The Font Awesome "rss-square" icon unicode character. - /// - RssSquare = 0xF143, - - /// - /// The Font Awesome "ruble-sign" icon unicode character. - /// - RubleSign = 0xF158, - - /// - /// The Font Awesome "ruler" icon unicode character. - /// - Ruler = 0xF545, - - /// - /// The Font Awesome "ruler-combined" icon unicode character. - /// - RulerCombined = 0xF546, - - /// - /// The Font Awesome "ruler-horizontal" icon unicode character. - /// - RulerHorizontal = 0xF547, - - /// - /// The Font Awesome "ruler-vertical" icon unicode character. - /// - RulerVertical = 0xF548, - - /// - /// The Font Awesome "running" icon unicode character. - /// - Running = 0xF70C, - - /// - /// The Font Awesome "rupee-sign" icon unicode character. - /// - RupeeSign = 0xF156, - - /// - /// The Font Awesome "sad-cry" icon unicode character. - /// - SadCry = 0xF5B3, - - /// - /// The Font Awesome "sad-tear" icon unicode character. - /// - SadTear = 0xF5B4, - - /// - /// The Font Awesome "safari" icon unicode character. - /// - Safari = 0xF267, - - /// - /// The Font Awesome "salesforce" icon unicode character. - /// - Salesforce = 0xF83B, - - /// - /// The Font Awesome "sass" icon unicode character. - /// - Sass = 0xF41E, - - /// - /// The Font Awesome "satellite" icon unicode character. - /// - Satellite = 0xF7BF, - - /// - /// The Font Awesome "satellite-dish" icon unicode character. - /// - SatelliteDish = 0xF7C, - - /// - /// The Font Awesome "save" icon unicode character. - /// - Save = 0xF0C7, - - /// - /// The Font Awesome "schlix" icon unicode character. - /// - Schlix = 0xF3EA, - - /// - /// The Font Awesome "school" icon unicode character. - /// - School = 0xF549, - - /// - /// The Font Awesome "screwdriver" icon unicode character. - /// - Screwdriver = 0xF54A, - - /// - /// The Font Awesome "scribd" icon unicode character. - /// - Scribd = 0xF28A, - - /// - /// The Font Awesome "scroll" icon unicode character. - /// - Scroll = 0xF70E, - - /// - /// The Font Awesome "sd-card" icon unicode character. - /// - SdCard = 0xF7C2, - - /// - /// The Font Awesome "search" icon unicode character. - /// - Search = 0xF002, - - /// - /// The Font Awesome "search-dollar" icon unicode character. - /// - SearchDollar = 0xF688, - - /// - /// The Font Awesome "searchengin" icon unicode character. - /// - Searchengin = 0xF3EB, - - /// - /// The Font Awesome "search-location" icon unicode character. - /// - SearchLocation = 0xF689, - - /// - /// The Font Awesome "search-minus" icon unicode character. - /// - SearchMinus = 0xF01, - - /// - /// The Font Awesome "search-plus" icon unicode character. - /// - SearchPlus = 0xF00E, - - /// - /// The Font Awesome "seedling" icon unicode character. - /// - Seedling = 0xF4D8, - - /// - /// The Font Awesome "sellcast" icon unicode character. - /// - Sellcast = 0xF2DA, - - /// - /// The Font Awesome "sellsy" icon unicode character. - /// - Sellsy = 0xF213, - - /// - /// The Font Awesome "server" icon unicode character. - /// - Server = 0xF233, - - /// - /// The Font Awesome "servicestack" icon unicode character. - /// - Servicestack = 0xF3EC, - - /// - /// The Font Awesome "shapes" icon unicode character. - /// - Shapes = 0xF61F, - - /// - /// The Font Awesome "share" icon unicode character. - /// - Share = 0xF064, - - /// - /// The Font Awesome "share-alt" icon unicode character. - /// - ShareAlt = 0xF1E, - - /// - /// The Font Awesome "share-alt-square" icon unicode character. - /// - ShareAltSquare = 0xF1E1, - - /// - /// The Font Awesome "share-square" icon unicode character. - /// - ShareSquare = 0xF14D, - - /// - /// The Font Awesome "shekel-sign" icon unicode character. - /// - ShekelSign = 0xF20B, - - /// - /// The Font Awesome "shield-alt" icon unicode character. - /// - ShieldAlt = 0xF3ED, - - /// - /// The Font Awesome "ship" icon unicode character. - /// - Ship = 0xF21A, - - /// - /// The Font Awesome "shipping-fast" icon unicode character. - /// - ShippingFast = 0xF48B, - - /// - /// The Font Awesome "shirtsinbulk" icon unicode character. - /// - Shirtsinbulk = 0xF214, - - /// - /// The Font Awesome "shoe-prints" icon unicode character. - /// - ShoePrints = 0xF54B, - - /// - /// The Font Awesome "shopify" icon unicode character. - /// - Shopify = 0xF957, - - /// - /// The Font Awesome "shopping-bag" icon unicode character. - /// - ShoppingBag = 0xF29, - - /// - /// The Font Awesome "shopping-basket" icon unicode character. - /// - ShoppingBasket = 0xF291, - - /// - /// The Font Awesome "shopping-cart" icon unicode character. - /// - ShoppingCart = 0xF07A, - - /// - /// The Font Awesome "shopware" icon unicode character. - /// - Shopware = 0xF5B5, - - /// - /// The Font Awesome "shower" icon unicode character. - /// - Shower = 0xF2CC, - - /// - /// The Font Awesome "shuttle-van" icon unicode character. - /// - ShuttleVan = 0xF5B6, - - /// - /// The Font Awesome "sign" icon unicode character. - /// - Sign = 0xF4D9, - - /// - /// The Font Awesome "signal" icon unicode character. - /// - Signal = 0xF012, - - /// - /// The Font Awesome "signature" icon unicode character. - /// - Signature = 0xF5B7, - - /// - /// The Font Awesome "sign-in-alt" icon unicode character. - /// - SignInAlt = 0xF2F6, - - /// - /// The Font Awesome "sign-language" icon unicode character. - /// - SignLanguage = 0xF2A7, - - /// - /// The Font Awesome "sign-out-alt" icon unicode character. - /// - SignOutAlt = 0xF2F5, - - /// - /// The Font Awesome "sim-card" icon unicode character. - /// - SimCard = 0xF7C4, - - /// - /// The Font Awesome "simplybuilt" icon unicode character. - /// - Simplybuilt = 0xF215, - - /// - /// The Font Awesome "sistrix" icon unicode character. - /// - Sistrix = 0xF3EE, - - /// - /// The Font Awesome "sitemap" icon unicode character. - /// - Sitemap = 0xF0E8, - - /// - /// The Font Awesome "sith" icon unicode character. - /// - Sith = 0xF512, - - /// - /// The Font Awesome "skating" icon unicode character. - /// - Skating = 0xF7C5, - - /// - /// The Font Awesome "sketch" icon unicode character. - /// - Sketch = 0xF7C6, - - /// - /// The Font Awesome "skiing" icon unicode character. - /// - Skiing = 0xF7C9, - - /// - /// The Font Awesome "skiing-nordic" icon unicode character. - /// - SkiingNordic = 0xF7CA, - - /// - /// The Font Awesome "skull" icon unicode character. - /// - Skull = 0xF54C, - - /// - /// The Font Awesome "skull-crossbones" icon unicode character. - /// - SkullCrossbones = 0xF714, - - /// - /// The Font Awesome "skyatlas" icon unicode character. - /// - Skyatlas = 0xF216, - - /// - /// The Font Awesome "skype" icon unicode character. - /// - Skype = 0xF17E, - - /// - /// The Font Awesome "slack" icon unicode character. - /// - Slack = 0xF198, - - /// - /// The Font Awesome "slack-hash" icon unicode character. - /// - SlackHash = 0xF3EF, - - /// - /// The Font Awesome "slash" icon unicode character. - /// - Slash = 0xF715, - - /// - /// The Font Awesome "sleigh" icon unicode character. - /// - Sleigh = 0xF7CC, - - /// - /// The Font Awesome "sliders-h" icon unicode character. - /// - SlidersH = 0xF1DE, - - /// - /// The Font Awesome "slideshare" icon unicode character. - /// - Slideshare = 0xF1E7, - - /// - /// The Font Awesome "smile" icon unicode character. - /// - Smile = 0xF118, - - /// - /// The Font Awesome "smile-beam" icon unicode character. - /// - SmileBeam = 0xF5B8, - - /// - /// The Font Awesome "smile-wink" icon unicode character. - /// - SmileWink = 0xF4DA, - - /// - /// The Font Awesome "smog" icon unicode character. - /// - Smog = 0xF75F, - - /// - /// The Font Awesome "smoking" icon unicode character. - /// - Smoking = 0xF48D, - - /// - /// The Font Awesome "smoking-ban" icon unicode character. - /// - SmokingBan = 0xF54D, - - /// - /// The Font Awesome "sms" icon unicode character. - /// - Sms = 0xF7CD, - - /// - /// The Font Awesome "snapchat" icon unicode character. - /// - Snapchat = 0xF2AB, - - /// - /// The Font Awesome "snapchat-ghost" icon unicode character. - /// - SnapchatGhost = 0xF2AC, - - /// - /// The Font Awesome "snapchat-square" icon unicode character. - /// - SnapchatSquare = 0xF2AD, - - /// - /// The Font Awesome "snowboarding" icon unicode character. - /// - Snowboarding = 0xF7CE, - - /// - /// The Font Awesome "snowflake" icon unicode character. - /// - Snowflake = 0xF2DC, - - /// - /// The Font Awesome "snowman" icon unicode character. - /// - Snowman = 0xF7D, - - /// - /// The Font Awesome "snowplow" icon unicode character. - /// - Snowplow = 0xF7D2, - - /// - /// The Font Awesome "socks" icon unicode character. - /// - Socks = 0xF696, - - /// - /// The Font Awesome "solar-panel" icon unicode character. - /// - SolarPanel = 0xF5BA, - - /// - /// The Font Awesome "sort" icon unicode character. - /// - Sort = 0xF0DC, - - /// - /// The Font Awesome "sort-alpha-down" icon unicode character. - /// - SortAlphaDown = 0xF15D, - - /// - /// The Font Awesome "sort-alpha-down-alt" icon unicode character. - /// - SortAlphaDownAlt = 0xF881, - - /// - /// The Font Awesome "sort-alpha-up" icon unicode character. - /// - SortAlphaUp = 0xF15E, - - /// - /// The Font Awesome "sort-alpha-up-alt" icon unicode character. - /// - SortAlphaUpAlt = 0xF882, - - /// - /// The Font Awesome "sort-amount-down" icon unicode character. - /// - SortAmountDown = 0xF16, - - /// - /// The Font Awesome "sort-amount-down-alt" icon unicode character. - /// - SortAmountDownAlt = 0xF884, - - /// - /// The Font Awesome "sort-amount-up" icon unicode character. - /// - SortAmountUp = 0xF161, - - /// - /// The Font Awesome "sort-amount-up-alt" icon unicode character. - /// - SortAmountUpAlt = 0xF885, - - /// - /// The Font Awesome "sort-down" icon unicode character. - /// - SortDown = 0xF0DD, - - /// - /// The Font Awesome "sort-numeric-down" icon unicode character. - /// - SortNumericDown = 0xF162, - - /// - /// The Font Awesome "sort-numeric-down-alt" icon unicode character. - /// - SortNumericDownAlt = 0xF886, - - /// - /// The Font Awesome "sort-numeric-up" icon unicode character. - /// - SortNumericUp = 0xF163, - - /// - /// The Font Awesome "sort-numeric-up-alt" icon unicode character. - /// - SortNumericUpAlt = 0xF887, - - /// - /// The Font Awesome "sort-up" icon unicode character. - /// - SortUp = 0xF0DE, - - /// - /// The Font Awesome "soundcloud" icon unicode character. - /// - Soundcloud = 0xF1BE, - - /// - /// The Font Awesome "sourcetree" icon unicode character. - /// - Sourcetree = 0xF7D3, - - /// - /// The Font Awesome "spa" icon unicode character. - /// - Spa = 0xF5BB, - - /// - /// The Font Awesome "space-shuttle" icon unicode character. - /// - SpaceShuttle = 0xF197, - - /// - /// The Font Awesome "speakap" icon unicode character. - /// - Speakap = 0xF3F3, - - /// - /// The Font Awesome "speaker-deck" icon unicode character. - /// - SpeakerDeck = 0xF83C, - - /// - /// The Font Awesome "spell-check" icon unicode character. - /// - SpellCheck = 0xF891, - - /// - /// The Font Awesome "spider" icon unicode character. - /// - Spider = 0xF717, - - /// - /// The Font Awesome "spinner" icon unicode character. - /// - Spinner = 0xF11, - - /// - /// The Font Awesome "splotch" icon unicode character. - /// - Splotch = 0xF5BC, - - /// - /// The Font Awesome "spotify" icon unicode character. - /// - Spotify = 0xF1BC, - - /// - /// The Font Awesome "spray-can" icon unicode character. - /// - SprayCan = 0xF5BD, - - /// - /// The Font Awesome "square" icon unicode character. - /// - Square = 0xF0C8, - - /// - /// The Font Awesome "square-full" icon unicode character. - /// - SquareFull = 0xF45C, - - /// - /// The Font Awesome "square-root-alt" icon unicode character. - /// - SquareRootAlt = 0xF698, - - /// - /// The Font Awesome "squarespace" icon unicode character. - /// - Squarespace = 0xF5BE, - - /// - /// The Font Awesome "stack-exchange" icon unicode character. - /// - StackExchange = 0xF18D, - - /// - /// The Font Awesome "stack-overflow" icon unicode character. - /// - StackOverflow = 0xF16C, - - /// - /// The Font Awesome "stackpath" icon unicode character. - /// - Stackpath = 0xF842, - - /// - /// The Font Awesome "stamp" icon unicode character. - /// - Stamp = 0xF5BF, - - /// - /// The Font Awesome "star" icon unicode character. - /// - Star = 0xF005, - - /// - /// The Font Awesome "star-and-crescent" icon unicode character. - /// - StarAndCrescent = 0xF699, - - /// - /// The Font Awesome "star-half" icon unicode character. - /// - StarHalf = 0xF089, - - /// - /// The Font Awesome "star-half-alt" icon unicode character. - /// - StarHalfAlt = 0xF5C, - - /// - /// The Font Awesome "star-of-david" icon unicode character. - /// - StarOfDavid = 0xF69A, - - /// - /// The Font Awesome "star-of-life" icon unicode character. - /// - StarOfLife = 0xF621, - - /// - /// The Font Awesome "staylinked" icon unicode character. - /// - Staylinked = 0xF3F5, - - /// - /// The Font Awesome "steam" icon unicode character. - /// - Steam = 0xF1B6, - - /// - /// The Font Awesome "steam-square" icon unicode character. - /// - SteamSquare = 0xF1B7, - - /// - /// The Font Awesome "steam-symbol" icon unicode character. - /// - SteamSymbol = 0xF3F6, - - /// - /// The Font Awesome "step-backward" icon unicode character. - /// - StepBackward = 0xF048, - - /// - /// The Font Awesome "step-forward" icon unicode character. - /// - StepForward = 0xF051, - - /// - /// The Font Awesome "stethoscope" icon unicode character. - /// - Stethoscope = 0xF0F1, - - /// - /// The Font Awesome "sticker-mule" icon unicode character. - /// - StickerMule = 0xF3F7, - - /// - /// The Font Awesome "sticky-note" icon unicode character. - /// - StickyNote = 0xF249, - - /// - /// The Font Awesome "stop" icon unicode character. - /// - Stop = 0xF04D, - - /// - /// The Font Awesome "stop-circle" icon unicode character. - /// - StopCircle = 0xF28D, - - /// - /// The Font Awesome "stopwatch" icon unicode character. - /// - Stopwatch = 0xF2F2, - - /// - /// The Font Awesome "store" icon unicode character. - /// - Store = 0xF54E, - - /// - /// The Font Awesome "store-alt" icon unicode character. - /// - StoreAlt = 0xF54F, - - /// - /// The Font Awesome "strava" icon unicode character. - /// - Strava = 0xF428, - - /// - /// The Font Awesome "stream" icon unicode character. - /// - Stream = 0xF55, - - /// - /// The Font Awesome "street-view" icon unicode character. - /// - StreetView = 0xF21D, - - /// - /// The Font Awesome "strikethrough" icon unicode character. - /// - Strikethrough = 0xF0CC, - - /// - /// The Font Awesome "stripe" icon unicode character. - /// - Stripe = 0xF429, - - /// - /// The Font Awesome "stripe-s" icon unicode character. - /// - StripeS = 0xF42A, - - /// - /// The Font Awesome "stroopwafel" icon unicode character. - /// - Stroopwafel = 0xF551, - - /// - /// The Font Awesome "studiovinari" icon unicode character. - /// - Studiovinari = 0xF3F8, - - /// - /// The Font Awesome "stumbleupon" icon unicode character. - /// - Stumbleupon = 0xF1A4, - - /// - /// The Font Awesome "stumbleupon-circle" icon unicode character. - /// - StumbleuponCircle = 0xF1A3, - - /// - /// The Font Awesome "subscript" icon unicode character. - /// - Subscript = 0xF12C, - - /// - /// The Font Awesome "subway" icon unicode character. - /// - Subway = 0xF239, - - /// - /// The Font Awesome "suitcase" icon unicode character. - /// - Suitcase = 0xF0F2, - - /// - /// The Font Awesome "suitcase-rolling" icon unicode character. - /// - SuitcaseRolling = 0xF5C1, - - /// - /// The Font Awesome "sun" icon unicode character. - /// - Sun = 0xF185, - - /// - /// The Font Awesome "superpowers" icon unicode character. - /// - Superpowers = 0xF2DD, - - /// - /// The Font Awesome "superscript" icon unicode character. - /// - Superscript = 0xF12B, - - /// - /// The Font Awesome "supple" icon unicode character. - /// - Supple = 0xF3F9, - - /// - /// The Font Awesome "surprise" icon unicode character. - /// - Surprise = 0xF5C2, - - /// - /// The Font Awesome "suse" icon unicode character. - /// - Suse = 0xF7D6, - - /// - /// The Font Awesome "swatchbook" icon unicode character. - /// - Swatchbook = 0xF5C3, - - /// - /// The Font Awesome "swift" icon unicode character. - /// - Swift = 0xF8E1, - - /// - /// The Font Awesome "swimmer" icon unicode character. - /// - Swimmer = 0xF5C4, - - /// - /// The Font Awesome "swimming-pool" icon unicode character. - /// - SwimmingPool = 0xF5C5, - - /// - /// The Font Awesome "symfony" icon unicode character. - /// - Symfony = 0xF83D, - - /// - /// The Font Awesome "synagogue" icon unicode character. - /// - Synagogue = 0xF69B, - - /// - /// The Font Awesome "sync" icon unicode character. - /// - Sync = 0xF021, - - /// - /// The Font Awesome "sync-alt" icon unicode character. - /// - SyncAlt = 0xF2F1, - - /// - /// The Font Awesome "syringe" icon unicode character. - /// - Syringe = 0xF48E, - - /// - /// The Font Awesome "table" icon unicode character. - /// - Table = 0xF0CE, - - /// - /// The Font Awesome "tablet" icon unicode character. - /// - Tablet = 0xF10A, - - /// - /// The Font Awesome "tablet-alt" icon unicode character. - /// - TabletAlt = 0xF3FA, - - /// - /// The Font Awesome "table-tennis" icon unicode character. - /// - TableTennis = 0xF45D, - - /// - /// The Font Awesome "tablets" icon unicode character. - /// - Tablets = 0xF49, - - /// - /// The Font Awesome "tachometer-alt" icon unicode character. - /// - TachometerAlt = 0xF3FD, - - /// - /// The Font Awesome "tag" icon unicode character. - /// - Tag = 0xF02B, - - /// - /// The Font Awesome "tags" icon unicode character. - /// - Tags = 0xF02C, - - /// - /// The Font Awesome "tape" icon unicode character. - /// - Tape = 0xF4DB, - - /// - /// The Font Awesome "tasks" icon unicode character. - /// - Tasks = 0xF0AE, - - /// - /// The Font Awesome "taxi" icon unicode character. - /// - Taxi = 0xF1BA, - - /// - /// The Font Awesome "teamspeak" icon unicode character. - /// - Teamspeak = 0xF4F9, - - /// - /// The Font Awesome "teeth" icon unicode character. - /// - Teeth = 0xF62E, - - /// - /// The Font Awesome "teeth-open" icon unicode character. - /// - TeethOpen = 0xF62F, - - /// - /// The Font Awesome "telegram" icon unicode character. - /// - Telegram = 0xF2C6, - - /// - /// The Font Awesome "telegram-plane" icon unicode character. - /// - TelegramPlane = 0xF3FE, - - /// - /// The Font Awesome "temperature-high" icon unicode character. - /// - TemperatureHigh = 0xF769, - - /// - /// The Font Awesome "temperature-low" icon unicode character. - /// - TemperatureLow = 0xF76B, - - /// - /// The Font Awesome "tencent-weibo" icon unicode character. - /// - TencentWeibo = 0xF1D5, - - /// - /// The Font Awesome "tenge" icon unicode character. - /// - Tenge = 0xF7D7, - - /// - /// The Font Awesome "terminal" icon unicode character. - /// - Terminal = 0xF12, - - /// - /// The Font Awesome "text-height" icon unicode character. - /// - TextHeight = 0xF034, - - /// - /// The Font Awesome "text-width" icon unicode character. - /// - TextWidth = 0xF035, - - /// - /// The Font Awesome "th" icon unicode character. - /// - Th = 0xF00A, - - /// - /// The Font Awesome "theater-masks" icon unicode character. - /// - TheaterMasks = 0xF63, - - /// - /// The Font Awesome "themeco" icon unicode character. - /// - Themeco = 0xF5C6, - - /// - /// The Font Awesome "themeisle" icon unicode character. - /// - Themeisle = 0xF2B2, - - /// - /// The Font Awesome "the-red-yeti" icon unicode character. - /// - TheRedYeti = 0xF69D, - - /// - /// The Font Awesome "thermometer" icon unicode character. - /// - Thermometer = 0xF491, - - /// - /// The Font Awesome "thermometer-empty" icon unicode character. - /// - ThermometerEmpty = 0xF2CB, - - /// - /// The Font Awesome "thermometer-full" icon unicode character. - /// - ThermometerFull = 0xF2C7, - - /// - /// The Font Awesome "thermometer-half" icon unicode character. - /// - ThermometerHalf = 0xF2C9, - - /// - /// The Font Awesome "thermometer-quarter" icon unicode character. - /// - ThermometerQuarter = 0xF2CA, - - /// - /// The Font Awesome "thermometer-three-quarters" icon unicode character. - /// - ThermometerThreeQuarters = 0xF2C8, - - /// - /// The Font Awesome "think-peaks" icon unicode character. - /// - ThinkPeaks = 0xF731, - - /// - /// The Font Awesome "th-large" icon unicode character. - /// - ThLarge = 0xF009, - - /// - /// The Font Awesome "th-list" icon unicode character. - /// - ThList = 0xF00B, - - /// - /// The Font Awesome "thumbs-down" icon unicode character. - /// - ThumbsDown = 0xF165, - - /// - /// The Font Awesome "thumbs-up" icon unicode character. - /// - ThumbsUp = 0xF164, - - /// - /// The Font Awesome "thumbtack" icon unicode character. - /// - Thumbtack = 0xF08D, - - /// - /// The Font Awesome "ticket-alt" icon unicode character. - /// - TicketAlt = 0xF3FF, - - /// - /// The Font Awesome "times" icon unicode character. - /// - Times = 0xF00D, - - /// - /// The Font Awesome "times-circle" icon unicode character. - /// - TimesCircle = 0xF057, - - /// - /// The Font Awesome "tint" icon unicode character. - /// - Tint = 0xF043, - - /// - /// The Font Awesome "tint-slash" icon unicode character. - /// - TintSlash = 0xF5C7, - - /// - /// The Font Awesome "tired" icon unicode character. - /// - Tired = 0xF5C8, - - /// - /// The Font Awesome "toggle-off" icon unicode character. - /// - ToggleOff = 0xF204, - - /// - /// The Font Awesome "toggle-on" icon unicode character. - /// - ToggleOn = 0xF205, - - /// - /// The Font Awesome "toilet" icon unicode character. - /// - Toilet = 0xF7D8, - - /// - /// The Font Awesome "toilet-paper" icon unicode character. - /// - ToiletPaper = 0xF71E, - - /// - /// The Font Awesome "toolbox" icon unicode character. - /// - Toolbox = 0xF552, - - /// - /// The Font Awesome "tools" icon unicode character. - /// - Tools = 0xF7D9, - - /// - /// The Font Awesome "tooth" icon unicode character. - /// - Tooth = 0xF5C9, - - /// - /// The Font Awesome "torah" icon unicode character. - /// - Torah = 0xF6A, - - /// - /// The Font Awesome "torii-gate" icon unicode character. - /// - ToriiGate = 0xF6A1, - - /// - /// The Font Awesome "tractor" icon unicode character. - /// - Tractor = 0xF722, - - /// - /// The Font Awesome "trade-federation" icon unicode character. - /// - TradeFederation = 0xF513, - - /// - /// The Font Awesome "trademark" icon unicode character. - /// - Trademark = 0xF25C, - - /// - /// The Font Awesome "traffic-light" icon unicode character. - /// - TrafficLight = 0xF637, - - /// - /// The Font Awesome "trailer" icon unicode character. - /// - Trailer = 0xF941, - - /// - /// The Font Awesome "train" icon unicode character. - /// - Train = 0xF238, - - /// - /// The Font Awesome "tram" icon unicode character. - /// - Tram = 0xF7DA, - - /// - /// The Font Awesome "transgender" icon unicode character. - /// - Transgender = 0xF224, - - /// - /// The Font Awesome "transgender-alt" icon unicode character. - /// - TransgenderAlt = 0xF225, - - /// - /// The Font Awesome "trash" icon unicode character. - /// - Trash = 0xF1F8, - - /// - /// The Font Awesome "trash-alt" icon unicode character. - /// - TrashAlt = 0xF2ED, - - /// - /// The Font Awesome "trash-restore" icon unicode character. - /// - TrashRestore = 0xF829, - - /// - /// The Font Awesome "trash-restore-alt" icon unicode character. - /// - TrashRestoreAlt = 0xF82A, - - /// - /// The Font Awesome "tree" icon unicode character. - /// - Tree = 0xF1BB, - - /// - /// The Font Awesome "trello" icon unicode character. - /// - Trello = 0xF181, - - /// - /// The Font Awesome "tripadvisor" icon unicode character. - /// - Tripadvisor = 0xF262, - - /// - /// The Font Awesome "trophy" icon unicode character. - /// - Trophy = 0xF091, - - /// - /// The Font Awesome "truck" icon unicode character. - /// - Truck = 0xF0D1, - - /// - /// The Font Awesome "truck-loading" icon unicode character. - /// - TruckLoading = 0xF4DE, - - /// - /// The Font Awesome "truck-monster" icon unicode character. - /// - TruckMonster = 0xF63B, - - /// - /// The Font Awesome "truck-moving" icon unicode character. - /// - TruckMoving = 0xF4DF, - - /// - /// The Font Awesome "truck-pickup" icon unicode character. - /// - TruckPickup = 0xF63C, - - /// - /// The Font Awesome "tshirt" icon unicode character. - /// - Tshirt = 0xF553, - - /// - /// The Font Awesome "tty" icon unicode character. - /// - Tty = 0xF1E4, - - /// - /// The Font Awesome "tumblr" icon unicode character. - /// - Tumblr = 0xF173, - - /// - /// The Font Awesome "tumblr-square" icon unicode character. - /// - TumblrSquare = 0xF174, - - /// - /// The Font Awesome "tv" icon unicode character. - /// - Tv = 0xF26C, - - /// - /// The Font Awesome "twitch" icon unicode character. - /// - Twitch = 0xF1E8, - - /// - /// The Font Awesome "twitter" icon unicode character. - /// - Twitter = 0xF099, - - /// - /// The Font Awesome "twitter-square" icon unicode character. - /// - TwitterSquare = 0xF081, - - /// - /// The Font Awesome "typo3" icon unicode character. - /// - Typo3 = 0xF42B, - - /// - /// The Font Awesome "uber" icon unicode character. - /// - Uber = 0xF402, - - /// - /// The Font Awesome "ubuntu" icon unicode character. - /// - Ubuntu = 0xF7DF, - - /// - /// The Font Awesome "uikit" icon unicode character. - /// - Uikit = 0xF403, - - /// - /// The Font Awesome "umbraco" icon unicode character. - /// - Umbraco = 0xF8E8, - - /// - /// The Font Awesome "umbrella" icon unicode character. - /// - Umbrella = 0xF0E9, - - /// - /// The Font Awesome "umbrella-beach" icon unicode character. - /// - UmbrellaBeach = 0xF5CA, - - /// - /// The Font Awesome "underline" icon unicode character. - /// - Underline = 0xF0CD, - - /// - /// The Font Awesome "undo" icon unicode character. - /// - Undo = 0xF0E2, - - /// - /// The Font Awesome "undo-alt" icon unicode character. - /// - UndoAlt = 0xF2EA, - - /// - /// The Font Awesome "uniregistry" icon unicode character. - /// - Uniregistry = 0xF404, - - /// - /// The Font Awesome "unity" icon unicode character. - /// - Unity = 0xF949, - - /// - /// The Font Awesome "universal-access" icon unicode character. - /// - UniversalAccess = 0xF29A, - - /// - /// The Font Awesome "university" icon unicode character. - /// - University = 0xF19C, - - /// - /// The Font Awesome "unlink" icon unicode character. - /// - Unlink = 0xF127, - - /// - /// The Font Awesome "unlock" icon unicode character. - /// - Unlock = 0xF09C, - - /// - /// The Font Awesome "unlock-alt" icon unicode character. - /// - UnlockAlt = 0xF13E, - - /// - /// The Font Awesome "untappd" icon unicode character. - /// - Untappd = 0xF405, - - /// - /// The Font Awesome "upload" icon unicode character. - /// - Upload = 0xF093, - - /// - /// The Font Awesome "ups" icon unicode character. - /// - Ups = 0xF7E, - - /// - /// The Font Awesome "usb" icon unicode character. - /// - Usb = 0xF287, - - /// - /// The Font Awesome "user" icon unicode character. - /// - User = 0xF007, - - /// - /// The Font Awesome "user-alt" icon unicode character. - /// - UserAlt = 0xF406, - - /// - /// The Font Awesome "user-alt-slash" icon unicode character. - /// - UserAltSlash = 0xF4FA, - - /// - /// The Font Awesome "user-astronaut" icon unicode character. - /// - UserAstronaut = 0xF4FB, - - /// - /// The Font Awesome "user-check" icon unicode character. - /// - UserCheck = 0xF4FC, - - /// - /// The Font Awesome "user-circle" icon unicode character. - /// - UserCircle = 0xF2BD, - - /// - /// The Font Awesome "user-clock" icon unicode character. - /// - UserClock = 0xF4FD, - - /// - /// The Font Awesome "user-cog" icon unicode character. - /// - UserCog = 0xF4FE, - - /// - /// The Font Awesome "user-edit" icon unicode character. - /// - UserEdit = 0xF4FF, - - /// - /// The Font Awesome "user-friends" icon unicode character. - /// - UserFriends = 0xF5, - - /// - /// The Font Awesome "user-graduate" icon unicode character. - /// - UserGraduate = 0xF501, - - /// - /// The Font Awesome "user-injured" icon unicode character. - /// - UserInjured = 0xF728, - - /// - /// The Font Awesome "user-lock" icon unicode character. - /// - UserLock = 0xF502, - - /// - /// The Font Awesome "user-md" icon unicode character. - /// - UserMd = 0xF0F, - - /// - /// The Font Awesome "user-minus" icon unicode character. - /// - UserMinus = 0xF503, - - /// - /// The Font Awesome "user-ninja" icon unicode character. - /// - UserNinja = 0xF504, - - /// - /// The Font Awesome "user-nurse" icon unicode character. - /// - UserNurse = 0xF82F, - - /// - /// The Font Awesome "user-plus" icon unicode character. - /// - UserPlus = 0xF234, - - /// - /// The Font Awesome "users" icon unicode character. - /// - Users = 0xF0C, - - /// - /// The Font Awesome "users-cog" icon unicode character. - /// - UsersCog = 0xF509, - - /// - /// The Font Awesome "user-secret" icon unicode character. - /// - UserSecret = 0xF21B, - - /// - /// The Font Awesome "user-shield" icon unicode character. - /// - UserShield = 0xF505, - - /// - /// The Font Awesome "user-slash" icon unicode character. - /// - UserSlash = 0xF506, - - /// - /// The Font Awesome "user-tag" icon unicode character. - /// - UserTag = 0xF507, - - /// - /// The Font Awesome "user-tie" icon unicode character. - /// - UserTie = 0xF508, - - /// - /// The Font Awesome "user-times" icon unicode character. - /// - UserTimes = 0xF235, - - /// - /// The Font Awesome "usps" icon unicode character. - /// - Usps = 0xF7E1, - - /// - /// The Font Awesome "ussunnah" icon unicode character. - /// - Ussunnah = 0xF407, - - /// - /// The Font Awesome "utensils" icon unicode character. - /// - Utensils = 0xF2E7, - - /// - /// The Font Awesome "utensil-spoon" icon unicode character. - /// - UtensilSpoon = 0xF2E5, - - /// - /// The Font Awesome "vaadin" icon unicode character. - /// - Vaadin = 0xF408, - - /// - /// The Font Awesome "vector-square" icon unicode character. - /// - VectorSquare = 0xF5CB, - - /// - /// The Font Awesome "venus" icon unicode character. - /// - Venus = 0xF221, - - /// - /// The Font Awesome "venus-double" icon unicode character. - /// - VenusDouble = 0xF226, - - /// - /// The Font Awesome "venus-mars" icon unicode character. - /// - VenusMars = 0xF228, - - /// - /// The Font Awesome "viacoin" icon unicode character. - /// - Viacoin = 0xF237, - - /// - /// The Font Awesome "viadeo" icon unicode character. - /// - Viadeo = 0xF2A9, - - /// - /// The Font Awesome "viadeo-square" icon unicode character. - /// - ViadeoSquare = 0xF2AA, - - /// - /// The Font Awesome "vial" icon unicode character. - /// - Vial = 0xF492, - - /// - /// The Font Awesome "vials" icon unicode character. - /// - Vials = 0xF493, - - /// - /// The Font Awesome "viber" icon unicode character. - /// - Viber = 0xF409, - - /// - /// The Font Awesome "video" icon unicode character. - /// - Video = 0xF03D, - - /// - /// The Font Awesome "video-slash" icon unicode character. - /// - VideoSlash = 0xF4E2, - - /// - /// The Font Awesome "vihara" icon unicode character. - /// - Vihara = 0xF6A7, - - /// - /// The Font Awesome "vimeo" icon unicode character. - /// - Vimeo = 0xF40A, - - /// - /// The Font Awesome "vimeo-square" icon unicode character. - /// - VimeoSquare = 0xF194, - - /// - /// The Font Awesome "vimeo-v" icon unicode character. - /// - VimeoV = 0xF27D, - - /// - /// The Font Awesome "vine" icon unicode character. - /// - Vine = 0xF1CA, - - /// - /// The Font Awesome "vk" icon unicode character. - /// - Vk = 0xF189, - - /// - /// The Font Awesome "vnv" icon unicode character. - /// - Vnv = 0xF40B, - - /// - /// The Font Awesome "voicemail" icon unicode character. - /// - Voicemail = 0xF897, - - /// - /// The Font Awesome "volleyball-ball" icon unicode character. - /// - VolleyballBall = 0xF45F, - - /// - /// The Font Awesome "volume-down" icon unicode character. - /// - VolumeDown = 0xF027, - - /// - /// The Font Awesome "volume-mute" icon unicode character. - /// - VolumeMute = 0xF6A9, - - /// - /// The Font Awesome "volume-off" icon unicode character. - /// - VolumeOff = 0xF026, - - /// - /// The Font Awesome "volume-up" icon unicode character. - /// - VolumeUp = 0xF028, - - /// - /// The Font Awesome "vote-yea" icon unicode character. - /// - VoteYea = 0xF772, - - /// - /// The Font Awesome "vr-cardboard" icon unicode character. - /// - VrCardboard = 0xF729, - - /// - /// The Font Awesome "vuejs" icon unicode character. - /// - Vuejs = 0xF41F, - - /// - /// The Font Awesome "walking" icon unicode character. - /// - Walking = 0xF554, - - /// - /// The Font Awesome "wallet" icon unicode character. - /// - Wallet = 0xF555, - - /// - /// The Font Awesome "warehouse" icon unicode character. - /// - Warehouse = 0xF494, - - /// - /// The Font Awesome "water" icon unicode character. - /// - Water = 0xF773, - - /// - /// The Font Awesome "wave-square" icon unicode character. - /// - WaveSquare = 0xF83E, - - /// - /// The Font Awesome "waze" icon unicode character. - /// - Waze = 0xF83F, - - /// - /// The Font Awesome "weebly" icon unicode character. - /// - Weebly = 0xF5CC, - - /// - /// The Font Awesome "weibo" icon unicode character. - /// - Weibo = 0xF18A, - - /// - /// The Font Awesome "weight" icon unicode character. - /// - Weight = 0xF496, - - /// - /// The Font Awesome "weight-hanging" icon unicode character. - /// - WeightHanging = 0xF5CD, - - /// - /// The Font Awesome "weixin" icon unicode character. - /// - Weixin = 0xF1D7, - - /// - /// The Font Awesome "whatsapp" icon unicode character. - /// - Whatsapp = 0xF232, - - /// - /// The Font Awesome "whatsapp-square" icon unicode character. - /// - WhatsappSquare = 0xF40C, - - /// - /// The Font Awesome "wheelchair" icon unicode character. - /// - Wheelchair = 0xF193, - - /// - /// The Font Awesome "whmcs" icon unicode character. - /// - Whmcs = 0xF40D, - - /// - /// The Font Awesome "wifi" icon unicode character. - /// - Wifi = 0xF1EB, - - /// - /// The Font Awesome "wikipedia-w" icon unicode character. - /// - WikipediaW = 0xF266, - - /// - /// The Font Awesome "wind" icon unicode character. - /// - Wind = 0xF72E, - - /// - /// The Font Awesome "window-close" icon unicode character. - /// - WindowClose = 0xF41, - - /// - /// The Font Awesome "window-maximize" icon unicode character. - /// - WindowMaximize = 0xF2D, - - /// - /// The Font Awesome "window-minimize" icon unicode character. - /// - WindowMinimize = 0xF2D1, - - /// - /// The Font Awesome "window-restore" icon unicode character. - /// - WindowRestore = 0xF2D2, - - /// - /// The Font Awesome "windows" icon unicode character. - /// - Windows = 0xF17A, - - /// - /// The Font Awesome "wine-bottle" icon unicode character. - /// - WineBottle = 0xF72F, - - /// - /// The Font Awesome "wine-glass" icon unicode character. - /// - WineGlass = 0xF4E3, - - /// - /// The Font Awesome "wine-glass-alt" icon unicode character. - /// - WineGlassAlt = 0xF5CE, - - /// - /// The Font Awesome "wix" icon unicode character. - /// - Wix = 0xF5CF, - - /// - /// The Font Awesome "wizards-of-the-coast" icon unicode character. - /// - WizardsOfTheCoast = 0xF73, - - /// - /// The Font Awesome "wolf-pack-battalion" icon unicode character. - /// - WolfPackBattalion = 0xF514, - - /// - /// The Font Awesome "won-sign" icon unicode character. - /// - WonSign = 0xF159, - - /// - /// The Font Awesome "wordpress" icon unicode character. - /// - Wordpress = 0xF19A, - - /// - /// The Font Awesome "wordpress-simple" icon unicode character. - /// - WordpressSimple = 0xF411, - - /// - /// The Font Awesome "wpbeginner" icon unicode character. - /// - Wpbeginner = 0xF297, - - /// - /// The Font Awesome "wpexplorer" icon unicode character. - /// - Wpexplorer = 0xF2DE, - - /// - /// The Font Awesome "wpforms" icon unicode character. - /// - Wpforms = 0xF298, - - /// - /// The Font Awesome "wpressr" icon unicode character. - /// - Wpressr = 0xF3E4, - - /// - /// The Font Awesome "wrench" icon unicode character. - /// - Wrench = 0xF0AD, - - /// - /// The Font Awesome "xbox" icon unicode character. - /// - Xbox = 0xF412, - - /// - /// The Font Awesome "xing" icon unicode character. - /// - Xing = 0xF168, - - /// - /// The Font Awesome "xing-square" icon unicode character. - /// - XingSquare = 0xF169, - - /// - /// The Font Awesome "x-ray" icon unicode character. - /// - XRay = 0xF497, - - /// - /// The Font Awesome "yahoo" icon unicode character. - /// - Yahoo = 0xF19E, - - /// - /// The Font Awesome "yammer" icon unicode character. - /// - Yammer = 0xF84, - - /// - /// The Font Awesome "yandex" icon unicode character. - /// - Yandex = 0xF413, - - /// - /// The Font Awesome "yandex-international" icon unicode character. - /// - YandexInternational = 0xF414, - - /// - /// The Font Awesome "yarn" icon unicode character. - /// - Yarn = 0xF7E3, - - /// - /// The Font Awesome "y-combinator" icon unicode character. - /// - YCombinator = 0xF23B, - - /// - /// The Font Awesome "yelp" icon unicode character. - /// - Yelp = 0xF1E9, - - /// - /// The Font Awesome "yen-sign" icon unicode character. - /// - YenSign = 0xF157, - - /// - /// The Font Awesome "yin-yang" icon unicode character. - /// - YinYang = 0xF6AD, - - /// - /// The Font Awesome "yoast" icon unicode character. - /// - Yoast = 0xF2B1, - - /// - /// The Font Awesome "youtube" icon unicode character. - /// - Youtube = 0xF167, - - /// - /// The Font Awesome "youtube-square" icon unicode character. - /// - YoutubeSquare = 0xF431, - - /// - /// The Font Awesome "zhihu" icon unicode character. - /// - Zhihu = 0xF63F, - } + None = 0, + + /// + /// The Font Awesome "500px" icon unicode character. + /// + _500Px = 0xF26E, + + /// + /// The Font Awesome "accessible-icon" icon unicode character. + /// + AccessibleIcon = 0xF368, + + /// + /// The Font Awesome "accusoft" icon unicode character. + /// + Accusoft = 0xF369, + + /// + /// The Font Awesome "acquisitions-incorporated" icon unicode character. + /// + AcquisitionsIncorporated = 0xF6AF, + + /// + /// The Font Awesome "ad" icon unicode character. + /// + Ad = 0xF641, + + /// + /// The Font Awesome "address-book" icon unicode character. + /// + AddressBook = 0xF2B9, + + /// + /// The Font Awesome "address-card" icon unicode character. + /// + AddressCard = 0xF2BB, + + /// + /// The Font Awesome "adjust" icon unicode character. + /// + Adjust = 0xF042, + + /// + /// The Font Awesome "adn" icon unicode character. + /// + Adn = 0xF17, + + /// + /// The Font Awesome "adobe" icon unicode character. + /// + Adobe = 0xF778, + + /// + /// The Font Awesome "adversal" icon unicode character. + /// + Adversal = 0xF36A, + + /// + /// The Font Awesome "affiliatetheme" icon unicode character. + /// + Affiliatetheme = 0xF36B, + + /// + /// The Font Awesome "airbnb" icon unicode character. + /// + Airbnb = 0xF834, + + /// + /// The Font Awesome "air-freshener" icon unicode character. + /// + AirFreshener = 0xF5D, + + /// + /// The Font Awesome "algolia" icon unicode character. + /// + Algolia = 0xF36C, + + /// + /// The Font Awesome "align-center" icon unicode character. + /// + AlignCenter = 0xF037, + + /// + /// The Font Awesome "align-justify" icon unicode character. + /// + AlignJustify = 0xF039, + + /// + /// The Font Awesome "align-left" icon unicode character. + /// + AlignLeft = 0xF036, + + /// + /// The Font Awesome "align-right" icon unicode character. + /// + AlignRight = 0xF038, + + /// + /// The Font Awesome "alipay" icon unicode character. + /// + Alipay = 0xF642, + + /// + /// The Font Awesome "allergies" icon unicode character. + /// + Allergies = 0xF461, + + /// + /// The Font Awesome "amazon" icon unicode character. + /// + Amazon = 0xF27, + + /// + /// The Font Awesome "amazon-pay" icon unicode character. + /// + AmazonPay = 0xF42C, + + /// + /// The Font Awesome "ambulance" icon unicode character. + /// + Ambulance = 0xF0F9, + + /// + /// The Font Awesome "american-sign-language-interpreting" icon unicode character. + /// + AmericanSignLanguageInterpreting = 0xF2A3, + + /// + /// The Font Awesome "amilia" icon unicode character. + /// + Amilia = 0xF36D, + + /// + /// The Font Awesome "anchor" icon unicode character. + /// + Anchor = 0xF13D, + + /// + /// The Font Awesome "android" icon unicode character. + /// + Android = 0xF17B, + + /// + /// The Font Awesome "angellist" icon unicode character. + /// + Angellist = 0xF209, + + /// + /// The Font Awesome "angle-double-down" icon unicode character. + /// + AngleDoubleDown = 0xF103, + + /// + /// The Font Awesome "angle-double-left" icon unicode character. + /// + AngleDoubleLeft = 0xF1, + + /// + /// The Font Awesome "angle-double-right" icon unicode character. + /// + AngleDoubleRight = 0xF101, + + /// + /// The Font Awesome "angle-double-up" icon unicode character. + /// + AngleDoubleUp = 0xF102, + + /// + /// The Font Awesome "angle-down" icon unicode character. + /// + AngleDown = 0xF107, + + /// + /// The Font Awesome "angle-left" icon unicode character. + /// + AngleLeft = 0xF104, + + /// + /// The Font Awesome "angle-right" icon unicode character. + /// + AngleRight = 0xF105, + + /// + /// The Font Awesome "angle-up" icon unicode character. + /// + AngleUp = 0xF106, + + /// + /// The Font Awesome "angry" icon unicode character. + /// + Angry = 0xF556, + + /// + /// The Font Awesome "angrycreative" icon unicode character. + /// + Angrycreative = 0xF36E, + + /// + /// The Font Awesome "angular" icon unicode character. + /// + Angular = 0xF42, + + /// + /// The Font Awesome "ankh" icon unicode character. + /// + Ankh = 0xF644, + + /// + /// The Font Awesome "apper" icon unicode character. + /// + Apper = 0xF371, + + /// + /// The Font Awesome "apple" icon unicode character. + /// + Apple = 0xF179, + + /// + /// The Font Awesome "apple-alt" icon unicode character. + /// + AppleAlt = 0xF5D1, + + /// + /// The Font Awesome "apple-pay" icon unicode character. + /// + ApplePay = 0xF415, + + /// + /// The Font Awesome "app-store" icon unicode character. + /// + AppStore = 0xF36F, + + /// + /// The Font Awesome "app-store-ios" icon unicode character. + /// + AppStoreIos = 0xF37, + + /// + /// The Font Awesome "archive" icon unicode character. + /// + Archive = 0xF187, + + /// + /// The Font Awesome "archway" icon unicode character. + /// + Archway = 0xF557, + + /// + /// The Font Awesome "arrow-alt-circle-down" icon unicode character. + /// + ArrowAltCircleDown = 0xF358, + + /// + /// The Font Awesome "arrow-alt-circle-left" icon unicode character. + /// + ArrowAltCircleLeft = 0xF359, + + /// + /// The Font Awesome "arrow-alt-circle-right" icon unicode character. + /// + ArrowAltCircleRight = 0xF35A, + + /// + /// The Font Awesome "arrow-alt-circle-up" icon unicode character. + /// + ArrowAltCircleUp = 0xF35B, + + /// + /// The Font Awesome "arrow-circle-down" icon unicode character. + /// + ArrowCircleDown = 0xF0AB, + + /// + /// The Font Awesome "arrow-circle-left" icon unicode character. + /// + ArrowCircleLeft = 0xF0A8, + + /// + /// The Font Awesome "arrow-circle-right" icon unicode character. + /// + ArrowCircleRight = 0xF0A9, + + /// + /// The Font Awesome "arrow-circle-up" icon unicode character. + /// + ArrowCircleUp = 0xF0AA, + + /// + /// The Font Awesome "arrow-down" icon unicode character. + /// + ArrowDown = 0xF063, + + /// + /// The Font Awesome "arrow-left" icon unicode character. + /// + ArrowLeft = 0xF06, + + /// + /// The Font Awesome "arrow-right" icon unicode character. + /// + ArrowRight = 0xF061, + + /// + /// The Font Awesome "arrows-alt" icon unicode character. + /// + ArrowsAlt = 0xF0B2, + + /// + /// The Font Awesome "arrows-alt-h" icon unicode character. + /// + ArrowsAltH = 0xF337, + + /// + /// The Font Awesome "arrows-alt-v" icon unicode character. + /// + ArrowsAltV = 0xF338, + + /// + /// The Font Awesome "arrow-up" icon unicode character. + /// + ArrowUp = 0xF062, + + /// + /// The Font Awesome "artstation" icon unicode character. + /// + Artstation = 0xF77A, + + /// + /// The Font Awesome "assistive-listening-systems" icon unicode character. + /// + AssistiveListeningSystems = 0xF2A2, + + /// + /// The Font Awesome "asterisk" icon unicode character. + /// + Asterisk = 0xF069, + + /// + /// The Font Awesome "asymmetrik" icon unicode character. + /// + Asymmetrik = 0xF372, + + /// + /// The Font Awesome "at" icon unicode character. + /// + At = 0xF1FA, + + /// + /// The Font Awesome "atlas" icon unicode character. + /// + Atlas = 0xF558, + + /// + /// The Font Awesome "atlassian" icon unicode character. + /// + Atlassian = 0xF77B, + + /// + /// The Font Awesome "atom" icon unicode character. + /// + Atom = 0xF5D2, + + /// + /// The Font Awesome "audible" icon unicode character. + /// + Audible = 0xF373, + + /// + /// The Font Awesome "audio-description" icon unicode character. + /// + AudioDescription = 0xF29E, + + /// + /// The Font Awesome "autoprefixer" icon unicode character. + /// + Autoprefixer = 0xF41C, + + /// + /// The Font Awesome "avianex" icon unicode character. + /// + Avianex = 0xF374, + + /// + /// The Font Awesome "aviato" icon unicode character. + /// + Aviato = 0xF421, + + /// + /// The Font Awesome "award" icon unicode character. + /// + Award = 0xF559, + + /// + /// The Font Awesome "aws" icon unicode character. + /// + Aws = 0xF375, + + /// + /// The Font Awesome "baby" icon unicode character. + /// + Baby = 0xF77C, + + /// + /// The Font Awesome "baby-carriage" icon unicode character. + /// + BabyCarriage = 0xF77D, + + /// + /// The Font Awesome "backspace" icon unicode character. + /// + Backspace = 0xF55A, + + /// + /// The Font Awesome "backward" icon unicode character. + /// + Backward = 0xF04A, + + /// + /// The Font Awesome "bacon" icon unicode character. + /// + Bacon = 0xF7E5, + + /// + /// The Font Awesome "bahai" icon unicode character. + /// + Bahai = 0xF666, + + /// + /// The Font Awesome "balance-scale" icon unicode character. + /// + BalanceScale = 0xF24E, + + /// + /// The Font Awesome "balance-scale-left" icon unicode character. + /// + BalanceScaleLeft = 0xF515, + + /// + /// The Font Awesome "balance-scale-right" icon unicode character. + /// + BalanceScaleRight = 0xF516, + + /// + /// The Font Awesome "ban" icon unicode character. + /// + Ban = 0xF05E, + + /// + /// The Font Awesome "band-aid" icon unicode character. + /// + BandAid = 0xF462, + + /// + /// The Font Awesome "bandcamp" icon unicode character. + /// + Bandcamp = 0xF2D5, + + /// + /// The Font Awesome "barcode" icon unicode character. + /// + Barcode = 0xF02A, + + /// + /// The Font Awesome "bars" icon unicode character. + /// + Bars = 0xF0C9, + + /// + /// The Font Awesome "baseball-ball" icon unicode character. + /// + BaseballBall = 0xF433, + + /// + /// The Font Awesome "basketball-ball" icon unicode character. + /// + BasketballBall = 0xF434, + + /// + /// The Font Awesome "bath" icon unicode character. + /// + Bath = 0xF2CD, + + /// + /// The Font Awesome "battery-empty" icon unicode character. + /// + BatteryEmpty = 0xF244, + + /// + /// The Font Awesome "battery-full" icon unicode character. + /// + BatteryFull = 0xF24, + + /// + /// The Font Awesome "battery-half" icon unicode character. + /// + BatteryHalf = 0xF242, + + /// + /// The Font Awesome "battery-quarter" icon unicode character. + /// + BatteryQuarter = 0xF243, + + /// + /// The Font Awesome "battery-three-quarters" icon unicode character. + /// + BatteryThreeQuarters = 0xF241, + + /// + /// The Font Awesome "battle-net" icon unicode character. + /// + BattleNet = 0xF835, + + /// + /// The Font Awesome "bed" icon unicode character. + /// + Bed = 0xF236, + + /// + /// The Font Awesome "beer" icon unicode character. + /// + Beer = 0xF0FC, + + /// + /// The Font Awesome "behance" icon unicode character. + /// + Behance = 0xF1B4, + + /// + /// The Font Awesome "behance-square" icon unicode character. + /// + BehanceSquare = 0xF1B5, + + /// + /// The Font Awesome "bell" icon unicode character. + /// + Bell = 0xF0F3, + + /// + /// The Font Awesome "bell-slash" icon unicode character. + /// + BellSlash = 0xF1F6, + + /// + /// The Font Awesome "bezier-curve" icon unicode character. + /// + BezierCurve = 0xF55B, + + /// + /// The Font Awesome "bible" icon unicode character. + /// + Bible = 0xF647, + + /// + /// The Font Awesome "bicycle" icon unicode character. + /// + Bicycle = 0xF206, + + /// + /// The Font Awesome "biking" icon unicode character. + /// + Biking = 0xF84A, + + /// + /// The Font Awesome "bimobject" icon unicode character. + /// + Bimobject = 0xF378, + + /// + /// The Font Awesome "binoculars" icon unicode character. + /// + Binoculars = 0xF1E5, + + /// + /// The Font Awesome "biohazard" icon unicode character. + /// + Biohazard = 0xF78, + + /// + /// The Font Awesome "birthday-cake" icon unicode character. + /// + BirthdayCake = 0xF1FD, + + /// + /// The Font Awesome "bitbucket" icon unicode character. + /// + Bitbucket = 0xF171, + + /// + /// The Font Awesome "bitcoin" icon unicode character. + /// + Bitcoin = 0xF379, + + /// + /// The Font Awesome "bity" icon unicode character. + /// + Bity = 0xF37A, + + /// + /// The Font Awesome "blackberry" icon unicode character. + /// + Blackberry = 0xF37B, + + /// + /// The Font Awesome "black-tie" icon unicode character. + /// + BlackTie = 0xF27E, + + /// + /// The Font Awesome "blender" icon unicode character. + /// + Blender = 0xF517, + + /// + /// The Font Awesome "blender-phone" icon unicode character. + /// + BlenderPhone = 0xF6B6, + + /// + /// The Font Awesome "blind" icon unicode character. + /// + Blind = 0xF29D, + + /// + /// The Font Awesome "blog" icon unicode character. + /// + Blog = 0xF781, + + /// + /// The Font Awesome "blogger" icon unicode character. + /// + Blogger = 0xF37C, + + /// + /// The Font Awesome "blogger-b" icon unicode character. + /// + BloggerB = 0xF37D, + + /// + /// The Font Awesome "bluetooth" icon unicode character. + /// + Bluetooth = 0xF293, + + /// + /// The Font Awesome "bluetooth-b" icon unicode character. + /// + BluetoothB = 0xF294, + + /// + /// The Font Awesome "bold" icon unicode character. + /// + Bold = 0xF032, + + /// + /// The Font Awesome "bolt" icon unicode character. + /// + Bolt = 0xF0E7, + + /// + /// The Font Awesome "bomb" icon unicode character. + /// + Bomb = 0xF1E2, + + /// + /// The Font Awesome "bone" icon unicode character. + /// + Bone = 0xF5D7, + + /// + /// The Font Awesome "bong" icon unicode character. + /// + Bong = 0xF55C, + + /// + /// The Font Awesome "book" icon unicode character. + /// + Book = 0xF02D, + + /// + /// The Font Awesome "book-dead" icon unicode character. + /// + BookDead = 0xF6B7, + + /// + /// The Font Awesome "bookmark" icon unicode character. + /// + Bookmark = 0xF02E, + + /// + /// The Font Awesome "book-medical" icon unicode character. + /// + BookMedical = 0xF7E6, + + /// + /// The Font Awesome "book-open" icon unicode character. + /// + BookOpen = 0xF518, + + /// + /// The Font Awesome "book-reader" icon unicode character. + /// + BookReader = 0xF5DA, + + /// + /// The Font Awesome "bootstrap" icon unicode character. + /// + Bootstrap = 0xF836, + + /// + /// The Font Awesome "border-all" icon unicode character. + /// + BorderAll = 0xF84C, + + /// + /// The Font Awesome "border-none" icon unicode character. + /// + BorderNone = 0xF85, + + /// + /// The Font Awesome "border-style" icon unicode character. + /// + BorderStyle = 0xF853, + + /// + /// The Font Awesome "bowling-ball" icon unicode character. + /// + BowlingBall = 0xF436, + + /// + /// The Font Awesome "box" icon unicode character. + /// + Box = 0xF466, + + /// + /// The Font Awesome "boxes" icon unicode character. + /// + Boxes = 0xF468, + + /// + /// The Font Awesome "box-open" icon unicode character. + /// + BoxOpen = 0xF49E, + + /// + /// The Font Awesome "braille" icon unicode character. + /// + Braille = 0xF2A1, + + /// + /// The Font Awesome "brain" icon unicode character. + /// + Brain = 0xF5DC, + + /// + /// The Font Awesome "bread-slice" icon unicode character. + /// + BreadSlice = 0xF7EC, + + /// + /// The Font Awesome "briefcase" icon unicode character. + /// + Briefcase = 0xF0B1, + + /// + /// The Font Awesome "briefcase-medical" icon unicode character. + /// + BriefcaseMedical = 0xF469, + + /// + /// The Font Awesome "broadcast-tower" icon unicode character. + /// + BroadcastTower = 0xF519, + + /// + /// The Font Awesome "broom" icon unicode character. + /// + Broom = 0xF51A, + + /// + /// The Font Awesome "brush" icon unicode character. + /// + Brush = 0xF55D, + + /// + /// The Font Awesome "btc" icon unicode character. + /// + Btc = 0xF15A, + + /// + /// The Font Awesome "buffer" icon unicode character. + /// + Buffer = 0xF837, + + /// + /// The Font Awesome "bug" icon unicode character. + /// + Bug = 0xF188, + + /// + /// The Font Awesome "building" icon unicode character. + /// + Building = 0xF1AD, + + /// + /// The Font Awesome "bullhorn" icon unicode character. + /// + Bullhorn = 0xF0A1, + + /// + /// The Font Awesome "bullseye" icon unicode character. + /// + Bullseye = 0xF14, + + /// + /// The Font Awesome "burn" icon unicode character. + /// + Burn = 0xF46A, + + /// + /// The Font Awesome "buromobelexperte" icon unicode character. + /// + Buromobelexperte = 0xF37F, + + /// + /// The Font Awesome "bus" icon unicode character. + /// + Bus = 0xF207, + + /// + /// The Font Awesome "bus-alt" icon unicode character. + /// + BusAlt = 0xF55E, + + /// + /// The Font Awesome "business-time" icon unicode character. + /// + BusinessTime = 0xF64A, + + /// + /// The Font Awesome "buy-n-large" icon unicode character. + /// + BuyNLarge = 0xF8A6, + + /// + /// The Font Awesome "buysellads" icon unicode character. + /// + Buysellads = 0xF20D, + + /// + /// The Font Awesome "calculator" icon unicode character. + /// + Calculator = 0xF1EC, + + /// + /// The Font Awesome "calendar" icon unicode character. + /// + Calendar = 0xF133, + + /// + /// The Font Awesome "calendar-alt" icon unicode character. + /// + CalendarAlt = 0xF073, + + /// + /// The Font Awesome "calendar-check" icon unicode character. + /// + CalendarCheck = 0xF274, + + /// + /// The Font Awesome "calendar-day" icon unicode character. + /// + CalendarDay = 0xF783, + + /// + /// The Font Awesome "calendar-minus" icon unicode character. + /// + CalendarMinus = 0xF272, + + /// + /// The Font Awesome "calendar-plus" icon unicode character. + /// + CalendarPlus = 0xF271, + + /// + /// The Font Awesome "calendar-times" icon unicode character. + /// + CalendarTimes = 0xF273, + + /// + /// The Font Awesome "calendar-week" icon unicode character. + /// + CalendarWeek = 0xF784, + + /// + /// The Font Awesome "camera" icon unicode character. + /// + Camera = 0xF03, + + /// + /// The Font Awesome "camera-retro" icon unicode character. + /// + CameraRetro = 0xF083, + + /// + /// The Font Awesome "campground" icon unicode character. + /// + Campground = 0xF6BB, + + /// + /// The Font Awesome "canadian-maple-leaf" icon unicode character. + /// + CanadianMapleLeaf = 0xF785, + + /// + /// The Font Awesome "candy-cane" icon unicode character. + /// + CandyCane = 0xF786, + + /// + /// The Font Awesome "cannabis" icon unicode character. + /// + Cannabis = 0xF55F, + + /// + /// The Font Awesome "capsules" icon unicode character. + /// + Capsules = 0xF46B, + + /// + /// The Font Awesome "car" icon unicode character. + /// + Car = 0xF1B9, + + /// + /// The Font Awesome "car-alt" icon unicode character. + /// + CarAlt = 0xF5DE, + + /// + /// The Font Awesome "caravan" icon unicode character. + /// + Caravan = 0xF8FF, + + /// + /// The Font Awesome "car-battery" icon unicode character. + /// + CarBattery = 0xF5DF, + + /// + /// The Font Awesome "car-crash" icon unicode character. + /// + CarCrash = 0xF5E1, + + /// + /// The Font Awesome "caret-down" icon unicode character. + /// + CaretDown = 0xF0D7, + + /// + /// The Font Awesome "caret-left" icon unicode character. + /// + CaretLeft = 0xF0D9, + + /// + /// The Font Awesome "caret-right" icon unicode character. + /// + CaretRight = 0xF0DA, + + /// + /// The Font Awesome "caret-square-down" icon unicode character. + /// + CaretSquareDown = 0xF15, + + /// + /// The Font Awesome "caret-square-left" icon unicode character. + /// + CaretSquareLeft = 0xF191, + + /// + /// The Font Awesome "caret-square-right" icon unicode character. + /// + CaretSquareRight = 0xF152, + + /// + /// The Font Awesome "caret-square-up" icon unicode character. + /// + CaretSquareUp = 0xF151, + + /// + /// The Font Awesome "caret-up" icon unicode character. + /// + CaretUp = 0xF0D8, + + /// + /// The Font Awesome "carrot" icon unicode character. + /// + Carrot = 0xF787, + + /// + /// The Font Awesome "car-side" icon unicode character. + /// + CarSide = 0xF5E4, + + /// + /// The Font Awesome "cart-arrow-down" icon unicode character. + /// + CartArrowDown = 0xF218, + + /// + /// The Font Awesome "cart-plus" icon unicode character. + /// + CartPlus = 0xF217, + + /// + /// The Font Awesome "cash-register" icon unicode character. + /// + CashRegister = 0xF788, + + /// + /// The Font Awesome "cat" icon unicode character. + /// + Cat = 0xF6BE, + + /// + /// The Font Awesome "cc-amazon-pay" icon unicode character. + /// + CcAmazonPay = 0xF42D, + + /// + /// The Font Awesome "cc-amex" icon unicode character. + /// + CcAmex = 0xF1F3, + + /// + /// The Font Awesome "cc-apple-pay" icon unicode character. + /// + CcApplePay = 0xF416, + + /// + /// The Font Awesome "cc-diners-club" icon unicode character. + /// + CcDinersClub = 0xF24C, + + /// + /// The Font Awesome "cc-discover" icon unicode character. + /// + CcDiscover = 0xF1F2, + + /// + /// The Font Awesome "cc-jcb" icon unicode character. + /// + CcJcb = 0xF24B, + + /// + /// The Font Awesome "cc-mastercard" icon unicode character. + /// + CcMastercard = 0xF1F1, + + /// + /// The Font Awesome "cc-paypal" icon unicode character. + /// + CcPaypal = 0xF1F4, + + /// + /// The Font Awesome "cc-stripe" icon unicode character. + /// + CcStripe = 0xF1F5, + + /// + /// The Font Awesome "cc-visa" icon unicode character. + /// + CcVisa = 0xF1F, + + /// + /// The Font Awesome "centercode" icon unicode character. + /// + Centercode = 0xF38, + + /// + /// The Font Awesome "centos" icon unicode character. + /// + Centos = 0xF789, + + /// + /// The Font Awesome "certificate" icon unicode character. + /// + Certificate = 0xF0A3, + + /// + /// The Font Awesome "chair" icon unicode character. + /// + Chair = 0xF6C, + + /// + /// The Font Awesome "chalkboard" icon unicode character. + /// + Chalkboard = 0xF51B, + + /// + /// The Font Awesome "chalkboard-teacher" icon unicode character. + /// + ChalkboardTeacher = 0xF51C, + + /// + /// The Font Awesome "charging-station" icon unicode character. + /// + ChargingStation = 0xF5E7, + + /// + /// The Font Awesome "chart-area" icon unicode character. + /// + ChartArea = 0xF1FE, + + /// + /// The Font Awesome "chart-bar" icon unicode character. + /// + ChartBar = 0xF08, + + /// + /// The Font Awesome "chart-line" icon unicode character. + /// + ChartLine = 0xF201, + + /// + /// The Font Awesome "chart-pie" icon unicode character. + /// + ChartPie = 0xF2, + + /// + /// The Font Awesome "check" icon unicode character. + /// + Check = 0xF00C, + + /// + /// The Font Awesome "check-circle" icon unicode character. + /// + CheckCircle = 0xF058, + + /// + /// The Font Awesome "check-double" icon unicode character. + /// + CheckDouble = 0xF56, + + /// + /// The Font Awesome "check-square" icon unicode character. + /// + CheckSquare = 0xF14A, + + /// + /// The Font Awesome "cheese" icon unicode character. + /// + Cheese = 0xF7EF, + + /// + /// The Font Awesome "chess" icon unicode character. + /// + Chess = 0xF439, + + /// + /// The Font Awesome "chess-bishop" icon unicode character. + /// + ChessBishop = 0xF43A, + + /// + /// The Font Awesome "chess-board" icon unicode character. + /// + ChessBoard = 0xF43C, + + /// + /// The Font Awesome "chess-king" icon unicode character. + /// + ChessKing = 0xF43F, + + /// + /// The Font Awesome "chess-knight" icon unicode character. + /// + ChessKnight = 0xF441, + + /// + /// The Font Awesome "chess-pawn" icon unicode character. + /// + ChessPawn = 0xF443, + + /// + /// The Font Awesome "chess-queen" icon unicode character. + /// + ChessQueen = 0xF445, + + /// + /// The Font Awesome "chess-rook" icon unicode character. + /// + ChessRook = 0xF447, + + /// + /// The Font Awesome "chevron-circle-down" icon unicode character. + /// + ChevronCircleDown = 0xF13A, + + /// + /// The Font Awesome "chevron-circle-left" icon unicode character. + /// + ChevronCircleLeft = 0xF137, + + /// + /// The Font Awesome "chevron-circle-right" icon unicode character. + /// + ChevronCircleRight = 0xF138, + + /// + /// The Font Awesome "chevron-circle-up" icon unicode character. + /// + ChevronCircleUp = 0xF139, + + /// + /// The Font Awesome "chevron-down" icon unicode character. + /// + ChevronDown = 0xF078, + + /// + /// The Font Awesome "chevron-left" icon unicode character. + /// + ChevronLeft = 0xF053, + + /// + /// The Font Awesome "chevron-right" icon unicode character. + /// + ChevronRight = 0xF054, + + /// + /// The Font Awesome "chevron-up" icon unicode character. + /// + ChevronUp = 0xF077, + + /// + /// The Font Awesome "child" icon unicode character. + /// + Child = 0xF1AE, + + /// + /// The Font Awesome "chrome" icon unicode character. + /// + Chrome = 0xF268, + + /// + /// The Font Awesome "chromecast" icon unicode character. + /// + Chromecast = 0xF838, + + /// + /// The Font Awesome "church" icon unicode character. + /// + Church = 0xF51D, + + /// + /// The Font Awesome "circle" icon unicode character. + /// + Circle = 0xF111, + + /// + /// The Font Awesome "circle-notch" icon unicode character. + /// + CircleNotch = 0xF1CE, + + /// + /// The Font Awesome "city" icon unicode character. + /// + City = 0xF64F, + + /// + /// The Font Awesome "clinic-medical" icon unicode character. + /// + ClinicMedical = 0xF7F2, + + /// + /// The Font Awesome "clipboard" icon unicode character. + /// + Clipboard = 0xF328, + + /// + /// The Font Awesome "clipboard-check" icon unicode character. + /// + ClipboardCheck = 0xF46C, + + /// + /// The Font Awesome "clipboard-list" icon unicode character. + /// + ClipboardList = 0xF46D, + + /// + /// The Font Awesome "clock" icon unicode character. + /// + Clock = 0xF017, + + /// + /// The Font Awesome "clone" icon unicode character. + /// + Clone = 0xF24D, + + /// + /// The Font Awesome "closed-captioning" icon unicode character. + /// + ClosedCaptioning = 0xF20A, + + /// + /// The Font Awesome "cloud" icon unicode character. + /// + Cloud = 0xF0C2, + + /// + /// The Font Awesome "cloud-download-alt" icon unicode character. + /// + CloudDownloadAlt = 0xF381, + + /// + /// The Font Awesome "cloud-meatball" icon unicode character. + /// + CloudMeatball = 0xF73B, + + /// + /// The Font Awesome "cloud-moon" icon unicode character. + /// + CloudMoon = 0xF6C3, + + /// + /// The Font Awesome "cloud-moon-rain" icon unicode character. + /// + CloudMoonRain = 0xF73C, + + /// + /// The Font Awesome "cloud-rain" icon unicode character. + /// + CloudRain = 0xF73D, + + /// + /// The Font Awesome "cloudscale" icon unicode character. + /// + Cloudscale = 0xF383, + + /// + /// The Font Awesome "cloud-showers-heavy" icon unicode character. + /// + CloudShowersHeavy = 0xF74, + + /// + /// The Font Awesome "cloudsmith" icon unicode character. + /// + Cloudsmith = 0xF384, + + /// + /// The Font Awesome "cloud-sun" icon unicode character. + /// + CloudSun = 0xF6C4, + + /// + /// The Font Awesome "cloud-sun-rain" icon unicode character. + /// + CloudSunRain = 0xF743, + + /// + /// The Font Awesome "cloud-upload-alt" icon unicode character. + /// + CloudUploadAlt = 0xF382, + + /// + /// The Font Awesome "cloudversify" icon unicode character. + /// + Cloudversify = 0xF385, + + /// + /// The Font Awesome "cocktail" icon unicode character. + /// + Cocktail = 0xF561, + + /// + /// The Font Awesome "code" icon unicode character. + /// + Code = 0xF121, + + /// + /// The Font Awesome "code-branch" icon unicode character. + /// + CodeBranch = 0xF126, + + /// + /// The Font Awesome "codepen" icon unicode character. + /// + Codepen = 0xF1CB, + + /// + /// The Font Awesome "codiepie" icon unicode character. + /// + Codiepie = 0xF284, + + /// + /// The Font Awesome "coffee" icon unicode character. + /// + Coffee = 0xF0F4, + + /// + /// The Font Awesome "cog" icon unicode character. + /// + Cog = 0xF013, + + /// + /// The Font Awesome "cogs" icon unicode character. + /// + Cogs = 0xF085, + + /// + /// The Font Awesome "coins" icon unicode character. + /// + Coins = 0xF51E, + + /// + /// The Font Awesome "columns" icon unicode character. + /// + Columns = 0xF0DB, + + /// + /// The Font Awesome "comment" icon unicode character. + /// + Comment = 0xF075, + + /// + /// The Font Awesome "comment-alt" icon unicode character. + /// + CommentAlt = 0xF27A, + + /// + /// The Font Awesome "comment-dollar" icon unicode character. + /// + CommentDollar = 0xF651, + + /// + /// The Font Awesome "comment-dots" icon unicode character. + /// + CommentDots = 0xF4AD, + + /// + /// The Font Awesome "comment-medical" icon unicode character. + /// + CommentMedical = 0xF7F5, + + /// + /// The Font Awesome "comments" icon unicode character. + /// + Comments = 0xF086, + + /// + /// The Font Awesome "comments-dollar" icon unicode character. + /// + CommentsDollar = 0xF653, + + /// + /// The Font Awesome "comment-slash" icon unicode character. + /// + CommentSlash = 0xF4B3, + + /// + /// The Font Awesome "compact-disc" icon unicode character. + /// + CompactDisc = 0xF51F, + + /// + /// The Font Awesome "compass" icon unicode character. + /// + Compass = 0xF14E, + + /// + /// The Font Awesome "compress" icon unicode character. + /// + Compress = 0xF066, + + /// + /// The Font Awesome "compress-alt" icon unicode character. + /// + CompressAlt = 0xF422, + + /// + /// The Font Awesome "compress-arrows-alt" icon unicode character. + /// + CompressArrowsAlt = 0xF78C, + + /// + /// The Font Awesome "concierge-bell" icon unicode character. + /// + ConciergeBell = 0xF562, + + /// + /// The Font Awesome "confluence" icon unicode character. + /// + Confluence = 0xF78D, + + /// + /// The Font Awesome "connectdevelop" icon unicode character. + /// + Connectdevelop = 0xF20E, + + /// + /// The Font Awesome "contao" icon unicode character. + /// + Contao = 0xF26D, + + /// + /// The Font Awesome "cookie" icon unicode character. + /// + Cookie = 0xF563, + + /// + /// The Font Awesome "cookie-bite" icon unicode character. + /// + CookieBite = 0xF564, + + /// + /// The Font Awesome "copy" icon unicode character. + /// + Copy = 0xF0C5, + + /// + /// The Font Awesome "copyright" icon unicode character. + /// + Copyright = 0xF1F9, + + /// + /// The Font Awesome "cotton-bureau" icon unicode character. + /// + CottonBureau = 0xF89E, + + /// + /// The Font Awesome "couch" icon unicode character. + /// + Couch = 0xF4B8, + + /// + /// The Font Awesome "cpanel" icon unicode character. + /// + Cpanel = 0xF388, + + /// + /// The Font Awesome "creative-commons" icon unicode character. + /// + CreativeCommons = 0xF25E, + + /// + /// The Font Awesome "creative-commons-by" icon unicode character. + /// + CreativeCommonsBy = 0xF4E7, + + /// + /// The Font Awesome "creative-commons-nc" icon unicode character. + /// + CreativeCommonsNc = 0xF4E8, + + /// + /// The Font Awesome "creative-commons-nc-eu" icon unicode character. + /// + CreativeCommonsNcEu = 0xF4E9, + + /// + /// The Font Awesome "creative-commons-nc-jp" icon unicode character. + /// + CreativeCommonsNcJp = 0xF4EA, + + /// + /// The Font Awesome "creative-commons-nd" icon unicode character. + /// + CreativeCommonsNd = 0xF4EB, + + /// + /// The Font Awesome "creative-commons-pd" icon unicode character. + /// + CreativeCommonsPd = 0xF4EC, + + /// + /// The Font Awesome "creative-commons-pd-alt" icon unicode character. + /// + CreativeCommonsPdAlt = 0xF4ED, + + /// + /// The Font Awesome "creative-commons-remix" icon unicode character. + /// + CreativeCommonsRemix = 0xF4EE, + + /// + /// The Font Awesome "creative-commons-sa" icon unicode character. + /// + CreativeCommonsSa = 0xF4EF, + + /// + /// The Font Awesome "creative-commons-sampling" icon unicode character. + /// + CreativeCommonsSampling = 0xF4F, + + /// + /// The Font Awesome "creative-commons-sampling-plus" icon unicode character. + /// + CreativeCommonsSamplingPlus = 0xF4F1, + + /// + /// The Font Awesome "creative-commons-share" icon unicode character. + /// + CreativeCommonsShare = 0xF4F2, + + /// + /// The Font Awesome "creative-commons-zero" icon unicode character. + /// + CreativeCommonsZero = 0xF4F3, + + /// + /// The Font Awesome "credit-card" icon unicode character. + /// + CreditCard = 0xF09D, + + /// + /// The Font Awesome "critical-role" icon unicode character. + /// + CriticalRole = 0xF6C9, + + /// + /// The Font Awesome "crop" icon unicode character. + /// + Crop = 0xF125, + + /// + /// The Font Awesome "crop-alt" icon unicode character. + /// + CropAlt = 0xF565, + + /// + /// The Font Awesome "cross" icon unicode character. + /// + Cross = 0xF654, + + /// + /// The Font Awesome "crosshairs" icon unicode character. + /// + Crosshairs = 0xF05B, + + /// + /// The Font Awesome "crow" icon unicode character. + /// + Crow = 0xF52, + + /// + /// The Font Awesome "crown" icon unicode character. + /// + Crown = 0xF521, + + /// + /// The Font Awesome "crutch" icon unicode character. + /// + Crutch = 0xF7F7, + + /// + /// The Font Awesome "css3" icon unicode character. + /// + Css3 = 0xF13C, + + /// + /// The Font Awesome "css3-alt" icon unicode character. + /// + Css3Alt = 0xF38B, + + /// + /// The Font Awesome "cube" icon unicode character. + /// + Cube = 0xF1B2, + + /// + /// The Font Awesome "cubes" icon unicode character. + /// + Cubes = 0xF1B3, + + /// + /// The Font Awesome "cut" icon unicode character. + /// + Cut = 0xF0C4, + + /// + /// The Font Awesome "cuttlefish" icon unicode character. + /// + Cuttlefish = 0xF38C, + + /// + /// The Font Awesome "dailymotion" icon unicode character. + /// + Dailymotion = 0xF952, + + /// + /// The Font Awesome "d-and-d" icon unicode character. + /// + DAndD = 0xF38D, + + /// + /// The Font Awesome "d-and-d-beyond" icon unicode character. + /// + DAndDBeyond = 0xF6CA, + + /// + /// The Font Awesome "dashcube" icon unicode character. + /// + Dashcube = 0xF21, + + /// + /// The Font Awesome "database" icon unicode character. + /// + Database = 0xF1C, + + /// + /// The Font Awesome "deaf" icon unicode character. + /// + Deaf = 0xF2A4, + + /// + /// The Font Awesome "delicious" icon unicode character. + /// + Delicious = 0xF1A5, + + /// + /// The Font Awesome "democrat" icon unicode character. + /// + Democrat = 0xF747, + + /// + /// The Font Awesome "deploydog" icon unicode character. + /// + Deploydog = 0xF38E, + + /// + /// The Font Awesome "deskpro" icon unicode character. + /// + Deskpro = 0xF38F, + + /// + /// The Font Awesome "desktop" icon unicode character. + /// + Desktop = 0xF108, + + /// + /// The Font Awesome "dev" icon unicode character. + /// + Dev = 0xF6CC, + + /// + /// The Font Awesome "deviantart" icon unicode character. + /// + Deviantart = 0xF1BD, + + /// + /// The Font Awesome "dharmachakra" icon unicode character. + /// + Dharmachakra = 0xF655, + + /// + /// The Font Awesome "dhl" icon unicode character. + /// + Dhl = 0xF79, + + /// + /// The Font Awesome "diagnoses" icon unicode character. + /// + Diagnoses = 0xF47, + + /// + /// The Font Awesome "diaspora" icon unicode character. + /// + Diaspora = 0xF791, + + /// + /// The Font Awesome "dice" icon unicode character. + /// + Dice = 0xF522, + + /// + /// The Font Awesome "dice-d20" icon unicode character. + /// + DiceD20 = 0xF6CF, + + /// + /// The Font Awesome "dice-d6" icon unicode character. + /// + DiceD6 = 0xF6D1, + + /// + /// The Font Awesome "dice-five" icon unicode character. + /// + DiceFive = 0xF523, + + /// + /// The Font Awesome "dice-four" icon unicode character. + /// + DiceFour = 0xF524, + + /// + /// The Font Awesome "dice-one" icon unicode character. + /// + DiceOne = 0xF525, + + /// + /// The Font Awesome "dice-six" icon unicode character. + /// + DiceSix = 0xF526, + + /// + /// The Font Awesome "dice-three" icon unicode character. + /// + DiceThree = 0xF527, + + /// + /// The Font Awesome "dice-two" icon unicode character. + /// + DiceTwo = 0xF528, + + /// + /// The Font Awesome "digg" icon unicode character. + /// + Digg = 0xF1A6, + + /// + /// The Font Awesome "digital-ocean" icon unicode character. + /// + DigitalOcean = 0xF391, + + /// + /// The Font Awesome "digital-tachograph" icon unicode character. + /// + DigitalTachograph = 0xF566, + + /// + /// The Font Awesome "directions" icon unicode character. + /// + Directions = 0xF5EB, + + /// + /// The Font Awesome "discord" icon unicode character. + /// + Discord = 0xF392, + + /// + /// The Font Awesome "discourse" icon unicode character. + /// + Discourse = 0xF393, + + /// + /// The Font Awesome "divide" icon unicode character. + /// + Divide = 0xF529, + + /// + /// The Font Awesome "dizzy" icon unicode character. + /// + Dizzy = 0xF567, + + /// + /// The Font Awesome "dna" icon unicode character. + /// + Dna = 0xF471, + + /// + /// The Font Awesome "dochub" icon unicode character. + /// + Dochub = 0xF394, + + /// + /// The Font Awesome "docker" icon unicode character. + /// + Docker = 0xF395, + + /// + /// The Font Awesome "dog" icon unicode character. + /// + Dog = 0xF6D3, + + /// + /// The Font Awesome "dollar-sign" icon unicode character. + /// + DollarSign = 0xF155, + + /// + /// The Font Awesome "dolly" icon unicode character. + /// + Dolly = 0xF472, + + /// + /// The Font Awesome "dolly-flatbed" icon unicode character. + /// + DollyFlatbed = 0xF474, + + /// + /// The Font Awesome "donate" icon unicode character. + /// + Donate = 0xF4B9, + + /// + /// The Font Awesome "door-closed" icon unicode character. + /// + DoorClosed = 0xF52A, + + /// + /// The Font Awesome "door-open" icon unicode character. + /// + DoorOpen = 0xF52B, + + /// + /// The Font Awesome "dot-circle" icon unicode character. + /// + DotCircle = 0xF192, + + /// + /// The Font Awesome "dove" icon unicode character. + /// + Dove = 0xF4BA, + + /// + /// The Font Awesome "download" icon unicode character. + /// + Download = 0xF019, + + /// + /// The Font Awesome "draft2digital" icon unicode character. + /// + Draft2digital = 0xF396, + + /// + /// The Font Awesome "drafting-compass" icon unicode character. + /// + DraftingCompass = 0xF568, + + /// + /// The Font Awesome "dragon" icon unicode character. + /// + Dragon = 0xF6D5, + + /// + /// The Font Awesome "draw-polygon" icon unicode character. + /// + DrawPolygon = 0xF5EE, + + /// + /// The Font Awesome "dribbble" icon unicode character. + /// + Dribbble = 0xF17D, + + /// + /// The Font Awesome "dribbble-square" icon unicode character. + /// + DribbbleSquare = 0xF397, + + /// + /// The Font Awesome "dropbox" icon unicode character. + /// + Dropbox = 0xF16B, + + /// + /// The Font Awesome "drum" icon unicode character. + /// + Drum = 0xF569, + + /// + /// The Font Awesome "drum-steelpan" icon unicode character. + /// + DrumSteelpan = 0xF56A, + + /// + /// The Font Awesome "drumstick-bite" icon unicode character. + /// + DrumstickBite = 0xF6D7, + + /// + /// The Font Awesome "drupal" icon unicode character. + /// + Drupal = 0xF1A9, + + /// + /// The Font Awesome "dumbbell" icon unicode character. + /// + Dumbbell = 0xF44B, + + /// + /// The Font Awesome "dumpster" icon unicode character. + /// + Dumpster = 0xF793, + + /// + /// The Font Awesome "dumpster-fire" icon unicode character. + /// + DumpsterFire = 0xF794, + + /// + /// The Font Awesome "dungeon" icon unicode character. + /// + Dungeon = 0xF6D9, + + /// + /// The Font Awesome "dyalog" icon unicode character. + /// + Dyalog = 0xF399, + + /// + /// The Font Awesome "earlybirds" icon unicode character. + /// + Earlybirds = 0xF39A, + + /// + /// The Font Awesome "ebay" icon unicode character. + /// + Ebay = 0xF4F4, + + /// + /// The Font Awesome "edge" icon unicode character. + /// + Edge = 0xF282, + + /// + /// The Font Awesome "edit" icon unicode character. + /// + Edit = 0xF044, + + /// + /// The Font Awesome "egg" icon unicode character. + /// + Egg = 0xF7FB, + + /// + /// The Font Awesome "eject" icon unicode character. + /// + Eject = 0xF052, + + /// + /// The Font Awesome "elementor" icon unicode character. + /// + Elementor = 0xF43, + + /// + /// The Font Awesome "ellipsis-h" icon unicode character. + /// + EllipsisH = 0xF141, + + /// + /// The Font Awesome "ellipsis-v" icon unicode character. + /// + EllipsisV = 0xF142, + + /// + /// The Font Awesome "ello" icon unicode character. + /// + Ello = 0xF5F1, + + /// + /// The Font Awesome "ember" icon unicode character. + /// + Ember = 0xF423, + + /// + /// The Font Awesome "empire" icon unicode character. + /// + Empire = 0xF1D1, + + /// + /// The Font Awesome "envelope" icon unicode character. + /// + Envelope = 0xF0E, + + /// + /// The Font Awesome "envelope-open" icon unicode character. + /// + EnvelopeOpen = 0xF2B6, + + /// + /// The Font Awesome "envelope-open-text" icon unicode character. + /// + EnvelopeOpenText = 0xF658, + + /// + /// The Font Awesome "envelope-square" icon unicode character. + /// + EnvelopeSquare = 0xF199, + + /// + /// The Font Awesome "envira" icon unicode character. + /// + Envira = 0xF299, + + /// + /// The Font Awesome "equals" icon unicode character. + /// + Equals = 0xF52C, + + /// + /// The Font Awesome "eraser" icon unicode character. + /// + Eraser = 0xF12D, + + /// + /// The Font Awesome "erlang" icon unicode character. + /// + Erlang = 0xF39D, + + /// + /// The Font Awesome "ethereum" icon unicode character. + /// + Ethereum = 0xF42E, + + /// + /// The Font Awesome "ethernet" icon unicode character. + /// + Ethernet = 0xF796, + + /// + /// The Font Awesome "etsy" icon unicode character. + /// + Etsy = 0xF2D7, + + /// + /// The Font Awesome "euro-sign" icon unicode character. + /// + EuroSign = 0xF153, + + /// + /// The Font Awesome "evernote" icon unicode character. + /// + Evernote = 0xF839, + + /// + /// The Font Awesome "exchange-alt" icon unicode character. + /// + ExchangeAlt = 0xF362, + + /// + /// The Font Awesome "exclamation" icon unicode character. + /// + Exclamation = 0xF12A, + + /// + /// The Font Awesome "exclamation-circle" icon unicode character. + /// + ExclamationCircle = 0xF06A, + + /// + /// The Font Awesome "exclamation-triangle" icon unicode character. + /// + ExclamationTriangle = 0xF071, + + /// + /// The Font Awesome "expand" icon unicode character. + /// + Expand = 0xF065, + + /// + /// The Font Awesome "expand-alt" icon unicode character. + /// + ExpandAlt = 0xF424, + + /// + /// The Font Awesome "expand-arrows-alt" icon unicode character. + /// + ExpandArrowsAlt = 0xF31E, + + /// + /// The Font Awesome "expeditedssl" icon unicode character. + /// + Expeditedssl = 0xF23E, + + /// + /// The Font Awesome "external-link-alt" icon unicode character. + /// + ExternalLinkAlt = 0xF35D, + + /// + /// The Font Awesome "external-link-square-alt" icon unicode character. + /// + ExternalLinkSquareAlt = 0xF36, + + /// + /// The Font Awesome "eye" icon unicode character. + /// + Eye = 0xF06E, + + /// + /// The Font Awesome "eye-dropper" icon unicode character. + /// + EyeDropper = 0xF1FB, + + /// + /// The Font Awesome "eye-slash" icon unicode character. + /// + EyeSlash = 0xF07, + + /// + /// The Font Awesome "facebook" icon unicode character. + /// + Facebook = 0xF09A, + + /// + /// The Font Awesome "facebook-f" icon unicode character. + /// + FacebookF = 0xF39E, + + /// + /// The Font Awesome "facebook-messenger" icon unicode character. + /// + FacebookMessenger = 0xF39F, + + /// + /// The Font Awesome "facebook-square" icon unicode character. + /// + FacebookSquare = 0xF082, + + /// + /// The Font Awesome "fan" icon unicode character. + /// + Fan = 0xF863, + + /// + /// The Font Awesome "fantasy-flight-games" icon unicode character. + /// + FantasyFlightGames = 0xF6DC, + + /// + /// The Font Awesome "fast-backward" icon unicode character. + /// + FastBackward = 0xF049, + + /// + /// The Font Awesome "fast-forward" icon unicode character. + /// + FastForward = 0xF05, + + /// + /// The Font Awesome "fax" icon unicode character. + /// + Fax = 0xF1AC, + + /// + /// The Font Awesome "feather" icon unicode character. + /// + Feather = 0xF52D, + + /// + /// The Font Awesome "feather-alt" icon unicode character. + /// + FeatherAlt = 0xF56B, + + /// + /// The Font Awesome "fedex" icon unicode character. + /// + Fedex = 0xF797, + + /// + /// The Font Awesome "fedora" icon unicode character. + /// + Fedora = 0xF798, + + /// + /// The Font Awesome "female" icon unicode character. + /// + Female = 0xF182, + + /// + /// The Font Awesome "fighter-jet" icon unicode character. + /// + FighterJet = 0xF0FB, + + /// + /// The Font Awesome "figma" icon unicode character. + /// + Figma = 0xF799, + + /// + /// The Font Awesome "file" icon unicode character. + /// + File = 0xF15B, + + /// + /// The Font Awesome "file-alt" icon unicode character. + /// + FileAlt = 0xF15C, + + /// + /// The Font Awesome "file-archive" icon unicode character. + /// + FileArchive = 0xF1C6, + + /// + /// The Font Awesome "file-audio" icon unicode character. + /// + FileAudio = 0xF1C7, + + /// + /// The Font Awesome "file-code" icon unicode character. + /// + FileCode = 0xF1C9, + + /// + /// The Font Awesome "file-contract" icon unicode character. + /// + FileContract = 0xF56C, + + /// + /// The Font Awesome "file-csv" icon unicode character. + /// + FileCsv = 0xF6DD, + + /// + /// The Font Awesome "file-download" icon unicode character. + /// + FileDownload = 0xF56D, + + /// + /// The Font Awesome "file-excel" icon unicode character. + /// + FileExcel = 0xF1C3, + + /// + /// The Font Awesome "file-export" icon unicode character. + /// + FileExport = 0xF56E, + + /// + /// The Font Awesome "file-image" icon unicode character. + /// + FileImage = 0xF1C5, + + /// + /// The Font Awesome "file-import" icon unicode character. + /// + FileImport = 0xF56F, + + /// + /// The Font Awesome "file-invoice" icon unicode character. + /// + FileInvoice = 0xF57, + + /// + /// The Font Awesome "file-invoice-dollar" icon unicode character. + /// + FileInvoiceDollar = 0xF571, + + /// + /// The Font Awesome "file-medical" icon unicode character. + /// + FileMedical = 0xF477, + + /// + /// The Font Awesome "file-medical-alt" icon unicode character. + /// + FileMedicalAlt = 0xF478, + + /// + /// The Font Awesome "file-pdf" icon unicode character. + /// + FilePdf = 0xF1C1, + + /// + /// The Font Awesome "file-powerpoint" icon unicode character. + /// + FilePowerpoint = 0xF1C4, + + /// + /// The Font Awesome "file-prescription" icon unicode character. + /// + FilePrescription = 0xF572, + + /// + /// The Font Awesome "file-signature" icon unicode character. + /// + FileSignature = 0xF573, + + /// + /// The Font Awesome "file-upload" icon unicode character. + /// + FileUpload = 0xF574, + + /// + /// The Font Awesome "file-video" icon unicode character. + /// + FileVideo = 0xF1C8, + + /// + /// The Font Awesome "file-word" icon unicode character. + /// + FileWord = 0xF1C2, + + /// + /// The Font Awesome "fill" icon unicode character. + /// + Fill = 0xF575, + + /// + /// The Font Awesome "fill-drip" icon unicode character. + /// + FillDrip = 0xF576, + + /// + /// The Font Awesome "film" icon unicode character. + /// + Film = 0xF008, + + /// + /// The Font Awesome "filter" icon unicode character. + /// + Filter = 0xF0B, + + /// + /// The Font Awesome "fingerprint" icon unicode character. + /// + Fingerprint = 0xF577, + + /// + /// The Font Awesome "fire" icon unicode character. + /// + Fire = 0xF06D, + + /// + /// The Font Awesome "fire-alt" icon unicode character. + /// + FireAlt = 0xF7E4, + + /// + /// The Font Awesome "fire-extinguisher" icon unicode character. + /// + FireExtinguisher = 0xF134, + + /// + /// The Font Awesome "firefox" icon unicode character. + /// + Firefox = 0xF269, + + /// + /// The Font Awesome "firefox-browser" icon unicode character. + /// + FirefoxBrowser = 0xF907, + + /// + /// The Font Awesome "first-aid" icon unicode character. + /// + FirstAid = 0xF479, + + /// + /// The Font Awesome "firstdraft" icon unicode character. + /// + Firstdraft = 0xF3A1, + + /// + /// The Font Awesome "first-order" icon unicode character. + /// + FirstOrder = 0xF2B, + + /// + /// The Font Awesome "first-order-alt" icon unicode character. + /// + FirstOrderAlt = 0xF50A, + + /// + /// The Font Awesome "fish" icon unicode character. + /// + Fish = 0xF578, + + /// + /// The Font Awesome "fist-raised" icon unicode character. + /// + FistRaised = 0xF6DE, + + /// + /// The Font Awesome "flag" icon unicode character. + /// + Flag = 0xF024, + + /// + /// The Font Awesome "flag-checkered" icon unicode character. + /// + FlagCheckered = 0xF11E, + + /// + /// The Font Awesome "flag-usa" icon unicode character. + /// + FlagUsa = 0xF74D, + + /// + /// The Font Awesome "flask" icon unicode character. + /// + Flask = 0xF0C3, + + /// + /// The Font Awesome "flickr" icon unicode character. + /// + Flickr = 0xF16E, + + /// + /// The Font Awesome "flipboard" icon unicode character. + /// + Flipboard = 0xF44D, + + /// + /// The Font Awesome "flushed" icon unicode character. + /// + Flushed = 0xF579, + + /// + /// The Font Awesome "fly" icon unicode character. + /// + Fly = 0xF417, + + /// + /// The Font Awesome "folder" icon unicode character. + /// + Folder = 0xF07B, + + /// + /// The Font Awesome "folder-minus" icon unicode character. + /// + FolderMinus = 0xF65D, + + /// + /// The Font Awesome "folder-open" icon unicode character. + /// + FolderOpen = 0xF07C, + + /// + /// The Font Awesome "folder-plus" icon unicode character. + /// + FolderPlus = 0xF65E, + + /// + /// The Font Awesome "font" icon unicode character. + /// + Font = 0xF031, + + /// + /// The Font Awesome "font-awesome" icon unicode character. + /// + FontAwesome = 0xF2B4, + + /// + /// The Font Awesome "font-awesome-alt" icon unicode character. + /// + FontAwesomeAlt = 0xF35C, + + /// + /// The Font Awesome "font-awesome-flag" icon unicode character. + /// + FontAwesomeFlag = 0xF425, + + /// + /// The Font Awesome "font-awesome-logo-full" icon unicode character. + /// + FontAwesomeLogoFull = 0xF4E6, + + /// + /// The Font Awesome "fonticons" icon unicode character. + /// + Fonticons = 0xF28, + + /// + /// The Font Awesome "fonticons-fi" icon unicode character. + /// + FonticonsFi = 0xF3A2, + + /// + /// The Font Awesome "football-ball" icon unicode character. + /// + FootballBall = 0xF44E, + + /// + /// The Font Awesome "fort-awesome" icon unicode character. + /// + FortAwesome = 0xF286, + + /// + /// The Font Awesome "fort-awesome-alt" icon unicode character. + /// + FortAwesomeAlt = 0xF3A3, + + /// + /// The Font Awesome "forumbee" icon unicode character. + /// + Forumbee = 0xF211, + + /// + /// The Font Awesome "forward" icon unicode character. + /// + Forward = 0xF04E, + + /// + /// The Font Awesome "foursquare" icon unicode character. + /// + Foursquare = 0xF18, + + /// + /// The Font Awesome "freebsd" icon unicode character. + /// + Freebsd = 0xF3A4, + + /// + /// The Font Awesome "free-code-camp" icon unicode character. + /// + FreeCodeCamp = 0xF2C5, + + /// + /// The Font Awesome "frog" icon unicode character. + /// + Frog = 0xF52E, + + /// + /// The Font Awesome "frown" icon unicode character. + /// + Frown = 0xF119, + + /// + /// The Font Awesome "frown-open" icon unicode character. + /// + FrownOpen = 0xF57A, + + /// + /// The Font Awesome "fulcrum" icon unicode character. + /// + Fulcrum = 0xF50B, + + /// + /// The Font Awesome "funnel-dollar" icon unicode character. + /// + FunnelDollar = 0xF662, + + /// + /// The Font Awesome "futbol" icon unicode character. + /// + Futbol = 0xF1E3, + + /// + /// The Font Awesome "galactic-republic" icon unicode character. + /// + GalacticRepublic = 0xF50C, + + /// + /// The Font Awesome "galactic-senate" icon unicode character. + /// + GalacticSenate = 0xF50D, + + /// + /// The Font Awesome "gamepad" icon unicode character. + /// + Gamepad = 0xF11B, + + /// + /// The Font Awesome "gas-pump" icon unicode character. + /// + GasPump = 0xF52F, + + /// + /// The Font Awesome "gavel" icon unicode character. + /// + Gavel = 0xF0E3, + + /// + /// The Font Awesome "gem" icon unicode character. + /// + Gem = 0xF3A5, + + /// + /// The Font Awesome "genderless" icon unicode character. + /// + Genderless = 0xF22D, + + /// + /// The Font Awesome "get-pocket" icon unicode character. + /// + GetPocket = 0xF265, + + /// + /// The Font Awesome "gg" icon unicode character. + /// + Gg = 0xF26, + + /// + /// The Font Awesome "gg-circle" icon unicode character. + /// + GgCircle = 0xF261, + + /// + /// The Font Awesome "ghost" icon unicode character. + /// + Ghost = 0xF6E2, + + /// + /// The Font Awesome "gift" icon unicode character. + /// + Gift = 0xF06B, + + /// + /// The Font Awesome "gifts" icon unicode character. + /// + Gifts = 0xF79C, + + /// + /// The Font Awesome "git" icon unicode character. + /// + Git = 0xF1D3, + + /// + /// The Font Awesome "git-alt" icon unicode character. + /// + GitAlt = 0xF841, + + /// + /// The Font Awesome "github" icon unicode character. + /// + Github = 0xF09B, + + /// + /// The Font Awesome "github-alt" icon unicode character. + /// + GithubAlt = 0xF113, + + /// + /// The Font Awesome "github-square" icon unicode character. + /// + GithubSquare = 0xF092, + + /// + /// The Font Awesome "gitkraken" icon unicode character. + /// + Gitkraken = 0xF3A6, + + /// + /// The Font Awesome "gitlab" icon unicode character. + /// + Gitlab = 0xF296, + + /// + /// The Font Awesome "git-square" icon unicode character. + /// + GitSquare = 0xF1D2, + + /// + /// The Font Awesome "gitter" icon unicode character. + /// + Gitter = 0xF426, + + /// + /// The Font Awesome "glass-cheers" icon unicode character. + /// + GlassCheers = 0xF79F, + + /// + /// The Font Awesome "glasses" icon unicode character. + /// + Glasses = 0xF53, + + /// + /// The Font Awesome "glass-martini" icon unicode character. + /// + GlassMartini = 0xF, + + /// + /// The Font Awesome "glass-martini-alt" icon unicode character. + /// + GlassMartiniAlt = 0xF57B, + + /// + /// The Font Awesome "glass-whiskey" icon unicode character. + /// + GlassWhiskey = 0xF7A, + + /// + /// The Font Awesome "glide" icon unicode character. + /// + Glide = 0xF2A5, + + /// + /// The Font Awesome "glide-g" icon unicode character. + /// + GlideG = 0xF2A6, + + /// + /// The Font Awesome "globe" icon unicode character. + /// + Globe = 0xF0AC, + + /// + /// The Font Awesome "globe-africa" icon unicode character. + /// + GlobeAfrica = 0xF57C, + + /// + /// The Font Awesome "globe-americas" icon unicode character. + /// + GlobeAmericas = 0xF57D, + + /// + /// The Font Awesome "globe-asia" icon unicode character. + /// + GlobeAsia = 0xF57E, + + /// + /// The Font Awesome "globe-europe" icon unicode character. + /// + GlobeEurope = 0xF7A2, + + /// + /// The Font Awesome "gofore" icon unicode character. + /// + Gofore = 0xF3A7, + + /// + /// The Font Awesome "golf-ball" icon unicode character. + /// + GolfBall = 0xF45, + + /// + /// The Font Awesome "goodreads" icon unicode character. + /// + Goodreads = 0xF3A8, + + /// + /// The Font Awesome "goodreads-g" icon unicode character. + /// + GoodreadsG = 0xF3A9, + + /// + /// The Font Awesome "google" icon unicode character. + /// + Google = 0xF1A, + + /// + /// The Font Awesome "google-drive" icon unicode character. + /// + GoogleDrive = 0xF3AA, + + /// + /// The Font Awesome "google-play" icon unicode character. + /// + GooglePlay = 0xF3AB, + + /// + /// The Font Awesome "google-plus" icon unicode character. + /// + GooglePlus = 0xF2B3, + + /// + /// The Font Awesome "google-plus-g" icon unicode character. + /// + GooglePlusG = 0xF0D5, + + /// + /// The Font Awesome "google-plus-square" icon unicode character. + /// + GooglePlusSquare = 0xF0D4, + + /// + /// The Font Awesome "google-wallet" icon unicode character. + /// + GoogleWallet = 0xF1EE, + + /// + /// The Font Awesome "gopuram" icon unicode character. + /// + Gopuram = 0xF664, + + /// + /// The Font Awesome "graduation-cap" icon unicode character. + /// + GraduationCap = 0xF19D, + + /// + /// The Font Awesome "gratipay" icon unicode character. + /// + Gratipay = 0xF184, + + /// + /// The Font Awesome "grav" icon unicode character. + /// + Grav = 0xF2D6, + + /// + /// The Font Awesome "greater-than" icon unicode character. + /// + GreaterThan = 0xF531, + + /// + /// The Font Awesome "greater-than-equal" icon unicode character. + /// + GreaterThanEqual = 0xF532, + + /// + /// The Font Awesome "grimace" icon unicode character. + /// + Grimace = 0xF57F, + + /// + /// The Font Awesome "grin" icon unicode character. + /// + Grin = 0xF58, + + /// + /// The Font Awesome "grin-alt" icon unicode character. + /// + GrinAlt = 0xF581, + + /// + /// The Font Awesome "grin-beam" icon unicode character. + /// + GrinBeam = 0xF582, + + /// + /// The Font Awesome "grin-beam-sweat" icon unicode character. + /// + GrinBeamSweat = 0xF583, + + /// + /// The Font Awesome "grin-hearts" icon unicode character. + /// + GrinHearts = 0xF584, + + /// + /// The Font Awesome "grin-squint" icon unicode character. + /// + GrinSquint = 0xF585, + + /// + /// The Font Awesome "grin-squint-tears" icon unicode character. + /// + GrinSquintTears = 0xF586, + + /// + /// The Font Awesome "grin-stars" icon unicode character. + /// + GrinStars = 0xF587, + + /// + /// The Font Awesome "grin-tears" icon unicode character. + /// + GrinTears = 0xF588, + + /// + /// The Font Awesome "grin-tongue" icon unicode character. + /// + GrinTongue = 0xF589, + + /// + /// The Font Awesome "grin-tongue-squint" icon unicode character. + /// + GrinTongueSquint = 0xF58A, + + /// + /// The Font Awesome "grin-tongue-wink" icon unicode character. + /// + GrinTongueWink = 0xF58B, + + /// + /// The Font Awesome "grin-wink" icon unicode character. + /// + GrinWink = 0xF58C, + + /// + /// The Font Awesome "gripfire" icon unicode character. + /// + Gripfire = 0xF3AC, + + /// + /// The Font Awesome "grip-horizontal" icon unicode character. + /// + GripHorizontal = 0xF58D, + + /// + /// The Font Awesome "grip-lines" icon unicode character. + /// + GripLines = 0xF7A4, + + /// + /// The Font Awesome "grip-lines-vertical" icon unicode character. + /// + GripLinesVertical = 0xF7A5, + + /// + /// The Font Awesome "grip-vertical" icon unicode character. + /// + GripVertical = 0xF58E, + + /// + /// The Font Awesome "grunt" icon unicode character. + /// + Grunt = 0xF3AD, + + /// + /// The Font Awesome "guitar" icon unicode character. + /// + Guitar = 0xF7A6, + + /// + /// The Font Awesome "gulp" icon unicode character. + /// + Gulp = 0xF3AE, + + /// + /// The Font Awesome "hacker-news" icon unicode character. + /// + HackerNews = 0xF1D4, + + /// + /// The Font Awesome "hacker-news-square" icon unicode character. + /// + HackerNewsSquare = 0xF3AF, + + /// + /// The Font Awesome "hackerrank" icon unicode character. + /// + Hackerrank = 0xF5F7, + + /// + /// The Font Awesome "hamburger" icon unicode character. + /// + Hamburger = 0xF805, + + /// + /// The Font Awesome "hammer" icon unicode character. + /// + Hammer = 0xF6E3, + + /// + /// The Font Awesome "hamsa" icon unicode character. + /// + Hamsa = 0xF665, + + /// + /// The Font Awesome "hand-holding" icon unicode character. + /// + HandHolding = 0xF4BD, + + /// + /// The Font Awesome "hand-holding-heart" icon unicode character. + /// + HandHoldingHeart = 0xF4BE, + + /// + /// The Font Awesome "hand-holding-usd" icon unicode character. + /// + HandHoldingUsd = 0xF4C, + + /// + /// The Font Awesome "hand-lizard" icon unicode character. + /// + HandLizard = 0xF258, + + /// + /// The Font Awesome "hand-middle-finger" icon unicode character. + /// + HandMiddleFinger = 0xF806, + + /// + /// The Font Awesome "hand-paper" icon unicode character. + /// + HandPaper = 0xF256, + + /// + /// The Font Awesome "hand-peace" icon unicode character. + /// + HandPeace = 0xF25B, + + /// + /// The Font Awesome "hand-point-down" icon unicode character. + /// + HandPointDown = 0xF0A7, + + /// + /// The Font Awesome "hand-pointer" icon unicode character. + /// + HandPointer = 0xF25A, + + /// + /// The Font Awesome "hand-point-left" icon unicode character. + /// + HandPointLeft = 0xF0A5, + + /// + /// The Font Awesome "hand-point-right" icon unicode character. + /// + HandPointRight = 0xF0A4, + + /// + /// The Font Awesome "hand-point-up" icon unicode character. + /// + HandPointUp = 0xF0A6, + + /// + /// The Font Awesome "hand-rock" icon unicode character. + /// + HandRock = 0xF255, + + /// + /// The Font Awesome "hands" icon unicode character. + /// + Hands = 0xF4C2, + + /// + /// The Font Awesome "hand-scissors" icon unicode character. + /// + HandScissors = 0xF257, + + /// + /// The Font Awesome "handshake" icon unicode character. + /// + Handshake = 0xF2B5, + + /// + /// The Font Awesome "hands-helping" icon unicode character. + /// + HandsHelping = 0xF4C4, + + /// + /// The Font Awesome "hand-spock" icon unicode character. + /// + HandSpock = 0xF259, + + /// + /// The Font Awesome "hanukiah" icon unicode character. + /// + Hanukiah = 0xF6E6, + + /// + /// The Font Awesome "hard-hat" icon unicode character. + /// + HardHat = 0xF807, + + /// + /// The Font Awesome "hashtag" icon unicode character. + /// + Hashtag = 0xF292, + + /// + /// The Font Awesome "hat-cowboy" icon unicode character. + /// + HatCowboy = 0xF8C, + + /// + /// The Font Awesome "hat-cowboy-side" icon unicode character. + /// + HatCowboySide = 0xF8C1, + + /// + /// The Font Awesome "hat-wizard" icon unicode character. + /// + HatWizard = 0xF6E8, + + /// + /// The Font Awesome "hdd" icon unicode character. + /// + Hdd = 0xF0A, + + /// + /// The Font Awesome "heading" icon unicode character. + /// + Heading = 0xF1DC, + + /// + /// The Font Awesome "headphones" icon unicode character. + /// + Headphones = 0xF025, + + /// + /// The Font Awesome "headphones-alt" icon unicode character. + /// + HeadphonesAlt = 0xF58F, + + /// + /// The Font Awesome "headset" icon unicode character. + /// + Headset = 0xF59, + + /// + /// The Font Awesome "heart" icon unicode character. + /// + Heart = 0xF004, + + /// + /// The Font Awesome "heartbeat" icon unicode character. + /// + Heartbeat = 0xF21E, + + /// + /// The Font Awesome "heart-broken" icon unicode character. + /// + HeartBroken = 0xF7A9, + + /// + /// The Font Awesome "helicopter" icon unicode character. + /// + Helicopter = 0xF533, + + /// + /// The Font Awesome "highlighter" icon unicode character. + /// + Highlighter = 0xF591, + + /// + /// The Font Awesome "hiking" icon unicode character. + /// + Hiking = 0xF6EC, + + /// + /// The Font Awesome "hippo" icon unicode character. + /// + Hippo = 0xF6ED, + + /// + /// The Font Awesome "hips" icon unicode character. + /// + Hips = 0xF452, + + /// + /// The Font Awesome "hire-a-helper" icon unicode character. + /// + HireAHelper = 0xF3B, + + /// + /// The Font Awesome "history" icon unicode character. + /// + History = 0xF1DA, + + /// + /// The Font Awesome "hockey-puck" icon unicode character. + /// + HockeyPuck = 0xF453, + + /// + /// The Font Awesome "holly-berry" icon unicode character. + /// + HollyBerry = 0xF7AA, + + /// + /// The Font Awesome "home" icon unicode character. + /// + Home = 0xF015, + + /// + /// The Font Awesome "hooli" icon unicode character. + /// + Hooli = 0xF427, + + /// + /// The Font Awesome "hornbill" icon unicode character. + /// + Hornbill = 0xF592, + + /// + /// The Font Awesome "horse" icon unicode character. + /// + Horse = 0xF6F, + + /// + /// The Font Awesome "horse-head" icon unicode character. + /// + HorseHead = 0xF7AB, + + /// + /// The Font Awesome "hospital" icon unicode character. + /// + Hospital = 0xF0F8, + + /// + /// The Font Awesome "hospital-alt" icon unicode character. + /// + HospitalAlt = 0xF47D, + + /// + /// The Font Awesome "hospital-symbol" icon unicode character. + /// + HospitalSymbol = 0xF47E, + + /// + /// The Font Awesome "hotdog" icon unicode character. + /// + Hotdog = 0xF80F, + + /// + /// The Font Awesome "hotel" icon unicode character. + /// + Hotel = 0xF594, + + /// + /// The Font Awesome "hotjar" icon unicode character. + /// + Hotjar = 0xF3B1, + + /// + /// The Font Awesome "hot-tub" icon unicode character. + /// + HotTub = 0xF593, + + /// + /// The Font Awesome "hourglass" icon unicode character. + /// + Hourglass = 0xF254, + + /// + /// The Font Awesome "hourglass-end" icon unicode character. + /// + HourglassEnd = 0xF253, + + /// + /// The Font Awesome "hourglass-half" icon unicode character. + /// + HourglassHalf = 0xF252, + + /// + /// The Font Awesome "hourglass-start" icon unicode character. + /// + HourglassStart = 0xF251, + + /// + /// The Font Awesome "house-damage" icon unicode character. + /// + HouseDamage = 0xF6F1, + + /// + /// The Font Awesome "houzz" icon unicode character. + /// + Houzz = 0xF27C, + + /// + /// The Font Awesome "hryvnia" icon unicode character. + /// + Hryvnia = 0xF6F2, + + /// + /// The Font Awesome "h-square" icon unicode character. + /// + HSquare = 0xF0FD, + + /// + /// The Font Awesome "html5" icon unicode character. + /// + Html5 = 0xF13B, + + /// + /// The Font Awesome "hubspot" icon unicode character. + /// + Hubspot = 0xF3B2, + + /// + /// The Font Awesome "ice-cream" icon unicode character. + /// + IceCream = 0xF81, + + /// + /// The Font Awesome "icicles" icon unicode character. + /// + Icicles = 0xF7AD, + + /// + /// The Font Awesome "icons" icon unicode character. + /// + Icons = 0xF86D, + + /// + /// The Font Awesome "i-cursor" icon unicode character. + /// + ICursor = 0xF246, + + /// + /// The Font Awesome "id-badge" icon unicode character. + /// + IdBadge = 0xF2C1, + + /// + /// The Font Awesome "id-card" icon unicode character. + /// + IdCard = 0xF2C2, + + /// + /// The Font Awesome "id-card-alt" icon unicode character. + /// + IdCardAlt = 0xF47F, + + /// + /// The Font Awesome "ideal" icon unicode character. + /// + Ideal = 0xF913, + + /// + /// The Font Awesome "igloo" icon unicode character. + /// + Igloo = 0xF7AE, + + /// + /// The Font Awesome "image" icon unicode character. + /// + Image = 0xF03E, + + /// + /// The Font Awesome "images" icon unicode character. + /// + Images = 0xF302, + + /// + /// The Font Awesome "imdb" icon unicode character. + /// + Imdb = 0xF2D8, + + /// + /// The Font Awesome "inbox" icon unicode character. + /// + Inbox = 0xF01C, + + /// + /// The Font Awesome "indent" icon unicode character. + /// + Indent = 0xF03C, + + /// + /// The Font Awesome "industry" icon unicode character. + /// + Industry = 0xF275, + + /// + /// The Font Awesome "infinity" icon unicode character. + /// + Infinity = 0xF534, + + /// + /// The Font Awesome "info" icon unicode character. + /// + Info = 0xF129, + + /// + /// The Font Awesome "info-circle" icon unicode character. + /// + InfoCircle = 0xF05A, + + /// + /// The Font Awesome "instagram" icon unicode character. + /// + Instagram = 0xF16D, + + /// + /// The Font Awesome "instagram-square" icon unicode character. + /// + InstagramSquare = 0xF955, + + /// + /// The Font Awesome "intercom" icon unicode character. + /// + Intercom = 0xF7AF, + + /// + /// The Font Awesome "internet-explorer" icon unicode character. + /// + InternetExplorer = 0xF26B, + + /// + /// The Font Awesome "invision" icon unicode character. + /// + Invision = 0xF7B, + + /// + /// The Font Awesome "ioxhost" icon unicode character. + /// + Ioxhost = 0xF208, + + /// + /// The Font Awesome "italic" icon unicode character. + /// + Italic = 0xF033, + + /// + /// The Font Awesome "itch-io" icon unicode character. + /// + ItchIo = 0xF83A, + + /// + /// The Font Awesome "itunes" icon unicode character. + /// + Itunes = 0xF3B4, + + /// + /// The Font Awesome "itunes-note" icon unicode character. + /// + ItunesNote = 0xF3B5, + + /// + /// The Font Awesome "java" icon unicode character. + /// + Java = 0xF4E4, + + /// + /// The Font Awesome "jedi" icon unicode character. + /// + Jedi = 0xF669, + + /// + /// The Font Awesome "jedi-order" icon unicode character. + /// + JediOrder = 0xF50E, + + /// + /// The Font Awesome "jenkins" icon unicode character. + /// + Jenkins = 0xF3B6, + + /// + /// The Font Awesome "jira" icon unicode character. + /// + Jira = 0xF7B1, + + /// + /// The Font Awesome "joget" icon unicode character. + /// + Joget = 0xF3B7, + + /// + /// The Font Awesome "joint" icon unicode character. + /// + Joint = 0xF595, + + /// + /// The Font Awesome "joomla" icon unicode character. + /// + Joomla = 0xF1AA, + + /// + /// The Font Awesome "journal-whills" icon unicode character. + /// + JournalWhills = 0xF66A, + + /// + /// The Font Awesome "js" icon unicode character. + /// + Js = 0xF3B8, + + /// + /// The Font Awesome "jsfiddle" icon unicode character. + /// + Jsfiddle = 0xF1CC, + + /// + /// The Font Awesome "js-square" icon unicode character. + /// + JsSquare = 0xF3B9, + + /// + /// The Font Awesome "kaaba" icon unicode character. + /// + Kaaba = 0xF66B, + + /// + /// The Font Awesome "kaggle" icon unicode character. + /// + Kaggle = 0xF5FA, + + /// + /// The Font Awesome "key" icon unicode character. + /// + Key = 0xF084, + + /// + /// The Font Awesome "keybase" icon unicode character. + /// + Keybase = 0xF4F5, + + /// + /// The Font Awesome "keyboard" icon unicode character. + /// + Keyboard = 0xF11C, + + /// + /// The Font Awesome "keycdn" icon unicode character. + /// + Keycdn = 0xF3BA, + + /// + /// The Font Awesome "khanda" icon unicode character. + /// + Khanda = 0xF66D, + + /// + /// The Font Awesome "kickstarter" icon unicode character. + /// + Kickstarter = 0xF3BB, + + /// + /// The Font Awesome "kickstarter-k" icon unicode character. + /// + KickstarterK = 0xF3BC, + + /// + /// The Font Awesome "kiss" icon unicode character. + /// + Kiss = 0xF596, + + /// + /// The Font Awesome "kiss-beam" icon unicode character. + /// + KissBeam = 0xF597, + + /// + /// The Font Awesome "kiss-wink-heart" icon unicode character. + /// + KissWinkHeart = 0xF598, + + /// + /// The Font Awesome "kiwi-bird" icon unicode character. + /// + KiwiBird = 0xF535, + + /// + /// The Font Awesome "korvue" icon unicode character. + /// + Korvue = 0xF42F, + + /// + /// The Font Awesome "landmark" icon unicode character. + /// + Landmark = 0xF66F, + + /// + /// The Font Awesome "language" icon unicode character. + /// + Language = 0xF1AB, + + /// + /// The Font Awesome "laptop" icon unicode character. + /// + Laptop = 0xF109, + + /// + /// The Font Awesome "laptop-code" icon unicode character. + /// + LaptopCode = 0xF5FC, + + /// + /// The Font Awesome "laptop-medical" icon unicode character. + /// + LaptopMedical = 0xF812, + + /// + /// The Font Awesome "laravel" icon unicode character. + /// + Laravel = 0xF3BD, + + /// + /// The Font Awesome "lastfm" icon unicode character. + /// + Lastfm = 0xF202, + + /// + /// The Font Awesome "lastfm-square" icon unicode character. + /// + LastfmSquare = 0xF203, + + /// + /// The Font Awesome "laugh" icon unicode character. + /// + Laugh = 0xF599, + + /// + /// The Font Awesome "laugh-beam" icon unicode character. + /// + LaughBeam = 0xF59A, + + /// + /// The Font Awesome "laugh-squint" icon unicode character. + /// + LaughSquint = 0xF59B, + + /// + /// The Font Awesome "laugh-wink" icon unicode character. + /// + LaughWink = 0xF59C, + + /// + /// The Font Awesome "layer-group" icon unicode character. + /// + LayerGroup = 0xF5FD, + + /// + /// The Font Awesome "leaf" icon unicode character. + /// + Leaf = 0xF06C, + + /// + /// The Font Awesome "leanpub" icon unicode character. + /// + Leanpub = 0xF212, + + /// + /// The Font Awesome "lemon" icon unicode character. + /// + Lemon = 0xF094, + + /// + /// The Font Awesome "less" icon unicode character. + /// + Less = 0xF41D, + + /// + /// The Font Awesome "less-than" icon unicode character. + /// + LessThan = 0xF536, + + /// + /// The Font Awesome "less-than-equal" icon unicode character. + /// + LessThanEqual = 0xF537, + + /// + /// The Font Awesome "level-down-alt" icon unicode character. + /// + LevelDownAlt = 0xF3BE, + + /// + /// The Font Awesome "level-up-alt" icon unicode character. + /// + LevelUpAlt = 0xF3BF, + + /// + /// The Font Awesome "life-ring" icon unicode character. + /// + LifeRing = 0xF1CD, + + /// + /// The Font Awesome "lightbulb" icon unicode character. + /// + Lightbulb = 0xF0EB, + + /// + /// The Font Awesome "line" icon unicode character. + /// + Line = 0xF3C, + + /// + /// The Font Awesome "link" icon unicode character. + /// + Link = 0xF0C1, + + /// + /// The Font Awesome "linkedin" icon unicode character. + /// + Linkedin = 0xF08C, + + /// + /// The Font Awesome "linkedin-in" icon unicode character. + /// + LinkedinIn = 0xF0E1, + + /// + /// The Font Awesome "linode" icon unicode character. + /// + Linode = 0xF2B8, + + /// + /// The Font Awesome "linux" icon unicode character. + /// + Linux = 0xF17C, + + /// + /// The Font Awesome "lira-sign" icon unicode character. + /// + LiraSign = 0xF195, + + /// + /// The Font Awesome "list" icon unicode character. + /// + List = 0xF03A, + + /// + /// The Font Awesome "list-alt" icon unicode character. + /// + ListAlt = 0xF022, + + /// + /// The Font Awesome "list-ol" icon unicode character. + /// + ListOl = 0xF0CB, + + /// + /// The Font Awesome "list-ul" icon unicode character. + /// + ListUl = 0xF0CA, + + /// + /// The Font Awesome "location-arrow" icon unicode character. + /// + LocationArrow = 0xF124, + + /// + /// The Font Awesome "lock" icon unicode character. + /// + Lock = 0xF023, + + /// + /// The Font Awesome "lock-open" icon unicode character. + /// + LockOpen = 0xF3C1, + + /// + /// The Font Awesome "long-arrow-alt-down" icon unicode character. + /// + LongArrowAltDown = 0xF309, + + /// + /// The Font Awesome "long-arrow-alt-left" icon unicode character. + /// + LongArrowAltLeft = 0xF30A, + + /// + /// The Font Awesome "long-arrow-alt-right" icon unicode character. + /// + LongArrowAltRight = 0xF30B, + + /// + /// The Font Awesome "long-arrow-alt-up" icon unicode character. + /// + LongArrowAltUp = 0xF30C, + + /// + /// The Font Awesome "low-vision" icon unicode character. + /// + LowVision = 0xF2A8, + + /// + /// The Font Awesome "luggage-cart" icon unicode character. + /// + LuggageCart = 0xF59D, + + /// + /// The Font Awesome "lyft" icon unicode character. + /// + Lyft = 0xF3C3, + + /// + /// The Font Awesome "magento" icon unicode character. + /// + Magento = 0xF3C4, + + /// + /// The Font Awesome "magic" icon unicode character. + /// + Magic = 0xF0D, + + /// + /// The Font Awesome "magnet" icon unicode character. + /// + Magnet = 0xF076, + + /// + /// The Font Awesome "mail-bulk" icon unicode character. + /// + MailBulk = 0xF674, + + /// + /// The Font Awesome "mailchimp" icon unicode character. + /// + Mailchimp = 0xF59E, + + /// + /// The Font Awesome "male" icon unicode character. + /// + Male = 0xF183, + + /// + /// The Font Awesome "mandalorian" icon unicode character. + /// + Mandalorian = 0xF50F, + + /// + /// The Font Awesome "map" icon unicode character. + /// + Map = 0xF279, + + /// + /// The Font Awesome "map-marked" icon unicode character. + /// + MapMarked = 0xF59F, + + /// + /// The Font Awesome "map-marked-alt" icon unicode character. + /// + MapMarkedAlt = 0xF5A, + + /// + /// The Font Awesome "map-marker" icon unicode character. + /// + MapMarker = 0xF041, + + /// + /// The Font Awesome "map-marker-alt" icon unicode character. + /// + MapMarkerAlt = 0xF3C5, + + /// + /// The Font Awesome "map-pin" icon unicode character. + /// + MapPin = 0xF276, + + /// + /// The Font Awesome "map-signs" icon unicode character. + /// + MapSigns = 0xF277, + + /// + /// The Font Awesome "markdown" icon unicode character. + /// + Markdown = 0xF60F, + + /// + /// The Font Awesome "marker" icon unicode character. + /// + Marker = 0xF5A1, + + /// + /// The Font Awesome "mars" icon unicode character. + /// + Mars = 0xF222, + + /// + /// The Font Awesome "mars-double" icon unicode character. + /// + MarsDouble = 0xF227, + + /// + /// The Font Awesome "mars-stroke" icon unicode character. + /// + MarsStroke = 0xF229, + + /// + /// The Font Awesome "mars-stroke-h" icon unicode character. + /// + MarsStrokeH = 0xF22B, + + /// + /// The Font Awesome "mars-stroke-v" icon unicode character. + /// + MarsStrokeV = 0xF22A, + + /// + /// The Font Awesome "mask" icon unicode character. + /// + Mask = 0xF6FA, + + /// + /// The Font Awesome "mastodon" icon unicode character. + /// + Mastodon = 0xF4F6, + + /// + /// The Font Awesome "maxcdn" icon unicode character. + /// + Maxcdn = 0xF136, + + /// + /// The Font Awesome "mdb" icon unicode character. + /// + Mdb = 0xF8CA, + + /// + /// The Font Awesome "medal" icon unicode character. + /// + Medal = 0xF5A2, + + /// + /// The Font Awesome "medapps" icon unicode character. + /// + Medapps = 0xF3C6, + + /// + /// The Font Awesome "medium" icon unicode character. + /// + Medium = 0xF23A, + + /// + /// The Font Awesome "medium-m" icon unicode character. + /// + MediumM = 0xF3C7, + + /// + /// The Font Awesome "medkit" icon unicode character. + /// + Medkit = 0xF0FA, + + /// + /// The Font Awesome "medrt" icon unicode character. + /// + Medrt = 0xF3C8, + + /// + /// The Font Awesome "meetup" icon unicode character. + /// + Meetup = 0xF2E, + + /// + /// The Font Awesome "megaport" icon unicode character. + /// + Megaport = 0xF5A3, + + /// + /// The Font Awesome "meh" icon unicode character. + /// + Meh = 0xF11A, + + /// + /// The Font Awesome "meh-blank" icon unicode character. + /// + MehBlank = 0xF5A4, + + /// + /// The Font Awesome "meh-rolling-eyes" icon unicode character. + /// + MehRollingEyes = 0xF5A5, + + /// + /// The Font Awesome "memory" icon unicode character. + /// + Memory = 0xF538, + + /// + /// The Font Awesome "mendeley" icon unicode character. + /// + Mendeley = 0xF7B3, + + /// + /// The Font Awesome "menorah" icon unicode character. + /// + Menorah = 0xF676, + + /// + /// The Font Awesome "mercury" icon unicode character. + /// + Mercury = 0xF223, + + /// + /// The Font Awesome "meteor" icon unicode character. + /// + Meteor = 0xF753, + + /// + /// The Font Awesome "microblog" icon unicode character. + /// + Microblog = 0xF91A, + + /// + /// The Font Awesome "microchip" icon unicode character. + /// + Microchip = 0xF2DB, + + /// + /// The Font Awesome "microphone" icon unicode character. + /// + Microphone = 0xF13, + + /// + /// The Font Awesome "microphone-alt" icon unicode character. + /// + MicrophoneAlt = 0xF3C9, + + /// + /// The Font Awesome "microphone-alt-slash" icon unicode character. + /// + MicrophoneAltSlash = 0xF539, + + /// + /// The Font Awesome "microphone-slash" icon unicode character. + /// + MicrophoneSlash = 0xF131, + + /// + /// The Font Awesome "microscope" icon unicode character. + /// + Microscope = 0xF61, + + /// + /// The Font Awesome "microsoft" icon unicode character. + /// + Microsoft = 0xF3CA, + + /// + /// The Font Awesome "minus" icon unicode character. + /// + Minus = 0xF068, + + /// + /// The Font Awesome "minus-circle" icon unicode character. + /// + MinusCircle = 0xF056, + + /// + /// The Font Awesome "minus-square" icon unicode character. + /// + MinusSquare = 0xF146, + + /// + /// The Font Awesome "mitten" icon unicode character. + /// + Mitten = 0xF7B5, + + /// + /// The Font Awesome "mix" icon unicode character. + /// + Mix = 0xF3CB, + + /// + /// The Font Awesome "mixcloud" icon unicode character. + /// + Mixcloud = 0xF289, + + /// + /// The Font Awesome "mixer" icon unicode character. + /// + Mixer = 0xF956, + + /// + /// The Font Awesome "mizuni" icon unicode character. + /// + Mizuni = 0xF3CC, + + /// + /// The Font Awesome "mobile" icon unicode character. + /// + Mobile = 0xF10B, + + /// + /// The Font Awesome "mobile-alt" icon unicode character. + /// + MobileAlt = 0xF3CD, + + /// + /// The Font Awesome "modx" icon unicode character. + /// + Modx = 0xF285, + + /// + /// The Font Awesome "monero" icon unicode character. + /// + Monero = 0xF3D, + + /// + /// The Font Awesome "money-bill" icon unicode character. + /// + MoneyBill = 0xF0D6, + + /// + /// The Font Awesome "money-bill-alt" icon unicode character. + /// + MoneyBillAlt = 0xF3D1, + + /// + /// The Font Awesome "money-bill-wave" icon unicode character. + /// + MoneyBillWave = 0xF53A, + + /// + /// The Font Awesome "money-bill-wave-alt" icon unicode character. + /// + MoneyBillWaveAlt = 0xF53B, + + /// + /// The Font Awesome "money-check" icon unicode character. + /// + MoneyCheck = 0xF53C, + + /// + /// The Font Awesome "money-check-alt" icon unicode character. + /// + MoneyCheckAlt = 0xF53D, + + /// + /// The Font Awesome "monument" icon unicode character. + /// + Monument = 0xF5A6, + + /// + /// The Font Awesome "moon" icon unicode character. + /// + Moon = 0xF186, + + /// + /// The Font Awesome "mortar-pestle" icon unicode character. + /// + MortarPestle = 0xF5A7, + + /// + /// The Font Awesome "mosque" icon unicode character. + /// + Mosque = 0xF678, + + /// + /// The Font Awesome "motorcycle" icon unicode character. + /// + Motorcycle = 0xF21C, + + /// + /// The Font Awesome "mountain" icon unicode character. + /// + Mountain = 0xF6FC, + + /// + /// The Font Awesome "mouse" icon unicode character. + /// + Mouse = 0xF8CC, + + /// + /// The Font Awesome "mouse-pointer" icon unicode character. + /// + MousePointer = 0xF245, + + /// + /// The Font Awesome "mug-hot" icon unicode character. + /// + MugHot = 0xF7B6, + + /// + /// The Font Awesome "music" icon unicode character. + /// + Music = 0xF001, + + /// + /// The Font Awesome "napster" icon unicode character. + /// + Napster = 0xF3D2, + + /// + /// The Font Awesome "neos" icon unicode character. + /// + Neos = 0xF612, + + /// + /// The Font Awesome "network-wired" icon unicode character. + /// + NetworkWired = 0xF6FF, + + /// + /// The Font Awesome "neuter" icon unicode character. + /// + Neuter = 0xF22C, + + /// + /// The Font Awesome "newspaper" icon unicode character. + /// + Newspaper = 0xF1EA, + + /// + /// The Font Awesome "nimblr" icon unicode character. + /// + Nimblr = 0xF5A8, + + /// + /// The Font Awesome "node" icon unicode character. + /// + Node = 0xF419, + + /// + /// The Font Awesome "node-js" icon unicode character. + /// + NodeJs = 0xF3D3, + + /// + /// The Font Awesome "not-equal" icon unicode character. + /// + NotEqual = 0xF53E, + + /// + /// The Font Awesome "notes-medical" icon unicode character. + /// + NotesMedical = 0xF481, + + /// + /// The Font Awesome "npm" icon unicode character. + /// + Npm = 0xF3D4, + + /// + /// The Font Awesome "ns8" icon unicode character. + /// + Ns8 = 0xF3D5, + + /// + /// The Font Awesome "nutritionix" icon unicode character. + /// + Nutritionix = 0xF3D6, + + /// + /// The Font Awesome "object-group" icon unicode character. + /// + ObjectGroup = 0xF247, + + /// + /// The Font Awesome "object-ungroup" icon unicode character. + /// + ObjectUngroup = 0xF248, + + /// + /// The Font Awesome "odnoklassniki" icon unicode character. + /// + Odnoklassniki = 0xF263, + + /// + /// The Font Awesome "odnoklassniki-square" icon unicode character. + /// + OdnoklassnikiSquare = 0xF264, + + /// + /// The Font Awesome "oil-can" icon unicode character. + /// + OilCan = 0xF613, + + /// + /// The Font Awesome "old-republic" icon unicode character. + /// + OldRepublic = 0xF51, + + /// + /// The Font Awesome "om" icon unicode character. + /// + Om = 0xF679, + + /// + /// The Font Awesome "opencart" icon unicode character. + /// + Opencart = 0xF23D, + + /// + /// The Font Awesome "openid" icon unicode character. + /// + Openid = 0xF19B, + + /// + /// The Font Awesome "opera" icon unicode character. + /// + Opera = 0xF26A, + + /// + /// The Font Awesome "optin-monster" icon unicode character. + /// + OptinMonster = 0xF23C, + + /// + /// The Font Awesome "orcid" icon unicode character. + /// + Orcid = 0xF8D2, + + /// + /// The Font Awesome "osi" icon unicode character. + /// + Osi = 0xF41A, + + /// + /// The Font Awesome "otter" icon unicode character. + /// + Otter = 0xF7, + + /// + /// The Font Awesome "outdent" icon unicode character. + /// + Outdent = 0xF03B, + + /// + /// The Font Awesome "page4" icon unicode character. + /// + Page4 = 0xF3D7, + + /// + /// The Font Awesome "pagelines" icon unicode character. + /// + Pagelines = 0xF18C, + + /// + /// The Font Awesome "pager" icon unicode character. + /// + Pager = 0xF815, + + /// + /// The Font Awesome "paint-brush" icon unicode character. + /// + PaintBrush = 0xF1FC, + + /// + /// The Font Awesome "paint-roller" icon unicode character. + /// + PaintRoller = 0xF5AA, + + /// + /// The Font Awesome "palette" icon unicode character. + /// + Palette = 0xF53F, + + /// + /// The Font Awesome "palfed" icon unicode character. + /// + Palfed = 0xF3D8, + + /// + /// The Font Awesome "pallet" icon unicode character. + /// + Pallet = 0xF482, + + /// + /// The Font Awesome "paperclip" icon unicode character. + /// + Paperclip = 0xF0C6, + + /// + /// The Font Awesome "paper-plane" icon unicode character. + /// + PaperPlane = 0xF1D8, + + /// + /// The Font Awesome "parachute-box" icon unicode character. + /// + ParachuteBox = 0xF4CD, + + /// + /// The Font Awesome "paragraph" icon unicode character. + /// + Paragraph = 0xF1DD, + + /// + /// The Font Awesome "parking" icon unicode character. + /// + Parking = 0xF54, + + /// + /// The Font Awesome "passport" icon unicode character. + /// + Passport = 0xF5AB, + + /// + /// The Font Awesome "pastafarianism" icon unicode character. + /// + Pastafarianism = 0xF67B, + + /// + /// The Font Awesome "paste" icon unicode character. + /// + Paste = 0xF0EA, + + /// + /// The Font Awesome "patreon" icon unicode character. + /// + Patreon = 0xF3D9, + + /// + /// The Font Awesome "pause" icon unicode character. + /// + Pause = 0xF04C, + + /// + /// The Font Awesome "pause-circle" icon unicode character. + /// + PauseCircle = 0xF28B, + + /// + /// The Font Awesome "paw" icon unicode character. + /// + Paw = 0xF1B, + + /// + /// The Font Awesome "paypal" icon unicode character. + /// + Paypal = 0xF1ED, + + /// + /// The Font Awesome "peace" icon unicode character. + /// + Peace = 0xF67C, + + /// + /// The Font Awesome "pen" icon unicode character. + /// + Pen = 0xF304, + + /// + /// The Font Awesome "pen-alt" icon unicode character. + /// + PenAlt = 0xF305, + + /// + /// The Font Awesome "pencil-alt" icon unicode character. + /// + PencilAlt = 0xF303, + + /// + /// The Font Awesome "pencil-ruler" icon unicode character. + /// + PencilRuler = 0xF5AE, + + /// + /// The Font Awesome "pen-fancy" icon unicode character. + /// + PenFancy = 0xF5AC, + + /// + /// The Font Awesome "pen-nib" icon unicode character. + /// + PenNib = 0xF5AD, + + /// + /// The Font Awesome "penny-arcade" icon unicode character. + /// + PennyArcade = 0xF704, + + /// + /// The Font Awesome "pen-square" icon unicode character. + /// + PenSquare = 0xF14B, + + /// + /// The Font Awesome "people-carry" icon unicode character. + /// + PeopleCarry = 0xF4CE, + + /// + /// The Font Awesome "pepper-hot" icon unicode character. + /// + PepperHot = 0xF816, + + /// + /// The Font Awesome "percent" icon unicode character. + /// + Percent = 0xF295, + + /// + /// The Font Awesome "percentage" icon unicode character. + /// + Percentage = 0xF541, + + /// + /// The Font Awesome "periscope" icon unicode character. + /// + Periscope = 0xF3DA, + + /// + /// The Font Awesome "person-booth" icon unicode character. + /// + PersonBooth = 0xF756, + + /// + /// The Font Awesome "phabricator" icon unicode character. + /// + Phabricator = 0xF3DB, + + /// + /// The Font Awesome "phoenix-framework" icon unicode character. + /// + PhoenixFramework = 0xF3DC, + + /// + /// The Font Awesome "phoenix-squadron" icon unicode character. + /// + PhoenixSquadron = 0xF511, + + /// + /// The Font Awesome "phone" icon unicode character. + /// + Phone = 0xF095, + + /// + /// The Font Awesome "phone-alt" icon unicode character. + /// + PhoneAlt = 0xF879, + + /// + /// The Font Awesome "phone-slash" icon unicode character. + /// + PhoneSlash = 0xF3DD, + + /// + /// The Font Awesome "phone-square" icon unicode character. + /// + PhoneSquare = 0xF098, + + /// + /// The Font Awesome "phone-square-alt" icon unicode character. + /// + PhoneSquareAlt = 0xF87B, + + /// + /// The Font Awesome "phone-volume" icon unicode character. + /// + PhoneVolume = 0xF2A, + + /// + /// The Font Awesome "photo-video" icon unicode character. + /// + PhotoVideo = 0xF87C, + + /// + /// The Font Awesome "php" icon unicode character. + /// + Php = 0xF457, + + /// + /// The Font Awesome "pied-piper" icon unicode character. + /// + PiedPiper = 0xF2AE, + + /// + /// The Font Awesome "pied-piper-alt" icon unicode character. + /// + PiedPiperAlt = 0xF1A8, + + /// + /// The Font Awesome "pied-piper-hat" icon unicode character. + /// + PiedPiperHat = 0xF4E5, + + /// + /// The Font Awesome "pied-piper-pp" icon unicode character. + /// + PiedPiperPp = 0xF1A7, + + /// + /// The Font Awesome "pied-piper-square" icon unicode character. + /// + PiedPiperSquare = 0xF91E, + + /// + /// The Font Awesome "piggy-bank" icon unicode character. + /// + PiggyBank = 0xF4D3, + + /// + /// The Font Awesome "pills" icon unicode character. + /// + Pills = 0xF484, + + /// + /// The Font Awesome "pinterest" icon unicode character. + /// + Pinterest = 0xF0D2, + + /// + /// The Font Awesome "pinterest-p" icon unicode character. + /// + PinterestP = 0xF231, + + /// + /// The Font Awesome "pinterest-square" icon unicode character. + /// + PinterestSquare = 0xF0D3, + + /// + /// The Font Awesome "pizza-slice" icon unicode character. + /// + PizzaSlice = 0xF818, + + /// + /// The Font Awesome "place-of-worship" icon unicode character. + /// + PlaceOfWorship = 0xF67F, + + /// + /// The Font Awesome "plane" icon unicode character. + /// + Plane = 0xF072, + + /// + /// The Font Awesome "plane-arrival" icon unicode character. + /// + PlaneArrival = 0xF5AF, + + /// + /// The Font Awesome "plane-departure" icon unicode character. + /// + PlaneDeparture = 0xF5B, + + /// + /// The Font Awesome "play" icon unicode character. + /// + Play = 0xF04B, + + /// + /// The Font Awesome "play-circle" icon unicode character. + /// + PlayCircle = 0xF144, + + /// + /// The Font Awesome "playstation" icon unicode character. + /// + Playstation = 0xF3DF, + + /// + /// The Font Awesome "plug" icon unicode character. + /// + Plug = 0xF1E6, + + /// + /// The Font Awesome "plus" icon unicode character. + /// + Plus = 0xF067, + + /// + /// The Font Awesome "plus-circle" icon unicode character. + /// + PlusCircle = 0xF055, + + /// + /// The Font Awesome "plus-square" icon unicode character. + /// + PlusSquare = 0xF0FE, + + /// + /// The Font Awesome "podcast" icon unicode character. + /// + Podcast = 0xF2CE, + + /// + /// The Font Awesome "poll" icon unicode character. + /// + Poll = 0xF681, + + /// + /// The Font Awesome "poll-h" icon unicode character. + /// + PollH = 0xF682, + + /// + /// The Font Awesome "poo" icon unicode character. + /// + Poo = 0xF2FE, + + /// + /// The Font Awesome "poop" icon unicode character. + /// + Poop = 0xF619, + + /// + /// The Font Awesome "poo-storm" icon unicode character. + /// + PooStorm = 0xF75A, + + /// + /// The Font Awesome "portrait" icon unicode character. + /// + Portrait = 0xF3E, + + /// + /// The Font Awesome "pound-sign" icon unicode character. + /// + PoundSign = 0xF154, + + /// + /// The Font Awesome "power-off" icon unicode character. + /// + PowerOff = 0xF011, + + /// + /// The Font Awesome "pray" icon unicode character. + /// + Pray = 0xF683, + + /// + /// The Font Awesome "praying-hands" icon unicode character. + /// + PrayingHands = 0xF684, + + /// + /// The Font Awesome "prescription" icon unicode character. + /// + Prescription = 0xF5B1, + + /// + /// The Font Awesome "prescription-bottle" icon unicode character. + /// + PrescriptionBottle = 0xF485, + + /// + /// The Font Awesome "prescription-bottle-alt" icon unicode character. + /// + PrescriptionBottleAlt = 0xF486, + + /// + /// The Font Awesome "print" icon unicode character. + /// + Print = 0xF02F, + + /// + /// The Font Awesome "procedures" icon unicode character. + /// + Procedures = 0xF487, + + /// + /// The Font Awesome "product-hunt" icon unicode character. + /// + ProductHunt = 0xF288, + + /// + /// The Font Awesome "project-diagram" icon unicode character. + /// + ProjectDiagram = 0xF542, + + /// + /// The Font Awesome "pushed" icon unicode character. + /// + Pushed = 0xF3E1, + + /// + /// The Font Awesome "puzzle-piece" icon unicode character. + /// + PuzzlePiece = 0xF12E, + + /// + /// The Font Awesome "python" icon unicode character. + /// + Python = 0xF3E2, + + /// + /// The Font Awesome "qq" icon unicode character. + /// + Qq = 0xF1D6, + + /// + /// The Font Awesome "qrcode" icon unicode character. + /// + Qrcode = 0xF029, + + /// + /// The Font Awesome "question" icon unicode character. + /// + Question = 0xF128, + + /// + /// The Font Awesome "question-circle" icon unicode character. + /// + QuestionCircle = 0xF059, + + /// + /// The Font Awesome "quidditch" icon unicode character. + /// + Quidditch = 0xF458, + + /// + /// The Font Awesome "quinscape" icon unicode character. + /// + Quinscape = 0xF459, + + /// + /// The Font Awesome "quora" icon unicode character. + /// + Quora = 0xF2C4, + + /// + /// The Font Awesome "quote-left" icon unicode character. + /// + QuoteLeft = 0xF10D, + + /// + /// The Font Awesome "quote-right" icon unicode character. + /// + QuoteRight = 0xF10E, + + /// + /// The Font Awesome "quran" icon unicode character. + /// + Quran = 0xF687, + + /// + /// The Font Awesome "radiation" icon unicode character. + /// + Radiation = 0xF7B9, + + /// + /// The Font Awesome "radiation-alt" icon unicode character. + /// + RadiationAlt = 0xF7BA, + + /// + /// The Font Awesome "rainbow" icon unicode character. + /// + Rainbow = 0xF75B, + + /// + /// The Font Awesome "random" icon unicode character. + /// + Random = 0xF074, + + /// + /// The Font Awesome "raspberry-pi" icon unicode character. + /// + RaspberryPi = 0xF7BB, + + /// + /// The Font Awesome "ravelry" icon unicode character. + /// + Ravelry = 0xF2D9, + + /// + /// The Font Awesome "react" icon unicode character. + /// + React = 0xF41B, + + /// + /// The Font Awesome "reacteurope" icon unicode character. + /// + Reacteurope = 0xF75D, + + /// + /// The Font Awesome "readme" icon unicode character. + /// + Readme = 0xF4D5, + + /// + /// The Font Awesome "rebel" icon unicode character. + /// + Rebel = 0xF1D, + + /// + /// The Font Awesome "receipt" icon unicode character. + /// + Receipt = 0xF543, + + /// + /// The Font Awesome "record-vinyl" icon unicode character. + /// + RecordVinyl = 0xF8D9, + + /// + /// The Font Awesome "recycle" icon unicode character. + /// + Recycle = 0xF1B8, + + /// + /// The Font Awesome "reddit" icon unicode character. + /// + Reddit = 0xF1A1, + + /// + /// The Font Awesome "reddit-alien" icon unicode character. + /// + RedditAlien = 0xF281, + + /// + /// The Font Awesome "reddit-square" icon unicode character. + /// + RedditSquare = 0xF1A2, + + /// + /// The Font Awesome "redhat" icon unicode character. + /// + Redhat = 0xF7BC, + + /// + /// The Font Awesome "redo" icon unicode character. + /// + Redo = 0xF01E, + + /// + /// The Font Awesome "redo-alt" icon unicode character. + /// + RedoAlt = 0xF2F9, + + /// + /// The Font Awesome "red-river" icon unicode character. + /// + RedRiver = 0xF3E3, + + /// + /// The Font Awesome "registered" icon unicode character. + /// + Registered = 0xF25D, + + /// + /// The Font Awesome "remove-format" icon unicode character. + /// + RemoveFormat = 0xF87D, + + /// + /// The Font Awesome "renren" icon unicode character. + /// + Renren = 0xF18B, + + /// + /// The Font Awesome "reply" icon unicode character. + /// + Reply = 0xF3E5, + + /// + /// The Font Awesome "reply-all" icon unicode character. + /// + ReplyAll = 0xF122, + + /// + /// The Font Awesome "replyd" icon unicode character. + /// + Replyd = 0xF3E6, + + /// + /// The Font Awesome "republican" icon unicode character. + /// + Republican = 0xF75E, + + /// + /// The Font Awesome "researchgate" icon unicode character. + /// + Researchgate = 0xF4F8, + + /// + /// The Font Awesome "resolving" icon unicode character. + /// + Resolving = 0xF3E7, + + /// + /// The Font Awesome "restroom" icon unicode character. + /// + Restroom = 0xF7BD, + + /// + /// The Font Awesome "retweet" icon unicode character. + /// + Retweet = 0xF079, + + /// + /// The Font Awesome "rev" icon unicode character. + /// + Rev = 0xF5B2, + + /// + /// The Font Awesome "ribbon" icon unicode character. + /// + Ribbon = 0xF4D6, + + /// + /// The Font Awesome "ring" icon unicode character. + /// + Ring = 0xF70B, + + /// + /// The Font Awesome "road" icon unicode character. + /// + Road = 0xF018, + + /// + /// The Font Awesome "robot" icon unicode character. + /// + Robot = 0xF544, + + /// + /// The Font Awesome "rocket" icon unicode character. + /// + Rocket = 0xF135, + + /// + /// The Font Awesome "rocketchat" icon unicode character. + /// + Rocketchat = 0xF3E8, + + /// + /// The Font Awesome "rockrms" icon unicode character. + /// + Rockrms = 0xF3E9, + + /// + /// The Font Awesome "route" icon unicode character. + /// + Route = 0xF4D7, + + /// + /// The Font Awesome "r-project" icon unicode character. + /// + RProject = 0xF4F7, + + /// + /// The Font Awesome "rss" icon unicode character. + /// + Rss = 0xF09E, + + /// + /// The Font Awesome "rss-square" icon unicode character. + /// + RssSquare = 0xF143, + + /// + /// The Font Awesome "ruble-sign" icon unicode character. + /// + RubleSign = 0xF158, + + /// + /// The Font Awesome "ruler" icon unicode character. + /// + Ruler = 0xF545, + + /// + /// The Font Awesome "ruler-combined" icon unicode character. + /// + RulerCombined = 0xF546, + + /// + /// The Font Awesome "ruler-horizontal" icon unicode character. + /// + RulerHorizontal = 0xF547, + + /// + /// The Font Awesome "ruler-vertical" icon unicode character. + /// + RulerVertical = 0xF548, + + /// + /// The Font Awesome "running" icon unicode character. + /// + Running = 0xF70C, + + /// + /// The Font Awesome "rupee-sign" icon unicode character. + /// + RupeeSign = 0xF156, + + /// + /// The Font Awesome "sad-cry" icon unicode character. + /// + SadCry = 0xF5B3, + + /// + /// The Font Awesome "sad-tear" icon unicode character. + /// + SadTear = 0xF5B4, + + /// + /// The Font Awesome "safari" icon unicode character. + /// + Safari = 0xF267, + + /// + /// The Font Awesome "salesforce" icon unicode character. + /// + Salesforce = 0xF83B, + + /// + /// The Font Awesome "sass" icon unicode character. + /// + Sass = 0xF41E, + + /// + /// The Font Awesome "satellite" icon unicode character. + /// + Satellite = 0xF7BF, + + /// + /// The Font Awesome "satellite-dish" icon unicode character. + /// + SatelliteDish = 0xF7C, + + /// + /// The Font Awesome "save" icon unicode character. + /// + Save = 0xF0C7, + + /// + /// The Font Awesome "schlix" icon unicode character. + /// + Schlix = 0xF3EA, + + /// + /// The Font Awesome "school" icon unicode character. + /// + School = 0xF549, + + /// + /// The Font Awesome "screwdriver" icon unicode character. + /// + Screwdriver = 0xF54A, + + /// + /// The Font Awesome "scribd" icon unicode character. + /// + Scribd = 0xF28A, + + /// + /// The Font Awesome "scroll" icon unicode character. + /// + Scroll = 0xF70E, + + /// + /// The Font Awesome "sd-card" icon unicode character. + /// + SdCard = 0xF7C2, + + /// + /// The Font Awesome "search" icon unicode character. + /// + Search = 0xF002, + + /// + /// The Font Awesome "search-dollar" icon unicode character. + /// + SearchDollar = 0xF688, + + /// + /// The Font Awesome "searchengin" icon unicode character. + /// + Searchengin = 0xF3EB, + + /// + /// The Font Awesome "search-location" icon unicode character. + /// + SearchLocation = 0xF689, + + /// + /// The Font Awesome "search-minus" icon unicode character. + /// + SearchMinus = 0xF01, + + /// + /// The Font Awesome "search-plus" icon unicode character. + /// + SearchPlus = 0xF00E, + + /// + /// The Font Awesome "seedling" icon unicode character. + /// + Seedling = 0xF4D8, + + /// + /// The Font Awesome "sellcast" icon unicode character. + /// + Sellcast = 0xF2DA, + + /// + /// The Font Awesome "sellsy" icon unicode character. + /// + Sellsy = 0xF213, + + /// + /// The Font Awesome "server" icon unicode character. + /// + Server = 0xF233, + + /// + /// The Font Awesome "servicestack" icon unicode character. + /// + Servicestack = 0xF3EC, + + /// + /// The Font Awesome "shapes" icon unicode character. + /// + Shapes = 0xF61F, + + /// + /// The Font Awesome "share" icon unicode character. + /// + Share = 0xF064, + + /// + /// The Font Awesome "share-alt" icon unicode character. + /// + ShareAlt = 0xF1E, + + /// + /// The Font Awesome "share-alt-square" icon unicode character. + /// + ShareAltSquare = 0xF1E1, + + /// + /// The Font Awesome "share-square" icon unicode character. + /// + ShareSquare = 0xF14D, + + /// + /// The Font Awesome "shekel-sign" icon unicode character. + /// + ShekelSign = 0xF20B, + + /// + /// The Font Awesome "shield-alt" icon unicode character. + /// + ShieldAlt = 0xF3ED, + + /// + /// The Font Awesome "ship" icon unicode character. + /// + Ship = 0xF21A, + + /// + /// The Font Awesome "shipping-fast" icon unicode character. + /// + ShippingFast = 0xF48B, + + /// + /// The Font Awesome "shirtsinbulk" icon unicode character. + /// + Shirtsinbulk = 0xF214, + + /// + /// The Font Awesome "shoe-prints" icon unicode character. + /// + ShoePrints = 0xF54B, + + /// + /// The Font Awesome "shopify" icon unicode character. + /// + Shopify = 0xF957, + + /// + /// The Font Awesome "shopping-bag" icon unicode character. + /// + ShoppingBag = 0xF29, + + /// + /// The Font Awesome "shopping-basket" icon unicode character. + /// + ShoppingBasket = 0xF291, + + /// + /// The Font Awesome "shopping-cart" icon unicode character. + /// + ShoppingCart = 0xF07A, + + /// + /// The Font Awesome "shopware" icon unicode character. + /// + Shopware = 0xF5B5, + + /// + /// The Font Awesome "shower" icon unicode character. + /// + Shower = 0xF2CC, + + /// + /// The Font Awesome "shuttle-van" icon unicode character. + /// + ShuttleVan = 0xF5B6, + + /// + /// The Font Awesome "sign" icon unicode character. + /// + Sign = 0xF4D9, + + /// + /// The Font Awesome "signal" icon unicode character. + /// + Signal = 0xF012, + + /// + /// The Font Awesome "signature" icon unicode character. + /// + Signature = 0xF5B7, + + /// + /// The Font Awesome "sign-in-alt" icon unicode character. + /// + SignInAlt = 0xF2F6, + + /// + /// The Font Awesome "sign-language" icon unicode character. + /// + SignLanguage = 0xF2A7, + + /// + /// The Font Awesome "sign-out-alt" icon unicode character. + /// + SignOutAlt = 0xF2F5, + + /// + /// The Font Awesome "sim-card" icon unicode character. + /// + SimCard = 0xF7C4, + + /// + /// The Font Awesome "simplybuilt" icon unicode character. + /// + Simplybuilt = 0xF215, + + /// + /// The Font Awesome "sistrix" icon unicode character. + /// + Sistrix = 0xF3EE, + + /// + /// The Font Awesome "sitemap" icon unicode character. + /// + Sitemap = 0xF0E8, + + /// + /// The Font Awesome "sith" icon unicode character. + /// + Sith = 0xF512, + + /// + /// The Font Awesome "skating" icon unicode character. + /// + Skating = 0xF7C5, + + /// + /// The Font Awesome "sketch" icon unicode character. + /// + Sketch = 0xF7C6, + + /// + /// The Font Awesome "skiing" icon unicode character. + /// + Skiing = 0xF7C9, + + /// + /// The Font Awesome "skiing-nordic" icon unicode character. + /// + SkiingNordic = 0xF7CA, + + /// + /// The Font Awesome "skull" icon unicode character. + /// + Skull = 0xF54C, + + /// + /// The Font Awesome "skull-crossbones" icon unicode character. + /// + SkullCrossbones = 0xF714, + + /// + /// The Font Awesome "skyatlas" icon unicode character. + /// + Skyatlas = 0xF216, + + /// + /// The Font Awesome "skype" icon unicode character. + /// + Skype = 0xF17E, + + /// + /// The Font Awesome "slack" icon unicode character. + /// + Slack = 0xF198, + + /// + /// The Font Awesome "slack-hash" icon unicode character. + /// + SlackHash = 0xF3EF, + + /// + /// The Font Awesome "slash" icon unicode character. + /// + Slash = 0xF715, + + /// + /// The Font Awesome "sleigh" icon unicode character. + /// + Sleigh = 0xF7CC, + + /// + /// The Font Awesome "sliders-h" icon unicode character. + /// + SlidersH = 0xF1DE, + + /// + /// The Font Awesome "slideshare" icon unicode character. + /// + Slideshare = 0xF1E7, + + /// + /// The Font Awesome "smile" icon unicode character. + /// + Smile = 0xF118, + + /// + /// The Font Awesome "smile-beam" icon unicode character. + /// + SmileBeam = 0xF5B8, + + /// + /// The Font Awesome "smile-wink" icon unicode character. + /// + SmileWink = 0xF4DA, + + /// + /// The Font Awesome "smog" icon unicode character. + /// + Smog = 0xF75F, + + /// + /// The Font Awesome "smoking" icon unicode character. + /// + Smoking = 0xF48D, + + /// + /// The Font Awesome "smoking-ban" icon unicode character. + /// + SmokingBan = 0xF54D, + + /// + /// The Font Awesome "sms" icon unicode character. + /// + Sms = 0xF7CD, + + /// + /// The Font Awesome "snapchat" icon unicode character. + /// + Snapchat = 0xF2AB, + + /// + /// The Font Awesome "snapchat-ghost" icon unicode character. + /// + SnapchatGhost = 0xF2AC, + + /// + /// The Font Awesome "snapchat-square" icon unicode character. + /// + SnapchatSquare = 0xF2AD, + + /// + /// The Font Awesome "snowboarding" icon unicode character. + /// + Snowboarding = 0xF7CE, + + /// + /// The Font Awesome "snowflake" icon unicode character. + /// + Snowflake = 0xF2DC, + + /// + /// The Font Awesome "snowman" icon unicode character. + /// + Snowman = 0xF7D, + + /// + /// The Font Awesome "snowplow" icon unicode character. + /// + Snowplow = 0xF7D2, + + /// + /// The Font Awesome "socks" icon unicode character. + /// + Socks = 0xF696, + + /// + /// The Font Awesome "solar-panel" icon unicode character. + /// + SolarPanel = 0xF5BA, + + /// + /// The Font Awesome "sort" icon unicode character. + /// + Sort = 0xF0DC, + + /// + /// The Font Awesome "sort-alpha-down" icon unicode character. + /// + SortAlphaDown = 0xF15D, + + /// + /// The Font Awesome "sort-alpha-down-alt" icon unicode character. + /// + SortAlphaDownAlt = 0xF881, + + /// + /// The Font Awesome "sort-alpha-up" icon unicode character. + /// + SortAlphaUp = 0xF15E, + + /// + /// The Font Awesome "sort-alpha-up-alt" icon unicode character. + /// + SortAlphaUpAlt = 0xF882, + + /// + /// The Font Awesome "sort-amount-down" icon unicode character. + /// + SortAmountDown = 0xF16, + + /// + /// The Font Awesome "sort-amount-down-alt" icon unicode character. + /// + SortAmountDownAlt = 0xF884, + + /// + /// The Font Awesome "sort-amount-up" icon unicode character. + /// + SortAmountUp = 0xF161, + + /// + /// The Font Awesome "sort-amount-up-alt" icon unicode character. + /// + SortAmountUpAlt = 0xF885, + + /// + /// The Font Awesome "sort-down" icon unicode character. + /// + SortDown = 0xF0DD, + + /// + /// The Font Awesome "sort-numeric-down" icon unicode character. + /// + SortNumericDown = 0xF162, + + /// + /// The Font Awesome "sort-numeric-down-alt" icon unicode character. + /// + SortNumericDownAlt = 0xF886, + + /// + /// The Font Awesome "sort-numeric-up" icon unicode character. + /// + SortNumericUp = 0xF163, + + /// + /// The Font Awesome "sort-numeric-up-alt" icon unicode character. + /// + SortNumericUpAlt = 0xF887, + + /// + /// The Font Awesome "sort-up" icon unicode character. + /// + SortUp = 0xF0DE, + + /// + /// The Font Awesome "soundcloud" icon unicode character. + /// + Soundcloud = 0xF1BE, + + /// + /// The Font Awesome "sourcetree" icon unicode character. + /// + Sourcetree = 0xF7D3, + + /// + /// The Font Awesome "spa" icon unicode character. + /// + Spa = 0xF5BB, + + /// + /// The Font Awesome "space-shuttle" icon unicode character. + /// + SpaceShuttle = 0xF197, + + /// + /// The Font Awesome "speakap" icon unicode character. + /// + Speakap = 0xF3F3, + + /// + /// The Font Awesome "speaker-deck" icon unicode character. + /// + SpeakerDeck = 0xF83C, + + /// + /// The Font Awesome "spell-check" icon unicode character. + /// + SpellCheck = 0xF891, + + /// + /// The Font Awesome "spider" icon unicode character. + /// + Spider = 0xF717, + + /// + /// The Font Awesome "spinner" icon unicode character. + /// + Spinner = 0xF11, + + /// + /// The Font Awesome "splotch" icon unicode character. + /// + Splotch = 0xF5BC, + + /// + /// The Font Awesome "spotify" icon unicode character. + /// + Spotify = 0xF1BC, + + /// + /// The Font Awesome "spray-can" icon unicode character. + /// + SprayCan = 0xF5BD, + + /// + /// The Font Awesome "square" icon unicode character. + /// + Square = 0xF0C8, + + /// + /// The Font Awesome "square-full" icon unicode character. + /// + SquareFull = 0xF45C, + + /// + /// The Font Awesome "square-root-alt" icon unicode character. + /// + SquareRootAlt = 0xF698, + + /// + /// The Font Awesome "squarespace" icon unicode character. + /// + Squarespace = 0xF5BE, + + /// + /// The Font Awesome "stack-exchange" icon unicode character. + /// + StackExchange = 0xF18D, + + /// + /// The Font Awesome "stack-overflow" icon unicode character. + /// + StackOverflow = 0xF16C, + + /// + /// The Font Awesome "stackpath" icon unicode character. + /// + Stackpath = 0xF842, + + /// + /// The Font Awesome "stamp" icon unicode character. + /// + Stamp = 0xF5BF, + + /// + /// The Font Awesome "star" icon unicode character. + /// + Star = 0xF005, + + /// + /// The Font Awesome "star-and-crescent" icon unicode character. + /// + StarAndCrescent = 0xF699, + + /// + /// The Font Awesome "star-half" icon unicode character. + /// + StarHalf = 0xF089, + + /// + /// The Font Awesome "star-half-alt" icon unicode character. + /// + StarHalfAlt = 0xF5C, + + /// + /// The Font Awesome "star-of-david" icon unicode character. + /// + StarOfDavid = 0xF69A, + + /// + /// The Font Awesome "star-of-life" icon unicode character. + /// + StarOfLife = 0xF621, + + /// + /// The Font Awesome "staylinked" icon unicode character. + /// + Staylinked = 0xF3F5, + + /// + /// The Font Awesome "steam" icon unicode character. + /// + Steam = 0xF1B6, + + /// + /// The Font Awesome "steam-square" icon unicode character. + /// + SteamSquare = 0xF1B7, + + /// + /// The Font Awesome "steam-symbol" icon unicode character. + /// + SteamSymbol = 0xF3F6, + + /// + /// The Font Awesome "step-backward" icon unicode character. + /// + StepBackward = 0xF048, + + /// + /// The Font Awesome "step-forward" icon unicode character. + /// + StepForward = 0xF051, + + /// + /// The Font Awesome "stethoscope" icon unicode character. + /// + Stethoscope = 0xF0F1, + + /// + /// The Font Awesome "sticker-mule" icon unicode character. + /// + StickerMule = 0xF3F7, + + /// + /// The Font Awesome "sticky-note" icon unicode character. + /// + StickyNote = 0xF249, + + /// + /// The Font Awesome "stop" icon unicode character. + /// + Stop = 0xF04D, + + /// + /// The Font Awesome "stop-circle" icon unicode character. + /// + StopCircle = 0xF28D, + + /// + /// The Font Awesome "stopwatch" icon unicode character. + /// + Stopwatch = 0xF2F2, + + /// + /// The Font Awesome "store" icon unicode character. + /// + Store = 0xF54E, + + /// + /// The Font Awesome "store-alt" icon unicode character. + /// + StoreAlt = 0xF54F, + + /// + /// The Font Awesome "strava" icon unicode character. + /// + Strava = 0xF428, + + /// + /// The Font Awesome "stream" icon unicode character. + /// + Stream = 0xF55, + + /// + /// The Font Awesome "street-view" icon unicode character. + /// + StreetView = 0xF21D, + + /// + /// The Font Awesome "strikethrough" icon unicode character. + /// + Strikethrough = 0xF0CC, + + /// + /// The Font Awesome "stripe" icon unicode character. + /// + Stripe = 0xF429, + + /// + /// The Font Awesome "stripe-s" icon unicode character. + /// + StripeS = 0xF42A, + + /// + /// The Font Awesome "stroopwafel" icon unicode character. + /// + Stroopwafel = 0xF551, + + /// + /// The Font Awesome "studiovinari" icon unicode character. + /// + Studiovinari = 0xF3F8, + + /// + /// The Font Awesome "stumbleupon" icon unicode character. + /// + Stumbleupon = 0xF1A4, + + /// + /// The Font Awesome "stumbleupon-circle" icon unicode character. + /// + StumbleuponCircle = 0xF1A3, + + /// + /// The Font Awesome "subscript" icon unicode character. + /// + Subscript = 0xF12C, + + /// + /// The Font Awesome "subway" icon unicode character. + /// + Subway = 0xF239, + + /// + /// The Font Awesome "suitcase" icon unicode character. + /// + Suitcase = 0xF0F2, + + /// + /// The Font Awesome "suitcase-rolling" icon unicode character. + /// + SuitcaseRolling = 0xF5C1, + + /// + /// The Font Awesome "sun" icon unicode character. + /// + Sun = 0xF185, + + /// + /// The Font Awesome "superpowers" icon unicode character. + /// + Superpowers = 0xF2DD, + + /// + /// The Font Awesome "superscript" icon unicode character. + /// + Superscript = 0xF12B, + + /// + /// The Font Awesome "supple" icon unicode character. + /// + Supple = 0xF3F9, + + /// + /// The Font Awesome "surprise" icon unicode character. + /// + Surprise = 0xF5C2, + + /// + /// The Font Awesome "suse" icon unicode character. + /// + Suse = 0xF7D6, + + /// + /// The Font Awesome "swatchbook" icon unicode character. + /// + Swatchbook = 0xF5C3, + + /// + /// The Font Awesome "swift" icon unicode character. + /// + Swift = 0xF8E1, + + /// + /// The Font Awesome "swimmer" icon unicode character. + /// + Swimmer = 0xF5C4, + + /// + /// The Font Awesome "swimming-pool" icon unicode character. + /// + SwimmingPool = 0xF5C5, + + /// + /// The Font Awesome "symfony" icon unicode character. + /// + Symfony = 0xF83D, + + /// + /// The Font Awesome "synagogue" icon unicode character. + /// + Synagogue = 0xF69B, + + /// + /// The Font Awesome "sync" icon unicode character. + /// + Sync = 0xF021, + + /// + /// The Font Awesome "sync-alt" icon unicode character. + /// + SyncAlt = 0xF2F1, + + /// + /// The Font Awesome "syringe" icon unicode character. + /// + Syringe = 0xF48E, + + /// + /// The Font Awesome "table" icon unicode character. + /// + Table = 0xF0CE, + + /// + /// The Font Awesome "tablet" icon unicode character. + /// + Tablet = 0xF10A, + + /// + /// The Font Awesome "tablet-alt" icon unicode character. + /// + TabletAlt = 0xF3FA, + + /// + /// The Font Awesome "table-tennis" icon unicode character. + /// + TableTennis = 0xF45D, + + /// + /// The Font Awesome "tablets" icon unicode character. + /// + Tablets = 0xF49, + + /// + /// The Font Awesome "tachometer-alt" icon unicode character. + /// + TachometerAlt = 0xF3FD, + + /// + /// The Font Awesome "tag" icon unicode character. + /// + Tag = 0xF02B, + + /// + /// The Font Awesome "tags" icon unicode character. + /// + Tags = 0xF02C, + + /// + /// The Font Awesome "tape" icon unicode character. + /// + Tape = 0xF4DB, + + /// + /// The Font Awesome "tasks" icon unicode character. + /// + Tasks = 0xF0AE, + + /// + /// The Font Awesome "taxi" icon unicode character. + /// + Taxi = 0xF1BA, + + /// + /// The Font Awesome "teamspeak" icon unicode character. + /// + Teamspeak = 0xF4F9, + + /// + /// The Font Awesome "teeth" icon unicode character. + /// + Teeth = 0xF62E, + + /// + /// The Font Awesome "teeth-open" icon unicode character. + /// + TeethOpen = 0xF62F, + + /// + /// The Font Awesome "telegram" icon unicode character. + /// + Telegram = 0xF2C6, + + /// + /// The Font Awesome "telegram-plane" icon unicode character. + /// + TelegramPlane = 0xF3FE, + + /// + /// The Font Awesome "temperature-high" icon unicode character. + /// + TemperatureHigh = 0xF769, + + /// + /// The Font Awesome "temperature-low" icon unicode character. + /// + TemperatureLow = 0xF76B, + + /// + /// The Font Awesome "tencent-weibo" icon unicode character. + /// + TencentWeibo = 0xF1D5, + + /// + /// The Font Awesome "tenge" icon unicode character. + /// + Tenge = 0xF7D7, + + /// + /// The Font Awesome "terminal" icon unicode character. + /// + Terminal = 0xF12, + + /// + /// The Font Awesome "text-height" icon unicode character. + /// + TextHeight = 0xF034, + + /// + /// The Font Awesome "text-width" icon unicode character. + /// + TextWidth = 0xF035, + + /// + /// The Font Awesome "th" icon unicode character. + /// + Th = 0xF00A, + + /// + /// The Font Awesome "theater-masks" icon unicode character. + /// + TheaterMasks = 0xF63, + + /// + /// The Font Awesome "themeco" icon unicode character. + /// + Themeco = 0xF5C6, + + /// + /// The Font Awesome "themeisle" icon unicode character. + /// + Themeisle = 0xF2B2, + + /// + /// The Font Awesome "the-red-yeti" icon unicode character. + /// + TheRedYeti = 0xF69D, + + /// + /// The Font Awesome "thermometer" icon unicode character. + /// + Thermometer = 0xF491, + + /// + /// The Font Awesome "thermometer-empty" icon unicode character. + /// + ThermometerEmpty = 0xF2CB, + + /// + /// The Font Awesome "thermometer-full" icon unicode character. + /// + ThermometerFull = 0xF2C7, + + /// + /// The Font Awesome "thermometer-half" icon unicode character. + /// + ThermometerHalf = 0xF2C9, + + /// + /// The Font Awesome "thermometer-quarter" icon unicode character. + /// + ThermometerQuarter = 0xF2CA, + + /// + /// The Font Awesome "thermometer-three-quarters" icon unicode character. + /// + ThermometerThreeQuarters = 0xF2C8, + + /// + /// The Font Awesome "think-peaks" icon unicode character. + /// + ThinkPeaks = 0xF731, + + /// + /// The Font Awesome "th-large" icon unicode character. + /// + ThLarge = 0xF009, + + /// + /// The Font Awesome "th-list" icon unicode character. + /// + ThList = 0xF00B, + + /// + /// The Font Awesome "thumbs-down" icon unicode character. + /// + ThumbsDown = 0xF165, + + /// + /// The Font Awesome "thumbs-up" icon unicode character. + /// + ThumbsUp = 0xF164, + + /// + /// The Font Awesome "thumbtack" icon unicode character. + /// + Thumbtack = 0xF08D, + + /// + /// The Font Awesome "ticket-alt" icon unicode character. + /// + TicketAlt = 0xF3FF, + + /// + /// The Font Awesome "times" icon unicode character. + /// + Times = 0xF00D, + + /// + /// The Font Awesome "times-circle" icon unicode character. + /// + TimesCircle = 0xF057, + + /// + /// The Font Awesome "tint" icon unicode character. + /// + Tint = 0xF043, + + /// + /// The Font Awesome "tint-slash" icon unicode character. + /// + TintSlash = 0xF5C7, + + /// + /// The Font Awesome "tired" icon unicode character. + /// + Tired = 0xF5C8, + + /// + /// The Font Awesome "toggle-off" icon unicode character. + /// + ToggleOff = 0xF204, + + /// + /// The Font Awesome "toggle-on" icon unicode character. + /// + ToggleOn = 0xF205, + + /// + /// The Font Awesome "toilet" icon unicode character. + /// + Toilet = 0xF7D8, + + /// + /// The Font Awesome "toilet-paper" icon unicode character. + /// + ToiletPaper = 0xF71E, + + /// + /// The Font Awesome "toolbox" icon unicode character. + /// + Toolbox = 0xF552, + + /// + /// The Font Awesome "tools" icon unicode character. + /// + Tools = 0xF7D9, + + /// + /// The Font Awesome "tooth" icon unicode character. + /// + Tooth = 0xF5C9, + + /// + /// The Font Awesome "torah" icon unicode character. + /// + Torah = 0xF6A, + + /// + /// The Font Awesome "torii-gate" icon unicode character. + /// + ToriiGate = 0xF6A1, + + /// + /// The Font Awesome "tractor" icon unicode character. + /// + Tractor = 0xF722, + + /// + /// The Font Awesome "trade-federation" icon unicode character. + /// + TradeFederation = 0xF513, + + /// + /// The Font Awesome "trademark" icon unicode character. + /// + Trademark = 0xF25C, + + /// + /// The Font Awesome "traffic-light" icon unicode character. + /// + TrafficLight = 0xF637, + + /// + /// The Font Awesome "trailer" icon unicode character. + /// + Trailer = 0xF941, + + /// + /// The Font Awesome "train" icon unicode character. + /// + Train = 0xF238, + + /// + /// The Font Awesome "tram" icon unicode character. + /// + Tram = 0xF7DA, + + /// + /// The Font Awesome "transgender" icon unicode character. + /// + Transgender = 0xF224, + + /// + /// The Font Awesome "transgender-alt" icon unicode character. + /// + TransgenderAlt = 0xF225, + + /// + /// The Font Awesome "trash" icon unicode character. + /// + Trash = 0xF1F8, + + /// + /// The Font Awesome "trash-alt" icon unicode character. + /// + TrashAlt = 0xF2ED, + + /// + /// The Font Awesome "trash-restore" icon unicode character. + /// + TrashRestore = 0xF829, + + /// + /// The Font Awesome "trash-restore-alt" icon unicode character. + /// + TrashRestoreAlt = 0xF82A, + + /// + /// The Font Awesome "tree" icon unicode character. + /// + Tree = 0xF1BB, + + /// + /// The Font Awesome "trello" icon unicode character. + /// + Trello = 0xF181, + + /// + /// The Font Awesome "tripadvisor" icon unicode character. + /// + Tripadvisor = 0xF262, + + /// + /// The Font Awesome "trophy" icon unicode character. + /// + Trophy = 0xF091, + + /// + /// The Font Awesome "truck" icon unicode character. + /// + Truck = 0xF0D1, + + /// + /// The Font Awesome "truck-loading" icon unicode character. + /// + TruckLoading = 0xF4DE, + + /// + /// The Font Awesome "truck-monster" icon unicode character. + /// + TruckMonster = 0xF63B, + + /// + /// The Font Awesome "truck-moving" icon unicode character. + /// + TruckMoving = 0xF4DF, + + /// + /// The Font Awesome "truck-pickup" icon unicode character. + /// + TruckPickup = 0xF63C, + + /// + /// The Font Awesome "tshirt" icon unicode character. + /// + Tshirt = 0xF553, + + /// + /// The Font Awesome "tty" icon unicode character. + /// + Tty = 0xF1E4, + + /// + /// The Font Awesome "tumblr" icon unicode character. + /// + Tumblr = 0xF173, + + /// + /// The Font Awesome "tumblr-square" icon unicode character. + /// + TumblrSquare = 0xF174, + + /// + /// The Font Awesome "tv" icon unicode character. + /// + Tv = 0xF26C, + + /// + /// The Font Awesome "twitch" icon unicode character. + /// + Twitch = 0xF1E8, + + /// + /// The Font Awesome "twitter" icon unicode character. + /// + Twitter = 0xF099, + + /// + /// The Font Awesome "twitter-square" icon unicode character. + /// + TwitterSquare = 0xF081, + + /// + /// The Font Awesome "typo3" icon unicode character. + /// + Typo3 = 0xF42B, + + /// + /// The Font Awesome "uber" icon unicode character. + /// + Uber = 0xF402, + + /// + /// The Font Awesome "ubuntu" icon unicode character. + /// + Ubuntu = 0xF7DF, + + /// + /// The Font Awesome "uikit" icon unicode character. + /// + Uikit = 0xF403, + + /// + /// The Font Awesome "umbraco" icon unicode character. + /// + Umbraco = 0xF8E8, + + /// + /// The Font Awesome "umbrella" icon unicode character. + /// + Umbrella = 0xF0E9, + + /// + /// The Font Awesome "umbrella-beach" icon unicode character. + /// + UmbrellaBeach = 0xF5CA, + + /// + /// The Font Awesome "underline" icon unicode character. + /// + Underline = 0xF0CD, + + /// + /// The Font Awesome "undo" icon unicode character. + /// + Undo = 0xF0E2, + + /// + /// The Font Awesome "undo-alt" icon unicode character. + /// + UndoAlt = 0xF2EA, + + /// + /// The Font Awesome "uniregistry" icon unicode character. + /// + Uniregistry = 0xF404, + + /// + /// The Font Awesome "unity" icon unicode character. + /// + Unity = 0xF949, + + /// + /// The Font Awesome "universal-access" icon unicode character. + /// + UniversalAccess = 0xF29A, + + /// + /// The Font Awesome "university" icon unicode character. + /// + University = 0xF19C, + + /// + /// The Font Awesome "unlink" icon unicode character. + /// + Unlink = 0xF127, + + /// + /// The Font Awesome "unlock" icon unicode character. + /// + Unlock = 0xF09C, + + /// + /// The Font Awesome "unlock-alt" icon unicode character. + /// + UnlockAlt = 0xF13E, + + /// + /// The Font Awesome "untappd" icon unicode character. + /// + Untappd = 0xF405, + + /// + /// The Font Awesome "upload" icon unicode character. + /// + Upload = 0xF093, + + /// + /// The Font Awesome "ups" icon unicode character. + /// + Ups = 0xF7E, + + /// + /// The Font Awesome "usb" icon unicode character. + /// + Usb = 0xF287, + + /// + /// The Font Awesome "user" icon unicode character. + /// + User = 0xF007, + + /// + /// The Font Awesome "user-alt" icon unicode character. + /// + UserAlt = 0xF406, + + /// + /// The Font Awesome "user-alt-slash" icon unicode character. + /// + UserAltSlash = 0xF4FA, + + /// + /// The Font Awesome "user-astronaut" icon unicode character. + /// + UserAstronaut = 0xF4FB, + + /// + /// The Font Awesome "user-check" icon unicode character. + /// + UserCheck = 0xF4FC, + + /// + /// The Font Awesome "user-circle" icon unicode character. + /// + UserCircle = 0xF2BD, + + /// + /// The Font Awesome "user-clock" icon unicode character. + /// + UserClock = 0xF4FD, + + /// + /// The Font Awesome "user-cog" icon unicode character. + /// + UserCog = 0xF4FE, + + /// + /// The Font Awesome "user-edit" icon unicode character. + /// + UserEdit = 0xF4FF, + + /// + /// The Font Awesome "user-friends" icon unicode character. + /// + UserFriends = 0xF5, + + /// + /// The Font Awesome "user-graduate" icon unicode character. + /// + UserGraduate = 0xF501, + + /// + /// The Font Awesome "user-injured" icon unicode character. + /// + UserInjured = 0xF728, + + /// + /// The Font Awesome "user-lock" icon unicode character. + /// + UserLock = 0xF502, + + /// + /// The Font Awesome "user-md" icon unicode character. + /// + UserMd = 0xF0F, + + /// + /// The Font Awesome "user-minus" icon unicode character. + /// + UserMinus = 0xF503, + + /// + /// The Font Awesome "user-ninja" icon unicode character. + /// + UserNinja = 0xF504, + + /// + /// The Font Awesome "user-nurse" icon unicode character. + /// + UserNurse = 0xF82F, + + /// + /// The Font Awesome "user-plus" icon unicode character. + /// + UserPlus = 0xF234, + + /// + /// The Font Awesome "users" icon unicode character. + /// + Users = 0xF0C, + + /// + /// The Font Awesome "users-cog" icon unicode character. + /// + UsersCog = 0xF509, + + /// + /// The Font Awesome "user-secret" icon unicode character. + /// + UserSecret = 0xF21B, + + /// + /// The Font Awesome "user-shield" icon unicode character. + /// + UserShield = 0xF505, + + /// + /// The Font Awesome "user-slash" icon unicode character. + /// + UserSlash = 0xF506, + + /// + /// The Font Awesome "user-tag" icon unicode character. + /// + UserTag = 0xF507, + + /// + /// The Font Awesome "user-tie" icon unicode character. + /// + UserTie = 0xF508, + + /// + /// The Font Awesome "user-times" icon unicode character. + /// + UserTimes = 0xF235, + + /// + /// The Font Awesome "usps" icon unicode character. + /// + Usps = 0xF7E1, + + /// + /// The Font Awesome "ussunnah" icon unicode character. + /// + Ussunnah = 0xF407, + + /// + /// The Font Awesome "utensils" icon unicode character. + /// + Utensils = 0xF2E7, + + /// + /// The Font Awesome "utensil-spoon" icon unicode character. + /// + UtensilSpoon = 0xF2E5, + + /// + /// The Font Awesome "vaadin" icon unicode character. + /// + Vaadin = 0xF408, + + /// + /// The Font Awesome "vector-square" icon unicode character. + /// + VectorSquare = 0xF5CB, + + /// + /// The Font Awesome "venus" icon unicode character. + /// + Venus = 0xF221, + + /// + /// The Font Awesome "venus-double" icon unicode character. + /// + VenusDouble = 0xF226, + + /// + /// The Font Awesome "venus-mars" icon unicode character. + /// + VenusMars = 0xF228, + + /// + /// The Font Awesome "viacoin" icon unicode character. + /// + Viacoin = 0xF237, + + /// + /// The Font Awesome "viadeo" icon unicode character. + /// + Viadeo = 0xF2A9, + + /// + /// The Font Awesome "viadeo-square" icon unicode character. + /// + ViadeoSquare = 0xF2AA, + + /// + /// The Font Awesome "vial" icon unicode character. + /// + Vial = 0xF492, + + /// + /// The Font Awesome "vials" icon unicode character. + /// + Vials = 0xF493, + + /// + /// The Font Awesome "viber" icon unicode character. + /// + Viber = 0xF409, + + /// + /// The Font Awesome "video" icon unicode character. + /// + Video = 0xF03D, + + /// + /// The Font Awesome "video-slash" icon unicode character. + /// + VideoSlash = 0xF4E2, + + /// + /// The Font Awesome "vihara" icon unicode character. + /// + Vihara = 0xF6A7, + + /// + /// The Font Awesome "vimeo" icon unicode character. + /// + Vimeo = 0xF40A, + + /// + /// The Font Awesome "vimeo-square" icon unicode character. + /// + VimeoSquare = 0xF194, + + /// + /// The Font Awesome "vimeo-v" icon unicode character. + /// + VimeoV = 0xF27D, + + /// + /// The Font Awesome "vine" icon unicode character. + /// + Vine = 0xF1CA, + + /// + /// The Font Awesome "vk" icon unicode character. + /// + Vk = 0xF189, + + /// + /// The Font Awesome "vnv" icon unicode character. + /// + Vnv = 0xF40B, + + /// + /// The Font Awesome "voicemail" icon unicode character. + /// + Voicemail = 0xF897, + + /// + /// The Font Awesome "volleyball-ball" icon unicode character. + /// + VolleyballBall = 0xF45F, + + /// + /// The Font Awesome "volume-down" icon unicode character. + /// + VolumeDown = 0xF027, + + /// + /// The Font Awesome "volume-mute" icon unicode character. + /// + VolumeMute = 0xF6A9, + + /// + /// The Font Awesome "volume-off" icon unicode character. + /// + VolumeOff = 0xF026, + + /// + /// The Font Awesome "volume-up" icon unicode character. + /// + VolumeUp = 0xF028, + + /// + /// The Font Awesome "vote-yea" icon unicode character. + /// + VoteYea = 0xF772, + + /// + /// The Font Awesome "vr-cardboard" icon unicode character. + /// + VrCardboard = 0xF729, + + /// + /// The Font Awesome "vuejs" icon unicode character. + /// + Vuejs = 0xF41F, + + /// + /// The Font Awesome "walking" icon unicode character. + /// + Walking = 0xF554, + + /// + /// The Font Awesome "wallet" icon unicode character. + /// + Wallet = 0xF555, + + /// + /// The Font Awesome "warehouse" icon unicode character. + /// + Warehouse = 0xF494, + + /// + /// The Font Awesome "water" icon unicode character. + /// + Water = 0xF773, + + /// + /// The Font Awesome "wave-square" icon unicode character. + /// + WaveSquare = 0xF83E, + + /// + /// The Font Awesome "waze" icon unicode character. + /// + Waze = 0xF83F, + + /// + /// The Font Awesome "weebly" icon unicode character. + /// + Weebly = 0xF5CC, + + /// + /// The Font Awesome "weibo" icon unicode character. + /// + Weibo = 0xF18A, + + /// + /// The Font Awesome "weight" icon unicode character. + /// + Weight = 0xF496, + + /// + /// The Font Awesome "weight-hanging" icon unicode character. + /// + WeightHanging = 0xF5CD, + + /// + /// The Font Awesome "weixin" icon unicode character. + /// + Weixin = 0xF1D7, + + /// + /// The Font Awesome "whatsapp" icon unicode character. + /// + Whatsapp = 0xF232, + + /// + /// The Font Awesome "whatsapp-square" icon unicode character. + /// + WhatsappSquare = 0xF40C, + + /// + /// The Font Awesome "wheelchair" icon unicode character. + /// + Wheelchair = 0xF193, + + /// + /// The Font Awesome "whmcs" icon unicode character. + /// + Whmcs = 0xF40D, + + /// + /// The Font Awesome "wifi" icon unicode character. + /// + Wifi = 0xF1EB, + + /// + /// The Font Awesome "wikipedia-w" icon unicode character. + /// + WikipediaW = 0xF266, + + /// + /// The Font Awesome "wind" icon unicode character. + /// + Wind = 0xF72E, + + /// + /// The Font Awesome "window-close" icon unicode character. + /// + WindowClose = 0xF41, + + /// + /// The Font Awesome "window-maximize" icon unicode character. + /// + WindowMaximize = 0xF2D, + + /// + /// The Font Awesome "window-minimize" icon unicode character. + /// + WindowMinimize = 0xF2D1, + + /// + /// The Font Awesome "window-restore" icon unicode character. + /// + WindowRestore = 0xF2D2, + + /// + /// The Font Awesome "windows" icon unicode character. + /// + Windows = 0xF17A, + + /// + /// The Font Awesome "wine-bottle" icon unicode character. + /// + WineBottle = 0xF72F, + + /// + /// The Font Awesome "wine-glass" icon unicode character. + /// + WineGlass = 0xF4E3, + + /// + /// The Font Awesome "wine-glass-alt" icon unicode character. + /// + WineGlassAlt = 0xF5CE, + + /// + /// The Font Awesome "wix" icon unicode character. + /// + Wix = 0xF5CF, + + /// + /// The Font Awesome "wizards-of-the-coast" icon unicode character. + /// + WizardsOfTheCoast = 0xF73, + + /// + /// The Font Awesome "wolf-pack-battalion" icon unicode character. + /// + WolfPackBattalion = 0xF514, + + /// + /// The Font Awesome "won-sign" icon unicode character. + /// + WonSign = 0xF159, + + /// + /// The Font Awesome "wordpress" icon unicode character. + /// + Wordpress = 0xF19A, + + /// + /// The Font Awesome "wordpress-simple" icon unicode character. + /// + WordpressSimple = 0xF411, + + /// + /// The Font Awesome "wpbeginner" icon unicode character. + /// + Wpbeginner = 0xF297, + + /// + /// The Font Awesome "wpexplorer" icon unicode character. + /// + Wpexplorer = 0xF2DE, + + /// + /// The Font Awesome "wpforms" icon unicode character. + /// + Wpforms = 0xF298, + + /// + /// The Font Awesome "wpressr" icon unicode character. + /// + Wpressr = 0xF3E4, + + /// + /// The Font Awesome "wrench" icon unicode character. + /// + Wrench = 0xF0AD, + + /// + /// The Font Awesome "xbox" icon unicode character. + /// + Xbox = 0xF412, + + /// + /// The Font Awesome "xing" icon unicode character. + /// + Xing = 0xF168, + + /// + /// The Font Awesome "xing-square" icon unicode character. + /// + XingSquare = 0xF169, + + /// + /// The Font Awesome "x-ray" icon unicode character. + /// + XRay = 0xF497, + + /// + /// The Font Awesome "yahoo" icon unicode character. + /// + Yahoo = 0xF19E, + + /// + /// The Font Awesome "yammer" icon unicode character. + /// + Yammer = 0xF84, + + /// + /// The Font Awesome "yandex" icon unicode character. + /// + Yandex = 0xF413, + + /// + /// The Font Awesome "yandex-international" icon unicode character. + /// + YandexInternational = 0xF414, + + /// + /// The Font Awesome "yarn" icon unicode character. + /// + Yarn = 0xF7E3, + + /// + /// The Font Awesome "y-combinator" icon unicode character. + /// + YCombinator = 0xF23B, + + /// + /// The Font Awesome "yelp" icon unicode character. + /// + Yelp = 0xF1E9, + + /// + /// The Font Awesome "yen-sign" icon unicode character. + /// + YenSign = 0xF157, + + /// + /// The Font Awesome "yin-yang" icon unicode character. + /// + YinYang = 0xF6AD, + + /// + /// The Font Awesome "yoast" icon unicode character. + /// + Yoast = 0xF2B1, + + /// + /// The Font Awesome "youtube" icon unicode character. + /// + Youtube = 0xF167, + + /// + /// The Font Awesome "youtube-square" icon unicode character. + /// + YoutubeSquare = 0xF431, + + /// + /// The Font Awesome "zhihu" icon unicode character. + /// + Zhihu = 0xF63F, } diff --git a/Dalamud/Interface/GlyphRangesJapanese.cs b/Dalamud/Interface/GlyphRangesJapanese.cs index 3c44492ff..5859a7d41 100644 --- a/Dalamud/Interface/GlyphRangesJapanese.cs +++ b/Dalamud/Interface/GlyphRangesJapanese.cs @@ -1,15 +1,15 @@ -namespace Dalamud.Interface +namespace Dalamud.Interface; + +/// +/// Unicode glyph ranges for the Japanese language. +/// +public static class GlyphRangesJapanese { /// - /// Unicode glyph ranges for the Japanese language. + /// Gets the unicode glyph ranges for the Japanese language. /// - public static class GlyphRangesJapanese + public static ushort[] GlyphRanges => new ushort[] { - /// - /// Gets the unicode glyph ranges for the Japanese language. - /// - public static ushort[] GlyphRanges => new ushort[] - { 0x0020, 0x00FF, 0x0391, 0x03A1, 0x03A3, 0x03A9, 0x03B1, 0x03C1, 0x03C3, 0x03C9, 0x0401, 0x0401, 0x0410, 0x044F, 0x0451, 0x0451, 0x2000, 0x206F, 0x2103, 0x2103, 0x212B, 0x212B, 0x2190, 0x2193, 0x21D2, 0x21D2, 0x21D4, 0x21D4, 0x2200, 0x2200, 0x2202, 0x2203, 0x2207, 0x2208, 0x220B, 0x220B, 0x2212, 0x2212, 0x221A, 0x221A, 0x221D, 0x221E, 0x2220, 0x2220, 0x2227, 0x222C, 0x2234, 0x2235, @@ -524,6 +524,5 @@ namespace Dalamud.Interface 0x9F4E, 0x9F4F, 0x9F52, 0x9F52, 0x9F54, 0x9F54, 0x9F5F, 0x9F63, 0x9F66, 0x9F67, 0x9F6A, 0x9F6A, 0x9F6C, 0x9F6C, 0x9F72, 0x9F72, 0x9F76, 0x9F77, 0x9F8D, 0x9F8D, 0x9F95, 0x9F95, 0x9F9C, 0x9F9D, 0x9FA0, 0x9FA0, 0xFF01, 0xFF01, 0xFF03, 0xFF06, 0xFF08, 0xFF0C, 0xFF0E, 0xFF3B, 0xFF3D, 0xFF5D, 0xFF61, 0xFF9F, 0xFFE3, 0xFFE3, 0xFFE5, 0xFFE5, 0xFFFF, 0xFFFF, 0, - }; - } + }; } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs index 6630c0439..48031df36 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs @@ -3,416 +3,413 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Dalamud.Interface; +namespace Dalamud.Interface.ImGuiFileDialog; -namespace Dalamud.Interface.ImGuiFileDialog +/// +/// A file or folder picker. +/// +public partial class FileDialog { - /// - /// A file or folder picker. - /// - public partial class FileDialog + private readonly object filesLock = new(); + + private List files = new(); + private List filteredFiles = new(); + + private SortingField currentSortingField = SortingField.FileName; + private bool[] sortDescending = new[] { false, false, false, false }; + + private enum FileStructType { - private readonly object filesLock = new(); + File, + Directory, + } - private List files = new(); - private List filteredFiles = new(); + private enum SortingField + { + None, + FileName, + Type, + Size, + Date, + } - private SortingField currentSortingField = SortingField.FileName; - private bool[] sortDescending = new[] { false, false, false, false }; - - private enum FileStructType + private static string ComposeNewPath(List decomp) + { + if (decomp.Count == 1) { - File, - Directory, - } - - private enum SortingField - { - None, - FileName, - Type, - Size, - Date, - } - - private static string ComposeNewPath(List decomp) - { - if (decomp.Count == 1) - { - var drivePath = decomp[0]; - if (drivePath[^1] != Path.DirectorySeparatorChar) - { // turn C: into C:\ - drivePath += Path.DirectorySeparatorChar; - } - - return drivePath; + var drivePath = decomp[0]; + if (drivePath[^1] != Path.DirectorySeparatorChar) + { // turn C: into C:\ + drivePath += Path.DirectorySeparatorChar; } - return Path.Combine(decomp.ToArray()); + return drivePath; } - private static FileStruct GetFile(FileInfo file, string path) + return Path.Combine(decomp.ToArray()); + } + + private static FileStruct GetFile(FileInfo file, string path) + { + return new FileStruct { - return new FileStruct - { - FileName = file.Name, - FilePath = path, - FileModifiedDate = FormatModifiedDate(file.LastWriteTime), - FileSize = file.Length, - FormattedFileSize = BytesToString(file.Length), - Type = FileStructType.File, - Ext = file.Extension.Trim('.'), - }; + FileName = file.Name, + FilePath = path, + FileModifiedDate = FormatModifiedDate(file.LastWriteTime), + FileSize = file.Length, + FormattedFileSize = BytesToString(file.Length), + Type = FileStructType.File, + Ext = file.Extension.Trim('.'), + }; + } + + private static FileStruct GetDir(DirectoryInfo dir, string path) + { + return new FileStruct + { + FileName = dir.Name, + FilePath = path, + FileModifiedDate = FormatModifiedDate(dir.LastWriteTime), + FileSize = 0, + FormattedFileSize = string.Empty, + Type = FileStructType.Directory, + Ext = string.Empty, + }; + } + + private static int SortByFileNameDesc(FileStruct a, FileStruct b) + { + if (a.FileName[0] == '.' && b.FileName[0] != '.') + { + return 1; } - private static FileStruct GetDir(DirectoryInfo dir, string path) + if (a.FileName[0] != '.' && b.FileName[0] == '.') { - return new FileStruct - { - FileName = dir.Name, - FilePath = path, - FileModifiedDate = FormatModifiedDate(dir.LastWriteTime), - FileSize = 0, - FormattedFileSize = string.Empty, - Type = FileStructType.Directory, - Ext = string.Empty, - }; + return -1; } - private static int SortByFileNameDesc(FileStruct a, FileStruct b) + if (a.FileName[0] == '.' && b.FileName[0] == '.') { - if (a.FileName[0] == '.' && b.FileName[0] != '.') - { - return 1; - } - - if (a.FileName[0] != '.' && b.FileName[0] == '.') + if (a.FileName.Length == 1) { return -1; } - if (a.FileName[0] == '.' && b.FileName[0] == '.') - { - if (a.FileName.Length == 1) - { - return -1; - } - - if (b.FileName.Length == 1) - { - return 1; - } - - return -1 * string.Compare(a.FileName[1..], b.FileName[1..]); - } - - if (a.Type != b.Type) - { - return a.Type == FileStructType.Directory ? 1 : -1; - } - - return -1 * string.Compare(a.FileName, b.FileName); - } - - private static int SortByFileNameAsc(FileStruct a, FileStruct b) - { - if (a.FileName[0] == '.' && b.FileName[0] != '.') - { - return -1; - } - - if (a.FileName[0] != '.' && b.FileName[0] == '.') + if (b.FileName.Length == 1) { return 1; } - if (a.FileName[0] == '.' && b.FileName[0] == '.') + return -1 * string.Compare(a.FileName[1..], b.FileName[1..]); + } + + if (a.Type != b.Type) + { + return a.Type == FileStructType.Directory ? 1 : -1; + } + + return -1 * string.Compare(a.FileName, b.FileName); + } + + private static int SortByFileNameAsc(FileStruct a, FileStruct b) + { + if (a.FileName[0] == '.' && b.FileName[0] != '.') + { + return -1; + } + + if (a.FileName[0] != '.' && b.FileName[0] == '.') + { + return 1; + } + + if (a.FileName[0] == '.' && b.FileName[0] == '.') + { + if (a.FileName.Length == 1) { - if (a.FileName.Length == 1) + return 1; + } + + if (b.FileName.Length == 1) + { + return -1; + } + + return string.Compare(a.FileName[1..], b.FileName[1..]); + } + + if (a.Type != b.Type) + { + return a.Type == FileStructType.Directory ? -1 : 1; + } + + return string.Compare(a.FileName, b.FileName); + } + + private static int SortByTypeDesc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? 1 : -1; + } + + return string.Compare(a.Ext, b.Ext); + } + + private static int SortByTypeAsc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? -1 : 1; + } + + return -1 * string.Compare(a.Ext, b.Ext); + } + + private static int SortBySizeDesc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? 1 : -1; + } + + return (a.FileSize > b.FileSize) ? 1 : -1; + } + + private static int SortBySizeAsc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? -1 : 1; + } + + return (a.FileSize > b.FileSize) ? -1 : 1; + } + + private static int SortByDateDesc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? 1 : -1; + } + + return string.Compare(a.FileModifiedDate, b.FileModifiedDate); + } + + private static int SortByDateAsc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? -1 : 1; + } + + return -1 * string.Compare(a.FileModifiedDate, b.FileModifiedDate); + } + + private bool CreateDir(string dirPath) + { + var newPath = Path.Combine(this.currentPath, dirPath); + if (string.IsNullOrEmpty(newPath)) + { + return false; + } + + Directory.CreateDirectory(newPath); + return true; + } + + private void ScanDir(string path) + { + if (!Directory.Exists(path)) + { + return; + } + + if (this.pathDecomposition.Count == 0) + { + this.SetCurrentDir(path); + } + + if (this.pathDecomposition.Count > 0) + { + this.files.Clear(); + + if (this.pathDecomposition.Count > 1) + { + this.files.Add(new FileStruct { - return 1; - } - - if (b.FileName.Length == 1) - { - return -1; - } - - return string.Compare(a.FileName[1..], b.FileName[1..]); - } - - if (a.Type != b.Type) - { - return a.Type == FileStructType.Directory ? -1 : 1; - } - - return string.Compare(a.FileName, b.FileName); - } - - private static int SortByTypeDesc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? 1 : -1; - } - - return string.Compare(a.Ext, b.Ext); - } - - private static int SortByTypeAsc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? -1 : 1; - } - - return -1 * string.Compare(a.Ext, b.Ext); - } - - private static int SortBySizeDesc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? 1 : -1; - } - - return (a.FileSize > b.FileSize) ? 1 : -1; - } - - private static int SortBySizeAsc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? -1 : 1; - } - - return (a.FileSize > b.FileSize) ? -1 : 1; - } - - private static int SortByDateDesc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? 1 : -1; - } - - return string.Compare(a.FileModifiedDate, b.FileModifiedDate); - } - - private static int SortByDateAsc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? -1 : 1; - } - - return -1 * string.Compare(a.FileModifiedDate, b.FileModifiedDate); - } - - private bool CreateDir(string dirPath) - { - var newPath = Path.Combine(this.currentPath, dirPath); - if (string.IsNullOrEmpty(newPath)) - { - return false; - } - - Directory.CreateDirectory(newPath); - return true; - } - - private void ScanDir(string path) - { - if (!Directory.Exists(path)) - { - return; - } - - if (this.pathDecomposition.Count == 0) - { - this.SetCurrentDir(path); - } - - if (this.pathDecomposition.Count > 0) - { - this.files.Clear(); - - if (this.pathDecomposition.Count > 1) - { - this.files.Add(new FileStruct - { - Type = FileStructType.Directory, - FilePath = path, - FileName = "..", - FileSize = 0, - FileModifiedDate = string.Empty, - FormattedFileSize = string.Empty, - Ext = string.Empty, - }); - } - - var dirInfo = new DirectoryInfo(path); - - var dontShowHidden = this.flags.HasFlag(ImGuiFileDialogFlags.DontShowHiddenFiles); - - foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name)) - { - if (string.IsNullOrEmpty(dir.Name)) - { - continue; - } - - if (dontShowHidden && dir.Name[0] == '.') - { - continue; - } - - this.files.Add(GetDir(dir, path)); - } - - foreach (var file in dirInfo.EnumerateFiles().OrderBy(f => f.Name)) - { - if (string.IsNullOrEmpty(file.Name)) - { - continue; - } - - if (dontShowHidden && file.Name[0] == '.') - { - continue; - } - - if (!string.IsNullOrEmpty(file.Extension)) - { - var ext = file.Extension; - if (this.filters.Count > 0 && !this.selectedFilter.Empty() && !this.selectedFilter.FilterExists(ext) && this.selectedFilter.Filter != ".*") - { - continue; - } - } - - this.files.Add(GetFile(file, path)); - } - - this.SortFields(this.currentSortingField); - } - } - - private void SetupSideBar() - { - var drives = DriveInfo.GetDrives(); - foreach (var drive in drives) - { - this.drives.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Server, - Location = drive.Name, - Text = drive.Name, + Type = FileStructType.Directory, + FilePath = path, + FileName = "..", + FileSize = 0, + FileModifiedDate = string.Empty, + FormattedFileSize = string.Empty, + Ext = string.Empty, }); } - var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + var dirInfo = new DirectoryInfo(path); - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Desktop, - Location = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), - Text = "Desktop", - }); + var dontShowHidden = this.flags.HasFlag(ImGuiFileDialogFlags.DontShowHiddenFiles); - this.quickAccess.Add(new SideBarItem + foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name)) { - Icon = (char)FontAwesomeIcon.File, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - Text = "Documents", - }); - - if (!string.IsNullOrEmpty(personal)) - { - this.quickAccess.Add(new SideBarItem + if (string.IsNullOrEmpty(dir.Name)) { - Icon = (char)FontAwesomeIcon.Download, - Location = Path.Combine(personal, "Downloads"), - Text = "Downloads", - }); + continue; + } + + if (dontShowHidden && dir.Name[0] == '.') + { + continue; + } + + this.files.Add(GetDir(dir, path)); } - this.quickAccess.Add(new SideBarItem + foreach (var file in dirInfo.EnumerateFiles().OrderBy(f => f.Name)) { - Icon = (char)FontAwesomeIcon.Star, - Location = Environment.GetFolderPath(Environment.SpecialFolder.Favorites), - Text = "Favorites", - }); + if (string.IsNullOrEmpty(file.Name)) + { + continue; + } - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Music, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), - Text = "Music", - }); + if (dontShowHidden && file.Name[0] == '.') + { + continue; + } - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Image, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), - Text = "Pictures", - }); - - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Video, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), - Text = "Videos", - }); - } - - private void SortFields(SortingField sortingField, bool canChangeOrder = false) - { - switch (sortingField) - { - case SortingField.FileName: - if (canChangeOrder && sortingField == this.currentSortingField) + if (!string.IsNullOrEmpty(file.Extension)) + { + var ext = file.Extension; + if (this.filters.Count > 0 && !this.selectedFilter.Empty() && !this.selectedFilter.FilterExists(ext) && this.selectedFilter.Filter != ".*") { - this.sortDescending[0] = !this.sortDescending[0]; + continue; } + } - this.files.Sort(this.sortDescending[0] ? SortByFileNameDesc : SortByFileNameAsc); - break; - - case SortingField.Type: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[1] = !this.sortDescending[1]; - } - - this.files.Sort(this.sortDescending[1] ? SortByTypeDesc : SortByTypeAsc); - break; - - case SortingField.Size: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[2] = !this.sortDescending[2]; - } - - this.files.Sort(this.sortDescending[2] ? SortBySizeDesc : SortBySizeAsc); - break; - - case SortingField.Date: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[3] = !this.sortDescending[3]; - } - - this.files.Sort(this.sortDescending[3] ? SortByDateDesc : SortByDateAsc); - break; + this.files.Add(GetFile(file, path)); } - if (sortingField != SortingField.None) - { - this.currentSortingField = sortingField; - } - - this.ApplyFilteringOnFileList(); + this.SortFields(this.currentSortingField); } } + + private void SetupSideBar() + { + var drives = DriveInfo.GetDrives(); + foreach (var drive in drives) + { + this.drives.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Server, + Location = drive.Name, + Text = drive.Name, + }); + } + + var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Desktop, + Location = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + Text = "Desktop", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.File, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + Text = "Documents", + }); + + if (!string.IsNullOrEmpty(personal)) + { + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Download, + Location = Path.Combine(personal, "Downloads"), + Text = "Downloads", + }); + } + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Star, + Location = Environment.GetFolderPath(Environment.SpecialFolder.Favorites), + Text = "Favorites", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Music, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), + Text = "Music", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Image, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), + Text = "Pictures", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Video, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), + Text = "Videos", + }); + } + + private void SortFields(SortingField sortingField, bool canChangeOrder = false) + { + switch (sortingField) + { + case SortingField.FileName: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[0] = !this.sortDescending[0]; + } + + this.files.Sort(this.sortDescending[0] ? SortByFileNameDesc : SortByFileNameAsc); + break; + + case SortingField.Type: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[1] = !this.sortDescending[1]; + } + + this.files.Sort(this.sortDescending[1] ? SortByTypeDesc : SortByTypeAsc); + break; + + case SortingField.Size: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[2] = !this.sortDescending[2]; + } + + this.files.Sort(this.sortDescending[2] ? SortBySizeDesc : SortBySizeAsc); + break; + + case SortingField.Date: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[3] = !this.sortDescending[3]; + } + + this.files.Sort(this.sortDescending[3] ? SortByDateDesc : SortByDateAsc); + break; + } + + if (sortingField != SortingField.None) + { + this.currentSortingField = sortingField; + } + + this.ApplyFilteringOnFileList(); + } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs index 037155229..919e65592 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs @@ -1,108 +1,107 @@ using System.Collections.Generic; using System.Text.RegularExpressions; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// A file or folder picker. +/// +public partial class FileDialog { - /// - /// A file or folder picker. - /// - public partial class FileDialog + private static Regex filterRegex = new(@"[^,{}]+(\{([^{}]*?)\})?", RegexOptions.Compiled); + + private List filters = new(); + private FilterStruct selectedFilter; + + private void ParseFilters(string filters) { - private static Regex filterRegex = new(@"[^,{}]+(\{([^{}]*?)\})?", RegexOptions.Compiled); + // ".*,.cpp,.h,.hpp" + // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" - private List filters = new(); - private FilterStruct selectedFilter; + this.filters.Clear(); + if (filters.Length == 0) return; - private void ParseFilters(string filters) + var currentFilterFound = false; + var matches = filterRegex.Matches(filters); + foreach (Match m in matches) { - // ".*,.cpp,.h,.hpp" - // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" + var match = m.Value; + var filter = default(FilterStruct); - this.filters.Clear(); - if (filters.Length == 0) return; - - var currentFilterFound = false; - var matches = filterRegex.Matches(filters); - foreach (Match m in matches) + if (match.Contains("{")) { - var match = m.Value; - var filter = default(FilterStruct); - - if (match.Contains("{")) + var exts = m.Groups[2].Value; + filter = new FilterStruct { - var exts = m.Groups[2].Value; - filter = new FilterStruct - { - Filter = match.Split('{')[0], - CollectionFilters = new HashSet(exts.Split(',')), - }; - } - else + Filter = match.Split('{')[0], + CollectionFilters = new HashSet(exts.Split(',')), + }; + } + else + { + filter = new FilterStruct { - filter = new FilterStruct - { - Filter = match, - CollectionFilters = new(), - }; - } - - this.filters.Add(filter); - - if (!currentFilterFound && filter.Filter == this.selectedFilter.Filter) - { - currentFilterFound = true; - this.selectedFilter = filter; - } + Filter = match, + CollectionFilters = new(), + }; } - if (!currentFilterFound && !(this.filters.Count == 0)) + this.filters.Add(filter); + + if (!currentFilterFound && filter.Filter == this.selectedFilter.Filter) { - this.selectedFilter = this.filters[0]; + currentFilterFound = true; + this.selectedFilter = filter; } } - private void SetSelectedFilterWithExt(string ext) + if (!currentFilterFound && !(this.filters.Count == 0)) { - if (this.filters.Count == 0) return; - if (string.IsNullOrEmpty(ext)) return; + this.selectedFilter = this.filters[0]; + } + } - foreach (var filter in this.filters) - { - if (filter.FilterExists(ext)) - { - this.selectedFilter = filter; - } - } + private void SetSelectedFilterWithExt(string ext) + { + if (this.filters.Count == 0) return; + if (string.IsNullOrEmpty(ext)) return; - if (this.selectedFilter.Empty()) + foreach (var filter in this.filters) + { + if (filter.FilterExists(ext)) { - this.selectedFilter = this.filters[0]; + this.selectedFilter = filter; } } - private void ApplyFilteringOnFileList() + if (this.selectedFilter.Empty()) { - lock (this.filesLock) + this.selectedFilter = this.filters[0]; + } + } + + private void ApplyFilteringOnFileList() + { + lock (this.filesLock) + { + this.filteredFiles.Clear(); + + foreach (var file in this.files) { - this.filteredFiles.Clear(); - - foreach (var file in this.files) + var show = true; + if (!string.IsNullOrEmpty(this.searchBuffer) && !file.FileName.ToLower().Contains(this.searchBuffer.ToLower())) { - var show = true; - if (!string.IsNullOrEmpty(this.searchBuffer) && !file.FileName.ToLower().Contains(this.searchBuffer.ToLower())) - { - show = false; - } + show = false; + } - if (this.IsDirectoryMode() && file.Type != FileStructType.Directory) - { - show = false; - } + if (this.IsDirectoryMode() && file.Type != FileStructType.Directory) + { + show = false; + } - if (show) - { - this.filteredFiles.Add(file); - } + if (show) + { + this.filteredFiles.Add(file); } } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs index 16bc3e46f..96bdb3172 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs @@ -1,26 +1,25 @@ using System; -namespace Dalamud.Interface.ImGuiFileDialog -{ - /// - /// A file or folder picker. - /// - public partial class FileDialog - { - private static string FormatModifiedDate(DateTime date) - { - return date.ToString("yyyy/MM/dd HH:mm"); - } +namespace Dalamud.Interface.ImGuiFileDialog; - private static string BytesToString(long byteCount) - { - string[] suf = { " B", " KB", " MB", " GB", " TB" }; - if (byteCount == 0) - return "0" + suf[0]; - var bytes = Math.Abs(byteCount); - var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); - var num = Math.Round(bytes / Math.Pow(1024, place), 1); - return (Math.Sign(byteCount) * num).ToString() + suf[place]; - } +/// +/// A file or folder picker. +/// +public partial class FileDialog +{ + private static string FormatModifiedDate(DateTime date) + { + return date.ToString("yyyy/MM/dd HH:mm"); + } + + private static string BytesToString(long byteCount) + { + string[] suf = { " B", " KB", " MB", " GB", " TB" }; + if (byteCount == 0) + return "0" + suf[0]; + var bytes = Math.Abs(byteCount); + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString() + suf[place]; } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs index 475147518..18e8b4cf9 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs @@ -2,57 +2,56 @@ using System; using System.Collections.Generic; using System.Numerics; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// A file or folder picker. +/// +public partial class FileDialog { - /// - /// A file or folder picker. - /// - public partial class FileDialog + private struct FileStruct { - private struct FileStruct + public FileStructType Type; + public string FilePath; + public string FileName; + public string Ext; + public long FileSize; + public string FormattedFileSize; + public string FileModifiedDate; + } + + private struct SideBarItem + { + public char Icon; + public string Text; + public string Location; + } + + private struct FilterStruct + { + public string Filter; + public HashSet CollectionFilters; + + public void Clear() { - public FileStructType Type; - public string FilePath; - public string FileName; - public string Ext; - public long FileSize; - public string FormattedFileSize; - public string FileModifiedDate; + this.Filter = string.Empty; + this.CollectionFilters.Clear(); } - private struct SideBarItem + public bool Empty() { - public char Icon; - public string Text; - public string Location; + return string.IsNullOrEmpty(this.Filter) && ((this.CollectionFilters == null) || (this.CollectionFilters.Count == 0)); } - private struct FilterStruct + public bool FilterExists(string filter) { - public string Filter; - public HashSet CollectionFilters; - - public void Clear() - { - this.Filter = string.Empty; - this.CollectionFilters.Clear(); - } - - public bool Empty() - { - return string.IsNullOrEmpty(this.Filter) && ((this.CollectionFilters == null) || (this.CollectionFilters.Count == 0)); - } - - public bool FilterExists(string filter) - { - return (this.Filter == filter) || (this.CollectionFilters != null && this.CollectionFilters.Contains(filter)); - } - } - - private struct IconColorItem - { - public char Icon; - public Vector4 Color; + return (this.Filter == filter) || (this.CollectionFilters != null && this.CollectionFilters.Contains(filter)); } } + + private struct IconColorItem + { + public char Icon; + public Vector4 Color; + } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs index d5010f13c..68b7917b0 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs @@ -4,577 +4,590 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// A file or folder picker. +/// +public partial class FileDialog { + private static Vector4 pathDecompColor = new(0.188f, 0.188f, 0.2f, 1f); + private static Vector4 selectedTextColor = new(1.00000000000f, 0.33333333333f, 0.33333333333f, 1f); + private static Vector4 dirTextColor = new(0.54509803922f, 0.91372549020f, 0.99215686275f, 1f); + private static Vector4 codeTextColor = new(0.94509803922f, 0.98039215686f, 0.54901960784f, 1f); + private static Vector4 miscTextColor = new(1.00000000000f, 0.47450980392f, 0.77647058824f, 1f); + private static Vector4 imageTextColor = new(0.31372549020f, 0.98039215686f, 0.48235294118f, 1f); + private static Vector4 standardTextColor = new(1f); + + private static Dictionary iconMap; + /// - /// A file or folder picker. + /// Draws the dialog. /// - public partial class FileDialog + /// Whether a selection or cancel action was performed. + public bool Draw() { - private static Vector4 pathDecompColor = new(0.188f, 0.188f, 0.2f, 1f); - private static Vector4 selectedTextColor = new(1.00000000000f, 0.33333333333f, 0.33333333333f, 1f); - private static Vector4 dirTextColor = new(0.54509803922f, 0.91372549020f, 0.99215686275f, 1f); - private static Vector4 codeTextColor = new(0.94509803922f, 0.98039215686f, 0.54901960784f, 1f); - private static Vector4 miscTextColor = new(1.00000000000f, 0.47450980392f, 0.77647058824f, 1f); - private static Vector4 imageTextColor = new(0.31372549020f, 0.98039215686f, 0.48235294118f, 1f); - private static Vector4 standardTextColor = new(1f); + if (!this.visible) return false; - private static Dictionary iconMap; + var res = false; + var name = this.title + "###" + this.id; - /// - /// Draws the dialog. - /// - /// Whether a selection or cancel action was performed. - public bool Draw() + bool windowVisible; + this.isOk = false; + this.wantsToQuit = false; + + this.ResetEvents(); + + ImGui.SetNextWindowSize(new Vector2(800, 500), ImGuiCond.FirstUseEver); + + if (this.isModal && !this.okResultToConfirm) { - if (!this.visible) return false; + ImGui.OpenPopup(name); + windowVisible = ImGui.BeginPopupModal(name, ref this.visible, ImGuiWindowFlags.NoScrollbar); + } + else + { + windowVisible = ImGui.Begin(name, ref this.visible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoNav); + } - var res = false; - var name = this.title + "###" + this.id; - - bool windowVisible; - this.isOk = false; - this.wantsToQuit = false; - - this.ResetEvents(); - - ImGui.SetNextWindowSize(new Vector2(800, 500), ImGuiCond.FirstUseEver); - - if (this.isModal && !this.okResultToConfirm) - { - ImGui.OpenPopup(name); - windowVisible = ImGui.BeginPopupModal(name, ref this.visible, ImGuiWindowFlags.NoScrollbar); + bool wasClosed = false; + if (windowVisible) + { + if (!this.visible) + { // window closed + this.isOk = false; + wasClosed = true; } else { - windowVisible = ImGui.Begin(name, ref this.visible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoNav); - } - - bool wasClosed = false; - if (windowVisible) - { - if (!this.visible) - { // window closed - this.isOk = false; - wasClosed = true; - } - else + if (this.selectedFilter.Empty() && (this.filters.Count > 0)) { - if (this.selectedFilter.Empty() && (this.filters.Count > 0)) + this.selectedFilter = this.filters[0]; + } + + if (this.files.Count == 0) + { + if (!string.IsNullOrEmpty(this.defaultFileName)) { - this.selectedFilter = this.filters[0]; - } - - if (this.files.Count == 0) - { - if (!string.IsNullOrEmpty(this.defaultFileName)) - { - this.SetDefaultFileName(); - this.SetSelectedFilterWithExt(this.defaultExtension); - } - else if (this.IsDirectoryMode()) - { - this.SetDefaultFileName(); - } - - this.ScanDir(this.currentPath); - } - - this.DrawHeader(); - this.DrawContent(); - res = this.DrawFooter(); - } - - if (this.isModal && !this.okResultToConfirm) - { - ImGui.EndPopup(); - } - } - - if (!this.isModal || this.okResultToConfirm) - { - ImGui.End(); - } - - return wasClosed || this.ConfirmOrOpenOverWriteFileDialogIfNeeded(res); - } - - private static void AddToIconMap(string[] extensions, char icon, Vector4 color) - { - foreach (var ext in extensions) - { - iconMap[ext] = new IconColorItem - { - Icon = icon, - Color = color, - }; - } - } - - private static IconColorItem GetIcon(string ext) - { - if (iconMap == null) - { - iconMap = new(); - AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, (char)FontAwesomeIcon.FileVideo, miscTextColor); - AddToIconMap(new[] { "pdf" }, (char)FontAwesomeIcon.FilePdf, miscTextColor); - AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, (char)FontAwesomeIcon.FileImage, imageTextColor); - AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, (char)FontAwesomeIcon.FileCode, codeTextColor); - AddToIconMap(new[] { "txt", "md" }, (char)FontAwesomeIcon.FileAlt, standardTextColor); - AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, (char)FontAwesomeIcon.FileArchive, miscTextColor); - AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, (char)FontAwesomeIcon.FileAudio, miscTextColor); - AddToIconMap(new[] { "csv" }, (char)FontAwesomeIcon.FileCsv, miscTextColor); - } - - return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem - { - Icon = (char)FontAwesomeIcon.File, - Color = standardTextColor, - }; - } - - private void DrawHeader() - { - this.DrawPathComposer(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); - ImGui.Separator(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); - - this.DrawSearchBar(); - } - - private void DrawPathComposer() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{(this.pathInputActivated ? (char)FontAwesomeIcon.Times : (char)FontAwesomeIcon.Edit)}")) - { - this.pathInputActivated = !this.pathInputActivated; - } - - ImGui.PopFont(); - - ImGui.SameLine(); - - if (this.pathDecomposition.Count > 0) - { - ImGui.SameLine(); - - if (this.pathInputActivated) - { - ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255); - ImGui.PopItemWidth(); - } - else - { - for (var idx = 0; idx < this.pathDecomposition.Count; idx++) - { - if (idx > 0) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() - 3); - } - - ImGui.PushID(idx); - ImGui.PushStyleColor(ImGuiCol.Button, pathDecompColor); - var click = ImGui.Button(this.pathDecomposition[idx]); - ImGui.PopStyleColor(); - ImGui.PopID(); - - if (click) - { - this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); - this.pathClicked = true; - break; - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - this.pathInputBuffer = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); - this.pathInputActivated = true; - break; - } - } - } - } - } - - private void DrawSearchBar() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{(char)FontAwesomeIcon.Home}")) - { - this.SetPath("."); - } - - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Reset to current directory"); - } - - ImGui.SameLine(); - - this.DrawDirectoryCreation(); - - if (!this.createDirectoryMode) - { - ImGui.SameLine(); - ImGui.Text("Search :"); - ImGui.SameLine(); - ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); - var edited = ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255); - ImGui.PopItemWidth(); - if (edited) - { - this.ApplyFilteringOnFileList(); - } - } - } - - private void DrawDirectoryCreation() - { - if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return; - - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{(char)FontAwesomeIcon.FolderPlus}")) - { - if (!this.createDirectoryMode) - { - this.createDirectoryMode = true; - this.createDirectoryBuffer = string.Empty; - } - } - - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Create Directory"); - } - - if (this.createDirectoryMode) - { - ImGui.SameLine(); - ImGui.Text("New Directory Name"); - - ImGui.SameLine(); - ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - 100f); - ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255); - ImGui.PopItemWidth(); - - ImGui.SameLine(); - - if (ImGui.Button("Ok")) - { - if (this.CreateDir(this.createDirectoryBuffer)) - { - this.SetPath(Path.Combine(this.currentPath, this.createDirectoryBuffer)); - } - - this.createDirectoryMode = false; - } - - ImGui.SameLine(); - - if (ImGui.Button("Cancel")) - { - this.createDirectoryMode = false; - } - } - } - - private void DrawContent() - { - var size = ImGui.GetContentRegionAvail() - new Vector2(0, this.footerHeight); - - if (!this.flags.HasFlag(ImGuiFileDialogFlags.HideSideBar)) - { - ImGui.BeginChild("##FileDialog_ColumnChild", size); - ImGui.Columns(2, "##FileDialog_Columns"); - - this.DrawSideBar(new Vector2(150, size.Y)); - - ImGui.SetColumnWidth(0, 150); - ImGui.NextColumn(); - - this.DrawFileListView(size - new Vector2(160, 0)); - - ImGui.Columns(1); - ImGui.EndChild(); - } - else - { - this.DrawFileListView(size); - } - } - - private void DrawSideBar(Vector2 size) - { - ImGui.BeginChild("##FileDialog_SideBar", size); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5); - - foreach (var drive in this.drives) - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Selectable($"{drive.Icon}##{drive.Text}", drive.Text == this.selectedSideBar)) - { - this.SetPath(drive.Location); - this.selectedSideBar = drive.Text; - } - - ImGui.PopFont(); - - ImGui.SameLine(25); - - ImGui.Text(drive.Text); - } - - foreach (var quick in this.quickAccess) - { - if (string.IsNullOrEmpty(quick.Location)) - { - continue; - } - - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Selectable($"{quick.Icon}##{quick.Text}", quick.Text == this.selectedSideBar)) - { - this.SetPath(quick.Location); - this.selectedSideBar = quick.Text; - } - - ImGui.PopFont(); - - ImGui.SameLine(25); - - ImGui.Text(quick.Text); - } - - ImGui.EndChild(); - } - - private unsafe void DrawFileListView(Vector2 size) - { - ImGui.BeginChild("##FileDialog_FileList", size); - - var tableFlags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.Hideable | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoHostExtendX; - if (ImGui.BeginTable("##FileTable", 4, tableFlags, size)) - { - ImGui.TableSetupScrollFreeze(0, 1); - - var hideType = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnType); - var hideSize = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnSize); - var hideDate = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnDate); - - ImGui.TableSetupColumn(" File Name", ImGuiTableColumnFlags.WidthStretch, -1, 0); - ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed | (hideType ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 1); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed | (hideSize ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 2); - ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.WidthFixed | (hideDate ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 3); - - ImGui.TableNextRow(ImGuiTableRowFlags.Headers); - for (var column = 0; column < 4; column++) - { - ImGui.TableSetColumnIndex(column); - var columnName = ImGui.TableGetColumnName(column); - ImGui.PushID(column); - ImGui.TableHeader(columnName); - ImGui.PopID(); - if (ImGui.IsItemClicked()) - { - if (column == 0) - { - this.SortFields(SortingField.FileName, true); - } - else if (column == 1) - { - this.SortFields(SortingField.Type, true); - } - else if (column == 2) - { - this.SortFields(SortingField.Size, true); - } - else - { - this.SortFields(SortingField.Date, true); - } - } - } - - if (this.filteredFiles.Count > 0) - { - ImGuiListClipperPtr clipper; - unsafe - { - clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - } - - lock (this.filesLock) - { - clipper.Begin(this.filteredFiles.Count); - while (clipper.Step()) - { - for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - { - if (i < 0) continue; - - var file = this.filteredFiles[i]; - var selected = this.selectedFileNames.Contains(file.FileName); - var needToBreak = false; - - var dir = file.Type == FileStructType.Directory; - var item = !dir ? GetIcon(file.Ext) : new IconColorItem - { - Color = dirTextColor, - Icon = (char)FontAwesomeIcon.Folder, - }; - - ImGui.PushStyleColor(ImGuiCol.Text, item.Color); - if (selected) ImGui.PushStyleColor(ImGuiCol.Text, selectedTextColor); - - ImGui.TableNextRow(); - - if (ImGui.TableNextColumn()) - { - needToBreak = this.SelectableItem(file, selected, item.Icon); - } - - if (ImGui.TableNextColumn()) - { - ImGui.Text(file.Ext); - } - - if (ImGui.TableNextColumn()) - { - if (file.Type == FileStructType.File) - { - ImGui.Text(file.FormattedFileSize + " "); - } - else - { - ImGui.Text(" "); - } - } - - if (ImGui.TableNextColumn()) - { - var sz = ImGui.CalcTextSize(file.FileModifiedDate); - ImGui.PushItemWidth(sz.X + 5); - ImGui.Text(file.FileModifiedDate + " "); - ImGui.PopItemWidth(); - } - - if (selected) ImGui.PopStyleColor(); - ImGui.PopStyleColor(); - - if (needToBreak) break; - } - } - - clipper.End(); - } - } - - if (this.pathInputActivated) - { - if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Enter))) - { - if (Directory.Exists(this.pathInputBuffer)) this.SetPath(this.pathInputBuffer); - this.pathInputActivated = false; - } - - if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Escape))) - { - this.pathInputActivated = false; - } - } - - ImGui.EndTable(); - } - - if (this.pathClicked) - { - this.SetPath(this.currentPath); - } - - ImGui.EndChild(); - } - - private bool SelectableItem(FileStruct file, bool selected, char icon) - { - var flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns; - - ImGui.PushFont(UiBuilder.IconFont); - - ImGui.Text($"{icon}"); - ImGui.PopFont(); - - ImGui.SameLine(25f); - - if (ImGui.Selectable(file.FileName, selected, flags)) - { - if (file.Type == FileStructType.Directory) - { - if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) - { - this.pathClicked = this.SelectDirectory(file); - return true; + this.SetDefaultFileName(); + this.SetSelectedFilterWithExt(this.defaultExtension); } else if (this.IsDirectoryMode()) { - this.SelectFileName(file); + this.SetDefaultFileName(); + } + + this.ScanDir(this.currentPath); + } + + this.DrawHeader(); + this.DrawContent(); + res = this.DrawFooter(); + } + + if (this.isModal && !this.okResultToConfirm) + { + ImGui.EndPopup(); + } + } + + if (!this.isModal || this.okResultToConfirm) + { + ImGui.End(); + } + + return wasClosed || this.ConfirmOrOpenOverWriteFileDialogIfNeeded(res); + } + + private static void AddToIconMap(string[] extensions, char icon, Vector4 color) + { + foreach (var ext in extensions) + { + iconMap[ext] = new IconColorItem + { + Icon = icon, + Color = color, + }; + } + } + + private static IconColorItem GetIcon(string ext) + { + if (iconMap == null) + { + iconMap = new(); + AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, (char)FontAwesomeIcon.FileVideo, miscTextColor); + AddToIconMap(new[] { "pdf" }, (char)FontAwesomeIcon.FilePdf, miscTextColor); + AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, (char)FontAwesomeIcon.FileImage, imageTextColor); + AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, (char)FontAwesomeIcon.FileCode, codeTextColor); + AddToIconMap(new[] { "txt", "md" }, (char)FontAwesomeIcon.FileAlt, standardTextColor); + AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, (char)FontAwesomeIcon.FileArchive, miscTextColor); + AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, (char)FontAwesomeIcon.FileAudio, miscTextColor); + AddToIconMap(new[] { "csv" }, (char)FontAwesomeIcon.FileCsv, miscTextColor); + } + + return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem + { + Icon = (char)FontAwesomeIcon.File, + Color = standardTextColor, + }; + } + + private void DrawHeader() + { + this.DrawPathComposer(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); + ImGui.Separator(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); + + this.DrawSearchBar(); + } + + private void DrawPathComposer() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{(this.pathInputActivated ? (char)FontAwesomeIcon.Times : (char)FontAwesomeIcon.Edit)}")) + { + this.pathInputActivated = !this.pathInputActivated; + } + + ImGui.PopFont(); + + ImGui.SameLine(); + + if (this.pathDecomposition.Count > 0) + { + ImGui.SameLine(); + + if (this.pathInputActivated) + { + ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255); + ImGui.PopItemWidth(); + } + else + { + for (var idx = 0; idx < this.pathDecomposition.Count; idx++) + { + if (idx > 0) + { + ImGui.SameLine(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() - 3); + } + + ImGui.PushID(idx); + ImGui.PushStyleColor(ImGuiCol.Button, pathDecompColor); + var click = ImGui.Button(this.pathDecomposition[idx]); + ImGui.PopStyleColor(); + ImGui.PopID(); + + if (click) + { + this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); + this.pathClicked = true; + break; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + this.pathInputBuffer = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); + this.pathInputActivated = true; + break; } } - else + } + } + } + + private void DrawSearchBar() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{(char)FontAwesomeIcon.Home}")) + { + this.SetPath("."); + } + + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Reset to current directory"); + } + + ImGui.SameLine(); + + this.DrawDirectoryCreation(); + + if (!this.createDirectoryMode) + { + ImGui.SameLine(); + ImGui.Text("Search :"); + ImGui.SameLine(); + ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); + var edited = ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255); + ImGui.PopItemWidth(); + if (edited) + { + this.ApplyFilteringOnFileList(); + } + } + } + + private void DrawDirectoryCreation() + { + if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return; + + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{(char)FontAwesomeIcon.FolderPlus}")) + { + if (!this.createDirectoryMode) + { + this.createDirectoryMode = true; + this.createDirectoryBuffer = string.Empty; + } + } + + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Create Directory"); + } + + if (this.createDirectoryMode) + { + ImGui.SameLine(); + ImGui.Text("New Directory Name"); + + ImGui.SameLine(); + ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - 100f); + ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255); + ImGui.PopItemWidth(); + + ImGui.SameLine(); + + if (ImGui.Button("Ok")) + { + if (this.CreateDir(this.createDirectoryBuffer)) { - this.SelectFileName(file); - if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) + this.SetPath(Path.Combine(this.currentPath, this.createDirectoryBuffer)); + } + + this.createDirectoryMode = false; + } + + ImGui.SameLine(); + + if (ImGui.Button("Cancel")) + { + this.createDirectoryMode = false; + } + } + } + + private void DrawContent() + { + var size = ImGui.GetContentRegionAvail() - new Vector2(0, this.footerHeight); + + if (!this.flags.HasFlag(ImGuiFileDialogFlags.HideSideBar)) + { + ImGui.BeginChild("##FileDialog_ColumnChild", size); + ImGui.Columns(2, "##FileDialog_Columns"); + + this.DrawSideBar(new Vector2(150, size.Y)); + + ImGui.SetColumnWidth(0, 150); + ImGui.NextColumn(); + + this.DrawFileListView(size - new Vector2(160, 0)); + + ImGui.Columns(1); + ImGui.EndChild(); + } + else + { + this.DrawFileListView(size); + } + } + + private void DrawSideBar(Vector2 size) + { + ImGui.BeginChild("##FileDialog_SideBar", size); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5); + + foreach (var drive in this.drives) + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Selectable($"{drive.Icon}##{drive.Text}", drive.Text == this.selectedSideBar)) + { + this.SetPath(drive.Location); + this.selectedSideBar = drive.Text; + } + + ImGui.PopFont(); + + ImGui.SameLine(25); + + ImGui.Text(drive.Text); + } + + foreach (var quick in this.quickAccess) + { + if (string.IsNullOrEmpty(quick.Location)) + { + continue; + } + + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Selectable($"{quick.Icon}##{quick.Text}", quick.Text == this.selectedSideBar)) + { + this.SetPath(quick.Location); + this.selectedSideBar = quick.Text; + } + + ImGui.PopFont(); + + ImGui.SameLine(25); + + ImGui.Text(quick.Text); + } + + ImGui.EndChild(); + } + + private unsafe void DrawFileListView(Vector2 size) + { + ImGui.BeginChild("##FileDialog_FileList", size); + + var tableFlags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.Hideable | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoHostExtendX; + if (ImGui.BeginTable("##FileTable", 4, tableFlags, size)) + { + ImGui.TableSetupScrollFreeze(0, 1); + + var hideType = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnType); + var hideSize = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnSize); + var hideDate = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnDate); + + ImGui.TableSetupColumn(" File Name", ImGuiTableColumnFlags.WidthStretch, -1, 0); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed | (hideType ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 1); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed | (hideSize ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 2); + ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.WidthFixed | (hideDate ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 3); + + ImGui.TableNextRow(ImGuiTableRowFlags.Headers); + for (var column = 0; column < 4; column++) + { + ImGui.TableSetColumnIndex(column); + var columnName = ImGui.TableGetColumnName(column); + ImGui.PushID(column); + ImGui.TableHeader(columnName); + ImGui.PopID(); + if (ImGui.IsItemClicked()) + { + if (column == 0) { - this.wantsToQuit = true; - this.isOk = true; + this.SortFields(SortingField.FileName, true); + } + else if (column == 1) + { + this.SortFields(SortingField.Type, true); + } + else if (column == 2) + { + this.SortFields(SortingField.Size, true); + } + else + { + this.SortFields(SortingField.Date, true); } } } - return false; + if (this.filteredFiles.Count > 0) + { + ImGuiListClipperPtr clipper; + unsafe + { + clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + } + + lock (this.filesLock) + { + clipper.Begin(this.filteredFiles.Count); + while (clipper.Step()) + { + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + if (i < 0) continue; + + var file = this.filteredFiles[i]; + var selected = this.selectedFileNames.Contains(file.FileName); + var needToBreak = false; + + var dir = file.Type == FileStructType.Directory; + var item = !dir ? GetIcon(file.Ext) : new IconColorItem + { + Color = dirTextColor, + Icon = (char)FontAwesomeIcon.Folder, + }; + + ImGui.PushStyleColor(ImGuiCol.Text, item.Color); + if (selected) ImGui.PushStyleColor(ImGuiCol.Text, selectedTextColor); + + ImGui.TableNextRow(); + + if (ImGui.TableNextColumn()) + { + needToBreak = this.SelectableItem(file, selected, item.Icon); + } + + if (ImGui.TableNextColumn()) + { + ImGui.Text(file.Ext); + } + + if (ImGui.TableNextColumn()) + { + if (file.Type == FileStructType.File) + { + ImGui.Text(file.FormattedFileSize + " "); + } + else + { + ImGui.Text(" "); + } + } + + if (ImGui.TableNextColumn()) + { + var sz = ImGui.CalcTextSize(file.FileModifiedDate); + ImGui.PushItemWidth(sz.X + 5); + ImGui.Text(file.FileModifiedDate + " "); + ImGui.PopItemWidth(); + } + + if (selected) ImGui.PopStyleColor(); + ImGui.PopStyleColor(); + + if (needToBreak) break; + } + } + + clipper.End(); + } + } + + if (this.pathInputActivated) + { + if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Enter))) + { + if (Directory.Exists(this.pathInputBuffer)) this.SetPath(this.pathInputBuffer); + this.pathInputActivated = false; + } + + if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Escape))) + { + this.pathInputActivated = false; + } + } + + ImGui.EndTable(); } - private bool SelectDirectory(FileStruct file) + if (this.pathClicked) { - var pathClick = false; + this.SetPath(this.currentPath); + } - if (file.FileName == "..") + ImGui.EndChild(); + } + + private bool SelectableItem(FileStruct file, bool selected, char icon) + { + var flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns; + + ImGui.PushFont(UiBuilder.IconFont); + + ImGui.Text($"{icon}"); + ImGui.PopFont(); + + ImGui.SameLine(25f); + + if (ImGui.Selectable(file.FileName, selected, flags)) + { + if (file.Type == FileStructType.Directory) { - if (this.pathDecomposition.Count > 1) + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) { - this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, this.pathDecomposition.Count - 1)); - pathClick = true; + this.pathClicked = this.SelectDirectory(file); + return true; + } + else if (this.IsDirectoryMode()) + { + this.SelectFileName(file); } } else { - var newPath = Path.Combine(this.currentPath, file.FileName); - - if (Directory.Exists(newPath)) + this.SelectFileName(file); + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) { - this.currentPath = newPath; + this.wantsToQuit = true; + this.isOk = true; } - - pathClick = true; } - - return pathClick; } - private void SelectFileName(FileStruct file) + return false; + } + + private bool SelectDirectory(FileStruct file) + { + var pathClick = false; + + if (file.FileName == "..") { - if (ImGui.GetIO().KeyCtrl) + if (this.pathDecomposition.Count > 1) { - if (this.selectionCountMax == 0) - { // infinite select + this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, this.pathDecomposition.Count - 1)); + pathClick = true; + } + } + else + { + var newPath = Path.Combine(this.currentPath, file.FileName); + + if (Directory.Exists(newPath)) + { + this.currentPath = newPath; + } + + pathClick = true; + } + + return pathClick; + } + + private void SelectFileName(FileStruct file) + { + if (ImGui.GetIO().KeyCtrl) + { + if (this.selectionCountMax == 0) + { // infinite select + if (!this.selectedFileNames.Contains(file.FileName)) + { + this.AddFileNameInSelection(file.FileName, true); + } + else + { + this.RemoveFileNameInSelection(file.FileName); + } + } + else + { + if (this.selectedFileNames.Count < this.selectionCountMax) + { if (!this.selectedFileNames.Contains(file.FileName)) { this.AddFileNameInSelection(file.FileName, true); @@ -584,76 +597,39 @@ namespace Dalamud.Interface.ImGuiFileDialog this.RemoveFileNameInSelection(file.FileName); } } - else + } + } + else if (ImGui.GetIO().KeyShift) + { + if (this.selectionCountMax != 1) + { // can select a block + this.selectedFileNames.Clear(); + + var startMultiSelection = false; + var fileNameToSelect = file.FileName; + var savedLastSelectedFileName = string.Empty; + + foreach (var f in this.filteredFiles) { - if (this.selectedFileNames.Count < this.selectionCountMax) + // select top-to-bottom + if (f.FileName == this.lastSelectedFileName) + { // start (the previously selected one) + startMultiSelection = true; + this.AddFileNameInSelection(this.lastSelectedFileName, false); + } + else if (startMultiSelection) { - if (!this.selectedFileNames.Contains(file.FileName)) + if (this.selectionCountMax == 0) { - this.AddFileNameInSelection(file.FileName, true); + this.AddFileNameInSelection(f.FileName, false); } else { - this.RemoveFileNameInSelection(file.FileName); - } - } - } - } - else if (ImGui.GetIO().KeyShift) - { - if (this.selectionCountMax != 1) - { // can select a block - this.selectedFileNames.Clear(); - - var startMultiSelection = false; - var fileNameToSelect = file.FileName; - var savedLastSelectedFileName = string.Empty; - - foreach (var f in this.filteredFiles) - { - // select top-to-bottom - if (f.FileName == this.lastSelectedFileName) - { // start (the previously selected one) - startMultiSelection = true; - this.AddFileNameInSelection(this.lastSelectedFileName, false); - } - else if (startMultiSelection) - { - if (this.selectionCountMax == 0) + if (this.selectedFileNames.Count < this.selectionCountMax) { this.AddFileNameInSelection(f.FileName, false); } else - { - if (this.selectedFileNames.Count < this.selectionCountMax) - { - this.AddFileNameInSelection(f.FileName, false); - } - else - { - startMultiSelection = false; - if (!string.IsNullOrEmpty(savedLastSelectedFileName)) - { - this.lastSelectedFileName = savedLastSelectedFileName; - } - - break; - } - } - } - - // select bottom-to-top - if (f.FileName == fileNameToSelect) - { - if (!startMultiSelection) - { - savedLastSelectedFileName = this.lastSelectedFileName; - this.lastSelectedFileName = fileNameToSelect; - fileNameToSelect = savedLastSelectedFileName; - startMultiSelection = true; - this.AddFileNameInSelection(this.lastSelectedFileName, false); - } - else { startMultiSelection = false; if (!string.IsNullOrEmpty(savedLastSelectedFileName)) @@ -665,205 +641,228 @@ namespace Dalamud.Interface.ImGuiFileDialog } } } + + // select bottom-to-top + if (f.FileName == fileNameToSelect) + { + if (!startMultiSelection) + { + savedLastSelectedFileName = this.lastSelectedFileName; + this.lastSelectedFileName = fileNameToSelect; + fileNameToSelect = savedLastSelectedFileName; + startMultiSelection = true; + this.AddFileNameInSelection(this.lastSelectedFileName, false); + } + else + { + startMultiSelection = false; + if (!string.IsNullOrEmpty(savedLastSelectedFileName)) + { + this.lastSelectedFileName = savedLastSelectedFileName; + } + + break; + } + } } } - else - { - this.selectedFileNames.Clear(); - this.fileNameBuffer = string.Empty; - this.AddFileNameInSelection(file.FileName, true); - } + } + else + { + this.selectedFileNames.Clear(); + this.fileNameBuffer = string.Empty; + this.AddFileNameInSelection(file.FileName, true); + } + } + + private void AddFileNameInSelection(string name, bool setLastSelection) + { + this.selectedFileNames.Add(name); + if (this.selectedFileNames.Count == 1) + { + this.fileNameBuffer = name; + } + else + { + this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; } - private void AddFileNameInSelection(string name, bool setLastSelection) + if (setLastSelection) { - this.selectedFileNames.Add(name); - if (this.selectedFileNames.Count == 1) - { - this.fileNameBuffer = name; - } - else - { - this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; - } + this.lastSelectedFileName = name; + } + } - if (setLastSelection) - { - this.lastSelectedFileName = name; - } + private void RemoveFileNameInSelection(string name) + { + this.selectedFileNames.Remove(name); + if (this.selectedFileNames.Count == 1) + { + this.fileNameBuffer = name; + } + else + { + this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; + } + } + + private bool DrawFooter() + { + var posY = ImGui.GetCursorPosY(); + + if (this.IsDirectoryMode()) + { + ImGui.Text("Directory Path :"); + } + else + { + ImGui.Text("File Name :"); } - private void RemoveFileNameInSelection(string name) + ImGui.SameLine(); + + var width = ImGui.GetContentRegionAvail().X - 100; + if (this.filters.Count > 0) { - this.selectedFileNames.Remove(name); - if (this.selectedFileNames.Count == 1) - { - this.fileNameBuffer = name; - } - else - { - this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; - } + width -= 150f; } - private bool DrawFooter() + var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly); + + ImGui.PushItemWidth(width); + if (selectOnly) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + ImGui.InputText("##FileName", ref this.fileNameBuffer, 255, selectOnly ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None); + if (selectOnly) ImGui.PopStyleVar(); + ImGui.PopItemWidth(); + + if (this.filters.Count > 0) { - var posY = ImGui.GetCursorPosY(); - - if (this.IsDirectoryMode()) - { - ImGui.Text("Directory Path :"); - } - else - { - ImGui.Text("File Name :"); - } - ImGui.SameLine(); + var needToApplyNewFilter = false; - var width = ImGui.GetContentRegionAvail().X - 100; - if (this.filters.Count > 0) + ImGui.PushItemWidth(150f); + if (ImGui.BeginCombo("##Filters", this.selectedFilter.Filter, ImGuiComboFlags.None)) { - width -= 150f; - } - - var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly); - - ImGui.PushItemWidth(width); - if (selectOnly) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); - ImGui.InputText("##FileName", ref this.fileNameBuffer, 255, selectOnly ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None); - if (selectOnly) ImGui.PopStyleVar(); - ImGui.PopItemWidth(); - - if (this.filters.Count > 0) - { - ImGui.SameLine(); - var needToApplyNewFilter = false; - - ImGui.PushItemWidth(150f); - if (ImGui.BeginCombo("##Filters", this.selectedFilter.Filter, ImGuiComboFlags.None)) + var idx = 0; + foreach (var filter in this.filters) { - var idx = 0; - foreach (var filter in this.filters) + var selected = filter.Filter == this.selectedFilter.Filter; + ImGui.PushID(idx++); + if (ImGui.Selectable(filter.Filter, selected)) { - var selected = filter.Filter == this.selectedFilter.Filter; - ImGui.PushID(idx++); - if (ImGui.Selectable(filter.Filter, selected)) - { - this.selectedFilter = filter; - needToApplyNewFilter = true; - } - - ImGui.PopID(); + this.selectedFilter = filter; + needToApplyNewFilter = true; } - ImGui.EndCombo(); + ImGui.PopID(); } - ImGui.PopItemWidth(); + ImGui.EndCombo(); + } - if (needToApplyNewFilter) - { - this.SetPath(this.currentPath); + ImGui.PopItemWidth(); + + if (needToApplyNewFilter) + { + this.SetPath(this.currentPath); + } + } + + var res = false; + + ImGui.SameLine(); + + var disableOk = string.IsNullOrEmpty(this.fileNameBuffer) || (selectOnly && !this.IsItemSelected()); + if (disableOk) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + + if (ImGui.Button("Ok") && !disableOk) + { + this.isOk = true; + res = true; + } + + if (disableOk) ImGui.PopStyleVar(); + + ImGui.SameLine(); + + if (ImGui.Button("Cancel")) + { + this.isOk = false; + res = true; + } + + this.footerHeight = ImGui.GetCursorPosY() - posY; + + if (this.wantsToQuit && this.isOk) + { + res = true; + } + + return res; + } + + private bool IsItemSelected() + { + if (this.selectedFileNames.Count > 0) return true; + if (this.IsDirectoryMode()) return true; // current directory + return false; + } + + private bool ConfirmOrOpenOverWriteFileDialogIfNeeded(bool lastAction) + { + if (this.IsDirectoryMode()) return lastAction; + if (!this.isOk && lastAction) return true; // no need to confirm anything, since it was cancelled + + var confirmOverwrite = this.flags.HasFlag(ImGuiFileDialogFlags.ConfirmOverwrite); + + if (this.isOk && lastAction && !confirmOverwrite) return true; + + if (this.okResultToConfirm || (this.isOk && lastAction && confirmOverwrite)) + { // if waiting on a confirmation, or need to start one + if (this.isOk) + { + if (!File.Exists(this.GetFilePathName())) + { // quit dialog, it doesn't exist anyway + return true; + } + else + { // already exists, open dialog to confirm overwrite + this.isOk = false; + this.okResultToConfirm = true; } } + var name = $"The file Already Exists !##{this.title}{this.id}OverWriteDialog"; var res = false; + var open = true; - ImGui.SameLine(); - - var disableOk = string.IsNullOrEmpty(this.fileNameBuffer) || (selectOnly && !this.IsItemSelected()); - if (disableOk) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); - - if (ImGui.Button("Ok") && !disableOk) + ImGui.OpenPopup(name); + if (ImGui.BeginPopupModal(name, ref open, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove)) { - this.isOk = true; - res = true; - } + ImGui.Text("Would you like to Overwrite it ?"); + if (ImGui.Button("Confirm")) + { + this.okResultToConfirm = false; + this.isOk = true; + res = true; + ImGui.CloseCurrentPopup(); + } - if (disableOk) ImGui.PopStyleVar(); + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + this.okResultToConfirm = false; + this.isOk = false; + res = false; + ImGui.CloseCurrentPopup(); + } - ImGui.SameLine(); - - if (ImGui.Button("Cancel")) - { - this.isOk = false; - res = true; - } - - this.footerHeight = ImGui.GetCursorPosY() - posY; - - if (this.wantsToQuit && this.isOk) - { - res = true; + ImGui.EndPopup(); } return res; } - private bool IsItemSelected() - { - if (this.selectedFileNames.Count > 0) return true; - if (this.IsDirectoryMode()) return true; // current directory - return false; - } - - private bool ConfirmOrOpenOverWriteFileDialogIfNeeded(bool lastAction) - { - if (this.IsDirectoryMode()) return lastAction; - if (!this.isOk && lastAction) return true; // no need to confirm anything, since it was cancelled - - var confirmOverwrite = this.flags.HasFlag(ImGuiFileDialogFlags.ConfirmOverwrite); - - if (this.isOk && lastAction && !confirmOverwrite) return true; - - if (this.okResultToConfirm || (this.isOk && lastAction && confirmOverwrite)) - { // if waiting on a confirmation, or need to start one - if (this.isOk) - { - if (!File.Exists(this.GetFilePathName())) - { // quit dialog, it doesn't exist anyway - return true; - } - else - { // already exists, open dialog to confirm overwrite - this.isOk = false; - this.okResultToConfirm = true; - } - } - - var name = $"The file Already Exists !##{this.title}{this.id}OverWriteDialog"; - var res = false; - var open = true; - - ImGui.OpenPopup(name); - if (ImGui.BeginPopupModal(name, ref open, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove)) - { - ImGui.Text("Would you like to Overwrite it ?"); - if (ImGui.Button("Confirm")) - { - this.okResultToConfirm = false; - this.isOk = true; - res = true; - ImGui.CloseCurrentPopup(); - } - - ImGui.SameLine(); - if (ImGui.Button("Cancel")) - { - this.okResultToConfirm = false; - this.isOk = false; - res = false; - ImGui.CloseCurrentPopup(); - } - - ImGui.EndPopup(); - } - - return res; - } - - return false; - } + return false; } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs index 9e2a77f0d..dad1a8a50 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs @@ -3,238 +3,237 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// A file or folder picker. +/// +public partial class FileDialog { + private readonly string title; + private readonly int selectionCountMax; + private readonly ImGuiFileDialogFlags flags; + private readonly string id; + private readonly string defaultExtension; + private readonly string defaultFileName; + + private bool visible; + + private string currentPath; + private string fileNameBuffer = string.Empty; + + private List pathDecomposition = new(); + private bool pathClicked = true; + private bool pathInputActivated = false; + private string pathInputBuffer = string.Empty; + + private bool isModal = false; + private bool okResultToConfirm = false; + private bool isOk; + private bool wantsToQuit; + + private bool createDirectoryMode = false; + private string createDirectoryBuffer = string.Empty; + + private string searchBuffer = string.Empty; + + private string lastSelectedFileName = string.Empty; + private List selectedFileNames = new(); + + private float footerHeight = 0; + + private string selectedSideBar = string.Empty; + private List drives = new(); + private List quickAccess = new(); + /// - /// A file or folder picker. + /// Initializes a new instance of the class. /// - public partial class FileDialog + /// A unique id for the dialog. + /// The text which is shown at the top of the dialog. + /// Which file extension filters to apply. This should be left blank to select directories. + /// The directory which the dialog should start inside of. + /// The default file or directory name. + /// The default extension when creating new files. + /// The maximum amount of files or directories which can be selected. Set to 0 for an infinite number. + /// Whether the dialog should be a modal popup. + /// Settings flags for the dialog, see . + public FileDialog( + string id, + string title, + string filters, + string path, + string defaultFileName, + string defaultExtension, + int selectionCountMax, + bool isModal, + ImGuiFileDialogFlags flags) { - private readonly string title; - private readonly int selectionCountMax; - private readonly ImGuiFileDialogFlags flags; - private readonly string id; - private readonly string defaultExtension; - private readonly string defaultFileName; + this.id = id; + this.title = title; + this.flags = flags; + this.selectionCountMax = selectionCountMax; + this.isModal = isModal; - private bool visible; + this.currentPath = path; + this.defaultExtension = defaultExtension; + this.defaultFileName = defaultFileName; - private string currentPath; - private string fileNameBuffer = string.Empty; + this.ParseFilters(filters); + this.SetSelectedFilterWithExt(this.defaultExtension); + this.SetDefaultFileName(); + this.SetPath(this.currentPath); - private List pathDecomposition = new(); - private bool pathClicked = true; - private bool pathInputActivated = false; - private string pathInputBuffer = string.Empty; + this.SetupSideBar(); + } - private bool isModal = false; - private bool okResultToConfirm = false; - private bool isOk; - private bool wantsToQuit; + /// + /// Shows the dialog. + /// + public void Show() + { + this.visible = true; + } - private bool createDirectoryMode = false; - private string createDirectoryBuffer = string.Empty; + /// + /// Hides the dialog. + /// + public void Hide() + { + this.visible = false; + } - private string searchBuffer = string.Empty; + /// + /// Gets whether a file or folder was successfully selected. + /// + /// The success state. Will be false if the selection was canceled or was otherwise unsuccessful. + public bool GetIsOk() + { + return this.isOk; + } - private string lastSelectedFileName = string.Empty; - private List selectedFileNames = new(); - - private float footerHeight = 0; - - private string selectedSideBar = string.Empty; - private List drives = new(); - private List quickAccess = new(); - - /// - /// Initializes a new instance of the class. - /// - /// A unique id for the dialog. - /// The text which is shown at the top of the dialog. - /// Which file extension filters to apply. This should be left blank to select directories. - /// The directory which the dialog should start inside of. - /// The default file or directory name. - /// The default extension when creating new files. - /// The maximum amount of files or directories which can be selected. Set to 0 for an infinite number. - /// Whether the dialog should be a modal popup. - /// Settings flags for the dialog, see . - public FileDialog( - string id, - string title, - string filters, - string path, - string defaultFileName, - string defaultExtension, - int selectionCountMax, - bool isModal, - ImGuiFileDialogFlags flags) + /// + /// Gets the result of the selection. + /// + /// The result of the selection (file or folder path). If multiple entries were selected, they are separated with commas. + public string GetResult() + { + if (!this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly)) { - this.id = id; - this.title = title; - this.flags = flags; - this.selectionCountMax = selectionCountMax; - this.isModal = isModal; - - this.currentPath = path; - this.defaultExtension = defaultExtension; - this.defaultFileName = defaultFileName; - - this.ParseFilters(filters); - this.SetSelectedFilterWithExt(this.defaultExtension); - this.SetDefaultFileName(); - this.SetPath(this.currentPath); - - this.SetupSideBar(); + return this.GetFilePathName(); } - /// - /// Shows the dialog. - /// - public void Show() + if (this.IsDirectoryMode() && this.selectedFileNames.Count == 0) { - this.visible = true; + return this.GetFilePathName(); // current directory } - /// - /// Hides the dialog. - /// - public void Hide() + var fullPaths = this.selectedFileNames.Where(x => !string.IsNullOrEmpty(x)).Select(x => Path.Combine(this.currentPath, x)); + return string.Join(",", fullPaths.ToArray()); + } + + /// + /// Gets the current path of the dialog. + /// + /// The path of the directory which the dialog is current viewing. + public string GetCurrentPath() + { + if (this.IsDirectoryMode()) { - this.visible = false; + // combine path file with directory input + var selectedDirectory = this.fileNameBuffer; + if (!string.IsNullOrEmpty(selectedDirectory) && selectedDirectory != ".") + { + return string.IsNullOrEmpty(this.currentPath) ? selectedDirectory : Path.Combine(this.currentPath, selectedDirectory); + } } - /// - /// Gets whether a file or folder was successfully selected. - /// - /// The success state. Will be false if the selection was canceled or was otherwise unsuccessful. - public bool GetIsOk() + return this.currentPath; + } + + private string GetFilePathName() + { + var path = this.GetCurrentPath(); + var fileName = this.GetCurrentFileName(); + + if (!string.IsNullOrEmpty(fileName)) { - return this.isOk; + return Path.Combine(path, fileName); } - /// - /// Gets the result of the selection. - /// - /// The result of the selection (file or folder path). If multiple entries were selected, they are separated with commas. - public string GetResult() + return path; + } + + private string GetCurrentFileName() + { + if (this.IsDirectoryMode()) { - if (!this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly)) - { - return this.GetFilePathName(); - } - - if (this.IsDirectoryMode() && this.selectedFileNames.Count == 0) - { - return this.GetFilePathName(); // current directory - } - - var fullPaths = this.selectedFileNames.Where(x => !string.IsNullOrEmpty(x)).Select(x => Path.Combine(this.currentPath, x)); - return string.Join(",", fullPaths.ToArray()); + return string.Empty; } - /// - /// Gets the current path of the dialog. - /// - /// The path of the directory which the dialog is current viewing. - public string GetCurrentPath() + var result = this.fileNameBuffer; + + // a collection like {.cpp, .h}, so can't decide on an extension + if (this.selectedFilter.CollectionFilters != null && this.selectedFilter.CollectionFilters.Count > 0) { - if (this.IsDirectoryMode()) - { - // combine path file with directory input - var selectedDirectory = this.fileNameBuffer; - if (!string.IsNullOrEmpty(selectedDirectory) && selectedDirectory != ".") - { - return string.IsNullOrEmpty(this.currentPath) ? selectedDirectory : Path.Combine(this.currentPath, selectedDirectory); - } - } - - return this.currentPath; - } - - private string GetFilePathName() - { - var path = this.GetCurrentPath(); - var fileName = this.GetCurrentFileName(); - - if (!string.IsNullOrEmpty(fileName)) - { - return Path.Combine(path, fileName); - } - - return path; - } - - private string GetCurrentFileName() - { - if (this.IsDirectoryMode()) - { - return string.Empty; - } - - var result = this.fileNameBuffer; - - // a collection like {.cpp, .h}, so can't decide on an extension - if (this.selectedFilter.CollectionFilters != null && this.selectedFilter.CollectionFilters.Count > 0) - { - return result; - } - - // a single one, like .cpp - if (!this.selectedFilter.Filter.Contains('*') && result != this.selectedFilter.Filter) - { - var lastPoint = result.LastIndexOf('.'); - if (lastPoint != -1) - { - result = result.Substring(0, lastPoint); - } - - result += this.selectedFilter.Filter; - } - return result; } - private void SetDefaultFileName() + // a single one, like .cpp + if (!this.selectedFilter.Filter.Contains('*') && result != this.selectedFilter.Filter) { - this.fileNameBuffer = this.defaultFileName; - } - - private void SetPath(string path) - { - this.selectedSideBar = string.Empty; - this.currentPath = path; - this.files.Clear(); - this.pathDecomposition.Clear(); - this.selectedFileNames.Clear(); - if (this.IsDirectoryMode()) + var lastPoint = result.LastIndexOf('.'); + if (lastPoint != -1) { - this.SetDefaultFileName(); + result = result.Substring(0, lastPoint); } - this.ScanDir(this.currentPath); + result += this.selectedFilter.Filter; } - private void SetCurrentDir(string path) + return result; + } + + private void SetDefaultFileName() + { + this.fileNameBuffer = this.defaultFileName; + } + + private void SetPath(string path) + { + this.selectedSideBar = string.Empty; + this.currentPath = path; + this.files.Clear(); + this.pathDecomposition.Clear(); + this.selectedFileNames.Clear(); + if (this.IsDirectoryMode()) { - var dir = new DirectoryInfo(path); - this.currentPath = dir.FullName; - if (this.currentPath[^1] == Path.DirectorySeparatorChar) - { // handle selecting a drive, like C: -> C:\ - this.currentPath = this.currentPath[0..^1]; - } - - this.pathInputBuffer = this.currentPath; - this.pathDecomposition = new List(this.currentPath.Split(Path.DirectorySeparatorChar)); + this.SetDefaultFileName(); } - private bool IsDirectoryMode() - { - return this.filters.Count == 0; + this.ScanDir(this.currentPath); + } + + private void SetCurrentDir(string path) + { + var dir = new DirectoryInfo(path); + this.currentPath = dir.FullName; + if (this.currentPath[^1] == Path.DirectorySeparatorChar) + { // handle selecting a drive, like C: -> C:\ + this.currentPath = this.currentPath[0..^1]; } - private void ResetEvents() - { - this.pathClicked = false; - } + this.pathInputBuffer = this.currentPath; + this.pathDecomposition = new List(this.currentPath.Split(Path.DirectorySeparatorChar)); + } + + private bool IsDirectoryMode() + { + return this.filters.Count == 0; + } + + private void ResetEvents() + { + this.pathClicked = false; } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs index 18bd9dc14..9f1ddcc98 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs @@ -1,101 +1,100 @@ using System; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// A manager for the class. +/// +public class FileDialogManager { + private FileDialog dialog; + private string savedPath = "."; + private Action callback; + /// - /// A manager for the class. + /// Create a dialog which selects an already existing folder. /// - public class FileDialogManager + /// The header title of the dialog. + /// The action to execute when the dialog is finished. + public void OpenFolderDialog(string title, Action callback) { - private FileDialog dialog; - private string savedPath = "."; - private Action callback; + this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); + } - /// - /// Create a dialog which selects an already existing folder. - /// - /// The header title of the dialog. - /// The action to execute when the dialog is finished. - public void OpenFolderDialog(string title, Action callback) - { - this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); - } + /// + /// Create a dialog which selects an already existing folder or new folder. + /// + /// The header title of the dialog. + /// The default name to use when creating a new folder. + /// The action to execute when the dialog is finished. + public void SaveFolderDialog(string title, string defaultFolderName, Action callback) + { + this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None, callback); + } - /// - /// Create a dialog which selects an already existing folder or new folder. - /// - /// The header title of the dialog. - /// The default name to use when creating a new folder. - /// The action to execute when the dialog is finished. - public void SaveFolderDialog(string title, string defaultFolderName, Action callback) - { - this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None, callback); - } + /// + /// Create a dialog which selects an already existing file. + /// + /// The header title of the dialog. + /// Which files to show in the dialog. + /// The action to execute when the dialog is finished. + public void OpenFileDialog(string title, string filters, Action callback) + { + this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); + } - /// - /// Create a dialog which selects an already existing file. - /// - /// The header title of the dialog. - /// Which files to show in the dialog. - /// The action to execute when the dialog is finished. - public void OpenFileDialog(string title, string filters, Action callback) - { - this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); - } + /// + /// Create a dialog which selects an already existing folder or new file. + /// + /// The header title of the dialog. + /// Which files to show in the dialog. + /// The default name to use when creating a new file. + /// The extension to use when creating a new file. + /// The action to execute when the dialog is finished. + public void SaveFileDialog(string title, string filters, string defaultFileName, string defaultExtension, Action callback) + { + this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None, callback); + } - /// - /// Create a dialog which selects an already existing folder or new file. - /// - /// The header title of the dialog. - /// Which files to show in the dialog. - /// The default name to use when creating a new file. - /// The extension to use when creating a new file. - /// The action to execute when the dialog is finished. - public void SaveFileDialog(string title, string filters, string defaultFileName, string defaultExtension, Action callback) - { - this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None, callback); - } - - /// - /// Draws the current dialog, if any, and executes the callback if it is finished. - /// - public void Draw() - { - if (this.dialog == null) return; - if (this.dialog.Draw()) - { - this.callback(this.dialog.GetIsOk(), this.dialog.GetResult()); - this.savedPath = this.dialog.GetCurrentPath(); - this.Reset(); - } - } - - /// - /// Removes the current dialog, if any. - /// - public void Reset() - { - this.dialog?.Hide(); - this.dialog = null; - this.callback = null; - } - - private void SetDialog( - string id, - string title, - string filters, - string path, - string defaultFileName, - string defaultExtension, - int selectionCountMax, - bool isModal, - ImGuiFileDialogFlags flags, - Action callback) + /// + /// Draws the current dialog, if any, and executes the callback if it is finished. + /// + public void Draw() + { + if (this.dialog == null) return; + if (this.dialog.Draw()) { + this.callback(this.dialog.GetIsOk(), this.dialog.GetResult()); + this.savedPath = this.dialog.GetCurrentPath(); this.Reset(); - this.callback = callback; - this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags); - this.dialog.Show(); } } + + /// + /// Removes the current dialog, if any. + /// + public void Reset() + { + this.dialog?.Hide(); + this.dialog = null; + this.callback = null; + } + + private void SetDialog( + string id, + string title, + string filters, + string path, + string defaultFileName, + string defaultExtension, + int selectionCountMax, + bool isModal, + ImGuiFileDialogFlags flags, + Action callback) + { + this.Reset(); + this.callback = callback; + this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags); + this.dialog.Show(); + } } diff --git a/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs b/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs index fe189c77c..f62f5f721 100644 --- a/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs +++ b/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs @@ -4,57 +4,56 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Dalamud.Interface.ImGuiFileDialog +namespace Dalamud.Interface.ImGuiFileDialog; + +/// +/// Settings flags for the class. +/// +[Flags] +public enum ImGuiFileDialogFlags { /// - /// Settings flags for the class. + /// None. /// - [Flags] - public enum ImGuiFileDialogFlags - { - /// - /// None. - /// - None = 0, + None = 0, - /// - /// Confirm the selection when choosing a file which already exists. - /// - ConfirmOverwrite = 1, + /// + /// Confirm the selection when choosing a file which already exists. + /// + ConfirmOverwrite = 1, - /// - /// Only allow selection of files or folders which currently exist. - /// - SelectOnly = 2, + /// + /// Only allow selection of files or folders which currently exist. + /// + SelectOnly = 2, - /// - /// Hide files or folders which start with a period. - /// - DontShowHiddenFiles = 3, + /// + /// Hide files or folders which start with a period. + /// + DontShowHiddenFiles = 3, - /// - /// Disable the creation of new folders within the dialog. - /// - DisableCreateDirectoryButton = 4, + /// + /// Disable the creation of new folders within the dialog. + /// + DisableCreateDirectoryButton = 4, - /// - /// Hide the type column. - /// - HideColumnType = 5, + /// + /// Hide the type column. + /// + HideColumnType = 5, - /// - /// Hide the file size column. - /// - HideColumnSize = 6, + /// + /// Hide the file size column. + /// + HideColumnSize = 6, - /// - /// Hide the last modified date column. - /// - HideColumnDate = 7, + /// + /// Hide the last modified date column. + /// + HideColumnDate = 7, - /// - /// Hide the quick access sidebar. - /// - HideSideBar = 8, - } + /// + /// Hide the quick access sidebar. + /// + HideSideBar = 8, } diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs index f138cdf40..17ce759fc 100644 --- a/Dalamud/Interface/ImGuiHelpers.cs +++ b/Dalamud/Interface/ImGuiHelpers.cs @@ -3,121 +3,120 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface +namespace Dalamud.Interface; + +/// +/// Class containing various helper methods for use with ImGui inside Dalamud. +/// +public static class ImGuiHelpers { /// - /// Class containing various helper methods for use with ImGui inside Dalamud. + /// Gets the main viewport. /// - public static class ImGuiHelpers + public static ImGuiViewportPtr MainViewport { get; internal set; } + + /// + /// Gets the global Dalamud scale. + /// + public static float GlobalScale { get; private set; } + + /// + /// Gets a that is pre-scaled with the multiplier. + /// + /// Vector2 X parameter. + /// Vector2 Y parameter. + /// A scaled Vector2. + public static Vector2 ScaledVector2(float x, float y) => new Vector2(x, y) * GlobalScale; + + /// + /// Gets a that is pre-scaled with the multiplier. + /// + /// Vector4 X parameter. + /// Vector4 Y parameter. + /// Vector4 Z parameter. + /// Vector4 W parameter. + /// A scaled Vector2. + public static Vector4 ScaledVector4(float x, float y, float z, float w) => new Vector4(x, y, z, w) * GlobalScale; + + /// + /// Force the next ImGui window to stay inside the main game window. + /// + public static void ForceNextWindowMainViewport() => ImGui.SetNextWindowViewport(MainViewport.ID); + + /// + /// Create a dummy scaled by the global Dalamud scale. + /// + /// The size of the dummy. + public static void ScaledDummy(float size) => ScaledDummy(size, size); + + /// + /// Create a dummy scaled by the global Dalamud scale. + /// + /// Vector2 X parameter. + /// Vector2 Y parameter. + public static void ScaledDummy(float x, float y) => ScaledDummy(new Vector2(x, y)); + + /// + /// Create a dummy scaled by the global Dalamud scale. + /// + /// The size of the dummy. + public static void ScaledDummy(Vector2 size) => ImGui.Dummy(size * GlobalScale); + + /// + /// Use a relative ImGui.SameLine() from your current cursor position, scaled by the Dalamud global scale. + /// + /// The offset from your current cursor position. + /// The spacing to use. + public static void ScaledRelativeSameLine(float offset, float spacing = -1.0f) + => ImGui.SameLine(ImGui.GetCursorPosX() + (offset * GlobalScale), spacing); + + /// + /// Set the position of the next window relative to the main viewport. + /// + /// The position of the next window. + /// When to set the position. + /// The pivot to set the position around. + public static void SetNextWindowPosRelativeMainViewport(Vector2 position, ImGuiCond condition = ImGuiCond.None, Vector2 pivot = default) + => ImGui.SetNextWindowPos(position + MainViewport.Pos, condition, pivot); + + /// + /// Set the position of a window relative to the main viewport. + /// + /// The name/ID of the window. + /// The position of the window. + /// When to set the position. + public static void SetWindowPosRelativeMainViewport(string name, Vector2 position, ImGuiCond condition = ImGuiCond.None) + => ImGui.SetWindowPos(name, position + MainViewport.Pos, condition); + + /// + /// Creates default color palette for use with color pickers. + /// + /// The total number of swatches to use. + /// Default color palette. + public static List DefaultColorPalette(int swatchCount = 32) { - /// - /// Gets the main viewport. - /// - public static ImGuiViewportPtr MainViewport { get; internal set; } - - /// - /// Gets the global Dalamud scale. - /// - public static float GlobalScale { get; private set; } - - /// - /// Gets a that is pre-scaled with the multiplier. - /// - /// Vector2 X parameter. - /// Vector2 Y parameter. - /// A scaled Vector2. - public static Vector2 ScaledVector2(float x, float y) => new Vector2(x, y) * GlobalScale; - - /// - /// Gets a that is pre-scaled with the multiplier. - /// - /// Vector4 X parameter. - /// Vector4 Y parameter. - /// Vector4 Z parameter. - /// Vector4 W parameter. - /// A scaled Vector2. - public static Vector4 ScaledVector4(float x, float y, float z, float w) => new Vector4(x, y, z, w) * GlobalScale; - - /// - /// Force the next ImGui window to stay inside the main game window. - /// - public static void ForceNextWindowMainViewport() => ImGui.SetNextWindowViewport(MainViewport.ID); - - /// - /// Create a dummy scaled by the global Dalamud scale. - /// - /// The size of the dummy. - public static void ScaledDummy(float size) => ScaledDummy(size, size); - - /// - /// Create a dummy scaled by the global Dalamud scale. - /// - /// Vector2 X parameter. - /// Vector2 Y parameter. - public static void ScaledDummy(float x, float y) => ScaledDummy(new Vector2(x, y)); - - /// - /// Create a dummy scaled by the global Dalamud scale. - /// - /// The size of the dummy. - public static void ScaledDummy(Vector2 size) => ImGui.Dummy(size * GlobalScale); - - /// - /// Use a relative ImGui.SameLine() from your current cursor position, scaled by the Dalamud global scale. - /// - /// The offset from your current cursor position. - /// The spacing to use. - public static void ScaledRelativeSameLine(float offset, float spacing = -1.0f) - => ImGui.SameLine(ImGui.GetCursorPosX() + (offset * GlobalScale), spacing); - - /// - /// Set the position of the next window relative to the main viewport. - /// - /// The position of the next window. - /// When to set the position. - /// The pivot to set the position around. - public static void SetNextWindowPosRelativeMainViewport(Vector2 position, ImGuiCond condition = ImGuiCond.None, Vector2 pivot = default) - => ImGui.SetNextWindowPos(position + MainViewport.Pos, condition, pivot); - - /// - /// Set the position of a window relative to the main viewport. - /// - /// The name/ID of the window. - /// The position of the window. - /// When to set the position. - public static void SetWindowPosRelativeMainViewport(string name, Vector2 position, ImGuiCond condition = ImGuiCond.None) - => ImGui.SetWindowPos(name, position + MainViewport.Pos, condition); - - /// - /// Creates default color palette for use with color pickers. - /// - /// The total number of swatches to use. - /// Default color palette. - public static List DefaultColorPalette(int swatchCount = 32) + var colorPalette = new List(); + for (var i = 0; i < swatchCount; i++) { - var colorPalette = new List(); - for (var i = 0; i < swatchCount; i++) - { - ImGui.ColorConvertHSVtoRGB(i / 31.0f, 0.7f, 0.8f, out var r, out var g, out var b); - colorPalette.Add(new Vector4(r, g, b, 1.0f)); - } - - return colorPalette; + ImGui.ColorConvertHSVtoRGB(i / 31.0f, 0.7f, 0.8f, out var r, out var g, out var b); + colorPalette.Add(new Vector4(r, g, b, 1.0f)); } - /// - /// Get the size of a button considering the default frame padding. - /// - /// Text in the button. - /// with the size of the button. - public static Vector2 GetButtonSize(string text) => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); + return colorPalette; + } - /// - /// Get data needed for each new frame. - /// - internal static void NewFrame() - { - GlobalScale = ImGui.GetIO().FontGlobalScale; - } + /// + /// Get the size of a button considering the default frame padding. + /// + /// Text in the button. + /// with the size of the button. + public static Vector2 GetButtonSize(string text) => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); + + /// + /// Get data needed for each new frame. + /// + internal static void NewFrame() + { + GlobalScale = ImGui.GetIO().FontGlobalScale; } } diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index 0300885bb..aa18d25b9 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -11,338 +11,337 @@ using Dalamud.Game.Gui; using Dalamud.Plugin.Internal; using Serilog; -namespace Dalamud.Interface.Internal +namespace Dalamud.Interface.Internal; + +/// +/// Class handling Dalamud core commands. +/// +internal class DalamudCommands { /// - /// Class handling Dalamud core commands. + /// Initializes a new instance of the class. /// - internal class DalamudCommands + public DalamudCommands() { - /// - /// Initializes a new instance of the class. - /// - public DalamudCommands() + } + + /// + /// Register all command handlers with the Dalamud instance. + /// + public void SetupCommands() + { + var commandManager = Service.Get(); + + commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) { - } + HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), + ShowInHelp = false, + }); - /// - /// Register all command handlers with the Dalamud instance. - /// - public void SetupCommands() + commandManager.AddHandler("/xldreloadplugins", new CommandInfo(this.OnPluginReloadCommand) { - var commandManager = Service.Get(); + HelpMessage = Loc.Localize("DalamudPluginReloadHelp", "Reloads all plugins."), + ShowInHelp = false, + }); - commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) - { - HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xldreloadplugins", new CommandInfo(this.OnPluginReloadCommand) - { - HelpMessage = Loc.Localize("DalamudPluginReloadHelp", "Reloads all plugins."), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand) - { - HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."), - }); - - commandManager.AddHandler("/xlmute", new CommandInfo(this.OnBadWordsAddCommand) - { - HelpMessage = Loc.Localize("DalamudMuteHelp", "Mute a word or sentence from appearing in chat. Usage: /xlmute "), - }); - - commandManager.AddHandler("/xlmutelist", new CommandInfo(this.OnBadWordsListCommand) - { - HelpMessage = Loc.Localize("DalamudMuteListHelp", "List muted words or sentences."), - }); - - commandManager.AddHandler("/xlunmute", new CommandInfo(this.OnBadWordsRemoveCommand) - { - HelpMessage = Loc.Localize("DalamudUnmuteHelp", "Unmute a word or sentence. Usage: /xlunmute "), - }); - - commandManager.AddHandler("/ll", new CommandInfo(this.OnLastLinkCommand) - { - HelpMessage = Loc.Localize("DalamudLastLinkHelp", "Open the last posted link in your default browser."), - }); - - commandManager.AddHandler("/xlbgmset", new CommandInfo(this.OnBgmSetCommand) - { - HelpMessage = Loc.Localize("DalamudBgmSetHelp", "Set the Game background music. Usage: /xlbgmset "), - }); - - commandManager.AddHandler("/xldev", new CommandInfo(this.OnDebugDrawDevMenu) - { - HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu) - { - HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel) - { - HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog) - { - HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand) - { - HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"), - }); - - commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand) - { - HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."), - }); - - commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand) - { - HelpMessage = - Loc.Localize( - "DalamudLanguageHelp", - "Set the language for the in-game addon and plugins that support it. Available languages: ") + - Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code), - }); - - commandManager.AddHandler("/xlsettings", new CommandInfo(this.OnOpenSettingsCommand) - { - HelpMessage = Loc.Localize( - "DalamudSettingsHelp", - "Change various In-Game-Addon settings like chat channels and the discord bot setup."), - }); - - commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand) - { - HelpMessage = "ImGui DEBUG", - ShowInHelp = false, - }); - } - - private void OnUnloadCommand(string command, string arguments) + commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand) { - Service.Get().Print("Unloading..."); - Service.Get().Unload(); - } + HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."), + }); - private void OnHelpCommand(string command, string arguments) + commandManager.AddHandler("/xlmute", new CommandInfo(this.OnBadWordsAddCommand) { - var chatGui = Service.Get(); - var commandManager = Service.Get(); + HelpMessage = Loc.Localize("DalamudMuteHelp", "Mute a word or sentence from appearing in chat. Usage: /xlmute "), + }); - var showDebug = arguments.Contains("debug"); - - chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:")); - foreach (var cmd in commandManager.Commands) - { - if (!cmd.Value.ShowInHelp && !showDebug) - continue; - - chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}"); - } - } - - private void OnPluginReloadCommand(string command, string arguments) + commandManager.AddHandler("/xlmutelist", new CommandInfo(this.OnBadWordsListCommand) { - var chatGui = Service.Get(); + HelpMessage = Loc.Localize("DalamudMuteListHelp", "List muted words or sentences."), + }); - chatGui.Print("Reloading..."); - - try - { - Service.Get().ReloadAllPlugins(); - chatGui.Print("OK"); - } - catch (Exception ex) - { - Log.Error(ex, "Plugin reload failed."); - chatGui.PrintError("Reload failed."); - } - } - - private void OnBadWordsAddCommand(string command, string arguments) + commandManager.AddHandler("/xlunmute", new CommandInfo(this.OnBadWordsRemoveCommand) { - var chatGui = Service.Get(); - var configuration = Service.Get(); + HelpMessage = Loc.Localize("DalamudUnmuteHelp", "Unmute a word or sentence. Usage: /xlunmute "), + }); - configuration.BadWords ??= new List(); - - if (string.IsNullOrEmpty(arguments)) - { - chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute.")); - return; - } - - configuration.BadWords.Add(arguments); - - configuration.Save(); - - chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments)); - } - - private void OnBadWordsListCommand(string command, string arguments) + commandManager.AddHandler("/ll", new CommandInfo(this.OnLastLinkCommand) { - var chatGui = Service.Get(); - var configuration = Service.Get(); + HelpMessage = Loc.Localize("DalamudLastLinkHelp", "Open the last posted link in your default browser."), + }); - configuration.BadWords ??= new List(); - - if (configuration.BadWords.Count == 0) - { - chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences.")); - return; - } - - configuration.Save(); - - foreach (var word in configuration.BadWords) - chatGui.Print($"\"{word}\""); - } - - private void OnBadWordsRemoveCommand(string command, string arguments) + commandManager.AddHandler("/xlbgmset", new CommandInfo(this.OnBgmSetCommand) { - var chatGui = Service.Get(); - var configuration = Service.Get(); + HelpMessage = Loc.Localize("DalamudBgmSetHelp", "Set the Game background music. Usage: /xlbgmset "), + }); - configuration.BadWords ??= new List(); - - configuration.BadWords.RemoveAll(x => x == arguments); - - configuration.Save(); - - chatGui.Print(string.Format(Loc.Localize("DalamudUnmuted", "Unmuted \"{0}\"."), arguments)); - } - - private void OnLastLinkCommand(string command, string arguments) + commandManager.AddHandler("/xldev", new CommandInfo(this.OnDebugDrawDevMenu) { - var chatHandlers = Service.Get(); - var chatGui = Service.Get(); + HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"), + ShowInHelp = false, + }); - if (string.IsNullOrEmpty(chatHandlers.LastLink)) - { - chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link...")); - return; - } - - chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink)); - Process.Start(new ProcessStartInfo(chatHandlers.LastLink) - { - UseShellExecute = true, - }); - } - - private void OnBgmSetCommand(string command, string arguments) + commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu) { - var gameGui = Service.Get(); + HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"), + ShowInHelp = false, + }); - if (ushort.TryParse(arguments, out var value)) - { - gameGui.SetBgm(value); - } - else - { - // Revert to the original BGM by specifying an invalid one - gameGui.SetBgm(9999); - } - } - - private void OnDebugDrawDevMenu(string command, string arguments) + commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel) { - Service.Get().ToggleDevMenu(); - } + HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"), + ShowInHelp = false, + }); - private void OnDebugDrawDataMenu(string command, string arguments) + commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog) { - var dalamudInterface = Service.Get(); + HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"), + ShowInHelp = false, + }); - if (string.IsNullOrEmpty(arguments)) - dalamudInterface.ToggleDataWindow(); - else - dalamudInterface.ToggleDataWindow(arguments); - } - - private void OnDebugDrawIMEPanel(string command, string arguments) + commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand) { - Service.Get().OpenIMEWindow(); - } + HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"), + }); - private void OnOpenLog(string command, string arguments) + commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand) { - Service.Get().ToggleLogWindow(); - } + HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."), + }); - private void OnDebugImInfoCommand(string command, string arguments) + commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand) { - var io = Service.Get().LastImGuiIoPtr; - var info = $"WantCaptureKeyboard: {io.WantCaptureKeyboard}\n"; - info += $"WantCaptureMouse: {io.WantCaptureMouse}\n"; - info += $"WantSetMousePos: {io.WantSetMousePos}\n"; - info += $"WantTextInput: {io.WantTextInput}\n"; - info += $"WantSaveIniSettings: {io.WantSaveIniSettings}\n"; - info += $"BackendFlags: {(int)io.BackendFlags}\n"; - info += $"DeltaTime: {io.DeltaTime}\n"; - info += $"DisplaySize: {io.DisplaySize.X} {io.DisplaySize.Y}\n"; - info += $"Framerate: {io.Framerate}\n"; - info += $"MetricsActiveWindows: {io.MetricsActiveWindows}\n"; - info += $"MetricsRenderWindows: {io.MetricsRenderWindows}\n"; - info += $"MousePos: {io.MousePos.X} {io.MousePos.Y}\n"; - info += $"MouseClicked: {io.MouseClicked}\n"; - info += $"MouseDown: {io.MouseDown}\n"; - info += $"NavActive: {io.NavActive}\n"; - info += $"NavVisible: {io.NavVisible}\n"; + HelpMessage = + Loc.Localize( + "DalamudLanguageHelp", + "Set the language for the in-game addon and plugins that support it. Available languages: ") + + Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code), + }); - Log.Information(info); - } - - private void OnOpenInstallerCommand(string command, string arguments) + commandManager.AddHandler("/xlsettings", new CommandInfo(this.OnOpenSettingsCommand) { - Service.Get().TogglePluginInstallerWindow(); - } + HelpMessage = Loc.Localize( + "DalamudSettingsHelp", + "Change various In-Game-Addon settings like chat channels and the discord bot setup."), + }); - private void OnOpenCreditsCommand(string command, string arguments) + commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand) { - Service.Get().ToggleCreditsWindow(); - } + HelpMessage = "ImGui DEBUG", + ShowInHelp = false, + }); + } - private void OnSetLanguageCommand(string command, string arguments) + private void OnUnloadCommand(string command, string arguments) + { + Service.Get().Print("Unloading..."); + Service.Get().Unload(); + } + + private void OnHelpCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var commandManager = Service.Get(); + + var showDebug = arguments.Contains("debug"); + + chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:")); + foreach (var cmd in commandManager.Commands) { - var chatGui = Service.Get(); - var configuration = Service.Get(); - var localization = Service.Get(); + if (!cmd.Value.ShowInHelp && !showDebug) + continue; - if (Localization.ApplicableLangCodes.Contains(arguments.ToLower()) || arguments.ToLower() == "en") - { - localization.SetupWithLangCode(arguments.ToLower()); - configuration.LanguageOverride = arguments.ToLower(); - - chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), arguments)); - } - else - { - localization.SetupWithUiCulture(); - configuration.LanguageOverride = null; - - chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), "default")); - } - - configuration.Save(); - } - - private void OnOpenSettingsCommand(string command, string arguments) - { - Service.Get().ToggleSettingsWindow(); + chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}"); } } + + private void OnPluginReloadCommand(string command, string arguments) + { + var chatGui = Service.Get(); + + chatGui.Print("Reloading..."); + + try + { + Service.Get().ReloadAllPlugins(); + chatGui.Print("OK"); + } + catch (Exception ex) + { + Log.Error(ex, "Plugin reload failed."); + chatGui.PrintError("Reload failed."); + } + } + + private void OnBadWordsAddCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + + configuration.BadWords ??= new List(); + + if (string.IsNullOrEmpty(arguments)) + { + chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute.")); + return; + } + + configuration.BadWords.Add(arguments); + + configuration.Save(); + + chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments)); + } + + private void OnBadWordsListCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + + configuration.BadWords ??= new List(); + + if (configuration.BadWords.Count == 0) + { + chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences.")); + return; + } + + configuration.Save(); + + foreach (var word in configuration.BadWords) + chatGui.Print($"\"{word}\""); + } + + private void OnBadWordsRemoveCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + + configuration.BadWords ??= new List(); + + configuration.BadWords.RemoveAll(x => x == arguments); + + configuration.Save(); + + chatGui.Print(string.Format(Loc.Localize("DalamudUnmuted", "Unmuted \"{0}\"."), arguments)); + } + + private void OnLastLinkCommand(string command, string arguments) + { + var chatHandlers = Service.Get(); + var chatGui = Service.Get(); + + if (string.IsNullOrEmpty(chatHandlers.LastLink)) + { + chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link...")); + return; + } + + chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink)); + Process.Start(new ProcessStartInfo(chatHandlers.LastLink) + { + UseShellExecute = true, + }); + } + + private void OnBgmSetCommand(string command, string arguments) + { + var gameGui = Service.Get(); + + if (ushort.TryParse(arguments, out var value)) + { + gameGui.SetBgm(value); + } + else + { + // Revert to the original BGM by specifying an invalid one + gameGui.SetBgm(9999); + } + } + + private void OnDebugDrawDevMenu(string command, string arguments) + { + Service.Get().ToggleDevMenu(); + } + + private void OnDebugDrawDataMenu(string command, string arguments) + { + var dalamudInterface = Service.Get(); + + if (string.IsNullOrEmpty(arguments)) + dalamudInterface.ToggleDataWindow(); + else + dalamudInterface.ToggleDataWindow(arguments); + } + + private void OnDebugDrawIMEPanel(string command, string arguments) + { + Service.Get().OpenIMEWindow(); + } + + private void OnOpenLog(string command, string arguments) + { + Service.Get().ToggleLogWindow(); + } + + private void OnDebugImInfoCommand(string command, string arguments) + { + var io = Service.Get().LastImGuiIoPtr; + var info = $"WantCaptureKeyboard: {io.WantCaptureKeyboard}\n"; + info += $"WantCaptureMouse: {io.WantCaptureMouse}\n"; + info += $"WantSetMousePos: {io.WantSetMousePos}\n"; + info += $"WantTextInput: {io.WantTextInput}\n"; + info += $"WantSaveIniSettings: {io.WantSaveIniSettings}\n"; + info += $"BackendFlags: {(int)io.BackendFlags}\n"; + info += $"DeltaTime: {io.DeltaTime}\n"; + info += $"DisplaySize: {io.DisplaySize.X} {io.DisplaySize.Y}\n"; + info += $"Framerate: {io.Framerate}\n"; + info += $"MetricsActiveWindows: {io.MetricsActiveWindows}\n"; + info += $"MetricsRenderWindows: {io.MetricsRenderWindows}\n"; + info += $"MousePos: {io.MousePos.X} {io.MousePos.Y}\n"; + info += $"MouseClicked: {io.MouseClicked}\n"; + info += $"MouseDown: {io.MouseDown}\n"; + info += $"NavActive: {io.NavActive}\n"; + info += $"NavVisible: {io.NavVisible}\n"; + + Log.Information(info); + } + + private void OnOpenInstallerCommand(string command, string arguments) + { + Service.Get().TogglePluginInstallerWindow(); + } + + private void OnOpenCreditsCommand(string command, string arguments) + { + Service.Get().ToggleCreditsWindow(); + } + + private void OnSetLanguageCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + var localization = Service.Get(); + + if (Localization.ApplicableLangCodes.Contains(arguments.ToLower()) || arguments.ToLower() == "en") + { + localization.SetupWithLangCode(arguments.ToLower()); + configuration.LanguageOverride = arguments.ToLower(); + + chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), arguments)); + } + else + { + localization.SetupWithUiCulture(); + configuration.LanguageOverride = null; + + chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), "default")); + } + + configuration.Save(); + } + + private void OnOpenSettingsCommand(string command, string arguments) + { + Service.Get().ToggleSettingsWindow(); + } } diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index ece1b4cc6..5320a8c41 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -26,660 +26,659 @@ using ImGuiNET; using PInvoke; using Serilog.Events; -namespace Dalamud.Interface.Internal +namespace Dalamud.Interface.Internal; + +/// +/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. +/// +internal class DalamudInterface : IDisposable { - /// - /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. - /// - internal class DalamudInterface : IDisposable - { - private static readonly ModuleLog Log = new("DUI"); + private static readonly ModuleLog Log = new("DUI"); - private readonly ChangelogWindow changelogWindow; - private readonly ColorDemoWindow colorDemoWindow; - private readonly ComponentDemoWindow componentDemoWindow; - private readonly CreditsWindow creditsWindow; - private readonly DataWindow dataWindow; - private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; - private readonly IMEWindow imeWindow; - private readonly ConsoleWindow consoleWindow; - private readonly PluginStatWindow pluginStatWindow; - private readonly PluginInstallerWindow pluginWindow; - private readonly SettingsWindow settingsWindow; - private readonly SelfTestWindow selfTestWindow; - private readonly StyleEditorWindow styleEditorWindow; + private readonly ChangelogWindow changelogWindow; + private readonly ColorDemoWindow colorDemoWindow; + private readonly ComponentDemoWindow componentDemoWindow; + private readonly CreditsWindow creditsWindow; + private readonly DataWindow dataWindow; + private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; + private readonly IMEWindow imeWindow; + private readonly ConsoleWindow consoleWindow; + private readonly PluginStatWindow pluginStatWindow; + private readonly PluginInstallerWindow pluginWindow; + private readonly SettingsWindow settingsWindow; + private readonly SelfTestWindow selfTestWindow; + private readonly StyleEditorWindow styleEditorWindow; - private ulong frameCount = 0; + private ulong frameCount = 0; #if DEBUG - private bool isImGuiDrawDevMenu = true; + private bool isImGuiDrawDevMenu = true; #else private bool isImGuiDrawDevMenu = false; #endif - private bool isImGuiDrawDemoWindow = false; - private bool isImGuiDrawMetricsWindow = false; + private bool isImGuiDrawDemoWindow = false; + private bool isImGuiDrawMetricsWindow = false; - /// - /// Initializes a new instance of the class. - /// - public DalamudInterface() + /// + /// Initializes a new instance of the class. + /// + public DalamudInterface() + { + var configuration = Service.Get(); + + this.WindowSystem = new WindowSystem("DalamudCore"); + + this.changelogWindow = new ChangelogWindow() { IsOpen = false }; + this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; + this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; + this.creditsWindow = new CreditsWindow() { IsOpen = false }; + this.dataWindow = new DataWindow() { IsOpen = false }; + this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false }; + this.imeWindow = new IMEWindow() { IsOpen = false }; + this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup }; + this.pluginStatWindow = new PluginStatWindow() { IsOpen = false }; + this.pluginWindow = new PluginInstallerWindow() { IsOpen = false }; + this.settingsWindow = new SettingsWindow() { IsOpen = false }; + this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; + this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; + + this.WindowSystem.AddWindow(this.changelogWindow); + this.WindowSystem.AddWindow(this.colorDemoWindow); + this.WindowSystem.AddWindow(this.componentDemoWindow); + this.WindowSystem.AddWindow(this.creditsWindow); + this.WindowSystem.AddWindow(this.dataWindow); + this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow); + this.WindowSystem.AddWindow(this.imeWindow); + this.WindowSystem.AddWindow(this.consoleWindow); + this.WindowSystem.AddWindow(this.pluginStatWindow); + this.WindowSystem.AddWindow(this.pluginWindow); + this.WindowSystem.AddWindow(this.settingsWindow); + this.WindowSystem.AddWindow(this.selfTestWindow); + this.WindowSystem.AddWindow(this.styleEditorWindow); + + ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; + + Service.Get().Draw += this.OnDraw; + } + + /// + /// Gets the controlling all Dalamud-internal windows. + /// + public WindowSystem WindowSystem { get; init; } + + /// + /// Gets or sets a value indicating whether the /xldev menu is open. + /// + public bool IsDevMenuOpen + { + get => this.isImGuiDrawDevMenu; + set => this.isImGuiDrawDevMenu = value; + } + + /// + public void Dispose() + { + Service.Get().Draw -= this.OnDraw; + + this.WindowSystem.RemoveAllWindows(); + + this.creditsWindow.Dispose(); + this.consoleWindow.Dispose(); + this.pluginWindow.Dispose(); + } + + #region Open + + /// + /// Opens the . + /// + public void OpenChangelogWindow() => this.changelogWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenColorsDemoWindow() => this.colorDemoWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenCreditsWindow() => this.creditsWindow.IsOpen = true; + + /// + /// Opens the . + /// + /// The data kind to switch to after opening. + public void OpenDataWindow(string dataKind = null) + { + this.dataWindow.IsOpen = true; + if (dataKind != null && this.dataWindow.IsOpen) { - var configuration = Service.Get(); - - this.WindowSystem = new WindowSystem("DalamudCore"); - - this.changelogWindow = new ChangelogWindow() { IsOpen = false }; - this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; - this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; - this.creditsWindow = new CreditsWindow() { IsOpen = false }; - this.dataWindow = new DataWindow() { IsOpen = false }; - this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false }; - this.imeWindow = new IMEWindow() { IsOpen = false }; - this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup }; - this.pluginStatWindow = new PluginStatWindow() { IsOpen = false }; - this.pluginWindow = new PluginInstallerWindow() { IsOpen = false }; - this.settingsWindow = new SettingsWindow() { IsOpen = false }; - this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; - this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; - - this.WindowSystem.AddWindow(this.changelogWindow); - this.WindowSystem.AddWindow(this.colorDemoWindow); - this.WindowSystem.AddWindow(this.componentDemoWindow); - this.WindowSystem.AddWindow(this.creditsWindow); - this.WindowSystem.AddWindow(this.dataWindow); - this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow); - this.WindowSystem.AddWindow(this.imeWindow); - this.WindowSystem.AddWindow(this.consoleWindow); - this.WindowSystem.AddWindow(this.pluginStatWindow); - this.WindowSystem.AddWindow(this.pluginWindow); - this.WindowSystem.AddWindow(this.settingsWindow); - this.WindowSystem.AddWindow(this.selfTestWindow); - this.WindowSystem.AddWindow(this.styleEditorWindow); - - ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; - - Service.Get().Draw += this.OnDraw; + this.dataWindow.SetDataKind(dataKind); } + } - /// - /// Gets the controlling all Dalamud-internal windows. - /// - public WindowSystem WindowSystem { get; init; } + /// + /// Opens the dev menu bar. + /// + public void OpenDevMenu() => this.isImGuiDrawDevMenu = true; - /// - /// Gets or sets a value indicating whether the /xldev menu is open. - /// - public bool IsDevMenuOpen + /// + /// Opens the . + /// + public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenIMEWindow() => this.imeWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenLogWindow() => this.consoleWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenPluginStats() => this.pluginStatWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenSettings() => this.settingsWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenSelfTest() => this.selfTestWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true; + + #endregion + + #region Close + + /// + /// Closes the . + /// + public void CloseIMEWindow() => this.imeWindow.IsOpen = false; + + #endregion + + #region Toggle + + /// + /// Toggles the . + /// + public void ToggleChangelogWindow() => this.changelogWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleColorsDemoWindow() => this.colorDemoWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleComponentDemoWindow() => this.componentDemoWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleCreditsWindow() => this.creditsWindow.Toggle(); + + /// + /// Toggles the . + /// + /// The data kind to switch to after opening. + public void ToggleDataWindow(string dataKind = null) + { + this.dataWindow.Toggle(); + if (dataKind != null && this.dataWindow.IsOpen) { - get => this.isImGuiDrawDevMenu; - set => this.isImGuiDrawDevMenu = value; + this.dataWindow.SetDataKind(dataKind); } + } - /// - public void Dispose() + /// + /// Toggles the dev menu bar. + /// + public void ToggleDevMenu() => this.isImGuiDrawDevMenu ^= true; + + /// + /// Toggles the . + /// + public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleIMEWindow() => this.imeWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleLogWindow() => this.consoleWindow.Toggle(); + + /// + /// Toggles the . + /// + public void TogglePluginStatsWindow() => this.pluginStatWindow.Toggle(); + + /// + /// Toggles the . + /// + public void TogglePluginInstallerWindow() => this.pluginWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleSettingsWindow() => this.settingsWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); + + #endregion + + private void OnDraw() + { + this.frameCount++; + + try { - Service.Get().Draw -= this.OnDraw; + this.DrawHiddenDevMenuOpener(); + this.DrawDevMenu(); - this.WindowSystem.RemoveAllWindows(); + if (Service.Get().GameUiHidden) + return; - this.creditsWindow.Dispose(); - this.consoleWindow.Dispose(); - this.pluginWindow.Dispose(); - } + this.WindowSystem.Draw(); - #region Open + if (this.isImGuiDrawDemoWindow) + ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow); - /// - /// Opens the . - /// - public void OpenChangelogWindow() => this.changelogWindow.IsOpen = true; + if (this.isImGuiDrawMetricsWindow) + ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow); - /// - /// Opens the . - /// - public void OpenColorsDemoWindow() => this.colorDemoWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenCreditsWindow() => this.creditsWindow.IsOpen = true; - - /// - /// Opens the . - /// - /// The data kind to switch to after opening. - public void OpenDataWindow(string dataKind = null) - { - this.dataWindow.IsOpen = true; - if (dataKind != null && this.dataWindow.IsOpen) + // Release focus of any ImGui window if we click into the game. + var io = ImGui.GetIO(); + if (!io.WantCaptureMouse && (User32.GetKeyState((int)User32.VirtualKey.VK_LBUTTON) & 0x8000) != 0) { - this.dataWindow.SetDataKind(dataKind); + ImGui.SetWindowFocus(null); } } - - /// - /// Opens the dev menu bar. - /// - public void OpenDevMenu() => this.isImGuiDrawDevMenu = true; - - /// - /// Opens the . - /// - public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenIMEWindow() => this.imeWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenLogWindow() => this.consoleWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenPluginStats() => this.pluginStatWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenSettings() => this.settingsWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenSelfTest() => this.selfTestWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true; - - #endregion - - #region Close - - /// - /// Closes the . - /// - public void CloseIMEWindow() => this.imeWindow.IsOpen = false; - - #endregion - - #region Toggle - - /// - /// Toggles the . - /// - public void ToggleChangelogWindow() => this.changelogWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleColorsDemoWindow() => this.colorDemoWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleComponentDemoWindow() => this.componentDemoWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleCreditsWindow() => this.creditsWindow.Toggle(); - - /// - /// Toggles the . - /// - /// The data kind to switch to after opening. - public void ToggleDataWindow(string dataKind = null) + catch (Exception ex) { - this.dataWindow.Toggle(); - if (dataKind != null && this.dataWindow.IsOpen) - { - this.dataWindow.SetDataKind(dataKind); - } + PluginLog.Error(ex, "Error during OnDraw"); } + } - /// - /// Toggles the dev menu bar. - /// - public void ToggleDevMenu() => this.isImGuiDrawDevMenu ^= true; + private void DrawHiddenDevMenuOpener() + { + var condition = Service.Get(); - /// - /// Toggles the . - /// - public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleIMEWindow() => this.imeWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleLogWindow() => this.consoleWindow.Toggle(); - - /// - /// Toggles the . - /// - public void TogglePluginStatsWindow() => this.pluginStatWindow.Toggle(); - - /// - /// Toggles the . - /// - public void TogglePluginInstallerWindow() => this.pluginWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleSettingsWindow() => this.settingsWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); - - #endregion - - private void OnDraw() + if (!this.isImGuiDrawDevMenu && !condition.Any()) { - this.frameCount++; + ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 1)); - try + var mainViewportPos = ImGui.GetMainViewport().Pos; + ImGui.SetNextWindowPos(new Vector2(mainViewportPos.X, mainViewportPos.Y), ImGuiCond.Always); + ImGui.SetNextWindowBgAlpha(1); + + if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) { - this.DrawHiddenDevMenuOpener(); - this.DrawDevMenu(); + if (ImGui.Button("###devMenuOpener", new Vector2(40, 25))) + this.isImGuiDrawDevMenu = true; + + ImGui.End(); + } + + ImGui.PopStyleColor(8); + } + } + + private void DrawDevMenu() + { + if (this.isImGuiDrawDevMenu) + { + if (ImGui.BeginMainMenuBar()) + { + var dalamud = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + if (ImGui.BeginMenu("Dalamud")) + { + ImGui.MenuItem("Draw Dalamud dev menu", string.Empty, ref this.isImGuiDrawDevMenu); + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Log window")) + { + this.OpenLogWindow(); + } + + if (ImGui.BeginMenu("Set log level...")) + { + foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast()) + { + if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, dalamud.LogLevelSwitch.MinimumLevel == logLevel)) + { + dalamud.LogLevelSwitch.MinimumLevel = logLevel; + configuration.LogLevel = logLevel; + configuration.Save(); + } + } + + ImGui.EndMenu(); + } + + var antiDebug = Service.Get(); + if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled)) + { + var newEnabled = !antiDebug.IsEnabled; + if (newEnabled) + antiDebug.Enable(); + else + antiDebug.Disable(); + + configuration.IsAntiAntiDebugEnabled = newEnabled; + configuration.Save(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Data window")) + { + this.OpenDataWindow(); + } + + if (ImGui.MenuItem("Open Credits window")) + { + this.OpenCreditsWindow(); + } + + if (ImGui.MenuItem("Open Settings window")) + { + this.OpenSettings(); + } + + if (ImGui.MenuItem("Open Changelog window")) + { + this.OpenChangelogWindow(); + } + + if (ImGui.MenuItem("Open Components Demo")) + { + this.OpenComponentDemoWindow(); + } + + if (ImGui.MenuItem("Open Colors Demo")) + { + this.OpenColorsDemoWindow(); + } + + if (ImGui.MenuItem("Open Self-Test")) + { + this.OpenSelfTest(); + } + + if (ImGui.MenuItem("Open Style Editor")) + { + this.OpenStyleEditor(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Unload Dalamud")) + { + Service.Get().Unload(); + } + + if (ImGui.MenuItem("Kill game")) + { + Process.GetCurrentProcess().Kill(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Access Violation")) + { + Marshal.ReadByte(IntPtr.Zero); + } + + if (ImGui.MenuItem("Crash game")) + { + unsafe + { + var framework = Framework.Instance(); + framework->UIModule = (UIModule*)0; + } + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Enable Dalamud testing", string.Empty, configuration.DoDalamudTest)) + { + configuration.DoDalamudTest ^= true; + configuration.Save(); + } + + var startInfo = Service.Get(); + ImGui.MenuItem(Util.AssemblyVersion, false); + ImGui.MenuItem(startInfo.GameVersion.ToString(), false); + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("GUI")) + { + ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); + + ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow); + + ImGui.Separator(); + + var val = ImGuiManagedAsserts.AssertsEnabled; + if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) + { + ImGuiManagedAsserts.AssertsEnabled = val; + } + + if (ImGui.MenuItem("Enable asserts at startup", null, configuration.AssertsEnabledAtStartup)) + { + configuration.AssertsEnabledAtStartup = !configuration.AssertsEnabledAtStartup; + configuration.Save(); + } + + if (ImGui.MenuItem("Clear focus")) + { + ImGui.SetWindowFocus(null); + } + + if (ImGui.MenuItem("Dump style")) + { + var info = string.Empty; + var style = StyleModelV1.Get(); + var enCulture = new CultureInfo("en-US"); + + foreach (var propertyInfo in typeof(StyleModel).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (propertyInfo.PropertyType == typeof(Vector2)) + { + var vec2 = (Vector2)propertyInfo.GetValue(style); + info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n"; + } + else + { + info += $"{propertyInfo.Name} = {propertyInfo.GetValue(style)},\n"; + } + } + + info += "Colors = new Dictionary()\n"; + info += "{\n"; + + foreach (var color in style.Colors) + { + info += + $"{{\"{color.Key}\", new Vector4({color.Value.X.ToString(enCulture)}f, {color.Value.Y.ToString(enCulture)}f, {color.Value.Z.ToString(enCulture)}f, {color.Value.W.ToString(enCulture)}f)}},\n"; + } + + info += "},"; + + Log.Information(info); + } + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Game")) + { + if (ImGui.MenuItem("Replace ExceptionHandler")) + { + dalamud.ReplaceExceptionHandler(); + } + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Plugins")) + { + if (ImGui.MenuItem("Open Plugin installer")) + { + this.OpenPluginInstaller(); + } + + if (ImGui.MenuItem("Clear cached images/icons")) + { + this.pluginWindow?.ClearIconCache(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Plugin Stats")) + { + this.OpenPluginStats(); + } + + if (ImGui.MenuItem("Print plugin info")) + { + foreach (var plugin in pluginManager.InstalledPlugins) + { + // TODO: some more here, state maybe? + PluginLog.Information($"{plugin.Name}"); + } + } + + if (ImGui.MenuItem("Reload plugins")) + { + try + { + pluginManager.ReloadAllPlugins(); + } + catch (Exception ex) + { + Service.Get().PrintError("Reload failed."); + PluginLog.Error(ex, "Plugin reload failed."); + } + } + + if (ImGui.MenuItem("Scan dev plugins")) + { + pluginManager.ScanDevPlugins(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Load all API levels", null, configuration.LoadAllApiLevels)) + { + configuration.LoadAllApiLevels = !configuration.LoadAllApiLevels; + configuration.Save(); + } + + if (ImGui.MenuItem("Load banned plugins", null, configuration.LoadBannedPlugins)) + { + configuration.LoadBannedPlugins = !configuration.LoadBannedPlugins; + configuration.Save(); + } + + ImGui.Separator(); + ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); + ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count, false); + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Localization")) + { + var localization = Service.Get(); + + if (ImGui.MenuItem("Export localizable")) + { + localization.ExportLocalizable(); + } + + if (ImGui.BeginMenu("Load language...")) + { + if (ImGui.MenuItem("From Fallbacks")) + { + localization.SetupWithFallbacks(); + } + + if (ImGui.MenuItem("From UICulture")) + { + localization.SetupWithUiCulture(); + } + + foreach (var applicableLangCode in Localization.ApplicableLangCodes) + { + if (ImGui.MenuItem($"Applicable: {applicableLangCode}")) + { + localization.SetupWithLangCode(applicableLangCode); + } + } + + ImGui.EndMenu(); + } + + ImGui.EndMenu(); + } if (Service.Get().GameUiHidden) - return; + ImGui.BeginMenu("UI is hidden...", false); - this.WindowSystem.Draw(); + ImGui.BeginMenu(Util.GetGitHash(), false); + ImGui.BeginMenu(this.frameCount.ToString(), false); + ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("F2"), false); - if (this.isImGuiDrawDemoWindow) - ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow); - - if (this.isImGuiDrawMetricsWindow) - ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow); - - // Release focus of any ImGui window if we click into the game. - var io = ImGui.GetIO(); - if (!io.WantCaptureMouse && (User32.GetKeyState((int)User32.VirtualKey.VK_LBUTTON) & 0x8000) != 0) - { - ImGui.SetWindowFocus(null); - } - } - catch (Exception ex) - { - PluginLog.Error(ex, "Error during OnDraw"); - } - } - - private void DrawHiddenDevMenuOpener() - { - var condition = Service.Get(); - - if (!this.isImGuiDrawDevMenu && !condition.Any()) - { - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 1)); - - var mainViewportPos = ImGui.GetMainViewport().Pos; - ImGui.SetNextWindowPos(new Vector2(mainViewportPos.X, mainViewportPos.Y), ImGuiCond.Always); - ImGui.SetNextWindowBgAlpha(1); - - if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) - { - if (ImGui.Button("###devMenuOpener", new Vector2(40, 25))) - this.isImGuiDrawDevMenu = true; - - ImGui.End(); - } - - ImGui.PopStyleColor(8); - } - } - - private void DrawDevMenu() - { - if (this.isImGuiDrawDevMenu) - { - if (ImGui.BeginMainMenuBar()) - { - var dalamud = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - if (ImGui.BeginMenu("Dalamud")) - { - ImGui.MenuItem("Draw Dalamud dev menu", string.Empty, ref this.isImGuiDrawDevMenu); - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Log window")) - { - this.OpenLogWindow(); - } - - if (ImGui.BeginMenu("Set log level...")) - { - foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast()) - { - if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, dalamud.LogLevelSwitch.MinimumLevel == logLevel)) - { - dalamud.LogLevelSwitch.MinimumLevel = logLevel; - configuration.LogLevel = logLevel; - configuration.Save(); - } - } - - ImGui.EndMenu(); - } - - var antiDebug = Service.Get(); - if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled)) - { - var newEnabled = !antiDebug.IsEnabled; - if (newEnabled) - antiDebug.Enable(); - else - antiDebug.Disable(); - - configuration.IsAntiAntiDebugEnabled = newEnabled; - configuration.Save(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Data window")) - { - this.OpenDataWindow(); - } - - if (ImGui.MenuItem("Open Credits window")) - { - this.OpenCreditsWindow(); - } - - if (ImGui.MenuItem("Open Settings window")) - { - this.OpenSettings(); - } - - if (ImGui.MenuItem("Open Changelog window")) - { - this.OpenChangelogWindow(); - } - - if (ImGui.MenuItem("Open Components Demo")) - { - this.OpenComponentDemoWindow(); - } - - if (ImGui.MenuItem("Open Colors Demo")) - { - this.OpenColorsDemoWindow(); - } - - if (ImGui.MenuItem("Open Self-Test")) - { - this.OpenSelfTest(); - } - - if (ImGui.MenuItem("Open Style Editor")) - { - this.OpenStyleEditor(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Unload Dalamud")) - { - Service.Get().Unload(); - } - - if (ImGui.MenuItem("Kill game")) - { - Process.GetCurrentProcess().Kill(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Access Violation")) - { - Marshal.ReadByte(IntPtr.Zero); - } - - if (ImGui.MenuItem("Crash game")) - { - unsafe - { - var framework = Framework.Instance(); - framework->UIModule = (UIModule*)0; - } - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Enable Dalamud testing", string.Empty, configuration.DoDalamudTest)) - { - configuration.DoDalamudTest ^= true; - configuration.Save(); - } - - var startInfo = Service.Get(); - ImGui.MenuItem(Util.AssemblyVersion, false); - ImGui.MenuItem(startInfo.GameVersion.ToString(), false); - - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("GUI")) - { - ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); - - ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow); - - ImGui.Separator(); - - var val = ImGuiManagedAsserts.AssertsEnabled; - if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) - { - ImGuiManagedAsserts.AssertsEnabled = val; - } - - if (ImGui.MenuItem("Enable asserts at startup", null, configuration.AssertsEnabledAtStartup)) - { - configuration.AssertsEnabledAtStartup = !configuration.AssertsEnabledAtStartup; - configuration.Save(); - } - - if (ImGui.MenuItem("Clear focus")) - { - ImGui.SetWindowFocus(null); - } - - if (ImGui.MenuItem("Dump style")) - { - var info = string.Empty; - var style = StyleModelV1.Get(); - var enCulture = new CultureInfo("en-US"); - - foreach (var propertyInfo in typeof(StyleModel).GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (propertyInfo.PropertyType == typeof(Vector2)) - { - var vec2 = (Vector2)propertyInfo.GetValue(style); - info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n"; - } - else - { - info += $"{propertyInfo.Name} = {propertyInfo.GetValue(style)},\n"; - } - } - - info += "Colors = new Dictionary()\n"; - info += "{\n"; - - foreach (var color in style.Colors) - { - info += - $"{{\"{color.Key}\", new Vector4({color.Value.X.ToString(enCulture)}f, {color.Value.Y.ToString(enCulture)}f, {color.Value.Z.ToString(enCulture)}f, {color.Value.W.ToString(enCulture)}f)}},\n"; - } - - info += "},"; - - Log.Information(info); - } - - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("Game")) - { - if (ImGui.MenuItem("Replace ExceptionHandler")) - { - dalamud.ReplaceExceptionHandler(); - } - - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("Plugins")) - { - if (ImGui.MenuItem("Open Plugin installer")) - { - this.OpenPluginInstaller(); - } - - if (ImGui.MenuItem("Clear cached images/icons")) - { - this.pluginWindow?.ClearIconCache(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Plugin Stats")) - { - this.OpenPluginStats(); - } - - if (ImGui.MenuItem("Print plugin info")) - { - foreach (var plugin in pluginManager.InstalledPlugins) - { - // TODO: some more here, state maybe? - PluginLog.Information($"{plugin.Name}"); - } - } - - if (ImGui.MenuItem("Reload plugins")) - { - try - { - pluginManager.ReloadAllPlugins(); - } - catch (Exception ex) - { - Service.Get().PrintError("Reload failed."); - PluginLog.Error(ex, "Plugin reload failed."); - } - } - - if (ImGui.MenuItem("Scan dev plugins")) - { - pluginManager.ScanDevPlugins(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Load all API levels", null, configuration.LoadAllApiLevels)) - { - configuration.LoadAllApiLevels = !configuration.LoadAllApiLevels; - configuration.Save(); - } - - if (ImGui.MenuItem("Load banned plugins", null, configuration.LoadBannedPlugins)) - { - configuration.LoadBannedPlugins = !configuration.LoadBannedPlugins; - configuration.Save(); - } - - ImGui.Separator(); - ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); - ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count, false); - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("Localization")) - { - var localization = Service.Get(); - - if (ImGui.MenuItem("Export localizable")) - { - localization.ExportLocalizable(); - } - - if (ImGui.BeginMenu("Load language...")) - { - if (ImGui.MenuItem("From Fallbacks")) - { - localization.SetupWithFallbacks(); - } - - if (ImGui.MenuItem("From UICulture")) - { - localization.SetupWithUiCulture(); - } - - foreach (var applicableLangCode in Localization.ApplicableLangCodes) - { - if (ImGui.MenuItem($"Applicable: {applicableLangCode}")) - { - localization.SetupWithLangCode(applicableLangCode); - } - } - - ImGui.EndMenu(); - } - - ImGui.EndMenu(); - } - - if (Service.Get().GameUiHidden) - ImGui.BeginMenu("UI is hidden...", false); - - ImGui.BeginMenu(Util.GetGitHash(), false); - ImGui.BeginMenu(this.frameCount.ToString(), false); - ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("F2"), false); - - ImGui.EndMainMenuBar(); - } + ImGui.EndMainMenuBar(); } } } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 33806fd73..24bd612e7 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -39,667 +39,666 @@ using SharpDX.Direct3D11; * - Might eventually want to render to a separate target and composite, especially with reshade etc in the mix. */ -namespace Dalamud.Interface.Internal +namespace Dalamud.Interface.Internal; + +/// +/// This class manages interaction with the ImGui interface. +/// +internal class InterfaceManager : IDisposable { + private readonly string rtssPath; + + private readonly Hook presentHook; + private readonly Hook resizeBuffersHook; + private readonly Hook setCursorHook; + + private readonly ManualResetEvent fontBuildSignal; + private readonly SwapChainVtableResolver address; + private RawDX11Scene? scene; + + // can't access imgui IO before first present call + private bool lastWantCapture = false; + private bool isRebuildingFonts = false; + /// - /// This class manages interaction with the ImGui interface. + /// Initializes a new instance of the class. /// - internal class InterfaceManager : IDisposable + public InterfaceManager() { - private readonly string rtssPath; + Service.Set(); - private readonly Hook presentHook; - private readonly Hook resizeBuffersHook; - private readonly Hook setCursorHook; + var scanner = Service.Get(); - private readonly ManualResetEvent fontBuildSignal; - private readonly SwapChainVtableResolver address; - private RawDX11Scene? scene; + this.fontBuildSignal = new ManualResetEvent(false); - // can't access imgui IO before first present call - private bool lastWantCapture = false; - private bool isRebuildingFonts = false; + this.address = new SwapChainVtableResolver(); + this.address.Setup(scanner); - /// - /// Initializes a new instance of the class. - /// - public InterfaceManager() + try { - Service.Set(); + var rtss = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); - var scanner = Service.Get(); - - this.fontBuildSignal = new ManualResetEvent(false); - - this.address = new SwapChainVtableResolver(); - this.address.Setup(scanner); - - try + if (rtss != IntPtr.Zero) { - var rtss = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); + var fileName = new StringBuilder(255); + _ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity); + this.rtssPath = fileName.ToString(); + Log.Verbose($"RTSS at {this.rtssPath}"); - if (rtss != IntPtr.Zero) - { - var fileName = new StringBuilder(255); - _ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity); - this.rtssPath = fileName.ToString(); - Log.Verbose($"RTSS at {this.rtssPath}"); - - if (!NativeFunctions.FreeLibrary(rtss)) - throw new Win32Exception(); - } - } - catch (Exception e) - { - Log.Error(e, "RTSS Free failed"); - } - - this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); - this.presentHook = new Hook(this.address.Present, this.PresentDetour); - this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); - - var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; - - Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); - Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); - Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); - } - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetCursorDelegate(IntPtr hCursor); - - private delegate void InstallRTSSHook(); - - /// - /// This event gets called each frame to facilitate ImGui drawing. - /// - public event RawDX11Scene.BuildUIDelegate Draw; - - /// - /// This event gets called when ResizeBuffers is called. - /// - public event Action ResizeBuffers; - - /// - /// Gets or sets an action that is executed when fonts are rebuilt. - /// - public event Action BuildFonts; - - /// - /// Gets the default ImGui font. - /// - public static ImFontPtr DefaultFont { get; private set; } - - /// - /// Gets an included FontAwesome icon font. - /// - public static ImFontPtr IconFont { get; private set; } - - /// - /// Gets an included monospaced font. - /// - public static ImFontPtr MonoFont { get; private set; } - - /// - /// Gets or sets the pointer to ImGui.IO(), when it was last used. - /// - public ImGuiIOPtr LastImGuiIoPtr { get; set; } - - /// - /// Gets the D3D11 device instance. - /// - public Device? Device => this.scene?.Device; - - /// - /// Gets the address handle to the main process window. - /// - public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; - - /// - /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. - /// - public bool OverrideGameCursor - { - get => this.scene.UpdateCursor; - set => this.scene.UpdateCursor = value; - } - - /// - /// Gets or sets a value indicating whether the fonts are built and ready to use. - /// - public bool FontsReady { get; set; } = false; - - /// - /// Gets a value indicating whether the Dalamud interface ready to use. - /// - public bool IsReady => this.scene != null; - - /// - /// Enable this module. - /// - public void Enable() - { - this.setCursorHook?.Enable(); - this.presentHook.Enable(); - this.resizeBuffersHook.Enable(); - - try - { - if (!string.IsNullOrEmpty(this.rtssPath)) - { - NativeFunctions.LoadLibraryW(this.rtssPath); - var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); - var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook"); - - Marshal.GetDelegateForFunctionPointer(installAddr).Invoke(); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not reload RTSS"); + if (!NativeFunctions.FreeLibrary(rtss)) + throw new Win32Exception(); } } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() + catch (Exception e) { - // HACK: this is usually called on a separate thread from PresentDetour (likely on a dedicated render thread) - // and if we aren't already disabled, disposing of the scene and hook can frequently crash due to the hook - // being disposed of in this thread while it is actively in use in the render thread. - // This is a terrible way to prevent issues, but should basically always work to ensure that all outstanding - // calls to PresentDetour have finished (and Disable means no new ones will start), before we try to cleanup - // So... not great, but much better than constantly crashing on unload - this.Disable(); - Thread.Sleep(500); - - this.scene?.Dispose(); - this.setCursorHook?.Dispose(); - this.presentHook.Dispose(); - this.resizeBuffersHook.Dispose(); + Log.Error(e, "RTSS Free failed"); } + this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); + this.presentHook = new Hook(this.address.Present, this.PresentDetour); + this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); + + var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; + + Log.Verbose("===== S W A P C H A I N ====="); + Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); + Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); + Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); + } + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr SetCursorDelegate(IntPtr hCursor); + + private delegate void InstallRTSSHook(); + + /// + /// This event gets called each frame to facilitate ImGui drawing. + /// + public event RawDX11Scene.BuildUIDelegate Draw; + + /// + /// This event gets called when ResizeBuffers is called. + /// + public event Action ResizeBuffers; + + /// + /// Gets or sets an action that is executed when fonts are rebuilt. + /// + public event Action BuildFonts; + + /// + /// Gets the default ImGui font. + /// + public static ImFontPtr DefaultFont { get; private set; } + + /// + /// Gets an included FontAwesome icon font. + /// + public static ImFontPtr IconFont { get; private set; } + + /// + /// Gets an included monospaced font. + /// + public static ImFontPtr MonoFont { get; private set; } + + /// + /// Gets or sets the pointer to ImGui.IO(), when it was last used. + /// + public ImGuiIOPtr LastImGuiIoPtr { get; set; } + + /// + /// Gets the D3D11 device instance. + /// + public Device? Device => this.scene?.Device; + + /// + /// Gets the address handle to the main process window. + /// + public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; + + /// + /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. + /// + public bool OverrideGameCursor + { + get => this.scene.UpdateCursor; + set => this.scene.UpdateCursor = value; + } + + /// + /// Gets or sets a value indicating whether the fonts are built and ready to use. + /// + public bool FontsReady { get; set; } = false; + + /// + /// Gets a value indicating whether the Dalamud interface ready to use. + /// + public bool IsReady => this.scene != null; + + /// + /// Enable this module. + /// + public void Enable() + { + this.setCursorHook?.Enable(); + this.presentHook.Enable(); + this.resizeBuffersHook.Enable(); + + try + { + if (!string.IsNullOrEmpty(this.rtssPath)) + { + NativeFunctions.LoadLibraryW(this.rtssPath); + var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); + var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook"); + + Marshal.GetDelegateForFunctionPointer(installAddr).Invoke(); + } + } + catch (Exception ex) + { + Log.Error(ex, "Could not reload RTSS"); + } + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + // HACK: this is usually called on a separate thread from PresentDetour (likely on a dedicated render thread) + // and if we aren't already disabled, disposing of the scene and hook can frequently crash due to the hook + // being disposed of in this thread while it is actively in use in the render thread. + // This is a terrible way to prevent issues, but should basically always work to ensure that all outstanding + // calls to PresentDetour have finished (and Disable means no new ones will start), before we try to cleanup + // So... not great, but much better than constantly crashing on unload + this.Disable(); + Thread.Sleep(500); + + this.scene?.Dispose(); + this.setCursorHook?.Dispose(); + this.presentHook.Dispose(); + this.resizeBuffersHook.Dispose(); + } + #nullable enable - /// - /// Load an image from disk. - /// - /// The filepath to load. - /// A texture, ready to use in ImGui. - public TextureWrap? LoadImage(string filePath) + /// + /// Load an image from disk. + /// + /// The filepath to load. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImage(string filePath) + { + try { - try - { - return this.scene?.LoadImage(filePath) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, $"Failed to load image from {filePath}"); - } - - return null; + return this.scene?.LoadImage(filePath) ?? null; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to load image from {filePath}"); } - /// - /// Load an image from an array of bytes. - /// - /// The data to load. - /// A texture, ready to use in ImGui. - public TextureWrap? LoadImage(byte[] imageData) - { - try - { - return this.scene?.LoadImage(imageData) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from memory"); - } + return null; + } - return null; + /// + /// Load an image from an array of bytes. + /// + /// The data to load. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImage(byte[] imageData) + { + try + { + return this.scene?.LoadImage(imageData) ?? null; + } + catch (Exception ex) + { + Log.Error(ex, "Failed to load image from memory"); } - /// - /// Load an image from an array of bytes. - /// - /// The data to load. - /// The width in pixels. - /// The height in pixels. - /// The number of channels. - /// A texture, ready to use in ImGui. - public TextureWrap? LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - { - try - { - return this.scene?.LoadImageRaw(imageData, width, height, numChannels) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from raw data"); - } + return null; + } - return null; + /// + /// Load an image from an array of bytes. + /// + /// The data to load. + /// The width in pixels. + /// The height in pixels. + /// The number of channels. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImageRaw(byte[] imageData, int width, int height, int numChannels) + { + try + { + return this.scene?.LoadImageRaw(imageData, width, height, numChannels) ?? null; } + catch (Exception ex) + { + Log.Error(ex, "Failed to load image from raw data"); + } + + return null; + } #nullable restore - /// - /// Sets up a deferred invocation of font rebuilding, before the next render frame. - /// - public void RebuildFonts() + /// + /// Sets up a deferred invocation of font rebuilding, before the next render frame. + /// + public void RebuildFonts() + { + Log.Verbose("[FONT] RebuildFonts() called"); + + // don't invoke this multiple times per frame, in case multiple plugins call it + if (!this.isRebuildingFonts) { - Log.Verbose("[FONT] RebuildFonts() called"); + Log.Verbose("[FONT] RebuildFonts() trigger"); - // don't invoke this multiple times per frame, in case multiple plugins call it - if (!this.isRebuildingFonts) + this.isRebuildingFonts = true; + this.scene.OnNewRenderFrame += this.RebuildFontsInternal; + } + } + + /// + /// Wait for the rebuilding fonts to complete. + /// + public void WaitForFontRebuild() + { + this.fontBuildSignal.WaitOne(); + } + + private static void ShowFontError(string path) + { + Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error"); + } + + /* + * NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg. + * Seems to work fine regardless, I guess, so whatever. + */ + private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) + { + if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) + return this.presentHook.Original(swapChain, syncInterval, presentFlags); + + if (this.scene == null) + { + try { - Log.Verbose("[FONT] RebuildFonts() trigger"); - - this.isRebuildingFonts = true; - this.scene.OnNewRenderFrame += this.RebuildFontsInternal; + this.scene = new RawDX11Scene(swapChain); } - } - - /// - /// Wait for the rebuilding fonts to complete. - /// - public void WaitForFontRebuild() - { - this.fontBuildSignal.WaitOne(); - } - - private static void ShowFontError(string path) - { - Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error"); - } - - /* - * NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg. - * Seems to work fine regardless, I guess, so whatever. - */ - private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) - { - if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) - return this.presentHook.Original(swapChain, syncInterval, presentFlags); - - if (this.scene == null) + catch (DllNotFoundException ex) { - try - { - this.scene = new RawDX11Scene(swapChain); - } - catch (DllNotFoundException ex) - { - Log.Error(ex, "Could not load ImGui dependencies."); + Log.Error(ex, "Could not load ImGui dependencies."); - var res = PInvoke.User32.MessageBox( - IntPtr.Zero, - "Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?", - "Dalamud Error", - User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR); + var res = PInvoke.User32.MessageBox( + IntPtr.Zero, + "Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?", + "Dalamud Error", + User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR); - if (res == User32.MessageBoxResult.IDYES) + if (res == User32.MessageBoxResult.IDYES) + { + var psi = new ProcessStartInfo { - var psi = new ProcessStartInfo - { - FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe", - UseShellExecute = true, - }; - Process.Start(psi); - } - - Environment.Exit(-1); + FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe", + UseShellExecute = true, + }; + Process.Start(psi); } - var startInfo = Service.Get(); - var configuration = Service.Get(); - - var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini")); - - try - { - if (iniFileInfo.Length > 1200000) - { - Log.Warning("dalamudUI.ini was over 1mb, deleting"); - iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); - iniFileInfo.Delete(); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not delete dalamudUI.ini"); - } - - this.scene.ImGuiIniPath = iniFileInfo.FullName; - this.scene.OnBuildUI += this.Display; - this.scene.OnNewInputFrame += this.OnNewInputFrame; - - this.SetupFonts(); - - StyleModel.TransferOldModels(); - - if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name)) - { - configuration.SavedStyles = new List { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic }; - configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name; - } - else if (configuration.SavedStyles.Count == 1) - { - configuration.SavedStyles.Add(StyleModelV1.DalamudClassic); - } - else if (configuration.SavedStyles[1].Name != StyleModelV1.DalamudClassic.Name) - { - configuration.SavedStyles.Insert(1, StyleModelV1.DalamudClassic); - } - - configuration.SavedStyles[0] = StyleModelV1.DalamudStandard; - configuration.SavedStyles[1] = StyleModelV1.DalamudClassic; - - var style = configuration.SavedStyles.FirstOrDefault(x => x.Name == configuration.ChosenStyle); - if (style == null) - { - style = StyleModelV1.DalamudStandard; - configuration.ChosenStyle = style.Name; - configuration.Save(); - } - - style.Apply(); - - ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; - - if (!configuration.IsDocking) - { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; - } - else - { - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; - } - - // NOTE (Chiv) Toggle gamepad navigation via setting - if (!configuration.IsGamepadNavigationEnabled) - { - ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; - } - else - { - ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; - } - - // NOTE (Chiv) Explicitly deactivate on dalamud boot - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; - - ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); - - Log.Information("[IM] Scene & ImGui setup OK!"); - - Service.Get().Enable(); + Environment.Exit(-1); } - if (this.address.IsReshade) + var startInfo = Service.Get(); + var configuration = Service.Get(); + + var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini")); + + try { - var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags); - - this.RenderImGui(); - - return pRes; + if (iniFileInfo.Length > 1200000) + { + Log.Warning("dalamudUI.ini was over 1mb, deleting"); + iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); + iniFileInfo.Delete(); + } } + catch (Exception ex) + { + Log.Error(ex, "Could not delete dalamudUI.ini"); + } + + this.scene.ImGuiIniPath = iniFileInfo.FullName; + this.scene.OnBuildUI += this.Display; + this.scene.OnNewInputFrame += this.OnNewInputFrame; + + this.SetupFonts(); + + StyleModel.TransferOldModels(); + + if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name)) + { + configuration.SavedStyles = new List { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic }; + configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name; + } + else if (configuration.SavedStyles.Count == 1) + { + configuration.SavedStyles.Add(StyleModelV1.DalamudClassic); + } + else if (configuration.SavedStyles[1].Name != StyleModelV1.DalamudClassic.Name) + { + configuration.SavedStyles.Insert(1, StyleModelV1.DalamudClassic); + } + + configuration.SavedStyles[0] = StyleModelV1.DalamudStandard; + configuration.SavedStyles[1] = StyleModelV1.DalamudClassic; + + var style = configuration.SavedStyles.FirstOrDefault(x => x.Name == configuration.ChosenStyle); + if (style == null) + { + style = StyleModelV1.DalamudStandard; + configuration.ChosenStyle = style.Name; + configuration.Save(); + } + + style.Apply(); + + ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; + + if (!configuration.IsDocking) + { + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; + } + else + { + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; + } + + // NOTE (Chiv) Toggle gamepad navigation via setting + if (!configuration.IsGamepadNavigationEnabled) + { + ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; + } + else + { + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; + } + + // NOTE (Chiv) Explicitly deactivate on dalamud boot + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; + + ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); + + Log.Information("[IM] Scene & ImGui setup OK!"); + + Service.Get().Enable(); + } + + if (this.address.IsReshade) + { + var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags); this.RenderImGui(); - return this.presentHook.Original(swapChain, syncInterval, presentFlags); + return pRes; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void RenderImGui() + this.RenderImGui(); + + return this.presentHook.Original(swapChain, syncInterval, presentFlags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderImGui() + { + // Process information needed by ImGuiHelpers each frame. + ImGuiHelpers.NewFrame(); + + // Check if we can still enable viewports without any issues. + this.CheckViewportState(); + + this.scene.Render(); + } + + private void CheckViewportState() + { + var configuration = Service.Get(); + + if (configuration.IsDisableViewport || this.scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1) { - // Process information needed by ImGuiHelpers each frame. - ImGuiHelpers.NewFrame(); - - // Check if we can still enable viewports without any issues. - this.CheckViewportState(); - - this.scene.Render(); + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; + return; } - private void CheckViewportState() - { - var configuration = Service.Get(); + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; + } - if (configuration.IsDisableViewport || this.scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1) + private unsafe void SetupFonts() + { + var dalamud = Service.Get(); + + this.fontBuildSignal.Reset(); + + ImGui.GetIO().Fonts.Clear(); + + ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); + fontConfig.MergeMode = true; + fontConfig.PixelSnapH = true; + + var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); + + if (!File.Exists(fontPathJp)) + ShowFontError(fontPathJp); + + var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned); + + DefaultFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, japaneseRangeHandle.AddrOfPinnedObject()); + + var fontPathGame = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "gamesym.ttf"); + + if (!File.Exists(fontPathGame)) + ShowFontError(fontPathGame); + + var gameRangeHandle = GCHandle.Alloc( + new ushort[] { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; - return; - } - - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; - } - - private unsafe void SetupFonts() - { - var dalamud = Service.Get(); - - this.fontBuildSignal.Reset(); - - ImGui.GetIO().Fonts.Clear(); - - ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); - fontConfig.MergeMode = true; - fontConfig.PixelSnapH = true; - - var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); - - if (!File.Exists(fontPathJp)) - ShowFontError(fontPathJp); - - var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned); - - DefaultFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, japaneseRangeHandle.AddrOfPinnedObject()); - - var fontPathGame = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "gamesym.ttf"); - - if (!File.Exists(fontPathGame)) - ShowFontError(fontPathGame); - - var gameRangeHandle = GCHandle.Alloc( - new ushort[] - { 0xE020, 0xE0DB, 0, - }, - GCHandleType.Pinned); + }, + GCHandleType.Pinned); - ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, gameRangeHandle.AddrOfPinnedObject()); + ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, gameRangeHandle.AddrOfPinnedObject()); - var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesome5FreeSolid.otf"); + var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesome5FreeSolid.otf"); - if (!File.Exists(fontPathIcon)) - ShowFontError(fontPathIcon); + if (!File.Exists(fontPathIcon)) + ShowFontError(fontPathIcon); - var iconRangeHandle = GCHandle.Alloc( - new ushort[] - { + var iconRangeHandle = GCHandle.Alloc( + new ushort[] + { 0xE000, 0xF8FF, 0, - }, - GCHandleType.Pinned); - IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject()); + }, + GCHandleType.Pinned); + IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject()); - var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf"); + var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf"); - if (!File.Exists(fontPathMono)) - ShowFontError(fontPathMono); + if (!File.Exists(fontPathMono)) + ShowFontError(fontPathMono); - MonoFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathMono, 16.0f); + MonoFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathMono, 16.0f); - Log.Verbose("[FONT] Invoke OnBuildFonts"); - this.BuildFonts?.Invoke(); - Log.Verbose("[FONT] OnBuildFonts OK!"); + Log.Verbose("[FONT] Invoke OnBuildFonts"); + this.BuildFonts?.Invoke(); + Log.Verbose("[FONT] OnBuildFonts OK!"); - for (var i = 0; i < ImGui.GetIO().Fonts.Fonts.Size; i++) - { - Log.Verbose("{0} - {1}", i, ImGui.GetIO().Fonts.Fonts[i].GetDebugName()); - } - - ImGui.GetIO().Fonts.Build(); - - Log.Verbose("[FONT] Fonts built!"); - - this.fontBuildSignal.Set(); - - fontConfig.Destroy(); - japaneseRangeHandle.Free(); - gameRangeHandle.Free(); - iconRangeHandle.Free(); - - this.FontsReady = true; + for (var i = 0; i < ImGui.GetIO().Fonts.Fonts.Size; i++) + { + Log.Verbose("{0} - {1}", i, ImGui.GetIO().Fonts.Fonts[i].GetDebugName()); } - private void Disable() - { - this.setCursorHook?.Disable(); - this.presentHook.Disable(); - this.resizeBuffersHook.Disable(); - } + ImGui.GetIO().Fonts.Build(); - // This is intended to only be called as a handler attached to scene.OnNewRenderFrame - private void RebuildFontsInternal() - { - Log.Verbose("[FONT] RebuildFontsInternal() called"); - this.SetupFonts(); + Log.Verbose("[FONT] Fonts built!"); - Log.Verbose("[FONT] RebuildFontsInternal() detaching"); - this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; - this.scene.InvalidateFonts(); + this.fontBuildSignal.Set(); - Log.Verbose("[FONT] Font Rebuild OK!"); + fontConfig.Destroy(); + japaneseRangeHandle.Free(); + gameRangeHandle.Free(); + iconRangeHandle.Free(); - this.isRebuildingFonts = false; - } + this.FontsReady = true; + } - private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) - { + private void Disable() + { + this.setCursorHook?.Disable(); + this.presentHook.Disable(); + this.resizeBuffersHook.Disable(); + } + + // This is intended to only be called as a handler attached to scene.OnNewRenderFrame + private void RebuildFontsInternal() + { + Log.Verbose("[FONT] RebuildFontsInternal() called"); + this.SetupFonts(); + + Log.Verbose("[FONT] RebuildFontsInternal() detaching"); + this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; + this.scene.InvalidateFonts(); + + Log.Verbose("[FONT] Font Rebuild OK!"); + + this.isRebuildingFonts = false; + } + + private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) + { #if DEBUG - Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); + Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); #endif - this.ResizeBuffers?.Invoke(); + this.ResizeBuffers?.Invoke(); - // We have to ensure we're working with the main swapchain, - // as viewports might be resizing as well - if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) - return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + // We have to ensure we're working with the main swapchain, + // as viewports might be resizing as well + if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) + return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); - this.scene?.OnPreResize(); + this.scene?.OnPreResize(); - var ret = this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); - if (ret.ToInt64() == 0x887A0001) - { - Log.Error("invalid call to resizeBuffers"); - } - - this.scene?.OnPostResize((int)width, (int)height); - - return ret; + var ret = this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + if (ret.ToInt64() == 0x887A0001) + { + Log.Error("invalid call to resizeBuffers"); } - private IntPtr SetCursorDetour(IntPtr hCursor) - { - if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) - return IntPtr.Zero; + this.scene?.OnPostResize((int)width, (int)height); - return this.setCursorHook.Original(hCursor); + return ret; + } + + private IntPtr SetCursorDetour(IntPtr hCursor) + { + if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) + return IntPtr.Zero; + + return this.setCursorHook.Original(hCursor); + } + + private void OnNewInputFrame() + { + var dalamudInterface = Service.GetNullable(); + var gamepadState = Service.GetNullable(); + var keyState = Service.GetNullable(); + + // fix for keys in game getting stuck, if you were holding a game key (like run) + // and then clicked on an imgui textbox - imgui would swallow the keyup event, + // so the game would think the key remained pressed continuously until you left + // imgui and pressed and released the key again + if (ImGui.GetIO().WantTextInput) + { + keyState.ClearAll(); } - private void OnNewInputFrame() + // TODO: mouse state? + + var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0; + + // NOTE (Chiv) Activate ImGui navigation via L1+L3 press + // (mimicking how mouse navigation is activated via L1+R3 press in game). + if (gamepadEnabled + && gamepadState.Raw(GamepadButtons.L1) > 0 + && gamepadState.Pressed(GamepadButtons.L3) > 0) { - var dalamudInterface = Service.GetNullable(); - var gamepadState = Service.GetNullable(); - var keyState = Service.GetNullable(); - - // fix for keys in game getting stuck, if you were holding a game key (like run) - // and then clicked on an imgui textbox - imgui would swallow the keyup event, - // so the game would think the key remained pressed continuously until you left - // imgui and pressed and released the key again - if (ImGui.GetIO().WantTextInput) - { - keyState.ClearAll(); - } - - // TODO: mouse state? - - var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0; - - // NOTE (Chiv) Activate ImGui navigation via L1+L3 press - // (mimicking how mouse navigation is activated via L1+R3 press in game). - if (gamepadEnabled - && gamepadState.Raw(GamepadButtons.L1) > 0 - && gamepadState.Pressed(GamepadButtons.L3) > 0) - { - ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; - gamepadState.NavEnableGamepad ^= true; - dalamudInterface.ToggleGamepadModeNotifierWindow(); - } - - if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) - { - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = gamepadState.Raw(GamepadButtons.South); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = gamepadState.Raw(GamepadButtons.East); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = gamepadState.Raw(GamepadButtons.North); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = gamepadState.Raw(GamepadButtons.West); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = gamepadState.Raw(GamepadButtons.DpadLeft); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = gamepadState.Raw(GamepadButtons.DpadRight); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = gamepadState.Raw(GamepadButtons.DpadUp); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = gamepadState.Raw(GamepadButtons.DpadDown); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = gamepadState.LeftStickLeft; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = gamepadState.LeftStickRight; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = gamepadState.LeftStickUp; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = gamepadState.LeftStickDown; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = gamepadState.Raw(GamepadButtons.L1); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = gamepadState.Raw(GamepadButtons.R1); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = gamepadState.Raw(GamepadButtons.L2); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = gamepadState.Raw(GamepadButtons.R2); - - if (gamepadState.Pressed(GamepadButtons.R3) > 0) - { - dalamudInterface.TogglePluginInstallerWindow(); - } - } + ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; + gamepadState.NavEnableGamepad ^= true; + dalamudInterface.ToggleGamepadModeNotifierWindow(); } - private void Display() + if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) { - // this is more or less part of what reshade/etc do to avoid having to manually - // set the cursor inside the ui - // This will just tell ImGui to draw its own software cursor instead of using the hardware cursor - // The scene internally will handle hiding and showing the hardware (game) cursor - // If the player has the game software cursor enabled, we can't really do anything about that and - // they will see both cursors. - // Doing this here because it's somewhat application-specific behavior - // ImGui.GetIO().MouseDrawCursor = ImGui.GetIO().WantCaptureMouse; - this.LastImGuiIoPtr = ImGui.GetIO(); - this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = gamepadState.Raw(GamepadButtons.South); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = gamepadState.Raw(GamepadButtons.East); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = gamepadState.Raw(GamepadButtons.North); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = gamepadState.Raw(GamepadButtons.West); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = gamepadState.Raw(GamepadButtons.DpadLeft); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = gamepadState.Raw(GamepadButtons.DpadRight); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = gamepadState.Raw(GamepadButtons.DpadUp); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = gamepadState.Raw(GamepadButtons.DpadDown); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = gamepadState.LeftStickLeft; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = gamepadState.LeftStickRight; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = gamepadState.LeftStickUp; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = gamepadState.LeftStickDown; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = gamepadState.Raw(GamepadButtons.L1); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = gamepadState.Raw(GamepadButtons.R1); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = gamepadState.Raw(GamepadButtons.L2); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = gamepadState.Raw(GamepadButtons.R2); - WindowSystem.HasAnyWindowSystemFocus = false; - WindowSystem.FocusedWindowSystemNamespace = string.Empty; - - var snap = ImGuiManagedAsserts.GetSnapshot(); - this.Draw?.Invoke(); - ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); - - Service.Get().Draw(); + if (gamepadState.Pressed(GamepadButtons.R3) > 0) + { + dalamudInterface.TogglePluginInstallerWindow(); + } } } + + private void Display() + { + // this is more or less part of what reshade/etc do to avoid having to manually + // set the cursor inside the ui + // This will just tell ImGui to draw its own software cursor instead of using the hardware cursor + // The scene internally will handle hiding and showing the hardware (game) cursor + // If the player has the game software cursor enabled, we can't really do anything about that and + // they will see both cursors. + // Doing this here because it's somewhat application-specific behavior + // ImGui.GetIO().MouseDrawCursor = ImGui.GetIO().WantCaptureMouse; + this.LastImGuiIoPtr = ImGui.GetIO(); + this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse; + + WindowSystem.HasAnyWindowSystemFocus = false; + WindowSystem.FocusedWindowSystemNamespace = string.Empty; + + var snap = ImGuiManagedAsserts.GetSnapshot(); + this.Draw?.Invoke(); + ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); + + Service.Get().Draw(); + } } diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs index 5c76854d2..fd203192f 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs @@ -1,22 +1,21 @@ -namespace Dalamud.Interface.Internal.ManagedAsserts +namespace Dalamud.Interface.Internal.ManagedAsserts; + +/// +/// Offsets to various data in ImGui context. +/// +/// +/// Last updated for ImGui 1.83. +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] +internal static class ImGuiContextOffsets { - /// - /// Offsets to various data in ImGui context. - /// - /// - /// Last updated for ImGui 1.83. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] - internal static class ImGuiContextOffsets - { - public const int CurrentWindowStackOffset = 0x73A; + public const int CurrentWindowStackOffset = 0x73A; - public const int ColorStackOffset = 0x79C; + public const int ColorStackOffset = 0x79C; - public const int StyleVarStackOffset = 0x7A0; + public const int StyleVarStackOffset = 0x7A0; - public const int FontStackOffset = 0x7A4; + public const int FontStackOffset = 0x7A4; - public const int BeginPopupStackOffset = 0x7B8; - } + public const int BeginPopupStackOffset = 0x7B8; } diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs index ae5a0c3ff..91b7deaf0 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs @@ -4,133 +4,132 @@ using ImGuiNET; using static Dalamud.NativeFunctions; -namespace Dalamud.Interface.Internal.ManagedAsserts +namespace Dalamud.Interface.Internal.ManagedAsserts; + +/// +/// Report ImGui problems with a MessageBox dialog. +/// +internal static class ImGuiManagedAsserts { /// - /// Report ImGui problems with a MessageBox dialog. + /// Gets or sets a value indicating whether asserts are enabled for ImGui. /// - internal static class ImGuiManagedAsserts + public static bool AssertsEnabled { get; set; } + + /// + /// Create a snapshot of the current ImGui context. + /// Should be called before rendering an ImGui frame. + /// + /// A snapshot of the current context. + public static unsafe ImGuiContextSnapshot GetSnapshot() { - /// - /// Gets or sets a value indicating whether asserts are enabled for ImGui. - /// - public static bool AssertsEnabled { get; set; } + var contextPtr = ImGui.GetCurrentContext(); - /// - /// Create a snapshot of the current ImGui context. - /// Should be called before rendering an ImGui frame. - /// - /// A snapshot of the current context. - public static unsafe ImGuiContextSnapshot GetSnapshot() + var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size + var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size + var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size + var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size + var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size + + return new ImGuiContextSnapshot { - var contextPtr = ImGui.GetCurrentContext(); + StyleVarStackSize = styleVarStack, + ColorStackSize = colorStack, + FontStackSize = fontStack, + BeginPopupStackSize = popupStack, + WindowStackSize = windowStack, + }; + } - var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size - var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size - var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size - var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size - var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size - - return new ImGuiContextSnapshot - { - StyleVarStackSize = styleVarStack, - ColorStackSize = colorStack, - FontStackSize = fontStack, - BeginPopupStackSize = popupStack, - WindowStackSize = windowStack, - }; + /// + /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. + /// + /// The source of any problems, something to blame. + /// ImGui context snapshot. + public static void ReportProblems(string source, ImGuiContextSnapshot before) + { + if (!AssertsEnabled) + { + return; } - /// - /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. - /// - /// The source of any problems, something to blame. - /// ImGui context snapshot. - public static void ReportProblems(string source, ImGuiContextSnapshot before) + var cSnap = GetSnapshot(); + + if (before.StyleVarStackSize != cSnap.StyleVarStackSize) { - if (!AssertsEnabled) - { - return; - } - - var cSnap = GetSnapshot(); - - if (before.StyleVarStackSize != cSnap.StyleVarStackSize) - { - ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); - return; - } - - if (before.ColorStackSize != cSnap.ColorStackSize) - { - ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); - return; - } - - if (before.FontStackSize != cSnap.FontStackSize) - { - ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); - return; - } - - if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) - { - ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); - return; - } - - if (cSnap.WindowStackSize != 1) - { - if (cSnap.WindowStackSize > 1) - { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); - } - else - { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); - } - } + ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); + return; } - private static void ShowAssert(string source, string message) + if (before.ColorStackSize != cSnap.ColorStackSize) { - var caption = $"You fucked up"; - message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; - var flags = MessageBoxType.Ok | MessageBoxType.IconError; - - _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); - AssertsEnabled = false; + ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); + return; } - /// - /// A snapshot of various ImGui context properties. - /// - public class ImGuiContextSnapshot + if (before.FontStackSize != cSnap.FontStackSize) { - /// - /// Gets the ImGui style var stack size. - /// - public int StyleVarStackSize { get; init; } + ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); + return; + } - /// - /// Gets the ImGui color stack size. - /// - public int ColorStackSize { get; init; } + if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) + { + ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); + return; + } - /// - /// Gets the ImGui font stack size. - /// - public int FontStackSize { get; init; } - - /// - /// Gets the ImGui begin popup stack size. - /// - public int BeginPopupStackSize { get; init; } - - /// - /// Gets the ImGui window stack size. - /// - public int WindowStackSize { get; init; } + if (cSnap.WindowStackSize != 1) + { + if (cSnap.WindowStackSize > 1) + { + ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + } + else + { + ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + } } } + + private static void ShowAssert(string source, string message) + { + var caption = $"You fucked up"; + message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; + var flags = MessageBoxType.Ok | MessageBoxType.IconError; + + _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + AssertsEnabled = false; + } + + /// + /// A snapshot of various ImGui context properties. + /// + public class ImGuiContextSnapshot + { + /// + /// Gets the ImGui style var stack size. + /// + public int StyleVarStackSize { get; init; } + + /// + /// Gets the ImGui color stack size. + /// + public int ColorStackSize { get; init; } + + /// + /// Gets the ImGui font stack size. + /// + public int FontStackSize { get; init; } + + /// + /// Gets the ImGui begin popup stack size. + /// + public int BeginPopupStackSize { get; init; } + + /// + /// Gets the ImGui window stack size. + /// + public int WindowStackSize { get; init; } + } } diff --git a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs index aeef3c934..f08193cd8 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs @@ -7,306 +7,305 @@ using Dalamud.Interface.Colors; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Notifications +namespace Dalamud.Interface.Internal.Notifications; + +/// +/// Class handling notifications/toasts in ImGui. +/// Ported from https://github.com/patrickcjk/imgui-notify. +/// +internal class NotificationManager { /// - /// Class handling notifications/toasts in ImGui. - /// Ported from https://github.com/patrickcjk/imgui-notify. + /// Value indicating the bottom-left X padding. /// - internal class NotificationManager + internal const float NotifyPaddingX = 20.0f; + + /// + /// Value indicating the bottom-left Y padding. + /// + internal const float NotifyPaddingY = 20.0f; + + /// + /// Value indicating the Y padding between each message. + /// + internal const float NotifyPaddingMessageY = 10.0f; + + /// + /// Value indicating the fade-in and out duration. + /// + internal const int NotifyFadeInOutTime = 500; + + /// + /// Value indicating the default time until the notification is dismissed. + /// + internal const int NotifyDefaultDismiss = 3000; + + /// + /// Value indicating the maximum opacity. + /// + internal const float NotifyOpacity = 0.82f; + + /// + /// Value indicating default window flags for the notifications. + /// + internal const ImGuiWindowFlags NotifyToastFlags = + ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs | + ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing; + + private readonly List notifications = new(); + + /// + /// Add a notification to the notification queue. + /// + /// The content of the notification. + /// The title of the notification. + /// The type of the notification. + /// The time the notification should be displayed for. + public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss) { - /// - /// Value indicating the bottom-left X padding. - /// - internal const float NotifyPaddingX = 20.0f; - - /// - /// Value indicating the bottom-left Y padding. - /// - internal const float NotifyPaddingY = 20.0f; - - /// - /// Value indicating the Y padding between each message. - /// - internal const float NotifyPaddingMessageY = 10.0f; - - /// - /// Value indicating the fade-in and out duration. - /// - internal const int NotifyFadeInOutTime = 500; - - /// - /// Value indicating the default time until the notification is dismissed. - /// - internal const int NotifyDefaultDismiss = 3000; - - /// - /// Value indicating the maximum opacity. - /// - internal const float NotifyOpacity = 0.82f; - - /// - /// Value indicating default window flags for the notifications. - /// - internal const ImGuiWindowFlags NotifyToastFlags = - ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs | - ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing; - - private readonly List notifications = new(); - - /// - /// Add a notification to the notification queue. - /// - /// The content of the notification. - /// The title of the notification. - /// The type of the notification. - /// The time the notification should be displayed for. - public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss) + this.notifications.Add(new Notification { - this.notifications.Add(new Notification - { - Content = content, - Title = title, - NotificationType = type, - DurationMs = msDelay, - }); - } + Content = content, + Title = title, + NotificationType = type, + DurationMs = msDelay, + }); + } - /// - /// Draw all currently queued notifications. - /// - public void Draw() + /// + /// Draw all currently queued notifications. + /// + public void Draw() + { + var viewportSize = ImGuiHelpers.MainViewport.Size; + var height = 0f; + + for (var i = 0; i < this.notifications.Count; i++) { - var viewportSize = ImGuiHelpers.MainViewport.Size; - var height = 0f; + var tn = this.notifications.ElementAt(i); - for (var i = 0; i < this.notifications.Count; i++) + if (tn.GetPhase() == Notification.Phase.Expired) { - var tn = this.notifications.ElementAt(i); + this.notifications.RemoveAt(i); + continue; + } - if (tn.GetPhase() == Notification.Phase.Expired) - { - this.notifications.RemoveAt(i); - continue; - } + var opacity = tn.GetFadePercent(); - var opacity = tn.GetFadePercent(); + var iconColor = tn.Color; + iconColor.W = opacity; - var iconColor = tn.Color; - iconColor.W = opacity; + var windowName = $"##NOTIFY{i}"; - var windowName = $"##NOTIFY{i}"; + ImGuiHelpers.ForceNextWindowMainViewport(); + ImGui.SetNextWindowBgAlpha(opacity); + ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One); + ImGui.Begin(windowName, NotifyToastFlags); - ImGuiHelpers.ForceNextWindowMainViewport(); - ImGui.SetNextWindowBgAlpha(opacity); - ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One); - ImGui.Begin(windowName, NotifyToastFlags); + ImGui.PushTextWrapPos(viewportSize.X / 3.0f); - ImGui.PushTextWrapPos(viewportSize.X / 3.0f); + var wasTitleRendered = false; - var wasTitleRendered = false; + if (!tn.Icon.IsNullOrEmpty()) + { + wasTitleRendered = true; + ImGui.PushFont(InterfaceManager.IconFont); + ImGui.TextColored(iconColor, tn.Icon); + ImGui.PopFont(); + } + var textColor = ImGuiColors.DalamudWhite; + textColor.W = opacity; + + ImGui.PushStyleColor(ImGuiCol.Text, textColor); + + if (!tn.Title.IsNullOrEmpty()) + { if (!tn.Icon.IsNullOrEmpty()) { - wasTitleRendered = true; - ImGui.PushFont(InterfaceManager.IconFont); - ImGui.TextColored(iconColor, tn.Icon); - ImGui.PopFont(); + ImGui.SameLine(); } - var textColor = ImGuiColors.DalamudWhite; - textColor.W = opacity; - - ImGui.PushStyleColor(ImGuiCol.Text, textColor); - - if (!tn.Title.IsNullOrEmpty()) - { - if (!tn.Icon.IsNullOrEmpty()) - { - ImGui.SameLine(); - } - - ImGui.TextUnformatted(tn.Title); - wasTitleRendered = true; - } - else if (!tn.DefaultTitle.IsNullOrEmpty()) - { - if (!tn.Icon.IsNullOrEmpty()) - { - ImGui.SameLine(); - } - - ImGui.TextUnformatted(tn.DefaultTitle); - wasTitleRendered = true; - } - - if (wasTitleRendered && !tn.Content.IsNullOrEmpty()) - { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f); - } - - if (!tn.Content.IsNullOrEmpty()) - { - if (wasTitleRendered) - { - ImGui.Separator(); - } - - ImGui.TextUnformatted(tn.Content); - } - - ImGui.PopStyleColor(); - - ImGui.PopTextWrapPos(); - - height += ImGui.GetWindowHeight() + NotifyPaddingMessageY; - - ImGui.End(); + ImGui.TextUnformatted(tn.Title); + wasTitleRendered = true; } + else if (!tn.DefaultTitle.IsNullOrEmpty()) + { + if (!tn.Icon.IsNullOrEmpty()) + { + ImGui.SameLine(); + } + + ImGui.TextUnformatted(tn.DefaultTitle); + wasTitleRendered = true; + } + + if (wasTitleRendered && !tn.Content.IsNullOrEmpty()) + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f); + } + + if (!tn.Content.IsNullOrEmpty()) + { + if (wasTitleRendered) + { + ImGui.Separator(); + } + + ImGui.TextUnformatted(tn.Content); + } + + ImGui.PopStyleColor(); + + ImGui.PopTextWrapPos(); + + height += ImGui.GetWindowHeight() + NotifyPaddingMessageY; + + ImGui.End(); + } + } + + /// + /// Container class for notifications. + /// + internal class Notification + { + /// + /// Possible notification phases. + /// + internal enum Phase + { + /// + /// Phase indicating fade-in. + /// + FadeIn, + + /// + /// Phase indicating waiting until fade-out. + /// + Wait, + + /// + /// Phase indicating fade-out. + /// + FadeOut, + + /// + /// Phase indicating that the notification has expired. + /// + Expired, } /// - /// Container class for notifications. + /// Gets the type of the notification. /// - internal class Notification + internal NotificationType NotificationType { get; init; } + + /// + /// Gets the title of the notification. + /// + internal string? Title { get; init; } + + /// + /// Gets the content of the notification. + /// + internal string Content { get; init; } + + /// + /// Gets the duration of the notification in milliseconds. + /// + internal uint DurationMs { get; init; } + + /// + /// Gets the creation time of the notification. + /// + internal DateTime CreationTime { get; init; } = DateTime.Now; + + /// + /// Gets the default color of the notification. + /// + /// Thrown when is set to an out-of-range value. + internal Vector4 Color => this.NotificationType switch { - /// - /// Possible notification phases. - /// - internal enum Phase + NotificationType.None => ImGuiColors.DalamudWhite, + NotificationType.Success => ImGuiColors.HealerGreen, + NotificationType.Warning => ImGuiColors.DalamudOrange, + NotificationType.Error => ImGuiColors.DalamudRed, + NotificationType.Info => ImGuiColors.TankBlue, + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Gets the icon of the notification. + /// + /// Thrown when is set to an out-of-range value. + internal string? Icon => this.NotificationType switch + { + NotificationType.None => null, + NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(), + NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(), + NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(), + NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(), + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Gets the default title of the notification. + /// + /// Thrown when is set to an out-of-range value. + internal string? DefaultTitle => this.NotificationType switch + { + NotificationType.None => null, + NotificationType.Success => NotificationType.Success.ToString(), + NotificationType.Warning => NotificationType.Warning.ToString(), + NotificationType.Error => NotificationType.Error.ToString(), + NotificationType.Info => NotificationType.Info.ToString(), + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Gets the elapsed time since creating the notification. + /// + internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime; + + /// + /// Gets the phase of the notification. + /// + /// The phase of the notification. + internal Phase GetPhase() + { + var elapsed = (int)this.ElapsedTime.TotalMilliseconds; + + if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime) + return Phase.Expired; + else if (elapsed > NotifyFadeInOutTime + this.DurationMs) + return Phase.FadeOut; + else if (elapsed > NotifyFadeInOutTime) + return Phase.Wait; + else + return Phase.FadeIn; + } + + /// + /// Gets the opacity of the notification. + /// + /// The opacity, in a range from 0 to 1. + internal float GetFadePercent() + { + var phase = this.GetPhase(); + var elapsed = this.ElapsedTime.TotalMilliseconds; + + if (phase == Phase.FadeIn) { - /// - /// Phase indicating fade-in. - /// - FadeIn, - - /// - /// Phase indicating waiting until fade-out. - /// - Wait, - - /// - /// Phase indicating fade-out. - /// - FadeOut, - - /// - /// Phase indicating that the notification has expired. - /// - Expired, + return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity; + } + else if (phase == Phase.FadeOut) + { + return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) / + NotifyFadeInOutTime)) * NotifyOpacity; } - /// - /// Gets the type of the notification. - /// - internal NotificationType NotificationType { get; init; } - - /// - /// Gets the title of the notification. - /// - internal string? Title { get; init; } - - /// - /// Gets the content of the notification. - /// - internal string Content { get; init; } - - /// - /// Gets the duration of the notification in milliseconds. - /// - internal uint DurationMs { get; init; } - - /// - /// Gets the creation time of the notification. - /// - internal DateTime CreationTime { get; init; } = DateTime.Now; - - /// - /// Gets the default color of the notification. - /// - /// Thrown when is set to an out-of-range value. - internal Vector4 Color => this.NotificationType switch - { - NotificationType.None => ImGuiColors.DalamudWhite, - NotificationType.Success => ImGuiColors.HealerGreen, - NotificationType.Warning => ImGuiColors.DalamudOrange, - NotificationType.Error => ImGuiColors.DalamudRed, - NotificationType.Info => ImGuiColors.TankBlue, - _ => throw new ArgumentOutOfRangeException(), - }; - - /// - /// Gets the icon of the notification. - /// - /// Thrown when is set to an out-of-range value. - internal string? Icon => this.NotificationType switch - { - NotificationType.None => null, - NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(), - NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(), - NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(), - NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(), - _ => throw new ArgumentOutOfRangeException(), - }; - - /// - /// Gets the default title of the notification. - /// - /// Thrown when is set to an out-of-range value. - internal string? DefaultTitle => this.NotificationType switch - { - NotificationType.None => null, - NotificationType.Success => NotificationType.Success.ToString(), - NotificationType.Warning => NotificationType.Warning.ToString(), - NotificationType.Error => NotificationType.Error.ToString(), - NotificationType.Info => NotificationType.Info.ToString(), - _ => throw new ArgumentOutOfRangeException(), - }; - - /// - /// Gets the elapsed time since creating the notification. - /// - internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime; - - /// - /// Gets the phase of the notification. - /// - /// The phase of the notification. - internal Phase GetPhase() - { - var elapsed = (int)this.ElapsedTime.TotalMilliseconds; - - if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime) - return Phase.Expired; - else if (elapsed > NotifyFadeInOutTime + this.DurationMs) - return Phase.FadeOut; - else if (elapsed > NotifyFadeInOutTime) - return Phase.Wait; - else - return Phase.FadeIn; - } - - /// - /// Gets the opacity of the notification. - /// - /// The opacity, in a range from 0 to 1. - internal float GetFadePercent() - { - var phase = this.GetPhase(); - var elapsed = this.ElapsedTime.TotalMilliseconds; - - if (phase == Phase.FadeIn) - { - return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity; - } - else if (phase == Phase.FadeOut) - { - return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) / - NotifyFadeInOutTime)) * NotifyOpacity; - } - - return 1.0f * NotifyOpacity; - } + return 1.0f * NotifyOpacity; } } } diff --git a/Dalamud/Interface/Internal/Notifications/NotificationType.cs b/Dalamud/Interface/Internal/Notifications/NotificationType.cs index d2cb56686..331621df4 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationType.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationType.cs @@ -1,33 +1,32 @@ -namespace Dalamud.Interface.Internal.Notifications +namespace Dalamud.Interface.Internal.Notifications; + +/// +/// Possible notification types. +/// +public enum NotificationType { /// - /// Possible notification types. + /// No special type. /// - public enum NotificationType - { - /// - /// No special type. - /// - None, + None, - /// - /// Type indicating success. - /// - Success, + /// + /// Type indicating success. + /// + Success, - /// - /// Type indicating a warning. - /// - Warning, + /// + /// Type indicating a warning. + /// + Warning, - /// - /// Type indicating an error. - /// - Error, + /// + /// Type indicating an error. + /// + Error, - /// - /// Type indicating generic information. - /// - Info, - } + /// + /// Type indicating generic information. + /// + Info, } diff --git a/Dalamud/Interface/Internal/PluginCategoryManager.cs b/Dalamud/Interface/Internal/PluginCategoryManager.cs index d1c7fdc50..f2c6a2769 100644 --- a/Dalamud/Interface/Internal/PluginCategoryManager.cs +++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs @@ -5,402 +5,401 @@ using System.Linq; using CheapLoc; using Dalamud.Plugin.Internal.Types; -namespace Dalamud.Interface.Internal +namespace Dalamud.Interface.Internal; + +/// +/// Manage category filters for PluginInstallerWindow. +/// +internal class PluginCategoryManager { /// - /// Manage category filters for PluginInstallerWindow. + /// First categoryId for tag based categories. /// - internal class PluginCategoryManager - { - /// - /// First categoryId for tag based categories. - /// - public const int FirstTagBasedCategoryId = 100; + public const int FirstTagBasedCategoryId = 100; - private readonly CategoryInfo[] categoryList = - { - new(0, "special.all", () => Locs.Category_All), - new(10, "special.devInstalled", () => Locs.Category_DevInstalled), - new(11, "special.devIconTester", () => Locs.Category_IconTester), - new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other), - new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs), - new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI), - new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames), - new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory), - new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound), - new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social), - new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility), + private readonly CategoryInfo[] categoryList = + { + new(0, "special.all", () => Locs.Category_All), + new(10, "special.devInstalled", () => Locs.Category_DevInstalled), + new(11, "special.devIconTester", () => Locs.Category_IconTester), + new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other), + new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs), + new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI), + new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames), + new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory), + new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound), + new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social), + new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility), // order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId - }; + }; - private GroupInfo[] groupList = - { - new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11), - new(GroupKind.Installed, () => Locs.Group_Installed, 0), - new(GroupKind.Available, () => Locs.Group_Available, 0), + private GroupInfo[] groupList = + { + new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11), + new(GroupKind.Installed, () => Locs.Group_Installed, 0), + new(GroupKind.Available, () => Locs.Group_Available, 0), // order important, used for drawing, keep in sync with defaults for currentGroupIdx - }; + }; - private int currentGroupIdx = 2; - private int currentCategoryIdx = 0; - private bool isContentDirty; + private int currentGroupIdx = 2; + private int currentCategoryIdx = 0; + private bool isContentDirty; - private Dictionary mapPluginCategories = new(); - private List highlightedCategoryIds = new(); + private Dictionary mapPluginCategories = new(); + private List highlightedCategoryIds = new(); + + /// + /// Type of category group. + /// + public enum GroupKind + { + /// + /// UI group: dev mode only. + /// + DevTools, /// - /// Type of category group. + /// UI group: installed plugins. /// - public enum GroupKind + Installed, + + /// + /// UI group: plugins that can be installed. + /// + Available, + } + + /// + /// Gets the list of all known categories. + /// + public CategoryInfo[] CategoryList => this.categoryList; + + /// + /// Gets the list of all known UI groups. + /// + public GroupInfo[] GroupList => this.groupList; + + /// + /// Gets or sets current group. + /// + public int CurrentGroupIdx + { + get => this.currentGroupIdx; + set { - /// - /// UI group: dev mode only. - /// - DevTools, - - /// - /// UI group: installed plugins. - /// - Installed, - - /// - /// UI group: plugins that can be installed. - /// - Available, - } - - /// - /// Gets the list of all known categories. - /// - public CategoryInfo[] CategoryList => this.categoryList; - - /// - /// Gets the list of all known UI groups. - /// - public GroupInfo[] GroupList => this.groupList; - - /// - /// Gets or sets current group. - /// - public int CurrentGroupIdx - { - get => this.currentGroupIdx; - set - { - if (this.currentGroupIdx != value) - { - this.currentGroupIdx = value; - this.currentCategoryIdx = 0; - this.isContentDirty = true; - } - } - } - - /// - /// Gets or sets current category, index in current Group.Categories array. - /// - public int CurrentCategoryIdx - { - get => this.currentCategoryIdx; - set - { - if (this.currentCategoryIdx != value) - { - this.currentCategoryIdx = value; - this.isContentDirty = true; - } - } - } - - /// - /// Gets a value indicating whether current group + category selection changed recently. - /// Changes in Available group should be followed with , everythine else can use . - /// - public bool IsContentDirty => this.isContentDirty; - - /// - /// Gets a value indicating whether and are valid. - /// - public bool IsSelectionValid => - (this.currentGroupIdx >= 0) && - (this.currentGroupIdx < this.groupList.Length) && - (this.currentCategoryIdx >= 0) && - (this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count); - - /// - /// Rebuild available categories based on currently available plugins. - /// - /// list of all available plugin manifests to install. - public void BuildCategories(IEnumerable availablePlugins) - { - // rebuild map plugin name -> categoryIds - this.mapPluginCategories.Clear(); - - var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available); - var prevCategoryIds = new List(); - prevCategoryIds.AddRange(groupAvail.Categories); - - var categoryList = new List(); - var allCategoryIndices = new List(); - - foreach (var plugin in availablePlugins) - { - categoryList.Clear(); - - var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin); - if (pluginCategoryTags != null) - { - foreach (var tag in pluginCategoryTags) - { - // only tags from whitelist can be accepted - var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase)); - if (matchIdx >= 0) - { - var categoryId = this.CategoryList[matchIdx].CategoryId; - if (categoryId >= FirstTagBasedCategoryId) - { - categoryList.Add(categoryId); - - if (!allCategoryIndices.Contains(matchIdx)) - { - allCategoryIndices.Add(matchIdx); - } - } - } - } - } - - // always add, even if empty - this.mapPluginCategories.Add(plugin, categoryList.ToArray()); - } - - // sort all categories by their loc name - allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name)); - - // rebuild all categories in group, leaving first entry = All intact and always on top - if (groupAvail.Categories.Count > 1) - { - groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1); - } - - foreach (var categoryIdx in allCategoryIndices) - { - groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId); - } - - // compare with prev state and mark as dirty if needed - var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories); - if (!noCategoryChanges) + if (this.currentGroupIdx != value) { + this.currentGroupIdx = value; + this.currentCategoryIdx = 0; this.isContentDirty = true; } } + } - /// - /// Filters list of available plugins based on currently selected category. - /// Resets . - /// - /// List of available plugins to install. - /// Filtered list of plugins. - public List GetCurrentCategoryContent(IEnumerable plugins) + /// + /// Gets or sets current category, index in current Group.Categories array. + /// + public int CurrentCategoryIdx + { + get => this.currentCategoryIdx; + set { - var result = new List(); - - if (this.IsSelectionValid) + if (this.currentCategoryIdx != value) { - var groupInfo = this.groupList[this.currentGroupIdx]; - - var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available); - if (includeAll) - { - result.AddRange(plugins); - } - else - { - var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]); - - foreach (var plugin in plugins) - { - if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds)) - { - var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId); - if (matchIdx >= 0) - { - result.Add(plugin); - } - } - } - } + this.currentCategoryIdx = value; + this.isContentDirty = true; } - - this.ResetContentDirty(); - return result; - } - - /// - /// Clears flag, indicating that all cached values about currently selected group + category have been updated. - /// - public void ResetContentDirty() - { - this.isContentDirty = false; - } - - /// - /// Sets category highlight based on list of plugins. Used for searching. - /// - /// List of plugins whose categories should be highlighted. - public void SetCategoryHighlightsForPlugins(IEnumerable plugins) - { - this.highlightedCategoryIds.Clear(); - - if (plugins != null) - { - foreach (var entry in plugins) - { - if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories)) - { - foreach (var categoryId in pluginCategories) - { - if (!this.highlightedCategoryIds.Contains(categoryId)) - { - this.highlightedCategoryIds.Add(categoryId); - } - } - } - } - } - } - - /// - /// Checks if category should be highlighted. - /// - /// CategoryId to check. - /// true if highlight is needed. - public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId); - - private IEnumerable GetCategoryTagsForManifest(PluginManifest pluginManifest) - { - if (pluginManifest.CategoryTags != null) - { - return pluginManifest.CategoryTags; - } - - return null; - } - - /// - /// Plugin installer category info. - /// - public struct CategoryInfo - { - /// - /// Unique Id number of category, tag match based should be greater of equal . - /// - public int CategoryId; - - /// - /// Tag from plugin manifest to match. - /// - public string Tag; - - private Func nameFunc; - - /// - /// Initializes a new instance of the struct. - /// - /// Unique id of category. - /// Tag to match. - /// Function returning localized name of category. - public CategoryInfo(int categoryId, string tag, Func nameFunc) - { - this.CategoryId = categoryId; - this.Tag = tag; - this.nameFunc = nameFunc; - } - - /// - /// Gets the name of category. - /// - public string Name => this.nameFunc(); - } - - /// - /// Plugin installer UI group, a container for categories. - /// - public struct GroupInfo - { - /// - /// Type of group. - /// - public GroupKind GroupKind; - - /// - /// List of categories in container. - /// - public List Categories; - - private Func nameFunc; - - /// - /// Initializes a new instance of the struct. - /// - /// Type of group. - /// Function returning localized name of category. - /// List of category Ids to hardcode. - public GroupInfo(GroupKind groupKind, Func nameFunc, params int[] categories) - { - this.GroupKind = groupKind; - this.nameFunc = nameFunc; - - this.Categories = new(); - this.Categories.AddRange(categories); - } - - /// - /// Gets the name of UI group. - /// - public string Name => this.nameFunc(); - } - - private static class Locs - { - #region UI groups - - public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools"); - - public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); - - public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); - - #endregion - - #region Categories - - public static string Category_All => Loc.Localize("InstallerCategoryAll", "All"); - - public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); - - public static string Category_IconTester => "Image/Icon Tester"; - - public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other"); - - public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs"); - - public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI"); - - public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games"); - - public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory"); - - public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound"); - - public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social"); - - public static string Category_Utility => Loc.Localize("InstallerCategoryUtility", "Utility"); - - #endregion } } + + /// + /// Gets a value indicating whether current group + category selection changed recently. + /// Changes in Available group should be followed with , everythine else can use . + /// + public bool IsContentDirty => this.isContentDirty; + + /// + /// Gets a value indicating whether and are valid. + /// + public bool IsSelectionValid => + (this.currentGroupIdx >= 0) && + (this.currentGroupIdx < this.groupList.Length) && + (this.currentCategoryIdx >= 0) && + (this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count); + + /// + /// Rebuild available categories based on currently available plugins. + /// + /// list of all available plugin manifests to install. + public void BuildCategories(IEnumerable availablePlugins) + { + // rebuild map plugin name -> categoryIds + this.mapPluginCategories.Clear(); + + var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available); + var prevCategoryIds = new List(); + prevCategoryIds.AddRange(groupAvail.Categories); + + var categoryList = new List(); + var allCategoryIndices = new List(); + + foreach (var plugin in availablePlugins) + { + categoryList.Clear(); + + var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin); + if (pluginCategoryTags != null) + { + foreach (var tag in pluginCategoryTags) + { + // only tags from whitelist can be accepted + var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase)); + if (matchIdx >= 0) + { + var categoryId = this.CategoryList[matchIdx].CategoryId; + if (categoryId >= FirstTagBasedCategoryId) + { + categoryList.Add(categoryId); + + if (!allCategoryIndices.Contains(matchIdx)) + { + allCategoryIndices.Add(matchIdx); + } + } + } + } + } + + // always add, even if empty + this.mapPluginCategories.Add(plugin, categoryList.ToArray()); + } + + // sort all categories by their loc name + allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name)); + + // rebuild all categories in group, leaving first entry = All intact and always on top + if (groupAvail.Categories.Count > 1) + { + groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1); + } + + foreach (var categoryIdx in allCategoryIndices) + { + groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId); + } + + // compare with prev state and mark as dirty if needed + var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories); + if (!noCategoryChanges) + { + this.isContentDirty = true; + } + } + + /// + /// Filters list of available plugins based on currently selected category. + /// Resets . + /// + /// List of available plugins to install. + /// Filtered list of plugins. + public List GetCurrentCategoryContent(IEnumerable plugins) + { + var result = new List(); + + if (this.IsSelectionValid) + { + var groupInfo = this.groupList[this.currentGroupIdx]; + + var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available); + if (includeAll) + { + result.AddRange(plugins); + } + else + { + var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]); + + foreach (var plugin in plugins) + { + if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds)) + { + var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId); + if (matchIdx >= 0) + { + result.Add(plugin); + } + } + } + } + } + + this.ResetContentDirty(); + return result; + } + + /// + /// Clears flag, indicating that all cached values about currently selected group + category have been updated. + /// + public void ResetContentDirty() + { + this.isContentDirty = false; + } + + /// + /// Sets category highlight based on list of plugins. Used for searching. + /// + /// List of plugins whose categories should be highlighted. + public void SetCategoryHighlightsForPlugins(IEnumerable plugins) + { + this.highlightedCategoryIds.Clear(); + + if (plugins != null) + { + foreach (var entry in plugins) + { + if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories)) + { + foreach (var categoryId in pluginCategories) + { + if (!this.highlightedCategoryIds.Contains(categoryId)) + { + this.highlightedCategoryIds.Add(categoryId); + } + } + } + } + } + } + + /// + /// Checks if category should be highlighted. + /// + /// CategoryId to check. + /// true if highlight is needed. + public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId); + + private IEnumerable GetCategoryTagsForManifest(PluginManifest pluginManifest) + { + if (pluginManifest.CategoryTags != null) + { + return pluginManifest.CategoryTags; + } + + return null; + } + + /// + /// Plugin installer category info. + /// + public struct CategoryInfo + { + /// + /// Unique Id number of category, tag match based should be greater of equal . + /// + public int CategoryId; + + /// + /// Tag from plugin manifest to match. + /// + public string Tag; + + private Func nameFunc; + + /// + /// Initializes a new instance of the struct. + /// + /// Unique id of category. + /// Tag to match. + /// Function returning localized name of category. + public CategoryInfo(int categoryId, string tag, Func nameFunc) + { + this.CategoryId = categoryId; + this.Tag = tag; + this.nameFunc = nameFunc; + } + + /// + /// Gets the name of category. + /// + public string Name => this.nameFunc(); + } + + /// + /// Plugin installer UI group, a container for categories. + /// + public struct GroupInfo + { + /// + /// Type of group. + /// + public GroupKind GroupKind; + + /// + /// List of categories in container. + /// + public List Categories; + + private Func nameFunc; + + /// + /// Initializes a new instance of the struct. + /// + /// Type of group. + /// Function returning localized name of category. + /// List of category Ids to hardcode. + public GroupInfo(GroupKind groupKind, Func nameFunc, params int[] categories) + { + this.GroupKind = groupKind; + this.nameFunc = nameFunc; + + this.Categories = new(); + this.Categories.AddRange(categories); + } + + /// + /// Gets the name of UI group. + /// + public string Name => this.nameFunc(); + } + + private static class Locs + { + #region UI groups + + public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools"); + + public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); + + public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); + + #endregion + + #region Categories + + public static string Category_All => Loc.Localize("InstallerCategoryAll", "All"); + + public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); + + public static string Category_IconTester => "Image/Icon Tester"; + + public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other"); + + public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs"); + + public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI"); + + public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games"); + + public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory"); + + public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound"); + + public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social"); + + public static string Category_Utility => Loc.Localize("InstallerCategoryUtility", "Utility"); + + #endregion + } } diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index e64ec6bc2..28adf10b5 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -13,19 +13,19 @@ using ImGuiNET; // Customised version of https://github.com/aers/FFXIVUIDebug -namespace Dalamud.Interface.Internal -{ - /// - /// This class displays a debug window to inspect native addons. - /// - internal unsafe class UIDebug - { - private const int UnitListCount = 18; +namespace Dalamud.Interface.Internal; - private readonly GetAtkStageSingleton getAtkStageSingleton; - private readonly bool[] selectedInList = new bool[UnitListCount]; - private readonly string[] listNames = new string[UnitListCount] - { +/// +/// This class displays a debug window to inspect native addons. +/// +internal unsafe class UIDebug +{ + private const int UnitListCount = 18; + + private readonly GetAtkStageSingleton getAtkStageSingleton; + private readonly bool[] selectedInList = new bool[UnitListCount]; + private readonly string[] listNames = new string[UnitListCount] + { "Depth Layer 1", "Depth Layer 2", "Depth Layer 3", @@ -44,681 +44,113 @@ namespace Dalamud.Interface.Internal "Units 16", "Units 17", "Units 18", - }; + }; - private bool doingSearch; - private string searchInput = string.Empty; - private AtkUnitBase* selectedUnitBase = null; - private ulong beginModule; - private ulong endModule; + private bool doingSearch; + private string searchInput = string.Empty; + private AtkUnitBase* selectedUnitBase = null; + private ulong beginModule; + private ulong endModule; - /// - /// Initializes a new instance of the class. - /// - public UIDebug() + /// + /// Initializes a new instance of the class. + /// + public UIDebug() + { + var sigScanner = Service.Get(); + var getSingletonAddr = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF"); + this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer(getSingletonAddr); + } + + private delegate AtkStage* GetAtkStageSingleton(); + + /// + /// Renders this window. + /// + public void Draw() + { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); + ImGui.BeginChild("st_uiDebug_unitBaseSelect", new Vector2(250, -1), true); + + ImGui.SetNextItemWidth(-1); + ImGui.InputTextWithHint("###atkUnitBaseSearch", "Search", ref this.searchInput, 0x20); + + this.DrawUnitBaseList(); + ImGui.EndChild(); + if (this.selectedUnitBase != null) { - var sigScanner = Service.Get(); - var getSingletonAddr = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF"); - this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer(getSingletonAddr); - } - - private delegate AtkStage* GetAtkStageSingleton(); - - /// - /// Renders this window. - /// - public void Draw() - { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); - ImGui.BeginChild("st_uiDebug_unitBaseSelect", new Vector2(250, -1), true); - - ImGui.SetNextItemWidth(-1); - ImGui.InputTextWithHint("###atkUnitBaseSearch", "Search", ref this.searchInput, 0x20); - - this.DrawUnitBaseList(); - ImGui.EndChild(); - if (this.selectedUnitBase != null) - { - ImGui.SameLine(); - ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true); - this.DrawUnitBase(this.selectedUnitBase); - ImGui.EndChild(); - } - - ImGui.PopStyleVar(); - } - - private static void ClickToCopyText(string text, string textCopy = null) - { - textCopy ??= text; - ImGui.Text($"{text}"); - if (ImGui.IsItemHovered()) - { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); - } - - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); - } - - private void DrawUnitBase(AtkUnitBase* atkUnitBase) - { - var isVisible = (atkUnitBase->Flags & 0x20) == 0x20; - var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name)); - var agent = Service.Get().FindAgentInterface(atkUnitBase); - - ImGui.Text($"{addonName}"); ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF); - ImGui.Text(isVisible ? "Visible" : "Not Visible"); - ImGui.PopStyleColor(); + ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true); + this.DrawUnitBase(this.selectedUnitBase); + ImGui.EndChild(); + } - ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - 25); - if (ImGui.SmallButton("V")) - { - atkUnitBase->Flags ^= 0x20; - } + ImGui.PopStyleVar(); + } - ImGui.Separator(); - ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); - ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); - ImGui.Separator(); + private static void ClickToCopyText(string text, string textCopy = null) + { + textCopy ??= text; + ImGui.Text($"{text}"); + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + if (textCopy != text) ImGui.SetTooltip(textCopy); + } - ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); - ImGui.Text($"Scale: {atkUnitBase->Scale * 100}%%"); - ImGui.Text($"Widget Count {atkUnitBase->UldManager.ObjectCount}"); + if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); + } - ImGui.Separator(); + private void DrawUnitBase(AtkUnitBase* atkUnitBase) + { + var isVisible = (atkUnitBase->Flags & 0x20) == 0x20; + var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name)); + var agent = Service.Get().FindAgentInterface(atkUnitBase); - object addonObj = *atkUnitBase; + ImGui.Text($"{addonName}"); + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF); + ImGui.Text(isVisible ? "Visible" : "Not Visible"); + ImGui.PopStyleColor(); - this.PrintOutObject(addonObj, (ulong)atkUnitBase, new List()); + ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - 25); + if (ImGui.SmallButton("V")) + { + atkUnitBase->Flags ^= 0x20; + } + ImGui.Separator(); + ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); + ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); + ImGui.Separator(); + + ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); + ImGui.Text($"Scale: {atkUnitBase->Scale * 100}%%"); + ImGui.Text($"Widget Count {atkUnitBase->UldManager.ObjectCount}"); + + ImGui.Separator(); + + object addonObj = *atkUnitBase; + + this.PrintOutObject(addonObj, (ulong)atkUnitBase, new List()); + + ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); + ImGui.Separator(); + if (atkUnitBase->RootNode != null) + this.PrintNode(atkUnitBase->RootNode); + + if (atkUnitBase->UldManager.NodeListCount > 0) + { ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); ImGui.Separator(); - if (atkUnitBase->RootNode != null) - this.PrintNode(atkUnitBase->RootNode); - - if (atkUnitBase->UldManager.NodeListCount > 0) - { - ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); - ImGui.Separator(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); - if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) - { - ImGui.PopStyleColor(); - - for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) - { - this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); - } - - ImGui.TreePop(); - } - else - { - ImGui.PopStyleColor(); - } - } - } - - private void PrintNode(AtkResNode* node, bool printSiblings = true, string treePrefix = "") - { - if (node == null) - return; - - if ((int)node->Type < 1000) - this.PrintSimpleNode(node, treePrefix); - else - this.PrintComponentNode(node, treePrefix); - - if (printSiblings) - { - var prevNode = node; - while ((prevNode = prevNode->PrevSiblingNode) != null) - this.PrintNode(prevNode, false, "prev "); - - var nextNode = node; - while ((nextNode = nextNode->NextSiblingNode) != null) - this.PrintNode(nextNode, false, "next "); - } - } - - private void PrintSimpleNode(AtkResNode* node, string treePrefix) - { - var popped = false; - var isVisible = (node->Flags & 0x10) == 0x10; - - if (isVisible) - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); - - if (ImGui.TreeNode($"{treePrefix}{node->Type} Node (ptr = {(long)node:X})###{(long)node}")) - { - if (ImGui.IsItemHovered()) - this.DrawOutline(node); - - if (isVisible) - { - ImGui.PopStyleColor(); - popped = true; - } - - ImGui.Text("Node: "); - ImGui.SameLine(); - ClickToCopyText($"{(ulong)node:X}"); - ImGui.SameLine(); - switch (node->Type) - { - case NodeType.Text: this.PrintOutObject(*(AtkTextNode*)node, (ulong)node, new List()); break; - case NodeType.Image: this.PrintOutObject(*(AtkImageNode*)node, (ulong)node, new List()); break; - case NodeType.Collision: this.PrintOutObject(*(AtkCollisionNode*)node, (ulong)node, new List()); break; - case NodeType.NineGrid: this.PrintOutObject(*(AtkNineGridNode*)node, (ulong)node, new List()); break; - case NodeType.Counter: this.PrintOutObject(*(AtkCounterNode*)node, (ulong)node, new List()); break; - default: this.PrintOutObject(*node, (ulong)node, new List()); break; - } - - this.PrintResNode(node); - - if (node->ChildNode != null) - this.PrintNode(node->ChildNode); - - switch (node->Type) - { - case NodeType.Text: - var textNode = (AtkTextNode*)node; - ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}"); - - ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); - - ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); - int b = textNode->AlignmentFontType; - if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) - { - while (b > byte.MaxValue) b -= byte.MaxValue; - while (b < byte.MinValue) b += byte.MaxValue; - textNode->AlignmentFontType = (byte)b; - textNode->AtkResNode.Flags_2 |= 0x1; - } - - ImGui.Text($"Color: #{textNode->TextColor.R:X2}{textNode->TextColor.G:X2}{textNode->TextColor.B:X2}{textNode->TextColor.A:X2}"); - ImGui.SameLine(); - ImGui.Text($"EdgeColor: #{textNode->EdgeColor.R:X2}{textNode->EdgeColor.G:X2}{textNode->EdgeColor.B:X2}{textNode->EdgeColor.A:X2}"); - ImGui.SameLine(); - ImGui.Text($"BGColor: #{textNode->BackgroundColor.R:X2}{textNode->BackgroundColor.G:X2}{textNode->BackgroundColor.B:X2}{textNode->BackgroundColor.A:X2}"); - - ImGui.Text($"TextFlags: {textNode->TextFlags}"); - ImGui.SameLine(); - ImGui.Text($"TextFlags2: {textNode->TextFlags2}"); - - break; - case NodeType.Counter: - var counterNode = (AtkCounterNode*)node; - ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(counterNode->NodeText.StringPtr))}"); - break; - case NodeType.Image: - var imageNode = (AtkImageNode*)node; - if (imageNode->PartsList != null) - { - if (imageNode->PartId > imageNode->PartsList->PartCount) - { - ImGui.Text("part id > part count?"); - } - else - { - var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].UldAsset; - var texType = textureInfo->AtkTexture.TextureType; - ImGui.Text($"texture type: {texType} part_id={imageNode->PartId} part_id_count={imageNode->PartsList->PartCount}"); - if (texType == TextureType.Resource) - { - var texFileNameStdString = &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; - var texString = texFileNameStdString->Length < 16 - ? Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->Buffer) - : Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->BufferPtr); - - ImGui.Text($"texture path: {texString}"); - var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; - - if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) - { - ImGui.Image(new IntPtr(kernelTexture->D3D11ShaderResourceView), new Vector2(kernelTexture->Width, kernelTexture->Height)); - ImGui.TreePop(); - } - } - else if (texType == TextureType.KernelTexture) - { - if (ImGui.TreeNode($"Texture##{(ulong)textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) - { - ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height)); - ImGui.TreePop(); - } - } - } - } - else - { - ImGui.Text("no texture loaded"); - } - - break; - } - - ImGui.TreePop(); - } - else if (ImGui.IsItemHovered()) - { - this.DrawOutline(node); - } - - if (isVisible && !popped) - ImGui.PopStyleColor(); - } - - private void PrintComponentNode(AtkResNode* node, string treePrefix) - { - var compNode = (AtkComponentNode*)node; - - var popped = false; - var isVisible = (node->Flags & 0x10) == 0x10; - - if (isVisible) - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); - - var componentInfo = compNode->Component->UldManager; - - var childCount = componentInfo.NodeListCount; - - var objectInfo = (AtkUldComponentInfo*)componentInfo.Objects; - if (ImGui.TreeNode($"{treePrefix}{objectInfo->ComponentType} Component Node (ptr = {(long)node:X}, component ptr = {(long)compNode->Component:X}) child count = {childCount} ###{(long)node}")) - { - if (ImGui.IsItemHovered()) - this.DrawOutline(node); - - if (isVisible) - { - ImGui.PopStyleColor(); - popped = true; - } - - ImGui.Text("Node: "); - ImGui.SameLine(); - ClickToCopyText($"{(ulong)node:X}"); - ImGui.SameLine(); - this.PrintOutObject(*compNode, (ulong)compNode, new List()); - ImGui.Text("Component: "); - ImGui.SameLine(); - ClickToCopyText($"{(ulong)compNode->Component:X}"); - ImGui.SameLine(); - - switch (objectInfo->ComponentType) - { - case ComponentType.Button: this.PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.Slider: this.PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.Window: this.PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.CheckBox: this.PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.GaugeBar: this.PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.RadioButton: this.PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.TextInput: this.PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.Icon: this.PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component, new List()); break; - default: this.PrintOutObject(*compNode->Component, (ulong)compNode->Component, new List()); break; - } - - this.PrintResNode(node); - this.PrintNode(componentInfo.RootNode); - - switch (objectInfo->ComponentType) - { - case ComponentType.TextInput: - var textInputComponent = (AtkComponentTextInput*)compNode->Component; - ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); - ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); - ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText1.StringPtr))}"); - ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText2.StringPtr))}"); - ImGui.Text($"Text3: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText3.StringPtr))}"); - ImGui.Text($"Text4: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText4.StringPtr))}"); - ImGui.Text($"Text5: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText5.StringPtr))}"); - break; - } - - ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); - if (ImGui.TreeNode($"Node List##{(ulong)node:X}")) - { - ImGui.PopStyleColor(); - - for (var i = 0; i < compNode->Component->UldManager.NodeListCount; i++) - { - this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); - } - - ImGui.TreePop(); - } - else - { - ImGui.PopStyleColor(); - } - - ImGui.TreePop(); - } - else if (ImGui.IsItemHovered()) - { - this.DrawOutline(node); - } - - if (isVisible && !popped) - ImGui.PopStyleColor(); - } - - private void PrintResNode(AtkResNode* node) - { - ImGui.Text($"NodeID: {node->NodeID}"); - ImGui.SameLine(); - if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) - { - node->Flags ^= 0x10; - } - - ImGui.SameLine(); - if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) - { - ImGui.SetClipboardText($"{(ulong)node:X}"); - } - - ImGui.Text( - $"X: {node->X} Y: {node->Y} " + - $"ScaleX: {node->ScaleX} ScaleY: {node->ScaleY} " + - $"Rotation: {node->Rotation} " + - $"Width: {node->Width} Height: {node->Height} " + - $"OriginX: {node->OriginX} OriginY: {node->OriginY}"); - ImGui.Text( - $"RGBA: 0x{node->Color.R:X2}{node->Color.G:X2}{node->Color.B:X2}{node->Color.A:X2} " + - $"AddRGB: {node->AddRed} {node->AddGreen} {node->AddBlue} " + - $"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}"); - } - - private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) - { - ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF); - if (!string.IsNullOrEmpty(this.searchInput) && !this.doingSearch) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Always); - } - else if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) - { - ImGui.SetNextItemOpen(false, ImGuiCond.Always); - } - - var treeNode = ImGui.TreeNode($"{this.listNames[index]}##unitList_{index}"); - ImGui.PopStyleColor(); - - ImGui.SameLine(); - ImGui.TextDisabled($"C:{count} {ptr:X}"); - return treeNode; - } - - private void DrawUnitBaseList() - { - var foundSelected = false; - var noResults = true; - var stage = this.getAtkStageSingleton(); - - var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList; - - var searchStr = this.searchInput; - var searching = !string.IsNullOrEmpty(searchStr); - - for (var i = 0; i < UnitListCount; i++) - { - var headerDrawn = false; - - var highlight = this.selectedUnitBase != null && this.selectedInList[i]; - this.selectedInList[i] = false; - var unitManager = &unitManagers[i]; - - var unitBaseArray = &unitManager->AtkUnitEntries; - - var headerOpen = true; - - if (!searching) - { - headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); - headerDrawn = true; - noResults = false; - } - - for (var j = 0; j < unitManager->Count && headerOpen; j++) - { - var unitBase = unitBaseArray[j]; - if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase) - { - this.selectedInList[i] = true; - foundSelected = true; - } - - var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); - if (searching) - { - if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; - } - - noResults = false; - if (!headerDrawn) - { - headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); - headerDrawn = true; - } - - if (headerOpen) - { - var visible = (unitBase->Flags & 0x20) == 0x20; - ImGui.PushStyleColor(ImGuiCol.Text, visible ? 0xFF00FF00 : 0xFF999999); - - if (ImGui.Selectable($"{name}##list{i}-{(ulong)unitBase:X}_{j}", this.selectedUnitBase == unitBase)) - { - this.selectedUnitBase = unitBase; - foundSelected = true; - this.selectedInList[i] = true; - } - - ImGui.PopStyleColor(); - } - } - - if (headerDrawn && headerOpen) - { - ImGui.TreePop(); - } - - if (this.selectedInList[i] == false && this.selectedUnitBase != null) - { - for (var j = 0; j < unitManager->Count; j++) - { - if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue; - this.selectedInList[i] = true; - foundSelected = true; - } - } - } - - if (noResults) - { - ImGui.TextDisabled("No Results"); - } - - if (!foundSelected) - { - this.selectedUnitBase = null; - } - - if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) - { - this.doingSearch = false; - } - else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) - { - this.doingSearch = true; - } - } - - private Vector2 GetNodePosition(AtkResNode* node) - { - var pos = new Vector2(node->X, node->Y); - var par = node->ParentNode; - while (par != null) - { - pos *= new Vector2(par->ScaleX, par->ScaleY); - pos += new Vector2(par->X, par->Y); - par = par->ParentNode; - } - - return pos; - } - - private Vector2 GetNodeScale(AtkResNode* node) - { - if (node == null) return new Vector2(1, 1); - var scale = new Vector2(node->ScaleX, node->ScaleY); - while (node->ParentNode != null) - { - node = node->ParentNode; - scale *= new Vector2(node->ScaleX, node->ScaleY); - } - - return scale; - } - - private bool GetNodeVisible(AtkResNode* node) - { - if (node == null) return false; - while (node != null) - { - if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false; - node = node->ParentNode; - } - - return true; - } - - private void DrawOutline(AtkResNode* node) - { - var position = this.GetNodePosition(node); - var scale = this.GetNodeScale(node); - var size = new Vector2(node->Width, node->Height) * scale; - - var nodeVisible = this.GetNodeVisible(node); - - position += ImGuiHelpers.MainViewport.Pos; - - ImGui.GetForegroundDrawList(ImGuiHelpers.MainViewport).AddRect(position, position + size, nodeVisible ? 0xFF00FF00 : 0xFF0000FF); - } - - private void PrintOutValue(ulong addr, IEnumerable path, Type type, object value) - { - if (type.IsPointer) - { - var val = (Pointer)value; - var unboxed = Pointer.Unbox(val); - if (unboxed != null) - { - var unboxedAddr = (ulong)unboxed; - ClickToCopyText($"{(ulong)unboxed:X}"); - if (this.beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) - { - ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - this.beginModule:X}"); - ImGui.PopStyleColor(); - } - - try - { - var eType = type.GetElementType(); - var ptrObj = Marshal.PtrToStructure(new IntPtr(unboxed), eType); - ImGui.SameLine(); - this.PrintOutObject(ptrObj, (ulong)unboxed, new List(path)); - } - catch - { - // Ignored - } - } - else - { - ImGui.Text("null"); - } - } - else - { - if (!type.IsPrimitive) - { - this.PrintOutObject(value, addr, new List(path)); - } - else - { - ImGui.Text($"{value}"); - } - } - } - - private void PrintOutObject(object obj, ulong addr, List path, bool autoExpand = false) - { - if (this.endModule == 0 && this.beginModule == 0) - { - try - { - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) - { - this.beginModule = (ulong)processModule.BaseAddress.ToInt64(); - this.endModule = this.beginModule + (ulong)processModule.ModuleMemorySize; - } - else - { - this.endModule = 1; - } - } - catch - { - this.endModule = 1; - } - } - - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - } - - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) + ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); + if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) { ImGui.PopStyleColor(); - foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + + for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) { - var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); - if (fixedBuffer != null) - { - ImGui.Text($"fixed"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); - } - else - { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); - } - - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); - ImGui.SameLine(); - - this.PrintOutValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); - } - - foreach (var p in obj.GetType().GetProperties()) - { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); - ImGui.SameLine(); - - this.PrintOutValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); + this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); } ImGui.TreePop(); @@ -729,4 +161,571 @@ namespace Dalamud.Interface.Internal } } } + + private void PrintNode(AtkResNode* node, bool printSiblings = true, string treePrefix = "") + { + if (node == null) + return; + + if ((int)node->Type < 1000) + this.PrintSimpleNode(node, treePrefix); + else + this.PrintComponentNode(node, treePrefix); + + if (printSiblings) + { + var prevNode = node; + while ((prevNode = prevNode->PrevSiblingNode) != null) + this.PrintNode(prevNode, false, "prev "); + + var nextNode = node; + while ((nextNode = nextNode->NextSiblingNode) != null) + this.PrintNode(nextNode, false, "next "); + } + } + + private void PrintSimpleNode(AtkResNode* node, string treePrefix) + { + var popped = false; + var isVisible = (node->Flags & 0x10) == 0x10; + + if (isVisible) + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); + + if (ImGui.TreeNode($"{treePrefix}{node->Type} Node (ptr = {(long)node:X})###{(long)node}")) + { + if (ImGui.IsItemHovered()) + this.DrawOutline(node); + + if (isVisible) + { + ImGui.PopStyleColor(); + popped = true; + } + + ImGui.Text("Node: "); + ImGui.SameLine(); + ClickToCopyText($"{(ulong)node:X}"); + ImGui.SameLine(); + switch (node->Type) + { + case NodeType.Text: this.PrintOutObject(*(AtkTextNode*)node, (ulong)node, new List()); break; + case NodeType.Image: this.PrintOutObject(*(AtkImageNode*)node, (ulong)node, new List()); break; + case NodeType.Collision: this.PrintOutObject(*(AtkCollisionNode*)node, (ulong)node, new List()); break; + case NodeType.NineGrid: this.PrintOutObject(*(AtkNineGridNode*)node, (ulong)node, new List()); break; + case NodeType.Counter: this.PrintOutObject(*(AtkCounterNode*)node, (ulong)node, new List()); break; + default: this.PrintOutObject(*node, (ulong)node, new List()); break; + } + + this.PrintResNode(node); + + if (node->ChildNode != null) + this.PrintNode(node->ChildNode); + + switch (node->Type) + { + case NodeType.Text: + var textNode = (AtkTextNode*)node; + ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}"); + + ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); + + ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); + int b = textNode->AlignmentFontType; + if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) + { + while (b > byte.MaxValue) b -= byte.MaxValue; + while (b < byte.MinValue) b += byte.MaxValue; + textNode->AlignmentFontType = (byte)b; + textNode->AtkResNode.Flags_2 |= 0x1; + } + + ImGui.Text($"Color: #{textNode->TextColor.R:X2}{textNode->TextColor.G:X2}{textNode->TextColor.B:X2}{textNode->TextColor.A:X2}"); + ImGui.SameLine(); + ImGui.Text($"EdgeColor: #{textNode->EdgeColor.R:X2}{textNode->EdgeColor.G:X2}{textNode->EdgeColor.B:X2}{textNode->EdgeColor.A:X2}"); + ImGui.SameLine(); + ImGui.Text($"BGColor: #{textNode->BackgroundColor.R:X2}{textNode->BackgroundColor.G:X2}{textNode->BackgroundColor.B:X2}{textNode->BackgroundColor.A:X2}"); + + ImGui.Text($"TextFlags: {textNode->TextFlags}"); + ImGui.SameLine(); + ImGui.Text($"TextFlags2: {textNode->TextFlags2}"); + + break; + case NodeType.Counter: + var counterNode = (AtkCounterNode*)node; + ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(counterNode->NodeText.StringPtr))}"); + break; + case NodeType.Image: + var imageNode = (AtkImageNode*)node; + if (imageNode->PartsList != null) + { + if (imageNode->PartId > imageNode->PartsList->PartCount) + { + ImGui.Text("part id > part count?"); + } + else + { + var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].UldAsset; + var texType = textureInfo->AtkTexture.TextureType; + ImGui.Text($"texture type: {texType} part_id={imageNode->PartId} part_id_count={imageNode->PartsList->PartCount}"); + if (texType == TextureType.Resource) + { + var texFileNameStdString = &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; + var texString = texFileNameStdString->Length < 16 + ? Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->Buffer) + : Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->BufferPtr); + + ImGui.Text($"texture path: {texString}"); + var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; + + if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) + { + ImGui.Image(new IntPtr(kernelTexture->D3D11ShaderResourceView), new Vector2(kernelTexture->Width, kernelTexture->Height)); + ImGui.TreePop(); + } + } + else if (texType == TextureType.KernelTexture) + { + if (ImGui.TreeNode($"Texture##{(ulong)textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) + { + ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height)); + ImGui.TreePop(); + } + } + } + } + else + { + ImGui.Text("no texture loaded"); + } + + break; + } + + ImGui.TreePop(); + } + else if (ImGui.IsItemHovered()) + { + this.DrawOutline(node); + } + + if (isVisible && !popped) + ImGui.PopStyleColor(); + } + + private void PrintComponentNode(AtkResNode* node, string treePrefix) + { + var compNode = (AtkComponentNode*)node; + + var popped = false; + var isVisible = (node->Flags & 0x10) == 0x10; + + if (isVisible) + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); + + var componentInfo = compNode->Component->UldManager; + + var childCount = componentInfo.NodeListCount; + + var objectInfo = (AtkUldComponentInfo*)componentInfo.Objects; + if (ImGui.TreeNode($"{treePrefix}{objectInfo->ComponentType} Component Node (ptr = {(long)node:X}, component ptr = {(long)compNode->Component:X}) child count = {childCount} ###{(long)node}")) + { + if (ImGui.IsItemHovered()) + this.DrawOutline(node); + + if (isVisible) + { + ImGui.PopStyleColor(); + popped = true; + } + + ImGui.Text("Node: "); + ImGui.SameLine(); + ClickToCopyText($"{(ulong)node:X}"); + ImGui.SameLine(); + this.PrintOutObject(*compNode, (ulong)compNode, new List()); + ImGui.Text("Component: "); + ImGui.SameLine(); + ClickToCopyText($"{(ulong)compNode->Component:X}"); + ImGui.SameLine(); + + switch (objectInfo->ComponentType) + { + case ComponentType.Button: this.PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Slider: this.PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Window: this.PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.CheckBox: this.PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.GaugeBar: this.PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.RadioButton: this.PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.TextInput: this.PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Icon: this.PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component, new List()); break; + default: this.PrintOutObject(*compNode->Component, (ulong)compNode->Component, new List()); break; + } + + this.PrintResNode(node); + this.PrintNode(componentInfo.RootNode); + + switch (objectInfo->ComponentType) + { + case ComponentType.TextInput: + var textInputComponent = (AtkComponentTextInput*)compNode->Component; + ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); + ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); + ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText1.StringPtr))}"); + ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText2.StringPtr))}"); + ImGui.Text($"Text3: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText3.StringPtr))}"); + ImGui.Text($"Text4: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText4.StringPtr))}"); + ImGui.Text($"Text5: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText5.StringPtr))}"); + break; + } + + ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); + if (ImGui.TreeNode($"Node List##{(ulong)node:X}")) + { + ImGui.PopStyleColor(); + + for (var i = 0; i < compNode->Component->UldManager.NodeListCount; i++) + { + this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); + } + + ImGui.TreePop(); + } + else + { + ImGui.PopStyleColor(); + } + + ImGui.TreePop(); + } + else if (ImGui.IsItemHovered()) + { + this.DrawOutline(node); + } + + if (isVisible && !popped) + ImGui.PopStyleColor(); + } + + private void PrintResNode(AtkResNode* node) + { + ImGui.Text($"NodeID: {node->NodeID}"); + ImGui.SameLine(); + if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) + { + node->Flags ^= 0x10; + } + + ImGui.SameLine(); + if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) + { + ImGui.SetClipboardText($"{(ulong)node:X}"); + } + + ImGui.Text( + $"X: {node->X} Y: {node->Y} " + + $"ScaleX: {node->ScaleX} ScaleY: {node->ScaleY} " + + $"Rotation: {node->Rotation} " + + $"Width: {node->Width} Height: {node->Height} " + + $"OriginX: {node->OriginX} OriginY: {node->OriginY}"); + ImGui.Text( + $"RGBA: 0x{node->Color.R:X2}{node->Color.G:X2}{node->Color.B:X2}{node->Color.A:X2} " + + $"AddRGB: {node->AddRed} {node->AddGreen} {node->AddBlue} " + + $"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}"); + } + + private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) + { + ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF); + if (!string.IsNullOrEmpty(this.searchInput) && !this.doingSearch) + { + ImGui.SetNextItemOpen(true, ImGuiCond.Always); + } + else if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) + { + ImGui.SetNextItemOpen(false, ImGuiCond.Always); + } + + var treeNode = ImGui.TreeNode($"{this.listNames[index]}##unitList_{index}"); + ImGui.PopStyleColor(); + + ImGui.SameLine(); + ImGui.TextDisabled($"C:{count} {ptr:X}"); + return treeNode; + } + + private void DrawUnitBaseList() + { + var foundSelected = false; + var noResults = true; + var stage = this.getAtkStageSingleton(); + + var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList; + + var searchStr = this.searchInput; + var searching = !string.IsNullOrEmpty(searchStr); + + for (var i = 0; i < UnitListCount; i++) + { + var headerDrawn = false; + + var highlight = this.selectedUnitBase != null && this.selectedInList[i]; + this.selectedInList[i] = false; + var unitManager = &unitManagers[i]; + + var unitBaseArray = &unitManager->AtkUnitEntries; + + var headerOpen = true; + + if (!searching) + { + headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); + headerDrawn = true; + noResults = false; + } + + for (var j = 0; j < unitManager->Count && headerOpen; j++) + { + var unitBase = unitBaseArray[j]; + if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase) + { + this.selectedInList[i] = true; + foundSelected = true; + } + + var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); + if (searching) + { + if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; + } + + noResults = false; + if (!headerDrawn) + { + headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); + headerDrawn = true; + } + + if (headerOpen) + { + var visible = (unitBase->Flags & 0x20) == 0x20; + ImGui.PushStyleColor(ImGuiCol.Text, visible ? 0xFF00FF00 : 0xFF999999); + + if (ImGui.Selectable($"{name}##list{i}-{(ulong)unitBase:X}_{j}", this.selectedUnitBase == unitBase)) + { + this.selectedUnitBase = unitBase; + foundSelected = true; + this.selectedInList[i] = true; + } + + ImGui.PopStyleColor(); + } + } + + if (headerDrawn && headerOpen) + { + ImGui.TreePop(); + } + + if (this.selectedInList[i] == false && this.selectedUnitBase != null) + { + for (var j = 0; j < unitManager->Count; j++) + { + if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue; + this.selectedInList[i] = true; + foundSelected = true; + } + } + } + + if (noResults) + { + ImGui.TextDisabled("No Results"); + } + + if (!foundSelected) + { + this.selectedUnitBase = null; + } + + if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) + { + this.doingSearch = false; + } + else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) + { + this.doingSearch = true; + } + } + + private Vector2 GetNodePosition(AtkResNode* node) + { + var pos = new Vector2(node->X, node->Y); + var par = node->ParentNode; + while (par != null) + { + pos *= new Vector2(par->ScaleX, par->ScaleY); + pos += new Vector2(par->X, par->Y); + par = par->ParentNode; + } + + return pos; + } + + private Vector2 GetNodeScale(AtkResNode* node) + { + if (node == null) return new Vector2(1, 1); + var scale = new Vector2(node->ScaleX, node->ScaleY); + while (node->ParentNode != null) + { + node = node->ParentNode; + scale *= new Vector2(node->ScaleX, node->ScaleY); + } + + return scale; + } + + private bool GetNodeVisible(AtkResNode* node) + { + if (node == null) return false; + while (node != null) + { + if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false; + node = node->ParentNode; + } + + return true; + } + + private void DrawOutline(AtkResNode* node) + { + var position = this.GetNodePosition(node); + var scale = this.GetNodeScale(node); + var size = new Vector2(node->Width, node->Height) * scale; + + var nodeVisible = this.GetNodeVisible(node); + + position += ImGuiHelpers.MainViewport.Pos; + + ImGui.GetForegroundDrawList(ImGuiHelpers.MainViewport).AddRect(position, position + size, nodeVisible ? 0xFF00FF00 : 0xFF0000FF); + } + + private void PrintOutValue(ulong addr, IEnumerable path, Type type, object value) + { + if (type.IsPointer) + { + var val = (Pointer)value; + var unboxed = Pointer.Unbox(val); + if (unboxed != null) + { + var unboxedAddr = (ulong)unboxed; + ClickToCopyText($"{(ulong)unboxed:X}"); + if (this.beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) + { + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); + ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - this.beginModule:X}"); + ImGui.PopStyleColor(); + } + + try + { + var eType = type.GetElementType(); + var ptrObj = Marshal.PtrToStructure(new IntPtr(unboxed), eType); + ImGui.SameLine(); + this.PrintOutObject(ptrObj, (ulong)unboxed, new List(path)); + } + catch + { + // Ignored + } + } + else + { + ImGui.Text("null"); + } + } + else + { + if (!type.IsPrimitive) + { + this.PrintOutObject(value, addr, new List(path)); + } + else + { + ImGui.Text($"{value}"); + } + } + } + + private void PrintOutObject(object obj, ulong addr, List path, bool autoExpand = false) + { + if (this.endModule == 0 && this.beginModule == 0) + { + try + { + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + this.beginModule = (ulong)processModule.BaseAddress.ToInt64(); + this.endModule = this.beginModule + (ulong)processModule.ModuleMemorySize; + } + else + { + this.endModule = 1; + } + } + catch + { + this.endModule = 1; + } + } + + ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); + if (autoExpand) + { + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + } + + if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) + { + ImGui.PopStyleColor(); + foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + { + var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + if (fixedBuffer != null) + { + ImGui.Text($"fixed"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); + } + else + { + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + } + + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); + ImGui.SameLine(); + + this.PrintOutValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); + } + + foreach (var p in obj.GetType().GetProperties()) + { + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); + ImGui.SameLine(); + + this.PrintOutValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); + } + + ImGui.TreePop(); + } + else + { + ImGui.PopStyleColor(); + } + } } diff --git a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs index fa0a805c3..b6f6f60ed 100644 --- a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs @@ -6,20 +6,20 @@ using Dalamud.Interface.Windowing; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// For major updates, an in-game Changelog window. +/// +internal sealed class ChangelogWindow : Window { /// - /// For major updates, an in-game Changelog window. + /// Whether the latest update warrants a changelog window. /// - internal sealed class ChangelogWindow : Window - { - /// - /// Whether the latest update warrants a changelog window. - /// - public const string WarrantsChangelogForMajorMinor = "6.0."; + public const string WarrantsChangelogForMajorMinor = "6.0."; - private const string ChangeLog = - @"This is the biggest update of the in-game addon to date. + private const string ChangeLog = + @"This is the biggest update of the in-game addon to date. We have redone most of the underlying systems, providing you with a better experience playing and browsing for plugins, including better performance, and developers with a better API and more comfortable development environment. We have also added some new features: @@ -29,100 +29,99 @@ We have also added some new features: If you note any issues or need help, please make sure to ask on our discord server."; - private const string UpdatePluginsInfo = - @"• All of your plugins were disabled automatically, due to this update. This is normal. + private const string UpdatePluginsInfo = + @"• All of your plugins were disabled automatically, due to this update. This is normal. • Open the plugin installer, then click 'update plugins'. Updated plugins should update and then re-enable themselves. => Please keep in mind that not all of your plugins may already be updated for the new version. => If some plugins are displayed with a red cross in the 'Installed Plugins' tab, they may not yet be available."; - private readonly string assemblyVersion = Util.AssemblyVersion; + private readonly string assemblyVersion = Util.AssemblyVersion; - /// - /// Initializes a new instance of the class. - /// - public ChangelogWindow() - : base("What's new in XIVLauncher?") + /// + /// Initializes a new instance of the class. + /// + public ChangelogWindow() + : base("What's new in XIVLauncher?") + { + this.Namespace = "DalamudChangelogWindow"; + + this.Size = new Vector2(885, 463); + this.SizeCondition = ImGuiCond.Appearing; + } + + /// + public override void Draw() + { + ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}."); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("The following changes were introduced:"); + ImGui.TextWrapped(ChangeLog); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.DalamudRed, " !!! ATTENTION !!!"); + + ImGui.TextWrapped(UpdatePluginsInfo); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("Thank you for using our tools!"); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.PushFont(UiBuilder.IconFont); + + if (ImGui.Button(FontAwesomeIcon.Download.ToIconString())) { - this.Namespace = "DalamudChangelogWindow"; - - this.Size = new Vector2(885, 463); - this.SizeCondition = ImGuiCond.Appearing; + Service.Get().OpenPluginInstaller(); } - /// - public override void Draw() + if (ImGui.IsItemHovered()) { - ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}."); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("The following changes were introduced:"); - ImGui.TextWrapped(ChangeLog); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.DalamudRed, " !!! ATTENTION !!!"); - - ImGui.TextWrapped(UpdatePluginsInfo); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("Thank you for using our tools!"); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.PushFont(UiBuilder.IconFont); - - if (ImGui.Button(FontAwesomeIcon.Download.ToIconString())) - { - Service.Get().OpenPluginInstaller(); - } - - if (ImGui.IsItemHovered()) - { - ImGui.PopFont(); - ImGui.SetTooltip("Open Plugin Installer"); - ImGui.PushFont(UiBuilder.IconFont); - } - - ImGui.SameLine(); - - if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) - { - Process.Start("https://discord.gg/3NMcUV5"); - } - - if (ImGui.IsItemHovered()) - { - ImGui.PopFont(); - ImGui.SetTooltip("Join our Discord server"); - ImGui.PushFont(UiBuilder.IconFont); - } - - ImGui.SameLine(); - - if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString())) - { - Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher"); - } - - if (ImGui.IsItemHovered()) - { - ImGui.PopFont(); - ImGui.SetTooltip("See our GitHub repository"); - ImGui.PushFont(UiBuilder.IconFont); - } - ImGui.PopFont(); + ImGui.SetTooltip("Open Plugin Installer"); + ImGui.PushFont(UiBuilder.IconFont); + } - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(20, 0); - ImGui.SameLine(); + ImGui.SameLine(); - if (ImGui.Button("Close")) - { - this.IsOpen = false; - } + if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) + { + Process.Start("https://discord.gg/3NMcUV5"); + } + + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("Join our Discord server"); + ImGui.PushFont(UiBuilder.IconFont); + } + + ImGui.SameLine(); + + if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString())) + { + Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher"); + } + + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("See our GitHub repository"); + ImGui.PushFont(UiBuilder.IconFont); + } + + ImGui.PopFont(); + + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(20, 0); + ImGui.SameLine(); + + if (ImGui.Button("Close")) + { + this.IsOpen = false; } } } diff --git a/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs index aeabf5e5f..8ae10bcd9 100644 --- a/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs @@ -7,25 +7,25 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// Color Demo Window to view custom ImGui colors. +/// +internal sealed class ColorDemoWindow : Window { + private readonly List<(string Name, Vector4 Color)> colors; + /// - /// Color Demo Window to view custom ImGui colors. + /// Initializes a new instance of the class. /// - internal sealed class ColorDemoWindow : Window + public ColorDemoWindow() + : base("Dalamud Colors Demo") { - private readonly List<(string Name, Vector4 Color)> colors; + this.Size = new Vector2(600, 500); + this.SizeCondition = ImGuiCond.FirstUseEver; - /// - /// Initializes a new instance of the class. - /// - public ColorDemoWindow() - : base("Dalamud Colors Demo") - { - this.Size = new Vector2(600, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.colors = new List<(string Name, Vector4 Color)>() + this.colors = new List<(string Name, Vector4 Color)>() { ("DalamudRed", ImGuiColors.DalamudRed), ("DalamudGrey", ImGuiColors.DalamudGrey), @@ -47,20 +47,19 @@ namespace Dalamud.Interface.Internal.Windows ("ParsedPink", ImGuiColors.ParsedPink), ("ParsedGold", ImGuiColors.ParsedGold), }.OrderBy(colorDemo => colorDemo.Name).ToList(); - } + } - /// - public override void Draw() + /// + public override void Draw() + { + ImGui.Text("This is a collection of UI colors you can use in your plugin."); + + ImGui.Separator(); + + foreach (var property in typeof(ImGuiColors).GetProperties(BindingFlags.Public | BindingFlags.Static)) { - ImGui.Text("This is a collection of UI colors you can use in your plugin."); - - ImGui.Separator(); - - foreach (var property in typeof(ImGuiColors).GetProperties(BindingFlags.Public | BindingFlags.Static)) - { - var color = (Vector4)property.GetValue(null); - ImGui.TextColored(color, property.Name); - } + var color = (Vector4)property.GetValue(null); + ImGui.TextColored(color, property.Name); } } } diff --git a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs index a3c1f48d5..6df474bc2 100644 --- a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs @@ -9,154 +9,153 @@ using Dalamud.Interface.Components; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows -{ - /// - /// Component Demo Window to view custom ImGui components. - /// - internal sealed class ComponentDemoWindow : Window - { - private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700); +namespace Dalamud.Interface.Internal.Windows; - private readonly List<(string Name, Action Demo)> componentDemos; - private readonly IReadOnlyList easings = new Easing[] - { +/// +/// Component Demo Window to view custom ImGui components. +/// +internal sealed class ComponentDemoWindow : Window +{ + private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700); + + private readonly List<(string Name, Action Demo)> componentDemos; + private readonly IReadOnlyList easings = new Easing[] + { new InSine(DefaultEasingTime), new OutSine(DefaultEasingTime), new InOutSine(DefaultEasingTime), new InCubic(DefaultEasingTime), new OutCubic(DefaultEasingTime), new InOutCubic(DefaultEasingTime), new InQuint(DefaultEasingTime), new OutQuint(DefaultEasingTime), new InOutQuint(DefaultEasingTime), new InCirc(DefaultEasingTime), new OutCirc(DefaultEasingTime), new InOutCirc(DefaultEasingTime), new InElastic(DefaultEasingTime), new OutElastic(DefaultEasingTime), new InOutElastic(DefaultEasingTime), - }; + }; - private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds; - private Vector4 defaultColor = ImGuiColors.DalamudOrange; + private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds; + private Vector4 defaultColor = ImGuiColors.DalamudOrange; - /// - /// Initializes a new instance of the class. - /// - public ComponentDemoWindow() - : base("Dalamud Components Demo") + /// + /// Initializes a new instance of the class. + /// + public ComponentDemoWindow() + : base("Dalamud Components Demo") + { + this.Size = new Vector2(600, 500); + this.SizeCondition = ImGuiCond.FirstUseEver; + + this.RespectCloseHotkey = false; + + this.componentDemos = new() { - this.Size = new Vector2(600, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; + ("Test", ImGuiComponents.Test), + ("HelpMarker", HelpMarkerDemo), + ("IconButton", IconButtonDemo), + ("TextWithLabel", TextWithLabelDemo), + ("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo), + }; + } - this.RespectCloseHotkey = false; + /// + public override void OnOpen() + { + foreach (var easing in this.easings) + { + easing.Restart(); + } + } - this.componentDemos = new() + /// + public override void OnClose() + { + foreach (var easing in this.easings) + { + easing.Stop(); + } + } + + /// + public override void Draw() + { + ImGui.Text("This is a collection of UI components you can use in your plugin."); + + for (var i = 0; i < this.componentDemos.Count; i++) + { + var componentDemo = this.componentDemos[i]; + + if (ImGui.CollapsingHeader($"{componentDemo.Name}###comp{i}")) { - ("Test", ImGuiComponents.Test), - ("HelpMarker", HelpMarkerDemo), - ("IconButton", IconButtonDemo), - ("TextWithLabel", TextWithLabelDemo), - ("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo), - }; + componentDemo.Demo(); + } } - /// - public override void OnOpen() + if (ImGui.CollapsingHeader("Easing animations")) { - foreach (var easing in this.easings) + this.EasingsDemo(); + } + } + + private static void HelpMarkerDemo() + { + ImGui.Text("Hover over the icon to learn more."); + ImGuiComponents.HelpMarker("help me!"); + } + + private static void IconButtonDemo() + { + ImGui.Text("Click on the icon to use as a button."); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(1, FontAwesomeIcon.Carrot)) + { + ImGui.OpenPopup("IconButtonDemoPopup"); + } + + if (ImGui.BeginPopup("IconButtonDemoPopup")) + { + ImGui.Text("You clicked!"); + ImGui.EndPopup(); + } + } + + private static void TextWithLabelDemo() + { + ImGuiComponents.TextWithLabel("Label", "Hover to see more", "more"); + } + + private void EasingsDemo() + { + ImGui.SliderInt("Speed in MS", ref this.animationTimeMs, 200, 5000); + + foreach (var easing in this.easings) + { + easing.Duration = new TimeSpan(0, 0, 0, 0, this.animationTimeMs); + + if (!easing.IsRunning) + { + easing.Start(); + } + + var cursor = ImGui.GetCursorPos(); + var p1 = new Vector2(cursor.X + 5, cursor.Y); + var p2 = p1 + new Vector2(45, 0); + easing.Point1 = p1; + easing.Point2 = p2; + easing.Update(); + + if (easing.IsDone) { easing.Restart(); } - } - /// - public override void OnClose() - { - foreach (var easing in this.easings) - { - easing.Stop(); - } - } + ImGui.SetCursorPos(easing.EasedPoint); + ImGui.Bullet(); - /// - public override void Draw() - { - ImGui.Text("This is a collection of UI components you can use in your plugin."); - - for (var i = 0; i < this.componentDemos.Count; i++) - { - var componentDemo = this.componentDemos[i]; - - if (ImGui.CollapsingHeader($"{componentDemo.Name}###comp{i}")) - { - componentDemo.Demo(); - } - } - - if (ImGui.CollapsingHeader("Easing animations")) - { - this.EasingsDemo(); - } - } - - private static void HelpMarkerDemo() - { - ImGui.Text("Hover over the icon to learn more."); - ImGuiComponents.HelpMarker("help me!"); - } - - private static void IconButtonDemo() - { - ImGui.Text("Click on the icon to use as a button."); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(1, FontAwesomeIcon.Carrot)) - { - ImGui.OpenPopup("IconButtonDemoPopup"); - } - - if (ImGui.BeginPopup("IconButtonDemoPopup")) - { - ImGui.Text("You clicked!"); - ImGui.EndPopup(); - } - } - - private static void TextWithLabelDemo() - { - ImGuiComponents.TextWithLabel("Label", "Hover to see more", "more"); - } - - private void EasingsDemo() - { - ImGui.SliderInt("Speed in MS", ref this.animationTimeMs, 200, 5000); - - foreach (var easing in this.easings) - { - easing.Duration = new TimeSpan(0, 0, 0, 0, this.animationTimeMs); - - if (!easing.IsRunning) - { - easing.Start(); - } - - var cursor = ImGui.GetCursorPos(); - var p1 = new Vector2(cursor.X + 5, cursor.Y); - var p2 = p1 + new Vector2(45, 0); - easing.Point1 = p1; - easing.Point2 = p2; - easing.Update(); - - if (easing.IsDone) - { - easing.Restart(); - } - - ImGui.SetCursorPos(easing.EasedPoint); - ImGui.Bullet(); - - ImGui.SetCursorPos(cursor + new Vector2(0, 10)); - ImGui.Text($"{easing.GetType().Name} ({easing.Value})"); - ImGuiHelpers.ScaledDummy(5); - } - } - - private void ColorPickerWithPaletteDemo() - { - ImGui.Text("Click on the color button to use the picker."); - ImGui.SameLine(); - this.defaultColor = ImGuiComponents.ColorPickerWithPalette(1, "ColorPickerWithPalette Demo", this.defaultColor); + ImGui.SetCursorPos(cursor + new Vector2(0, 10)); + ImGui.Text($"{easing.GetType().Name} ({easing.Value})"); + ImGuiHelpers.ScaledDummy(5); } } + + private void ColorPickerWithPaletteDemo() + { + ImGui.Text("Click on the color button to use the picker."); + ImGui.SameLine(); + this.defaultColor = ImGuiComponents.ColorPickerWithPalette(1, "ColorPickerWithPalette Demo", this.defaultColor); + } } diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index a34d758f7..d72f11544 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -16,488 +16,487 @@ using ImGuiNET; using Serilog; using Serilog.Events; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// The window that displays the Dalamud log file in-game. +/// +internal class ConsoleWindow : Window, IDisposable { + private readonly List logText = new(); + private readonly object renderLock = new(); + + private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" }; + + private List filteredLogText = new(); + private bool autoScroll; + private bool openAtStartup; + + private bool? lastCmdSuccess; + + private string commandText = string.Empty; + + private string textFilter = string.Empty; + private LogEventLevel? levelFilter = null; + private bool isFiltered = false; + + private int historyPos; + private List history = new(); + /// - /// The window that displays the Dalamud log file in-game. + /// Initializes a new instance of the class. /// - internal class ConsoleWindow : Window, IDisposable + public ConsoleWindow() + : base("Dalamud Console") { - private readonly List logText = new(); - private readonly object renderLock = new(); + var configuration = Service.Get(); - private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" }; + this.autoScroll = configuration.LogAutoScroll; + this.openAtStartup = configuration.LogOpenAtStartup; + SerilogEventSink.Instance.LogLine += this.OnLogLine; - private List filteredLogText = new(); - private bool autoScroll; - private bool openAtStartup; + this.Size = new Vector2(500, 400); + this.SizeCondition = ImGuiCond.FirstUseEver; - private bool? lastCmdSuccess; + this.RespectCloseHotkey = false; + } - private string commandText = string.Empty; + private List LogEntries => this.isFiltered ? this.filteredLogText : this.logText; - private string textFilter = string.Empty; - private LogEventLevel? levelFilter = null; - private bool isFiltered = false; + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + SerilogEventSink.Instance.LogLine -= this.OnLogLine; + } - private int historyPos; - private List history = new(); - - /// - /// Initializes a new instance of the class. - /// - public ConsoleWindow() - : base("Dalamud Console") + /// + /// Clear the window of all log entries. + /// + public void Clear() + { + lock (this.renderLock) { - var configuration = Service.Get(); - - this.autoScroll = configuration.LogAutoScroll; - this.openAtStartup = configuration.LogOpenAtStartup; - SerilogEventSink.Instance.LogLine += this.OnLogLine; - - this.Size = new Vector2(500, 400); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.RespectCloseHotkey = false; + this.logText.Clear(); + this.filteredLogText.Clear(); } + } - private List LogEntries => this.isFiltered ? this.filteredLogText : this.logText; - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() + /// + /// Add a single log line to the display. + /// + /// The line to add. + /// The level of the event. + /// The of the event. + public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset) + { + if (line.IndexOfAny(new[] { '\n', '\r' }) != -1) { - SerilogEventSink.Instance.LogLine -= this.OnLogLine; - } + var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); - /// - /// Clear the window of all log entries. - /// - public void Clear() - { - lock (this.renderLock) + this.AddAndFilter(subLines[0], level, offset, false); + + for (var i = 1; i < subLines.Length; i++) { - this.logText.Clear(); - this.filteredLogText.Clear(); + this.AddAndFilter(subLines[i], level, offset, true); } } - - /// - /// Add a single log line to the display. - /// - /// The line to add. - /// The level of the event. - /// The of the event. - public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset) + else { - if (line.IndexOfAny(new[] { '\n', '\r' }) != -1) + this.AddAndFilter(line, level, offset, false); + } + } + + /// + public override void Draw() + { + // Options menu + if (ImGui.BeginPopup("Options")) + { + var dalamud = Service.Get(); + var configuration = Service.Get(); + + if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) { - var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); + configuration.LogAutoScroll = this.autoScroll; + configuration.Save(); + } - this.AddAndFilter(subLines[0], level, offset, false); + if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) + { + configuration.LogOpenAtStartup = this.openAtStartup; + configuration.Save(); + } - for (var i = 1; i < subLines.Length; i++) + var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel; + if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast().Select(x => x.ToString()).ToArray(), 6)) + { + dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel; + configuration.LogLevel = (LogEventLevel)prevLevel; + configuration.Save(); + } + + ImGui.EndPopup(); + } + + // Filter menu + if (ImGui.BeginPopup("Filters")) + { + ImGui.Checkbox("Enabled", ref this.isFiltered); + + if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + this.Refilter(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm."); + + var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0; + if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7)) + { + this.levelFilter = (LogEventLevel)(filterVal - 1); + this.Refilter(); + } + + ImGui.EndPopup(); + } + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) + ImGui.OpenPopup("Options"); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Options"); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Search)) + ImGui.OpenPopup("Filters"); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Filters"); + + ImGui.SameLine(); + var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Clear Log"); + + ImGui.SameLine(); + var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Copy Log"); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull)) + Process.GetCurrentProcess().Kill(); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Kill game"); + + ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar); + + if (clear) + { + this.Clear(); + } + + if (copy) + { + ImGui.LogToClipboard(); + } + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + + ImGuiListClipperPtr clipper; + unsafe + { + clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + } + + ImGui.PushFont(InterfaceManager.MonoFont); + + var childPos = ImGui.GetWindowPos(); + var childDrawList = ImGui.GetWindowDrawList(); + var childSize = ImGui.GetWindowSize(); + + var cursorDiv = ImGuiHelpers.GlobalScale * 92; + var cursorLogLevel = ImGuiHelpers.GlobalScale * 100; + var cursorLogLine = ImGuiHelpers.GlobalScale * 135; + + lock (this.renderLock) + { + clipper.Begin(this.LogEntries.Count); + while (clipper.Step()) + { + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - this.AddAndFilter(subLines[i], level, offset, true); + var line = this.LogEntries[i]; + + if (!line.IsMultiline) + ImGui.Separator(); + + ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level)); + ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level)); + ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level)); + + ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns); + ImGui.SameLine(); + + ImGui.PopStyleColor(3); + + if (!line.IsMultiline) + { + ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff")); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursorDiv); + ImGui.TextUnformatted("|"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursorLogLevel); + ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level)); + ImGui.SameLine(); + } + + ImGui.SetCursorPosX(cursorLogLine); + ImGui.TextUnformatted(line.Line); } } + + clipper.End(); + } + + ImGui.PopFont(); + + ImGui.PopStyleVar(); + + if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) + { + ImGui.SetScrollHereY(1.0f); + } + + // Draw dividing line + var offset = ImGuiHelpers.GlobalScale * 127; + childDrawList.AddLine(new Vector2(childPos.X + offset, childPos.Y), new Vector2(childPos.X + offset, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f); + + ImGui.EndChild(); + + var hadColor = false; + if (this.lastCmdSuccess.HasValue) + { + hadColor = true; + if (this.lastCmdSuccess.Value) + { + ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f)); + } else { - this.AddAndFilter(line, level, offset, false); + ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f)); } } - /// - public override void Draw() + ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80); + + var getFocus = false; + unsafe { - // Options menu - if (ImGui.BeginPopup("Options")) - { - var dalamud = Service.Get(); - var configuration = Service.Get(); - - if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) - { - configuration.LogAutoScroll = this.autoScroll; - configuration.Save(); - } - - if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) - { - configuration.LogOpenAtStartup = this.openAtStartup; - configuration.Save(); - } - - var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel; - if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast().Select(x => x.ToString()).ToArray(), 6)) - { - dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel; - configuration.LogLevel = (LogEventLevel)prevLevel; - configuration.Save(); - } - - ImGui.EndPopup(); - } - - // Filter menu - if (ImGui.BeginPopup("Filters")) - { - ImGui.Checkbox("Enabled", ref this.isFiltered); - - if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue)) - { - this.Refilter(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm."); - - var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0; - if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7)) - { - this.levelFilter = (LogEventLevel)(filterVal - 1); - this.Refilter(); - } - - ImGui.EndPopup(); - } - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) - ImGui.OpenPopup("Options"); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Options"); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Search)) - ImGui.OpenPopup("Filters"); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Filters"); - - ImGui.SameLine(); - var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Clear Log"); - - ImGui.SameLine(); - var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Copy Log"); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull)) - Process.GetCurrentProcess().Kill(); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Kill game"); - - ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar); - - if (clear) - { - this.Clear(); - } - - if (copy) - { - ImGui.LogToClipboard(); - } - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - - ImGuiListClipperPtr clipper; - unsafe - { - clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - } - - ImGui.PushFont(InterfaceManager.MonoFont); - - var childPos = ImGui.GetWindowPos(); - var childDrawList = ImGui.GetWindowDrawList(); - var childSize = ImGui.GetWindowSize(); - - var cursorDiv = ImGuiHelpers.GlobalScale * 92; - var cursorLogLevel = ImGuiHelpers.GlobalScale * 100; - var cursorLogLine = ImGuiHelpers.GlobalScale * 135; - - lock (this.renderLock) - { - clipper.Begin(this.LogEntries.Count); - while (clipper.Step()) - { - for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - { - var line = this.LogEntries[i]; - - if (!line.IsMultiline) - ImGui.Separator(); - - ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level)); - ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level)); - ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level)); - - ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns); - ImGui.SameLine(); - - ImGui.PopStyleColor(3); - - if (!line.IsMultiline) - { - ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff")); - ImGui.SameLine(); - ImGui.SetCursorPosX(cursorDiv); - ImGui.TextUnformatted("|"); - ImGui.SameLine(); - ImGui.SetCursorPosX(cursorLogLevel); - ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level)); - ImGui.SameLine(); - } - - ImGui.SetCursorPosX(cursorLogLine); - ImGui.TextUnformatted(line.Line); - } - } - - clipper.End(); - } - - ImGui.PopFont(); - - ImGui.PopStyleVar(); - - if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) - { - ImGui.SetScrollHereY(1.0f); - } - - // Draw dividing line - var offset = ImGuiHelpers.GlobalScale * 127; - childDrawList.AddLine(new Vector2(childPos.X + offset, childPos.Y), new Vector2(childPos.X + offset, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f); - - ImGui.EndChild(); - - var hadColor = false; - if (this.lastCmdSuccess.HasValue) - { - hadColor = true; - if (this.lastCmdSuccess.Value) - { - ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f)); - } - else - { - ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f)); - } - } - - ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80); - - var getFocus = false; - unsafe - { - if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback)) - { - this.ProcessCommand(); - getFocus = true; - } - - ImGui.SameLine(); - } - - ImGui.SetItemDefaultFocus(); - if (getFocus) - ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget - - if (hadColor) - ImGui.PopStyleColor(); - - if (ImGui.Button("Send")) + if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback)) { this.ProcessCommand(); - } - } - - private void ProcessCommand() - { - try - { - this.historyPos = -1; - for (var i = this.history.Count - 1; i >= 0; i--) - { - if (this.history[i] == this.commandText) - { - this.history.RemoveAt(i); - break; - } - } - - this.history.Add(this.commandText); - - if (this.commandText == "clear" || this.commandText == "cls") - { - this.Clear(); - return; - } - - this.lastCmdSuccess = Service.Get().ProcessCommand("/" + this.commandText); - this.commandText = string.Empty; - - // TODO: Force scroll to bottom - } - catch (Exception ex) - { - Log.Error(ex, "Error during command dispatch"); - this.lastCmdSuccess = false; - } - } - - private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) - { - var ptr = new ImGuiInputTextCallbackDataPtr(data); - - switch (data->EventFlag) - { - case ImGuiInputTextFlags.CallbackCompletion: - var textBytes = new byte[data->BufTextLen]; - Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen); - var text = Encoding.UTF8.GetString(textBytes); - - var words = text.Split(); - - // We can't do any completion for parameters at the moment since it just calls into CommandHandler - if (words.Length > 1) - return 0; - - // TODO: Improve this, add partial completion - // https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484 - var candidates = Service.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList(); - if (candidates.Count > 0) - { - ptr.DeleteChars(0, ptr.BufTextLen); - ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty)); - } - - break; - case ImGuiInputTextFlags.CallbackHistory: - var prevPos = this.historyPos; - - if (ptr.EventKey == ImGuiKey.UpArrow) - { - if (this.historyPos == -1) - this.historyPos = this.history.Count - 1; - else if (this.historyPos > 0) - this.historyPos--; - } - else if (data->EventKey == ImGuiKey.DownArrow) - { - if (this.historyPos != -1) - { - if (++this.historyPos >= this.history.Count) - { - this.historyPos = -1; - } - } - } - - if (prevPos != this.historyPos) - { - var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty; - - ptr.DeleteChars(0, ptr.BufTextLen); - ptr.InsertChars(0, historyStr); - } - - break; + getFocus = true; } - return 0; + ImGui.SameLine(); } - private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline) + ImGui.SetItemDefaultFocus(); + if (getFocus) + ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget + + if (hadColor) + ImGui.PopStyleColor(); + + if (ImGui.Button("Send")) { - if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:")) - return; - - var entry = new LogEntry - { - IsMultiline = isMultiline, - Level = level, - Line = line, - TimeStamp = offset, - }; - - this.logText.Add(entry); - - if (!this.isFiltered) - return; - - if (this.IsFilterApplicable(entry)) - this.filteredLogText.Add(entry); - } - - private bool IsFilterApplicable(LogEntry entry) - { - if (this.levelFilter.HasValue) - { - return entry.Level == this.levelFilter.Value; - } - - if (!string.IsNullOrEmpty(this.textFilter)) - return entry.Line.Contains(this.textFilter); - - return true; - } - - private void Refilter() - { - lock (this.renderLock) - { - this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList(); - } - } - - private string GetTextForLogEventLevel(LogEventLevel level) => level switch - { - LogEventLevel.Error => "ERR", - LogEventLevel.Verbose => "VRB", - LogEventLevel.Debug => "DBG", - LogEventLevel.Information => "INF", - LogEventLevel.Warning => "WRN", - LogEventLevel.Fatal => "FTL", - _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), - }; - - private uint GetColorForLogEventLevel(LogEventLevel level) => level switch - { - LogEventLevel.Error => 0x800000EE, - LogEventLevel.Verbose => 0x00000000, - LogEventLevel.Debug => 0x00000000, - LogEventLevel.Information => 0x00000000, - LogEventLevel.Warning => 0x8A0070EE, - LogEventLevel.Fatal => 0xFF00000A, - _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), - }; - - private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset, Exception? Exception) logEvent) - { - this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset); - } - - private class LogEntry - { - public string Line { get; set; } - - public LogEventLevel Level { get; set; } - - public DateTimeOffset TimeStamp { get; set; } - - public bool IsMultiline { get; set; } + this.ProcessCommand(); } } + + private void ProcessCommand() + { + try + { + this.historyPos = -1; + for (var i = this.history.Count - 1; i >= 0; i--) + { + if (this.history[i] == this.commandText) + { + this.history.RemoveAt(i); + break; + } + } + + this.history.Add(this.commandText); + + if (this.commandText == "clear" || this.commandText == "cls") + { + this.Clear(); + return; + } + + this.lastCmdSuccess = Service.Get().ProcessCommand("/" + this.commandText); + this.commandText = string.Empty; + + // TODO: Force scroll to bottom + } + catch (Exception ex) + { + Log.Error(ex, "Error during command dispatch"); + this.lastCmdSuccess = false; + } + } + + private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) + { + var ptr = new ImGuiInputTextCallbackDataPtr(data); + + switch (data->EventFlag) + { + case ImGuiInputTextFlags.CallbackCompletion: + var textBytes = new byte[data->BufTextLen]; + Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen); + var text = Encoding.UTF8.GetString(textBytes); + + var words = text.Split(); + + // We can't do any completion for parameters at the moment since it just calls into CommandHandler + if (words.Length > 1) + return 0; + + // TODO: Improve this, add partial completion + // https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484 + var candidates = Service.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList(); + if (candidates.Count > 0) + { + ptr.DeleteChars(0, ptr.BufTextLen); + ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty)); + } + + break; + case ImGuiInputTextFlags.CallbackHistory: + var prevPos = this.historyPos; + + if (ptr.EventKey == ImGuiKey.UpArrow) + { + if (this.historyPos == -1) + this.historyPos = this.history.Count - 1; + else if (this.historyPos > 0) + this.historyPos--; + } + else if (data->EventKey == ImGuiKey.DownArrow) + { + if (this.historyPos != -1) + { + if (++this.historyPos >= this.history.Count) + { + this.historyPos = -1; + } + } + } + + if (prevPos != this.historyPos) + { + var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty; + + ptr.DeleteChars(0, ptr.BufTextLen); + ptr.InsertChars(0, historyStr); + } + + break; + } + + return 0; + } + + private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline) + { + if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:")) + return; + + var entry = new LogEntry + { + IsMultiline = isMultiline, + Level = level, + Line = line, + TimeStamp = offset, + }; + + this.logText.Add(entry); + + if (!this.isFiltered) + return; + + if (this.IsFilterApplicable(entry)) + this.filteredLogText.Add(entry); + } + + private bool IsFilterApplicable(LogEntry entry) + { + if (this.levelFilter.HasValue) + { + return entry.Level == this.levelFilter.Value; + } + + if (!string.IsNullOrEmpty(this.textFilter)) + return entry.Line.Contains(this.textFilter); + + return true; + } + + private void Refilter() + { + lock (this.renderLock) + { + this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList(); + } + } + + private string GetTextForLogEventLevel(LogEventLevel level) => level switch + { + LogEventLevel.Error => "ERR", + LogEventLevel.Verbose => "VRB", + LogEventLevel.Debug => "DBG", + LogEventLevel.Information => "INF", + LogEventLevel.Warning => "WRN", + LogEventLevel.Fatal => "FTL", + _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), + }; + + private uint GetColorForLogEventLevel(LogEventLevel level) => level switch + { + LogEventLevel.Error => 0x800000EE, + LogEventLevel.Verbose => 0x00000000, + LogEventLevel.Debug => 0x00000000, + LogEventLevel.Information => 0x00000000, + LogEventLevel.Warning => 0x8A0070EE, + LogEventLevel.Fatal => 0xFF00000A, + _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), + }; + + private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset, Exception? Exception) logEvent) + { + this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset); + } + + private class LogEntry + { + public string Line { get; set; } + + public LogEventLevel Level { get; set; } + + public DateTimeOffset TimeStamp { get; set; } + + public bool IsMultiline { get; set; } + } } diff --git a/Dalamud/Interface/Internal/Windows/CreditsWindow.cs b/Dalamud/Interface/Internal/Windows/CreditsWindow.cs index 7fd341048..f80357323 100644 --- a/Dalamud/Interface/Internal/Windows/CreditsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/CreditsWindow.cs @@ -11,15 +11,15 @@ using Dalamud.Plugin.Internal; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// A window documenting contributors to the project. +/// +internal class CreditsWindow : Window, IDisposable { - /// - /// A window documenting contributors to the project. - /// - internal class CreditsWindow : Window, IDisposable - { - private const float CreditFPS = 60.0f; - private const string CreditsTextTempl = @" + private const float CreditFPS = 60.0f; + private const string CreditsTextTempl = @" Dalamud A FFXIV Plugin Framework Version D{0} @@ -122,121 +122,120 @@ Contribute at: https://github.com/goatsoft/Dalamud Thank you for using XIVLauncher and Dalamud! "; - private readonly TextureWrap logoTexture; - private readonly Stopwatch creditsThrottler; + private readonly TextureWrap logoTexture; + private readonly Stopwatch creditsThrottler; - private string creditsText; + private string creditsText; - /// - /// Initializes a new instance of the class. - /// - public CreditsWindow() - : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true) + /// + /// Initializes a new instance of the class. + /// + public CreditsWindow() + : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true) + { + var dalamud = Service.Get(); + var interfaceManager = Service.Get(); + + this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); + this.creditsThrottler = new(); + + this.Size = new Vector2(500, 400); + this.SizeCondition = ImGuiCond.Always; + + this.PositionCondition = ImGuiCond.Always; + + this.BgAlpha = 0.8f; + } + + /// + public override void OnOpen() + { + var pluginCredits = Service.Get().InstalledPlugins + .Where(plugin => plugin.Manifest != null) + .Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n") + .Aggregate(string.Empty, (current, next) => $"{current}{next}"); + + this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits); + + Service.Get().SetBgm(132); + this.creditsThrottler.Restart(); + } + + /// + public override void OnClose() + { + this.creditsThrottler.Reset(); + Service.Get().SetBgm(9999); + } + + /// + public override void PreDraw() + { + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); + + base.PreDraw(); + } + + /// + public override void PostDraw() + { + ImGui.PopStyleVar(); + + base.PostDraw(); + } + + /// + public override void Draw() + { + var screenSize = ImGui.GetMainViewport().Size; + var windowSize = ImGui.GetWindowSize(); + + this.Position = (screenSize - windowSize) / 2; + + ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + + ImGuiHelpers.ScaledDummy(0, 340f); + ImGui.Text(string.Empty); + + ImGui.SameLine(150f); + ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(190f, 190f)); + + ImGuiHelpers.ScaledDummy(0, 20f); + + var windowX = ImGui.GetWindowSize().X; + + foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) { - var dalamud = Service.Get(); - var interfaceManager = Service.Get(); + var lineLenX = ImGui.CalcTextSize(creditsLine).X; - this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); - this.creditsThrottler = new(); - - this.Size = new Vector2(500, 400); - this.SizeCondition = ImGuiCond.Always; - - this.PositionCondition = ImGuiCond.Always; - - this.BgAlpha = 0.8f; + ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); + ImGui.SameLine(); + ImGui.TextUnformatted(creditsLine); } - /// - public override void OnOpen() + ImGui.PopStyleVar(); + + if (this.creditsThrottler.Elapsed.TotalMilliseconds > (1000.0f / CreditFPS)) { - var pluginCredits = Service.Get().InstalledPlugins - .Where(plugin => plugin.Manifest != null) - .Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n") - .Aggregate(string.Empty, (current, next) => $"{current}{next}"); + var curY = ImGui.GetScrollY(); + var maxY = ImGui.GetScrollMaxY(); - this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits); - - Service.Get().SetBgm(132); - this.creditsThrottler.Restart(); - } - - /// - public override void OnClose() - { - this.creditsThrottler.Reset(); - Service.Get().SetBgm(9999); - } - - /// - public override void PreDraw() - { - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); - - base.PreDraw(); - } - - /// - public override void PostDraw() - { - ImGui.PopStyleVar(); - - base.PostDraw(); - } - - /// - public override void Draw() - { - var screenSize = ImGui.GetMainViewport().Size; - var windowSize = ImGui.GetWindowSize(); - - this.Position = (screenSize - windowSize) / 2; - - ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - - ImGuiHelpers.ScaledDummy(0, 340f); - ImGui.Text(string.Empty); - - ImGui.SameLine(150f); - ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(190f, 190f)); - - ImGuiHelpers.ScaledDummy(0, 20f); - - var windowX = ImGui.GetWindowSize().X; - - foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) + if (curY < maxY - 1) { - var lineLenX = ImGui.CalcTextSize(creditsLine).X; - - ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); - ImGui.SameLine(); - ImGui.TextUnformatted(creditsLine); + ImGui.SetScrollY(curY + 1); } - - ImGui.PopStyleVar(); - - if (this.creditsThrottler.Elapsed.TotalMilliseconds > (1000.0f / CreditFPS)) - { - var curY = ImGui.GetScrollY(); - var maxY = ImGui.GetScrollMaxY(); - - if (curY < maxY - 1) - { - ImGui.SetScrollY(curY + 1); - } - } - - ImGui.EndChild(); } - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.logoTexture?.Dispose(); - } + ImGui.EndChild(); + } + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.logoTexture?.Dispose(); } } diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index 4128d41d3..561e414bc 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -43,1648 +43,1647 @@ using Newtonsoft.Json; using PInvoke; using Serilog; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// Class responsible for drawing the data/debug window. +/// +internal class DataWindow : Window { + private readonly string[] dataKindNames = Enum.GetNames(typeof(DataKind)).Select(k => k.Replace("_", " ")).ToArray(); + + private bool wasReady; + private string serverOpString; + private DataKind currentKind; + + private bool drawCharas = false; + private float maxCharaDrawDistance = 20; + + private string inputSig = string.Empty; + private IntPtr sigResult = IntPtr.Zero; + + private string inputAddonName = string.Empty; + private int inputAddonIndex; + + private IntPtr findAgentInterfacePtr; + + private bool resolveGameData = false; + private bool resolveObjects = false; + + private UIDebug addonInspector = null; + + private Hook? messageBoxMinHook; + private bool hookUseMinHook = false; + + // IPC + private ICallGateProvider ipcPub; + private ICallGateSubscriber ipcSub; + private string callGateResponse = string.Empty; + + // Toast fields + private string inputTextToast = string.Empty; + private int toastPosition = 0; + private int toastSpeed = 0; + private int questToastPosition = 0; + private bool questToastSound = false; + private int questToastIconId = 0; + private bool questToastCheckmark = false; + + // Fly text fields + private int flyActor; + private FlyTextKind flyKind; + private int flyVal1; + private int flyVal2; + private string flyText1 = string.Empty; + private string flyText2 = string.Empty; + private int flyIcon; + private Vector4 flyColor = new(1, 0, 0, 1); + + // ImGui fields + private string inputTexPath = string.Empty; + private TextureWrap debugTex = null; + private Vector2 inputTexUv0 = Vector2.Zero; + private Vector2 inputTexUv1 = Vector2.One; + private Vector4 inputTintCol = Vector4.One; + private Vector2 inputTexScale = Vector2.Zero; + + private uint copyButtonIndex = 0; + /// - /// Class responsible for drawing the data/debug window. + /// Initializes a new instance of the class. /// - internal class DataWindow : Window + public DataWindow() + : base("Dalamud Data") { - private readonly string[] dataKindNames = Enum.GetNames(typeof(DataKind)).Select(k => k.Replace("_", " ")).ToArray(); + this.Size = new Vector2(500, 500); + this.SizeCondition = ImGuiCond.FirstUseEver; - private bool wasReady; - private string serverOpString; - private DataKind currentKind; + this.RespectCloseHotkey = false; - private bool drawCharas = false; - private float maxCharaDrawDistance = 20; + this.Load(); + } - private string inputSig = string.Empty; - private IntPtr sigResult = IntPtr.Zero; + private delegate int MessageBoxWDelegate( + IntPtr hWnd, + [MarshalAs(UnmanagedType.LPWStr)] string text, + [MarshalAs(UnmanagedType.LPWStr)] string caption, + NativeFunctions.MessageBoxType type); - private string inputAddonName = string.Empty; - private int inputAddonIndex; + private enum DataKind + { + Server_OpCode, + Address, + Object_Table, + Fate_Table, + Font_Test, + Party_List, + Buddy_List, + Plugin_IPC, + Condition, + Gauge, + Command, + Addon, + Addon_Inspector, + AtkArrayData_Browser, + StartInfo, + Target, + Toast, + FlyText, + ImGui, + Tex, + KeyState, + Gamepad, + Configuration, + TaskSched, + Hook, + } - private IntPtr findAgentInterfacePtr; + /// + public override void OnOpen() + { + } - private bool resolveGameData = false; - private bool resolveObjects = false; + /// + public override void OnClose() + { + } - private UIDebug addonInspector = null; + /// + /// Set the DataKind dropdown menu. + /// + /// Data kind name, can be lower and/or without spaces. + public void SetDataKind(string dataKind) + { + if (string.IsNullOrEmpty(dataKind)) + return; - private Hook? messageBoxMinHook; - private bool hookUseMinHook = false; - - // IPC - private ICallGateProvider ipcPub; - private ICallGateSubscriber ipcSub; - private string callGateResponse = string.Empty; - - // Toast fields - private string inputTextToast = string.Empty; - private int toastPosition = 0; - private int toastSpeed = 0; - private int questToastPosition = 0; - private bool questToastSound = false; - private int questToastIconId = 0; - private bool questToastCheckmark = false; - - // Fly text fields - private int flyActor; - private FlyTextKind flyKind; - private int flyVal1; - private int flyVal2; - private string flyText1 = string.Empty; - private string flyText2 = string.Empty; - private int flyIcon; - private Vector4 flyColor = new(1, 0, 0, 1); - - // ImGui fields - private string inputTexPath = string.Empty; - private TextureWrap debugTex = null; - private Vector2 inputTexUv0 = Vector2.Zero; - private Vector2 inputTexUv1 = Vector2.One; - private Vector4 inputTintCol = Vector4.One; - private Vector2 inputTexScale = Vector2.Zero; - - private uint copyButtonIndex = 0; - - /// - /// Initializes a new instance of the class. - /// - public DataWindow() - : base("Dalamud Data") + dataKind = dataKind switch { - this.Size = new Vector2(500, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; + "ai" => "Addon Inspector", + "at" => "Object Table", // Actor Table + "ot" => "Object Table", + _ => dataKind, + }; - this.RespectCloseHotkey = false; + dataKind = dataKind.Replace(" ", string.Empty).ToLower(); + var matched = Enum.GetValues() + .Where(kind => Enum.GetName(kind).Replace("_", string.Empty).ToLower() == dataKind) + .FirstOrDefault(); + + if (matched != default) + { + this.currentKind = matched; + } + else + { + Service.Get().PrintError($"/xldata: Invalid data type {dataKind}"); + } + } + + /// + /// Draw the window via ImGui. + /// + public override void Draw() + { + this.copyButtonIndex = 0; + + // Main window + if (ImGui.Button("Force Reload")) this.Load(); + ImGui.SameLine(); + var copy = ImGui.Button("Copy all"); + ImGui.SameLine(); + + var currentKindIndex = (int)this.currentKind; + if (ImGui.Combo("Data kind", ref currentKindIndex, this.dataKindNames, this.dataKindNames.Length)) + { + this.currentKind = (DataKind)currentKindIndex; } - private delegate int MessageBoxWDelegate( - IntPtr hWnd, - [MarshalAs(UnmanagedType.LPWStr)] string text, - [MarshalAs(UnmanagedType.LPWStr)] string caption, - NativeFunctions.MessageBoxType type); + ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - private enum DataKind + ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar); + + if (copy) + ImGui.LogToClipboard(); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); + + try { - Server_OpCode, - Address, - Object_Table, - Fate_Table, - Font_Test, - Party_List, - Buddy_List, - Plugin_IPC, - Condition, - Gauge, - Command, - Addon, - Addon_Inspector, - AtkArrayData_Browser, - StartInfo, - Target, - Toast, - FlyText, - ImGui, - Tex, - KeyState, - Gamepad, - Configuration, - TaskSched, - Hook, - } - - /// - public override void OnOpen() - { - } - - /// - public override void OnClose() - { - } - - /// - /// Set the DataKind dropdown menu. - /// - /// Data kind name, can be lower and/or without spaces. - public void SetDataKind(string dataKind) - { - if (string.IsNullOrEmpty(dataKind)) - return; - - dataKind = dataKind switch + if (this.wasReady) { - "ai" => "Addon Inspector", - "at" => "Object Table", // Actor Table - "ot" => "Object Table", - _ => dataKind, - }; - - dataKind = dataKind.Replace(" ", string.Empty).ToLower(); - - var matched = Enum.GetValues() - .Where(kind => Enum.GetName(kind).Replace("_", string.Empty).ToLower() == dataKind) - .FirstOrDefault(); - - if (matched != default) - { - this.currentKind = matched; - } - else - { - Service.Get().PrintError($"/xldata: Invalid data type {dataKind}"); - } - } - - /// - /// Draw the window via ImGui. - /// - public override void Draw() - { - this.copyButtonIndex = 0; - - // Main window - if (ImGui.Button("Force Reload")) - this.Load(); - ImGui.SameLine(); - var copy = ImGui.Button("Copy all"); - ImGui.SameLine(); - - var currentKindIndex = (int)this.currentKind; - if (ImGui.Combo("Data kind", ref currentKindIndex, this.dataKindNames, this.dataKindNames.Length)) - { - this.currentKind = (DataKind)currentKindIndex; - } - - ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - - ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar); - - if (copy) - ImGui.LogToClipboard(); - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); - - try - { - if (this.wasReady) + switch (this.currentKind) { - switch (this.currentKind) - { - case DataKind.Server_OpCode: - this.DrawServerOpCode(); - break; - - case DataKind.Address: - this.DrawAddress(); - break; - - case DataKind.Object_Table: - this.DrawObjectTable(); - break; - - case DataKind.Fate_Table: - this.DrawFateTable(); - break; - - case DataKind.Font_Test: - this.DrawFontTest(); - break; - - case DataKind.Party_List: - this.DrawPartyList(); - break; - - case DataKind.Buddy_List: - this.DrawBuddyList(); - break; - - case DataKind.Plugin_IPC: - this.DrawPluginIPC(); - break; - - case DataKind.Condition: - this.DrawCondition(); - break; - - case DataKind.Gauge: - this.DrawGauge(); - break; - - case DataKind.Command: - this.DrawCommand(); - break; - - case DataKind.Addon: - this.DrawAddon(); - break; - - case DataKind.Addon_Inspector: - this.DrawAddonInspector(); - break; - - case DataKind.AtkArrayData_Browser: - this.DrawAtkArrayDataBrowser(); - break; - - case DataKind.StartInfo: - this.DrawStartInfo(); - break; - - case DataKind.Target: - this.DrawTarget(); - break; - - case DataKind.Toast: - this.DrawToast(); - break; - - case DataKind.FlyText: - this.DrawFlyText(); - break; - - case DataKind.ImGui: - this.DrawImGui(); - break; - - case DataKind.Tex: - this.DrawTex(); - break; - - case DataKind.KeyState: - this.DrawKeyState(); - break; - - case DataKind.Gamepad: - this.DrawGamepad(); - break; - - case DataKind.Configuration: - this.DrawConfiguration(); - break; - - case DataKind.TaskSched: - this.DrawTaskSched(); - break; - - case DataKind.Hook: - this.DrawHook(); - break; - } - } - else - { - ImGui.TextUnformatted("Data not ready."); - } - } - catch (Exception ex) - { - ImGui.TextUnformatted(ex.ToString()); - } - - ImGui.PopStyleVar(); - - ImGui.EndChild(); - } - - private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type) - { - Log.Information("[DATAHOOK] {0} {1} {2} {3}", hwnd, text, caption, type); - - var result = this.messageBoxMinHook.Original(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo); - - if (result == (int)User32.MessageBoxResult.IDYES) - { - Marshal.ReadByte(IntPtr.Zero); - } - - return result; - } - - private void DrawServerOpCode() - { - ImGui.TextUnformatted(this.serverOpString); - } - - private void DrawAddress() - { - ImGui.InputText(".text sig", ref this.inputSig, 400); - if (ImGui.Button("Resolve")) - { - try - { - var sigScanner = Service.Get(); - this.sigResult = sigScanner.ScanText(this.inputSig); - } - catch (KeyNotFoundException) - { - this.sigResult = new IntPtr(-1); - } - } - - ImGui.Text($"Result: {this.sigResult.ToInt64():X}"); - ImGui.SameLine(); - if (ImGui.Button($"C{this.copyButtonIndex++}")) - ImGui.SetClipboardText(this.sigResult.ToInt64().ToString("x")); - - foreach (var debugScannedValue in BaseAddressResolver.DebugScannedValues) - { - ImGui.TextUnformatted($"{debugScannedValue.Key}"); - foreach (var valueTuple in debugScannedValue.Value) - { - ImGui.TextUnformatted( - $" {valueTuple.ClassName} - 0x{valueTuple.Address.ToInt64():x}"); - ImGui.SameLine(); - - if (ImGui.Button($"C##copyAddress{this.copyButtonIndex++}")) - ImGui.SetClipboardText(valueTuple.Address.ToInt64().ToString("x")); - } - } - } - - private void DrawObjectTable() - { - var chatGui = Service.Get(); - var clientState = Service.Get(); - var framework = Service.Get(); - var gameGui = Service.Get(); - var objectTable = Service.Get(); - - var stateString = string.Empty; - - if (clientState.LocalPlayer == null) - { - ImGui.TextUnformatted("LocalPlayer null."); - } - else - { - stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; - stateString += $"ObjectTableLen: {objectTable.Length}\n"; - stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n"; - stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; - stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; - stateString += $"LocalCID: {clientState.LocalContentId:X}\n"; - stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n"; - stateString += $"TerritoryType: {clientState.TerritoryType}\n\n"; - - ImGui.TextUnformatted(stateString); - - ImGui.Checkbox("Draw characters on screen", ref this.drawCharas); - ImGui.SliderFloat("Draw Distance", ref this.maxCharaDrawDistance, 2f, 40f); - - for (var i = 0; i < objectTable.Length; i++) - { - var obj = objectTable[i]; - - if (obj == null) - continue; - - this.PrintGameObject(obj, i.ToString()); - - if (this.drawCharas && gameGui.WorldToScreen(obj.Position, out var screenCoords)) - { - // So, while WorldToScreen will return false if the point is off of game client screen, to - // to avoid performance issues, we have to manually determine if creating a window would - // produce a new viewport, and skip rendering it if so - var objectText = $"{obj.Address.ToInt64():X}:{obj.ObjectId:X}[{i}] - {obj.ObjectKind} - {obj.Name}"; - - var screenPos = ImGui.GetMainViewport().Pos; - var screenSize = ImGui.GetMainViewport().Size; - - var windowSize = ImGui.CalcTextSize(objectText); - - // Add some extra safety padding - windowSize.X += ImGui.GetStyle().WindowPadding.X + 10; - windowSize.Y += ImGui.GetStyle().WindowPadding.Y + 10; - - if (screenCoords.X + windowSize.X > screenPos.X + screenSize.X || - screenCoords.Y + windowSize.Y > screenPos.Y + screenSize.Y) - continue; - - if (obj.YalmDistanceX > this.maxCharaDrawDistance) - continue; - - ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y)); - - ImGui.SetNextWindowBgAlpha(Math.Max(1f - (obj.YalmDistanceX / this.maxCharaDrawDistance), 0.2f)); - if (ImGui.Begin( - $"Actor{i}##ActorWindow{i}", - ImGuiWindowFlags.NoDecoration | - ImGuiWindowFlags.AlwaysAutoResize | - ImGuiWindowFlags.NoSavedSettings | - ImGuiWindowFlags.NoMove | - ImGuiWindowFlags.NoMouseInputs | - ImGuiWindowFlags.NoDocking | - ImGuiWindowFlags.NoFocusOnAppearing | - ImGuiWindowFlags.NoNav)) - ImGui.Text(objectText); - ImGui.End(); - } - } - } - } - - private void DrawFateTable() - { - var fateTable = Service.Get(); - var framework = Service.Get(); - - var stateString = string.Empty; - if (fateTable.Length == 0) - { - ImGui.TextUnformatted("No fates or data not ready."); - } - else - { - stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; - stateString += $"FateTableLen: {fateTable.Length}\n"; - - ImGui.TextUnformatted(stateString); - - for (var i = 0; i < fateTable.Length; i++) - { - var fate = fateTable[i]; - if (fate == null) - continue; - - var fateString = $"{fate.Address.ToInt64():X}:[{i}]" + - $" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" + - $" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" + - $" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n"; - - fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" + - $" - Duration: {fate.Duration}" + - $" - State: {fate.State}" + - $" - GameData name: {(this.resolveGameData ? (fate.GameData?.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}"; - - ImGui.TextUnformatted(fateString); - ImGui.SameLine(); - if (ImGui.Button("C")) - { - ImGui.SetClipboardText(fate.Address.ToString("X")); - } - } - } - } - - private void DrawFontTest() - { - var specialChars = string.Empty; - - for (var i = 0xE020; i <= 0xE0DB; i++) - specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n"; - - ImGui.TextUnformatted(specialChars); - - foreach (var fontAwesomeIcon in Enum.GetValues(typeof(FontAwesomeIcon)).Cast()) - { - ImGui.Text(((int)fontAwesomeIcon.ToIconChar()).ToString("X") + " - "); - ImGui.SameLine(); - - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(fontAwesomeIcon.ToIconString()); - ImGui.PopFont(); - } - } - - private void DrawPartyList() - { - var partyList = Service.Get(); - - ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); - - ImGui.Text($"GroupManager: {partyList.GroupManagerAddress.ToInt64():X}"); - ImGui.Text($"GroupList: {partyList.GroupListAddress.ToInt64():X}"); - ImGui.Text($"AllianceList: {partyList.AllianceListAddress.ToInt64():X}"); - - ImGui.Text($"{partyList.Length} Members"); - - for (var i = 0; i < partyList.Length; i++) - { - var member = partyList[i]; - if (member == null) - { - ImGui.Text($"[{i}] was null"); - continue; - } - - ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject.ObjectId}"); - if (this.resolveObjects) - { - var actor = member.GameObject; - if (actor == null) - { - ImGui.Text("Actor was null"); - } - else - { - this.PrintGameObject(actor, "-"); - } - } - } - } - - private void DrawBuddyList() - { - var buddyList = Service.Get(); - - ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); - - ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); - { - var member = buddyList.CompanionBuddy; - if (member == null) - { - ImGui.Text("[Companion] null"); - } - else - { - ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); - if (this.resolveObjects) - { - var gameObject = member.GameObject; - if (gameObject == null) - { - ImGui.Text("GameObject was null"); - } - else - { - this.PrintGameObject(gameObject, "-"); - } - } - } - } - - { - var member = buddyList.PetBuddy; - if (member == null) - { - ImGui.Text("[Pet] null"); - } - else - { - ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); - if (this.resolveObjects) - { - var gameObject = member.GameObject; - if (gameObject == null) - { - ImGui.Text("GameObject was null"); - } - else - { - this.PrintGameObject(gameObject, "-"); - } - } - } - } - - { - var count = buddyList.Length; - if (count == 0) - { - ImGui.Text("[BattleBuddy] None present"); - } - else - { - for (var i = 0; i < count; i++) - { - var member = buddyList[i]; - ImGui.Text($"[BattleBuddy] [{i}] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); - if (this.resolveObjects) - { - var gameObject = member.GameObject; - if (gameObject == null) - { - ImGui.Text("GameObject was null"); - } - else - { - this.PrintGameObject(gameObject, "-"); - } - } - } - } - } - } - - private void DrawPluginIPC() - { - if (this.ipcPub == null) - { - this.ipcPub = new CallGatePubSub("dataDemo1"); - - this.ipcPub.RegisterAction((msg) => - { - Log.Information($"Data action was called: {msg}"); - }); - - this.ipcPub.RegisterFunc((msg) => - { - Log.Information($"Data func was called: {msg}"); - return Guid.NewGuid().ToString(); - }); - } - - if (this.ipcSub == null) - { - this.ipcSub = new CallGatePubSub("dataDemo1"); - this.ipcSub.Subscribe((msg) => - { - Log.Information("PONG1"); - }); - this.ipcSub.Subscribe((msg) => - { - Log.Information("PONG2"); - }); - this.ipcSub.Subscribe((msg) => - { - throw new Exception("PONG3"); - }); - } - - if (ImGui.Button("PING")) - { - this.ipcPub.SendMessage("PING"); - } - - if (ImGui.Button("Action")) - { - this.ipcSub.InvokeAction("button1"); - } - - if (ImGui.Button("Func")) - { - this.callGateResponse = this.ipcSub.InvokeFunc("button2"); - } - - if (!this.callGateResponse.IsNullOrEmpty()) - ImGui.Text($"Response: {this.callGateResponse}"); - } - - private void DrawCondition() - { - var condition = Service.Get(); - -#if DEBUG - ImGui.Text($"ptr: 0x{condition.Address.ToInt64():X}"); -#endif - - ImGui.Text("Current Conditions:"); - ImGui.Separator(); - - var didAny = false; - - for (var i = 0; i < Condition.MaxConditionEntries; i++) - { - var typedCondition = (ConditionFlag)i; - var cond = condition[typedCondition]; - - if (!cond) continue; - - didAny = true; - - ImGui.Text($"ID: {i} Enum: {typedCondition}"); - } - - if (!didAny) - ImGui.Text("None. Talk to a shop NPC or visit a market board to find out more!!!!!!!"); - } - - private void DrawGauge() - { - var clientState = Service.Get(); - var jobGauges = Service.Get(); - - var player = clientState.LocalPlayer; - if (player == null) - { - ImGui.Text("Player is not present"); - return; - } - - var jobID = player.ClassJob.Id; - if (jobID == 19) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.OathGauge)}: {gauge.OathGauge}"); - } - else if (jobID == 20) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Chakra)}: {gauge.Chakra}"); - } - else if (jobID == 21) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.BeastGauge)}: {gauge.BeastGauge}"); - } - else if (jobID == 22) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.BOTDTimer)}: {gauge.BOTDTimer}"); - ImGui.Text($"{nameof(gauge.BOTDState)}: {gauge.BOTDState}"); - ImGui.Text($"{nameof(gauge.EyeCount)}: {gauge.EyeCount}"); - } - else if (jobID == 23) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.SongTimer)}: {gauge.SongTimer}"); - ImGui.Text($"{nameof(gauge.Repertoire)}: {gauge.Repertoire}"); - ImGui.Text($"{nameof(gauge.SoulVoice)}: {gauge.SoulVoice}"); - ImGui.Text($"{nameof(gauge.Song)}: {gauge.Song}"); - } - else if (jobID == 24) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.LilyTimer)}: {gauge.LilyTimer}"); - ImGui.Text($"{nameof(gauge.Lily)}: {gauge.Lily}"); - ImGui.Text($"{nameof(gauge.BloodLily)}: {gauge.BloodLily}"); - } - else if (jobID == 25) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.EnochianTimer)}: {gauge.EnochianTimer}"); - ImGui.Text($"{nameof(gauge.ElementTimeRemaining)}: {gauge.ElementTimeRemaining}"); - ImGui.Text($"{nameof(gauge.PolyglotStacks)}: {gauge.PolyglotStacks}"); - ImGui.Text($"{nameof(gauge.UmbralHearts)}: {gauge.UmbralHearts}"); - ImGui.Text($"{nameof(gauge.UmbralIceStacks)}: {gauge.UmbralIceStacks}"); - ImGui.Text($"{nameof(gauge.AstralFireStacks)}: {gauge.AstralFireStacks}"); - ImGui.Text($"{nameof(gauge.InUmbralIce)}: {gauge.InUmbralIce}"); - ImGui.Text($"{nameof(gauge.InAstralFire)}: {gauge.InAstralFire}"); - ImGui.Text($"{nameof(gauge.IsEnochianActive)}: {gauge.IsEnochianActive}"); - } - else if (jobID == 27) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.TimerRemaining)}: {gauge.TimerRemaining}"); - ImGui.Text($"{nameof(gauge.ReturnSummon)}: {gauge.ReturnSummon}"); - ImGui.Text($"{nameof(gauge.ReturnSummonGlam)}: {gauge.ReturnSummonGlam}"); - ImGui.Text($"{nameof(gauge.AetherFlags)}: {gauge.AetherFlags}"); - ImGui.Text($"{nameof(gauge.IsPhoenixReady)}: {gauge.IsPhoenixReady}"); - ImGui.Text($"{nameof(gauge.IsBahamutReady)}: {gauge.IsBahamutReady}"); - ImGui.Text($"{nameof(gauge.HasAetherflowStacks)}: {gauge.HasAetherflowStacks}"); - } - else if (jobID == 28) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Aetherflow)}: {gauge.Aetherflow}"); - ImGui.Text($"{nameof(gauge.FairyGauge)}: {gauge.FairyGauge}"); - ImGui.Text($"{nameof(gauge.SeraphTimer)}: {gauge.SeraphTimer}"); - ImGui.Text($"{nameof(gauge.DismissedFairy)}: {gauge.DismissedFairy}"); - } - else if (jobID == 30) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.HutonTimer)}: {gauge.HutonTimer}"); - ImGui.Text($"{nameof(gauge.Ninki)}: {gauge.Ninki}"); - ImGui.Text($"{nameof(gauge.HutonManualCasts)}: {gauge.HutonManualCasts}"); - } - else if (jobID == 31) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.OverheatTimeRemaining)}: {gauge.OverheatTimeRemaining}"); - ImGui.Text($"{nameof(gauge.SummonTimeRemaining)}: {gauge.SummonTimeRemaining}"); - ImGui.Text($"{nameof(gauge.Heat)}: {gauge.Heat}"); - ImGui.Text($"{nameof(gauge.Battery)}: {gauge.Battery}"); - ImGui.Text($"{nameof(gauge.LastSummonBatteryPower)}: {gauge.LastSummonBatteryPower}"); - ImGui.Text($"{nameof(gauge.IsOverheated)}: {gauge.IsOverheated}"); - ImGui.Text($"{nameof(gauge.IsRobotActive)}: {gauge.IsRobotActive}"); - } - else if (jobID == 32) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Blood)}: {gauge.Blood}"); - ImGui.Text($"{nameof(gauge.DarksideTimeRemaining)}: {gauge.DarksideTimeRemaining}"); - ImGui.Text($"{nameof(gauge.ShadowTimeRemaining)}: {gauge.ShadowTimeRemaining}"); - ImGui.Text($"{nameof(gauge.HasDarkArts)}: {gauge.HasDarkArts}"); - } - else if (jobID == 33) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.DrawnCard)}: {gauge.DrawnCard}"); - foreach (var seal in Enum.GetValues(typeof(SealType)).Cast()) - { - var sealName = Enum.GetName(typeof(SealType), seal); - ImGui.Text($"{nameof(gauge.ContainsSeal)}({sealName}): {gauge.ContainsSeal(seal)}"); - } - } - else if (jobID == 34) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Kenki)}: {gauge.Kenki}"); - ImGui.Text($"{nameof(gauge.MeditationStacks)}: {gauge.MeditationStacks}"); - ImGui.Text($"{nameof(gauge.Sen)}: {gauge.Sen}"); - ImGui.Text($"{nameof(gauge.HasSetsu)}: {gauge.HasSetsu}"); - ImGui.Text($"{nameof(gauge.HasGetsu)}: {gauge.HasGetsu}"); - ImGui.Text($"{nameof(gauge.HasKa)}: {gauge.HasKa}"); - } - else if (jobID == 35) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.WhiteMana)}: {gauge.WhiteMana}"); - ImGui.Text($"{nameof(gauge.BlackMana)}: {gauge.BlackMana}"); - } - else if (jobID == 37) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Ammo)}: {gauge.Ammo}"); - ImGui.Text($"{nameof(gauge.MaxTimerDuration)}: {gauge.MaxTimerDuration}"); - ImGui.Text($"{nameof(gauge.AmmoComboStep)}: {gauge.AmmoComboStep}"); - } - else if (jobID == 38) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Feathers)}: {gauge.Feathers}"); - ImGui.Text($"{nameof(gauge.Esprit)}: {gauge.Esprit}"); - ImGui.Text($"{nameof(gauge.CompletedSteps)}: {gauge.CompletedSteps}"); - ImGui.Text($"{nameof(gauge.NextStep)}: {gauge.NextStep}"); - ImGui.Text($"{nameof(gauge.IsDancing)}: {gauge.IsDancing}"); - } - else - { - ImGui.Text("No supported gauge exists for this job."); - } - } - - private void DrawCommand() - { - var commandManager = Service.Get(); - - foreach (var command in commandManager.Commands) - { - ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n"); - } - } - - private unsafe void DrawAddon() - { - var gameGui = Service.Get(); - - ImGui.InputText("Addon name", ref this.inputAddonName, 256); - ImGui.InputInt("Addon Index", ref this.inputAddonIndex); - - if (this.inputAddonName.IsNullOrEmpty()) - return; - - var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); - - if (address == IntPtr.Zero) - { - ImGui.Text("Null"); - return; - } - - var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address; - var name = MemoryHelper.ReadStringNullTerminated((IntPtr)addon->Name); - ImGui.TextUnformatted($"{name} - 0x{address.ToInt64():x}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); - - if (ImGui.Button("Find Agent")) - { - this.findAgentInterfacePtr = gameGui.FindAgentInterface(address); - } - - if (this.findAgentInterfacePtr != IntPtr.Zero) - { - ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():x}"); - ImGui.SameLine(); - - if (ImGui.Button("C")) - ImGui.SetClipboardText(this.findAgentInterfacePtr.ToInt64().ToString("x")); - } - } - - private void DrawAddonInspector() - { - this.addonInspector ??= new UIDebug(); - this.addonInspector.Draw(); - } - - private unsafe void DrawAtkArrayDataBrowser() - { - var fontWidth = ImGui.CalcTextSize("A").X; - var fontHeight = ImGui.GetTextLineHeightWithSpacing(); - var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule(); - - if (uiModule == null) - { - ImGui.Text("UIModule unavailable."); - return; - } - - var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; - - if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar")) - { - if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]")) - { - if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; - if (numberArrayData != null) - { - ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9); - ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); - ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); - ImGui.TableHeadersRow(); - for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberIndex}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]}"); - ImGui.TableNextColumn(); - ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}"); - } - - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); - } - - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]")) - { - if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex]; - if (stringArrayData != null) - { - ImGui.Text($"{stringArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringIndex}"); - ImGui.TableNextColumn(); - if (stringArrayData->StringArray[stringIndex] != null) - { - ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}"); - } - else - { - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); - } - - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); - } - } - - private void DrawStartInfo() - { - var startInfo = Service.Get(); - - ImGui.Text(JsonConvert.SerializeObject(startInfo, Formatting.Indented)); - } - - private void DrawTarget() - { - var clientState = Service.Get(); - var targetMgr = Service.Get(); - - if (targetMgr.Target != null) - { - this.PrintGameObject(targetMgr.Target, "CurrentTarget"); - - ImGui.Text("Target"); - Util.ShowObject(targetMgr.Target); - - var tot = targetMgr.Target.TargetObject; - if (tot != null) - { - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("ToT"); - Util.ShowObject(tot); - } - - ImGuiHelpers.ScaledDummy(10); - } - - if (targetMgr.FocusTarget != null) - this.PrintGameObject(targetMgr.FocusTarget, "FocusTarget"); - - if (targetMgr.MouseOverTarget != null) - this.PrintGameObject(targetMgr.MouseOverTarget, "MouseOverTarget"); - - if (targetMgr.PreviousTarget != null) - this.PrintGameObject(targetMgr.PreviousTarget, "PreviousTarget"); - - if (targetMgr.SoftTarget != null) - this.PrintGameObject(targetMgr.SoftTarget, "SoftTarget"); - - if (ImGui.Button("Clear CT")) - targetMgr.ClearTarget(); - - if (ImGui.Button("Clear FT")) - targetMgr.ClearFocusTarget(); - - var localPlayer = clientState.LocalPlayer; - - if (localPlayer != null) - { - if (ImGui.Button("Set CT")) - targetMgr.SetTarget(localPlayer); - - if (ImGui.Button("Set FT")) - targetMgr.SetFocusTarget(localPlayer); - } - else - { - ImGui.Text("LocalPlayer is null."); - } - } - - private void DrawToast() - { - var toastGui = Service.Get(); - - ImGui.InputText("Toast text", ref this.inputTextToast, 200); - - ImGui.Combo("Toast Position", ref this.toastPosition, new[] { "Bottom", "Top", }, 2); - ImGui.Combo("Toast Speed", ref this.toastSpeed, new[] { "Slow", "Fast", }, 2); - ImGui.Combo("Quest Toast Position", ref this.questToastPosition, new[] { "Centre", "Right", "Left" }, 3); - ImGui.Checkbox("Quest Checkmark", ref this.questToastCheckmark); - ImGui.Checkbox("Quest Play Sound", ref this.questToastSound); - ImGui.InputInt("Quest Icon ID", ref this.questToastIconId); - - ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); - - if (ImGui.Button("Show toast")) - { - toastGui.ShowNormal(this.inputTextToast, new ToastOptions - { - Position = (ToastPosition)this.toastPosition, - Speed = (ToastSpeed)this.toastSpeed, - }); - } - - if (ImGui.Button("Show Quest toast")) - { - toastGui.ShowQuest(this.inputTextToast, new QuestToastOptions - { - Position = (QuestToastPosition)this.questToastPosition, - DisplayCheckmark = this.questToastCheckmark, - IconId = (uint)this.questToastIconId, - PlaySound = this.questToastSound, - }); - } - - if (ImGui.Button("Show Error toast")) - { - toastGui.ShowError(this.inputTextToast); - } - } - - private void DrawFlyText() - { - if (ImGui.BeginCombo("Kind", this.flyKind.ToString())) - { - var names = Enum.GetNames(typeof(FlyTextKind)); - for (var i = 0; i < names.Length; i++) - { - if (ImGui.Selectable($"{names[i]} ({i})")) - this.flyKind = (FlyTextKind)i; - } - - ImGui.EndCombo(); - } - - ImGui.InputText("Text1", ref this.flyText1, 200); - ImGui.InputText("Text2", ref this.flyText2, 200); - - ImGui.InputInt("Val1", ref this.flyVal1); - ImGui.InputInt("Val2", ref this.flyVal2); - - ImGui.InputInt("Icon ID", ref this.flyIcon); - ImGui.ColorEdit4("Color", ref this.flyColor); - ImGui.InputInt("Actor Index", ref this.flyActor); - var sendColor = ImGui.ColorConvertFloat4ToU32(this.flyColor); - - if (ImGui.Button("Send")) - { - Service.Get().AddFlyText( - this.flyKind, - unchecked((uint)this.flyActor), - unchecked((uint)this.flyVal1), - unchecked((uint)this.flyVal2), - this.flyText1, - this.flyText2, - sendColor, - unchecked((uint)this.flyIcon)); - } - } - - private void DrawImGui() - { - var interfaceManager = Service.Get(); - var notifications = Service.Get(); - - ImGui.Text("Monitor count: " + ImGui.GetPlatformIO().Monitors.Size); - ImGui.Text("OverrideGameCursor: " + interfaceManager.OverrideGameCursor); - - ImGui.Button("THIS IS A BUTTON###hoverTestButton"); - interfaceManager.OverrideGameCursor = !ImGui.IsItemHovered(); - - ImGui.Separator(); - - ImGui.TextUnformatted($"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms"); - - ImGui.Separator(); - - if (ImGui.Button("Add random notification")) - { - var rand = new Random(); - - var title = rand.Next(0, 5) switch - { - 0 => "This is a toast", - 1 => "Truly, a toast", - 2 => "I am testing this toast", - 3 => "I hope this looks right", - 4 => "Good stuff", - 5 => "Nice", - _ => null, - }; - - var type = rand.Next(0, 4) switch - { - 0 => Notifications.NotificationType.Error, - 1 => Notifications.NotificationType.Warning, - 2 => Notifications.NotificationType.Info, - 3 => Notifications.NotificationType.Success, - 4 => Notifications.NotificationType.None, - _ => Notifications.NotificationType.None, - }; - - var text = "Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla."; - - notifications.AddNotification(text, title, type); - } - } - - private void DrawTex() - { - var dataManager = Service.Get(); - - ImGui.InputText("Tex Path", ref this.inputTexPath, 255); - ImGui.InputFloat2("UV0", ref this.inputTexUv0); - ImGui.InputFloat2("UV1", ref this.inputTexUv1); - ImGui.InputFloat4("Tint", ref this.inputTintCol); - ImGui.InputFloat2("Scale", ref this.inputTexScale); - - if (ImGui.Button("Load Tex")) - { - try - { - this.debugTex = dataManager.GetImGuiTexture(this.inputTexPath); - this.inputTexScale = new Vector2(this.debugTex.Width, this.debugTex.Height); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load tex."); - } - } - - ImGuiHelpers.ScaledDummy(10); - - if (this.debugTex != null) - { - ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); - ImGuiHelpers.ScaledDummy(5); - Util.ShowObject(this.debugTex); - } - } - - private void DrawKeyState() - { - var keyState = Service.Get(); - - ImGui.Columns(4); - - var i = 0; - foreach (var vkCode in keyState.GetValidVirtualKeys()) - { - var code = (int)vkCode; - var value = keyState[code]; - - ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed); - - ImGui.Text($"{vkCode} ({code})"); - - ImGui.PopStyleColor(); - - i++; - if (i % 24 == 0) - ImGui.NextColumn(); - } - - ImGui.Columns(1); - } - - private void DrawGamepad() - { - var gamepadState = Service.Get(); - - static void DrawHelper(string text, uint mask, Func resolve) - { - ImGui.Text($"{text} {mask:X4}"); - ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " + - $"DPadUp {resolve(GamepadButtons.DpadUp)} " + - $"DPadRight {resolve(GamepadButtons.DpadRight)} " + - $"DPadDown {resolve(GamepadButtons.DpadDown)} "); - ImGui.Text($"West {resolve(GamepadButtons.West)} " + - $"North {resolve(GamepadButtons.North)} " + - $"East {resolve(GamepadButtons.East)} " + - $"South {resolve(GamepadButtons.South)} "); - ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " + - $"L2 {resolve(GamepadButtons.L2)} " + - $"R1 {resolve(GamepadButtons.R1)} " + - $"R2 {resolve(GamepadButtons.R2)} "); - ImGui.Text($"Select {resolve(GamepadButtons.Select)} " + - $"Start {resolve(GamepadButtons.Start)} " + - $"L3 {resolve(GamepadButtons.L3)} " + - $"R3 {resolve(GamepadButtons.R3)} "); - } - - ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}"); - -#if DEBUG - if (ImGui.IsItemHovered()) - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - - if (ImGui.IsItemClicked()) - ImGui.SetClipboardText($"0x{gamepadState.GamepadInputAddress.ToInt64():X}"); -#endif - - DrawHelper( - "Buttons Raw", - gamepadState.ButtonsRaw, - gamepadState.Raw); - DrawHelper( - "Buttons Pressed", - gamepadState.ButtonsPressed, - gamepadState.Pressed); - DrawHelper( - "Buttons Repeat", - gamepadState.ButtonsRepeat, - gamepadState.Repeat); - DrawHelper( - "Buttons Released", - gamepadState.ButtonsReleased, - gamepadState.Released); - ImGui.Text($"LeftStickLeft {gamepadState.LeftStickLeft:0.00} " + - $"LeftStickUp {gamepadState.LeftStickUp:0.00} " + - $"LeftStickRight {gamepadState.LeftStickRight:0.00} " + - $"LeftStickDown {gamepadState.LeftStickDown:0.00} "); - ImGui.Text($"RightStickLeft {gamepadState.RightStickLeft:0.00} " + - $"RightStickUp {gamepadState.RightStickUp:0.00} " + - $"RightStickRight {gamepadState.RightStickRight:0.00} " + - $"RightStickDown {gamepadState.RightStickDown:0.00} "); - } - - private void DrawConfiguration() - { - var config = Service.Get(); - Util.ShowObject(config); - } - - private void DrawTaskSched() - { - if (ImGui.Button("Clear list")) - { - TaskTracker.Clear(); - } - - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(10); - ImGui.SameLine(); - - if (ImGui.Button("Short Task.Run")) - { - Task.Run(() => { Thread.Sleep(500); }); - } - - ImGui.SameLine(); - - if (ImGui.Button("Task in task(Delay)")) - { - Task.Run(async () => await this.TestTaskInTaskDelay()); - } - - ImGui.SameLine(); - - if (ImGui.Button("Task in task(Sleep)")) - { - Task.Run(async () => await this.TestTaskInTaskSleep()); - } - - ImGui.SameLine(); - - if (ImGui.Button("Faulting task")) - { - Task.Run(() => - { - Thread.Sleep(200); - - string a = null; - a.Contains("dalamud"); - }); - } - - if (ImGui.Button("Drown in tasks")) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Thread.Sleep(1); - } - }); - } - }); - } - }); - } - }); - } - }); - } - - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(20); - - // Needed to init the task tracker, if we're not on a debug build - var tracker = Service.GetNullable() ?? Service.Set(); - - for (var i = 0; i < TaskTracker.Tasks.Count; i++) - { - var task = TaskTracker.Tasks[i]; - var subTime = DateTime.Now; - if (task.Task == null) - subTime = task.FinishTime; - - switch (task.Status) - { - case TaskStatus.Created: - case TaskStatus.WaitingForActivation: - case TaskStatus.WaitingToRun: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey); + case DataKind.Server_OpCode: + this.DrawServerOpCode(); break; - case TaskStatus.Running: - case TaskStatus.WaitingForChildrenToComplete: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue); + + case DataKind.Address: + this.DrawAddress(); break; - case TaskStatus.RanToCompletion: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen); + + case DataKind.Object_Table: + this.DrawObjectTable(); break; - case TaskStatus.Canceled: - case TaskStatus.Faulted: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed); + + case DataKind.Fate_Table: + this.DrawFateTable(); + break; + + case DataKind.Font_Test: + this.DrawFontTest(); + break; + + case DataKind.Party_List: + this.DrawPartyList(); + break; + + case DataKind.Buddy_List: + this.DrawBuddyList(); + break; + + case DataKind.Plugin_IPC: + this.DrawPluginIPC(); + break; + + case DataKind.Condition: + this.DrawCondition(); + break; + + case DataKind.Gauge: + this.DrawGauge(); + break; + + case DataKind.Command: + this.DrawCommand(); + break; + + case DataKind.Addon: + this.DrawAddon(); + break; + + case DataKind.Addon_Inspector: + this.DrawAddonInspector(); + break; + + case DataKind.AtkArrayData_Browser: + this.DrawAtkArrayDataBrowser(); + break; + + case DataKind.StartInfo: + this.DrawStartInfo(); + break; + + case DataKind.Target: + this.DrawTarget(); + break; + + case DataKind.Toast: + this.DrawToast(); + break; + + case DataKind.FlyText: + this.DrawFlyText(); + break; + + case DataKind.ImGui: + this.DrawImGui(); + break; + + case DataKind.Tex: + this.DrawTex(); + break; + + case DataKind.KeyState: + this.DrawKeyState(); + break; + + case DataKind.Gamepad: + this.DrawGamepad(); + break; + + case DataKind.Configuration: + this.DrawConfiguration(); + break; + + case DataKind.TaskSched: + this.DrawTaskSched(); + break; + + case DataKind.Hook: + this.DrawHook(); break; - default: - throw new ArgumentOutOfRangeException(); } - - if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}")) - { - task.IsBeingViewed = true; - - if (ImGui.Button("CANCEL (May not work)")) - { - try - { - var cancelFunc = - typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance); - cancelFunc.Invoke(task, null); - } - catch (Exception ex) - { - Log.Error(ex, "Could not cancel task."); - } - } - - ImGuiHelpers.ScaledDummy(10); - - ImGui.TextUnformatted(task.StackTrace.ToString()); - - if (task.Exception != null) - { - ImGuiHelpers.ScaledDummy(15); - ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:"); - ImGui.TextUnformatted(task.Exception.ToString()); - } - } - else - { - task.IsBeingViewed = false; - } - - ImGui.PopStyleColor(1); + } + else + { + ImGui.TextUnformatted("Data not ready."); } } + catch (Exception ex) + { + ImGui.TextUnformatted(ex.ToString()); + } - private void DrawHook() + ImGui.PopStyleVar(); + + ImGui.EndChild(); + } + + private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type) + { + Log.Information("[DATAHOOK] {0} {1} {2} {3}", hwnd, text, caption, type); + + var result = this.messageBoxMinHook.Original(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo); + + if (result == (int)User32.MessageBoxResult.IDYES) + { + Marshal.ReadByte(IntPtr.Zero); + } + + return result; + } + + private void DrawServerOpCode() + { + ImGui.TextUnformatted(this.serverOpString); + } + + private void DrawAddress() + { + ImGui.InputText(".text sig", ref this.inputSig, 400); + if (ImGui.Button("Resolve")) { try { - ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook); - - if (ImGui.Button("Create")) - this.messageBoxMinHook = Hook.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook); - - if (ImGui.Button("Enable")) - this.messageBoxMinHook?.Enable(); - - if (ImGui.Button("Disable")) - this.messageBoxMinHook?.Disable(); - - if (ImGui.Button("Call Original")) - this.messageBoxMinHook?.Original(IntPtr.Zero, "Hello from .Original", "Hook Test", NativeFunctions.MessageBoxType.Ok); - - if (ImGui.Button("Dispose")) - { - this.messageBoxMinHook?.Dispose(); - this.messageBoxMinHook = null; - } - - if (ImGui.Button("Test")) - _ = NativeFunctions.MessageBoxW(IntPtr.Zero, "Hi", "Hello", NativeFunctions.MessageBoxType.Ok); - - if (this.messageBoxMinHook != null) - ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled); + var sigScanner = Service.Get(); + this.sigResult = sigScanner.ScanText(this.inputSig); } - catch (Exception ex) + catch (KeyNotFoundException) { - Log.Error(ex, "MinHook error caught"); + this.sigResult = new IntPtr(-1); } } - private async Task TestTaskInTaskDelay() + ImGui.Text($"Result: {this.sigResult.ToInt64():X}"); + ImGui.SameLine(); + if (ImGui.Button($"C{this.copyButtonIndex++}")) + ImGui.SetClipboardText(this.sigResult.ToInt64().ToString("x")); + + foreach (var debugScannedValue in BaseAddressResolver.DebugScannedValues) { - await Task.Delay(5000); - } - -#pragma warning disable 1998 - private async Task TestTaskInTaskSleep() -#pragma warning restore 1998 - { - Thread.Sleep(5000); - } - - private void Load() - { - var dataManager = Service.Get(); - - if (dataManager.IsDataReady) + ImGui.TextUnformatted($"{debugScannedValue.Key}"); + foreach (var valueTuple in debugScannedValue.Value) { - this.serverOpString = JsonConvert.SerializeObject(dataManager.ServerOpCodes, Formatting.Indented); - this.wasReady = true; - } - } + ImGui.TextUnformatted( + $" {valueTuple.ClassName} - 0x{valueTuple.Address.ToInt64():x}"); + ImGui.SameLine(); - private void PrintGameObject(GameObject actor, string tag) - { - var actorString = - $"{actor.Address.ToInt64():X}:{actor.ObjectId:X}[{tag}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation} - Target: {actor.TargetObjectId:X}\n"; - - if (actor is Npc npc) - actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n"; - - if (actor is Character chara) - { - actorString += - $" Level: {chara.Level} ClassJob: {(this.resolveGameData ? chara.ClassJob.GameData.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; - } - - if (actor is PlayerCharacter pc) - { - actorString += - $" HomeWorld: {(this.resolveGameData ? pc.HomeWorld.GameData.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(this.resolveGameData ? pc.CurrentWorld.GameData.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; - } - - ImGui.TextUnformatted(actorString); - ImGui.SameLine(); - if (ImGui.Button($"C##{this.copyButtonIndex++}")) - { - ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X")); + if (ImGui.Button($"C##copyAddress{this.copyButtonIndex++}")) + ImGui.SetClipboardText(valueTuple.Address.ToInt64().ToString("x")); } } } + + private void DrawObjectTable() + { + var chatGui = Service.Get(); + var clientState = Service.Get(); + var framework = Service.Get(); + var gameGui = Service.Get(); + var objectTable = Service.Get(); + + var stateString = string.Empty; + + if (clientState.LocalPlayer == null) + { + ImGui.TextUnformatted("LocalPlayer null."); + } + else + { + stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; + stateString += $"ObjectTableLen: {objectTable.Length}\n"; + stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n"; + stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; + stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; + stateString += $"LocalCID: {clientState.LocalContentId:X}\n"; + stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n"; + stateString += $"TerritoryType: {clientState.TerritoryType}\n\n"; + + ImGui.TextUnformatted(stateString); + + ImGui.Checkbox("Draw characters on screen", ref this.drawCharas); + ImGui.SliderFloat("Draw Distance", ref this.maxCharaDrawDistance, 2f, 40f); + + for (var i = 0; i < objectTable.Length; i++) + { + var obj = objectTable[i]; + + if (obj == null) + continue; + + this.PrintGameObject(obj, i.ToString()); + + if (this.drawCharas && gameGui.WorldToScreen(obj.Position, out var screenCoords)) + { + // So, while WorldToScreen will return false if the point is off of game client screen, to + // to avoid performance issues, we have to manually determine if creating a window would + // produce a new viewport, and skip rendering it if so + var objectText = $"{obj.Address.ToInt64():X}:{obj.ObjectId:X}[{i}] - {obj.ObjectKind} - {obj.Name}"; + + var screenPos = ImGui.GetMainViewport().Pos; + var screenSize = ImGui.GetMainViewport().Size; + + var windowSize = ImGui.CalcTextSize(objectText); + + // Add some extra safety padding + windowSize.X += ImGui.GetStyle().WindowPadding.X + 10; + windowSize.Y += ImGui.GetStyle().WindowPadding.Y + 10; + + if (screenCoords.X + windowSize.X > screenPos.X + screenSize.X || + screenCoords.Y + windowSize.Y > screenPos.Y + screenSize.Y) + continue; + + if (obj.YalmDistanceX > this.maxCharaDrawDistance) + continue; + + ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y)); + + ImGui.SetNextWindowBgAlpha(Math.Max(1f - (obj.YalmDistanceX / this.maxCharaDrawDistance), 0.2f)); + if (ImGui.Begin( + $"Actor{i}##ActorWindow{i}", + ImGuiWindowFlags.NoDecoration | + ImGuiWindowFlags.AlwaysAutoResize | + ImGuiWindowFlags.NoSavedSettings | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoMouseInputs | + ImGuiWindowFlags.NoDocking | + ImGuiWindowFlags.NoFocusOnAppearing | + ImGuiWindowFlags.NoNav)) + ImGui.Text(objectText); + ImGui.End(); + } + } + } + } + + private void DrawFateTable() + { + var fateTable = Service.Get(); + var framework = Service.Get(); + + var stateString = string.Empty; + if (fateTable.Length == 0) + { + ImGui.TextUnformatted("No fates or data not ready."); + } + else + { + stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; + stateString += $"FateTableLen: {fateTable.Length}\n"; + + ImGui.TextUnformatted(stateString); + + for (var i = 0; i < fateTable.Length; i++) + { + var fate = fateTable[i]; + if (fate == null) + continue; + + var fateString = $"{fate.Address.ToInt64():X}:[{i}]" + + $" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" + + $" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" + + $" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n"; + + fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" + + $" - Duration: {fate.Duration}" + + $" - State: {fate.State}" + + $" - GameData name: {(this.resolveGameData ? (fate.GameData?.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}"; + + ImGui.TextUnformatted(fateString); + ImGui.SameLine(); + if (ImGui.Button("C")) + { + ImGui.SetClipboardText(fate.Address.ToString("X")); + } + } + } + } + + private void DrawFontTest() + { + var specialChars = string.Empty; + + for (var i = 0xE020; i <= 0xE0DB; i++) + specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n"; + + ImGui.TextUnformatted(specialChars); + + foreach (var fontAwesomeIcon in Enum.GetValues(typeof(FontAwesomeIcon)).Cast()) + { + ImGui.Text(((int)fontAwesomeIcon.ToIconChar()).ToString("X") + " - "); + ImGui.SameLine(); + + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(fontAwesomeIcon.ToIconString()); + ImGui.PopFont(); + } + } + + private void DrawPartyList() + { + var partyList = Service.Get(); + + ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); + + ImGui.Text($"GroupManager: {partyList.GroupManagerAddress.ToInt64():X}"); + ImGui.Text($"GroupList: {partyList.GroupListAddress.ToInt64():X}"); + ImGui.Text($"AllianceList: {partyList.AllianceListAddress.ToInt64():X}"); + + ImGui.Text($"{partyList.Length} Members"); + + for (var i = 0; i < partyList.Length; i++) + { + var member = partyList[i]; + if (member == null) + { + ImGui.Text($"[{i}] was null"); + continue; + } + + ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject.ObjectId}"); + if (this.resolveObjects) + { + var actor = member.GameObject; + if (actor == null) + { + ImGui.Text("Actor was null"); + } + else + { + this.PrintGameObject(actor, "-"); + } + } + } + } + + private void DrawBuddyList() + { + var buddyList = Service.Get(); + + ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); + + ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); + { + var member = buddyList.CompanionBuddy; + if (member == null) + { + ImGui.Text("[Companion] null"); + } + else + { + ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + if (this.resolveObjects) + { + var gameObject = member.GameObject; + if (gameObject == null) + { + ImGui.Text("GameObject was null"); + } + else + { + this.PrintGameObject(gameObject, "-"); + } + } + } + } + + { + var member = buddyList.PetBuddy; + if (member == null) + { + ImGui.Text("[Pet] null"); + } + else + { + ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + if (this.resolveObjects) + { + var gameObject = member.GameObject; + if (gameObject == null) + { + ImGui.Text("GameObject was null"); + } + else + { + this.PrintGameObject(gameObject, "-"); + } + } + } + } + + { + var count = buddyList.Length; + if (count == 0) + { + ImGui.Text("[BattleBuddy] None present"); + } + else + { + for (var i = 0; i < count; i++) + { + var member = buddyList[i]; + ImGui.Text($"[BattleBuddy] [{i}] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + if (this.resolveObjects) + { + var gameObject = member.GameObject; + if (gameObject == null) + { + ImGui.Text("GameObject was null"); + } + else + { + this.PrintGameObject(gameObject, "-"); + } + } + } + } + } + } + + private void DrawPluginIPC() + { + if (this.ipcPub == null) + { + this.ipcPub = new CallGatePubSub("dataDemo1"); + + this.ipcPub.RegisterAction((msg) => + { + Log.Information($"Data action was called: {msg}"); + }); + + this.ipcPub.RegisterFunc((msg) => + { + Log.Information($"Data func was called: {msg}"); + return Guid.NewGuid().ToString(); + }); + } + + if (this.ipcSub == null) + { + this.ipcSub = new CallGatePubSub("dataDemo1"); + this.ipcSub.Subscribe((msg) => + { + Log.Information("PONG1"); + }); + this.ipcSub.Subscribe((msg) => + { + Log.Information("PONG2"); + }); + this.ipcSub.Subscribe((msg) => + { + throw new Exception("PONG3"); + }); + } + + if (ImGui.Button("PING")) + { + this.ipcPub.SendMessage("PING"); + } + + if (ImGui.Button("Action")) + { + this.ipcSub.InvokeAction("button1"); + } + + if (ImGui.Button("Func")) + { + this.callGateResponse = this.ipcSub.InvokeFunc("button2"); + } + + if (!this.callGateResponse.IsNullOrEmpty()) + ImGui.Text($"Response: {this.callGateResponse}"); + } + + private void DrawCondition() + { + var condition = Service.Get(); + +#if DEBUG + ImGui.Text($"ptr: 0x{condition.Address.ToInt64():X}"); +#endif + + ImGui.Text("Current Conditions:"); + ImGui.Separator(); + + var didAny = false; + + for (var i = 0; i < Condition.MaxConditionEntries; i++) + { + var typedCondition = (ConditionFlag)i; + var cond = condition[typedCondition]; + + if (!cond) continue; + + didAny = true; + + ImGui.Text($"ID: {i} Enum: {typedCondition}"); + } + + if (!didAny) + ImGui.Text("None. Talk to a shop NPC or visit a market board to find out more!!!!!!!"); + } + + private void DrawGauge() + { + var clientState = Service.Get(); + var jobGauges = Service.Get(); + + var player = clientState.LocalPlayer; + if (player == null) + { + ImGui.Text("Player is not present"); + return; + } + + var jobID = player.ClassJob.Id; + if (jobID == 19) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.OathGauge)}: {gauge.OathGauge}"); + } + else if (jobID == 20) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Chakra)}: {gauge.Chakra}"); + } + else if (jobID == 21) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.BeastGauge)}: {gauge.BeastGauge}"); + } + else if (jobID == 22) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.BOTDTimer)}: {gauge.BOTDTimer}"); + ImGui.Text($"{nameof(gauge.BOTDState)}: {gauge.BOTDState}"); + ImGui.Text($"{nameof(gauge.EyeCount)}: {gauge.EyeCount}"); + } + else if (jobID == 23) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.SongTimer)}: {gauge.SongTimer}"); + ImGui.Text($"{nameof(gauge.Repertoire)}: {gauge.Repertoire}"); + ImGui.Text($"{nameof(gauge.SoulVoice)}: {gauge.SoulVoice}"); + ImGui.Text($"{nameof(gauge.Song)}: {gauge.Song}"); + } + else if (jobID == 24) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.LilyTimer)}: {gauge.LilyTimer}"); + ImGui.Text($"{nameof(gauge.Lily)}: {gauge.Lily}"); + ImGui.Text($"{nameof(gauge.BloodLily)}: {gauge.BloodLily}"); + } + else if (jobID == 25) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.EnochianTimer)}: {gauge.EnochianTimer}"); + ImGui.Text($"{nameof(gauge.ElementTimeRemaining)}: {gauge.ElementTimeRemaining}"); + ImGui.Text($"{nameof(gauge.PolyglotStacks)}: {gauge.PolyglotStacks}"); + ImGui.Text($"{nameof(gauge.UmbralHearts)}: {gauge.UmbralHearts}"); + ImGui.Text($"{nameof(gauge.UmbralIceStacks)}: {gauge.UmbralIceStacks}"); + ImGui.Text($"{nameof(gauge.AstralFireStacks)}: {gauge.AstralFireStacks}"); + ImGui.Text($"{nameof(gauge.InUmbralIce)}: {gauge.InUmbralIce}"); + ImGui.Text($"{nameof(gauge.InAstralFire)}: {gauge.InAstralFire}"); + ImGui.Text($"{nameof(gauge.IsEnochianActive)}: {gauge.IsEnochianActive}"); + } + else if (jobID == 27) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.TimerRemaining)}: {gauge.TimerRemaining}"); + ImGui.Text($"{nameof(gauge.ReturnSummon)}: {gauge.ReturnSummon}"); + ImGui.Text($"{nameof(gauge.ReturnSummonGlam)}: {gauge.ReturnSummonGlam}"); + ImGui.Text($"{nameof(gauge.AetherFlags)}: {gauge.AetherFlags}"); + ImGui.Text($"{nameof(gauge.IsPhoenixReady)}: {gauge.IsPhoenixReady}"); + ImGui.Text($"{nameof(gauge.IsBahamutReady)}: {gauge.IsBahamutReady}"); + ImGui.Text($"{nameof(gauge.HasAetherflowStacks)}: {gauge.HasAetherflowStacks}"); + } + else if (jobID == 28) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Aetherflow)}: {gauge.Aetherflow}"); + ImGui.Text($"{nameof(gauge.FairyGauge)}: {gauge.FairyGauge}"); + ImGui.Text($"{nameof(gauge.SeraphTimer)}: {gauge.SeraphTimer}"); + ImGui.Text($"{nameof(gauge.DismissedFairy)}: {gauge.DismissedFairy}"); + } + else if (jobID == 30) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.HutonTimer)}: {gauge.HutonTimer}"); + ImGui.Text($"{nameof(gauge.Ninki)}: {gauge.Ninki}"); + ImGui.Text($"{nameof(gauge.HutonManualCasts)}: {gauge.HutonManualCasts}"); + } + else if (jobID == 31) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.OverheatTimeRemaining)}: {gauge.OverheatTimeRemaining}"); + ImGui.Text($"{nameof(gauge.SummonTimeRemaining)}: {gauge.SummonTimeRemaining}"); + ImGui.Text($"{nameof(gauge.Heat)}: {gauge.Heat}"); + ImGui.Text($"{nameof(gauge.Battery)}: {gauge.Battery}"); + ImGui.Text($"{nameof(gauge.LastSummonBatteryPower)}: {gauge.LastSummonBatteryPower}"); + ImGui.Text($"{nameof(gauge.IsOverheated)}: {gauge.IsOverheated}"); + ImGui.Text($"{nameof(gauge.IsRobotActive)}: {gauge.IsRobotActive}"); + } + else if (jobID == 32) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Blood)}: {gauge.Blood}"); + ImGui.Text($"{nameof(gauge.DarksideTimeRemaining)}: {gauge.DarksideTimeRemaining}"); + ImGui.Text($"{nameof(gauge.ShadowTimeRemaining)}: {gauge.ShadowTimeRemaining}"); + ImGui.Text($"{nameof(gauge.HasDarkArts)}: {gauge.HasDarkArts}"); + } + else if (jobID == 33) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.DrawnCard)}: {gauge.DrawnCard}"); + foreach (var seal in Enum.GetValues(typeof(SealType)).Cast()) + { + var sealName = Enum.GetName(typeof(SealType), seal); + ImGui.Text($"{nameof(gauge.ContainsSeal)}({sealName}): {gauge.ContainsSeal(seal)}"); + } + } + else if (jobID == 34) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Kenki)}: {gauge.Kenki}"); + ImGui.Text($"{nameof(gauge.MeditationStacks)}: {gauge.MeditationStacks}"); + ImGui.Text($"{nameof(gauge.Sen)}: {gauge.Sen}"); + ImGui.Text($"{nameof(gauge.HasSetsu)}: {gauge.HasSetsu}"); + ImGui.Text($"{nameof(gauge.HasGetsu)}: {gauge.HasGetsu}"); + ImGui.Text($"{nameof(gauge.HasKa)}: {gauge.HasKa}"); + } + else if (jobID == 35) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.WhiteMana)}: {gauge.WhiteMana}"); + ImGui.Text($"{nameof(gauge.BlackMana)}: {gauge.BlackMana}"); + } + else if (jobID == 37) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Ammo)}: {gauge.Ammo}"); + ImGui.Text($"{nameof(gauge.MaxTimerDuration)}: {gauge.MaxTimerDuration}"); + ImGui.Text($"{nameof(gauge.AmmoComboStep)}: {gauge.AmmoComboStep}"); + } + else if (jobID == 38) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Feathers)}: {gauge.Feathers}"); + ImGui.Text($"{nameof(gauge.Esprit)}: {gauge.Esprit}"); + ImGui.Text($"{nameof(gauge.CompletedSteps)}: {gauge.CompletedSteps}"); + ImGui.Text($"{nameof(gauge.NextStep)}: {gauge.NextStep}"); + ImGui.Text($"{nameof(gauge.IsDancing)}: {gauge.IsDancing}"); + } + else + { + ImGui.Text("No supported gauge exists for this job."); + } + } + + private void DrawCommand() + { + var commandManager = Service.Get(); + + foreach (var command in commandManager.Commands) + { + ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n"); + } + } + + private unsafe void DrawAddon() + { + var gameGui = Service.Get(); + + ImGui.InputText("Addon name", ref this.inputAddonName, 256); + ImGui.InputInt("Addon Index", ref this.inputAddonIndex); + + if (this.inputAddonName.IsNullOrEmpty()) + return; + + var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); + + if (address == IntPtr.Zero) + { + ImGui.Text("Null"); + return; + } + + var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address; + var name = MemoryHelper.ReadStringNullTerminated((IntPtr)addon->Name); + ImGui.TextUnformatted($"{name} - 0x{address.ToInt64():x}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); + + if (ImGui.Button("Find Agent")) + { + this.findAgentInterfacePtr = gameGui.FindAgentInterface(address); + } + + if (this.findAgentInterfacePtr != IntPtr.Zero) + { + ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():x}"); + ImGui.SameLine(); + + if (ImGui.Button("C")) + ImGui.SetClipboardText(this.findAgentInterfacePtr.ToInt64().ToString("x")); + } + } + + private void DrawAddonInspector() + { + this.addonInspector ??= new UIDebug(); + this.addonInspector.Draw(); + } + + private unsafe void DrawAtkArrayDataBrowser() + { + var fontWidth = ImGui.CalcTextSize("A").X; + var fontHeight = ImGui.GetTextLineHeightWithSpacing(); + var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule(); + + if (uiModule == null) + { + ImGui.Text("UIModule unavailable."); + return; + } + + var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; + + if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar")) + { + if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]")) + { + if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); + ImGui.TableNextColumn(); + var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; + if (numberArrayData != null) + { + ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); + ImGui.TableNextColumn(); + if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + ImGui.NewLine(); + var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); + if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); + ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9); + ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); + ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); + ImGui.TableHeadersRow(); + for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{numberIndex}"); + ImGui.TableNextColumn(); + ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}"); + ImGui.TableNextColumn(); + ImGui.Text($"{numberArrayData->IntArray[numberIndex]}"); + ImGui.TableNextColumn(); + ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}"); + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } + else + { + ImGui.TextDisabled("--"); + ImGui.TableNextColumn(); + ImGui.TextDisabled("--"); + } + } + + ImGui.EndTable(); + } + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]")) + { + if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]"); + ImGui.TableNextColumn(); + var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex]; + if (stringArrayData != null) + { + ImGui.Text($"{stringArrayData->AtkArrayData.Size}"); + ImGui.TableNextColumn(); + if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + ImGui.NewLine(); + var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); + if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); + ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{stringIndex}"); + ImGui.TableNextColumn(); + if (stringArrayData->StringArray[stringIndex] != null) + { + ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}"); + } + else + { + ImGui.TextDisabled("--"); + } + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } + else + { + ImGui.TextDisabled("--"); + ImGui.TableNextColumn(); + ImGui.TextDisabled("--"); + } + } + + ImGui.EndTable(); + } + + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + } + + private void DrawStartInfo() + { + var startInfo = Service.Get(); + + ImGui.Text(JsonConvert.SerializeObject(startInfo, Formatting.Indented)); + } + + private void DrawTarget() + { + var clientState = Service.Get(); + var targetMgr = Service.Get(); + + if (targetMgr.Target != null) + { + this.PrintGameObject(targetMgr.Target, "CurrentTarget"); + + ImGui.Text("Target"); + Util.ShowObject(targetMgr.Target); + + var tot = targetMgr.Target.TargetObject; + if (tot != null) + { + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("ToT"); + Util.ShowObject(tot); + } + + ImGuiHelpers.ScaledDummy(10); + } + + if (targetMgr.FocusTarget != null) + this.PrintGameObject(targetMgr.FocusTarget, "FocusTarget"); + + if (targetMgr.MouseOverTarget != null) + this.PrintGameObject(targetMgr.MouseOverTarget, "MouseOverTarget"); + + if (targetMgr.PreviousTarget != null) + this.PrintGameObject(targetMgr.PreviousTarget, "PreviousTarget"); + + if (targetMgr.SoftTarget != null) + this.PrintGameObject(targetMgr.SoftTarget, "SoftTarget"); + + if (ImGui.Button("Clear CT")) + targetMgr.ClearTarget(); + + if (ImGui.Button("Clear FT")) + targetMgr.ClearFocusTarget(); + + var localPlayer = clientState.LocalPlayer; + + if (localPlayer != null) + { + if (ImGui.Button("Set CT")) + targetMgr.SetTarget(localPlayer); + + if (ImGui.Button("Set FT")) + targetMgr.SetFocusTarget(localPlayer); + } + else + { + ImGui.Text("LocalPlayer is null."); + } + } + + private void DrawToast() + { + var toastGui = Service.Get(); + + ImGui.InputText("Toast text", ref this.inputTextToast, 200); + + ImGui.Combo("Toast Position", ref this.toastPosition, new[] { "Bottom", "Top", }, 2); + ImGui.Combo("Toast Speed", ref this.toastSpeed, new[] { "Slow", "Fast", }, 2); + ImGui.Combo("Quest Toast Position", ref this.questToastPosition, new[] { "Centre", "Right", "Left" }, 3); + ImGui.Checkbox("Quest Checkmark", ref this.questToastCheckmark); + ImGui.Checkbox("Quest Play Sound", ref this.questToastSound); + ImGui.InputInt("Quest Icon ID", ref this.questToastIconId); + + ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); + + if (ImGui.Button("Show toast")) + { + toastGui.ShowNormal(this.inputTextToast, new ToastOptions + { + Position = (ToastPosition)this.toastPosition, + Speed = (ToastSpeed)this.toastSpeed, + }); + } + + if (ImGui.Button("Show Quest toast")) + { + toastGui.ShowQuest(this.inputTextToast, new QuestToastOptions + { + Position = (QuestToastPosition)this.questToastPosition, + DisplayCheckmark = this.questToastCheckmark, + IconId = (uint)this.questToastIconId, + PlaySound = this.questToastSound, + }); + } + + if (ImGui.Button("Show Error toast")) + { + toastGui.ShowError(this.inputTextToast); + } + } + + private void DrawFlyText() + { + if (ImGui.BeginCombo("Kind", this.flyKind.ToString())) + { + var names = Enum.GetNames(typeof(FlyTextKind)); + for (var i = 0; i < names.Length; i++) + { + if (ImGui.Selectable($"{names[i]} ({i})")) + this.flyKind = (FlyTextKind)i; + } + + ImGui.EndCombo(); + } + + ImGui.InputText("Text1", ref this.flyText1, 200); + ImGui.InputText("Text2", ref this.flyText2, 200); + + ImGui.InputInt("Val1", ref this.flyVal1); + ImGui.InputInt("Val2", ref this.flyVal2); + + ImGui.InputInt("Icon ID", ref this.flyIcon); + ImGui.ColorEdit4("Color", ref this.flyColor); + ImGui.InputInt("Actor Index", ref this.flyActor); + var sendColor = ImGui.ColorConvertFloat4ToU32(this.flyColor); + + if (ImGui.Button("Send")) + { + Service.Get().AddFlyText( + this.flyKind, + unchecked((uint)this.flyActor), + unchecked((uint)this.flyVal1), + unchecked((uint)this.flyVal2), + this.flyText1, + this.flyText2, + sendColor, + unchecked((uint)this.flyIcon)); + } + } + + private void DrawImGui() + { + var interfaceManager = Service.Get(); + var notifications = Service.Get(); + + ImGui.Text("Monitor count: " + ImGui.GetPlatformIO().Monitors.Size); + ImGui.Text("OverrideGameCursor: " + interfaceManager.OverrideGameCursor); + + ImGui.Button("THIS IS A BUTTON###hoverTestButton"); + interfaceManager.OverrideGameCursor = !ImGui.IsItemHovered(); + + ImGui.Separator(); + + ImGui.TextUnformatted($"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms"); + + ImGui.Separator(); + + if (ImGui.Button("Add random notification")) + { + var rand = new Random(); + + var title = rand.Next(0, 5) switch + { + 0 => "This is a toast", + 1 => "Truly, a toast", + 2 => "I am testing this toast", + 3 => "I hope this looks right", + 4 => "Good stuff", + 5 => "Nice", + _ => null, + }; + + var type = rand.Next(0, 4) switch + { + 0 => Notifications.NotificationType.Error, + 1 => Notifications.NotificationType.Warning, + 2 => Notifications.NotificationType.Info, + 3 => Notifications.NotificationType.Success, + 4 => Notifications.NotificationType.None, + _ => Notifications.NotificationType.None, + }; + + var text = "Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla."; + + notifications.AddNotification(text, title, type); + } + } + + private void DrawTex() + { + var dataManager = Service.Get(); + + ImGui.InputText("Tex Path", ref this.inputTexPath, 255); + ImGui.InputFloat2("UV0", ref this.inputTexUv0); + ImGui.InputFloat2("UV1", ref this.inputTexUv1); + ImGui.InputFloat4("Tint", ref this.inputTintCol); + ImGui.InputFloat2("Scale", ref this.inputTexScale); + + if (ImGui.Button("Load Tex")) + { + try + { + this.debugTex = dataManager.GetImGuiTexture(this.inputTexPath); + this.inputTexScale = new Vector2(this.debugTex.Width, this.debugTex.Height); + } + catch (Exception ex) + { + Log.Error(ex, "Could not load tex."); + } + } + + ImGuiHelpers.ScaledDummy(10); + + if (this.debugTex != null) + { + ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); + ImGuiHelpers.ScaledDummy(5); + Util.ShowObject(this.debugTex); + } + } + + private void DrawKeyState() + { + var keyState = Service.Get(); + + ImGui.Columns(4); + + var i = 0; + foreach (var vkCode in keyState.GetValidVirtualKeys()) + { + var code = (int)vkCode; + var value = keyState[code]; + + ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed); + + ImGui.Text($"{vkCode} ({code})"); + + ImGui.PopStyleColor(); + + i++; + if (i % 24 == 0) + ImGui.NextColumn(); + } + + ImGui.Columns(1); + } + + private void DrawGamepad() + { + var gamepadState = Service.Get(); + + static void DrawHelper(string text, uint mask, Func resolve) + { + ImGui.Text($"{text} {mask:X4}"); + ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " + + $"DPadUp {resolve(GamepadButtons.DpadUp)} " + + $"DPadRight {resolve(GamepadButtons.DpadRight)} " + + $"DPadDown {resolve(GamepadButtons.DpadDown)} "); + ImGui.Text($"West {resolve(GamepadButtons.West)} " + + $"North {resolve(GamepadButtons.North)} " + + $"East {resolve(GamepadButtons.East)} " + + $"South {resolve(GamepadButtons.South)} "); + ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " + + $"L2 {resolve(GamepadButtons.L2)} " + + $"R1 {resolve(GamepadButtons.R1)} " + + $"R2 {resolve(GamepadButtons.R2)} "); + ImGui.Text($"Select {resolve(GamepadButtons.Select)} " + + $"Start {resolve(GamepadButtons.Start)} " + + $"L3 {resolve(GamepadButtons.L3)} " + + $"R3 {resolve(GamepadButtons.R3)} "); + } + + ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}"); + +#if DEBUG + if (ImGui.IsItemHovered()) + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText($"0x{gamepadState.GamepadInputAddress.ToInt64():X}"); +#endif + + DrawHelper( + "Buttons Raw", + gamepadState.ButtonsRaw, + gamepadState.Raw); + DrawHelper( + "Buttons Pressed", + gamepadState.ButtonsPressed, + gamepadState.Pressed); + DrawHelper( + "Buttons Repeat", + gamepadState.ButtonsRepeat, + gamepadState.Repeat); + DrawHelper( + "Buttons Released", + gamepadState.ButtonsReleased, + gamepadState.Released); + ImGui.Text($"LeftStickLeft {gamepadState.LeftStickLeft:0.00} " + + $"LeftStickUp {gamepadState.LeftStickUp:0.00} " + + $"LeftStickRight {gamepadState.LeftStickRight:0.00} " + + $"LeftStickDown {gamepadState.LeftStickDown:0.00} "); + ImGui.Text($"RightStickLeft {gamepadState.RightStickLeft:0.00} " + + $"RightStickUp {gamepadState.RightStickUp:0.00} " + + $"RightStickRight {gamepadState.RightStickRight:0.00} " + + $"RightStickDown {gamepadState.RightStickDown:0.00} "); + } + + private void DrawConfiguration() + { + var config = Service.Get(); + Util.ShowObject(config); + } + + private void DrawTaskSched() + { + if (ImGui.Button("Clear list")) + { + TaskTracker.Clear(); + } + + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(10); + ImGui.SameLine(); + + if (ImGui.Button("Short Task.Run")) + { + Task.Run(() => { Thread.Sleep(500); }); + } + + ImGui.SameLine(); + + if (ImGui.Button("Task in task(Delay)")) + { + Task.Run(async () => await this.TestTaskInTaskDelay()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Task in task(Sleep)")) + { + Task.Run(async () => await this.TestTaskInTaskSleep()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Faulting task")) + { + Task.Run(() => + { + Thread.Sleep(200); + + string a = null; + a.Contains("dalamud"); + }); + } + + if (ImGui.Button("Drown in tasks")) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Thread.Sleep(1); + } + }); + } + }); + } + }); + } + }); + } + }); + } + + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(20); + + // Needed to init the task tracker, if we're not on a debug build + var tracker = Service.GetNullable() ?? Service.Set(); + + for (var i = 0; i < TaskTracker.Tasks.Count; i++) + { + var task = TaskTracker.Tasks[i]; + var subTime = DateTime.Now; + if (task.Task == null) + subTime = task.FinishTime; + + switch (task.Status) + { + case TaskStatus.Created: + case TaskStatus.WaitingForActivation: + case TaskStatus.WaitingToRun: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey); + break; + case TaskStatus.Running: + case TaskStatus.WaitingForChildrenToComplete: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue); + break; + case TaskStatus.RanToCompletion: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen); + break; + case TaskStatus.Canceled: + case TaskStatus.Faulted: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}")) + { + task.IsBeingViewed = true; + + if (ImGui.Button("CANCEL (May not work)")) + { + try + { + var cancelFunc = + typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance); + cancelFunc.Invoke(task, null); + } + catch (Exception ex) + { + Log.Error(ex, "Could not cancel task."); + } + } + + ImGuiHelpers.ScaledDummy(10); + + ImGui.TextUnformatted(task.StackTrace.ToString()); + + if (task.Exception != null) + { + ImGuiHelpers.ScaledDummy(15); + ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:"); + ImGui.TextUnformatted(task.Exception.ToString()); + } + } + else + { + task.IsBeingViewed = false; + } + + ImGui.PopStyleColor(1); + } + } + + private void DrawHook() + { + try + { + ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook); + + if (ImGui.Button("Create")) + this.messageBoxMinHook = Hook.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook); + + if (ImGui.Button("Enable")) + this.messageBoxMinHook?.Enable(); + + if (ImGui.Button("Disable")) + this.messageBoxMinHook?.Disable(); + + if (ImGui.Button("Call Original")) + this.messageBoxMinHook?.Original(IntPtr.Zero, "Hello from .Original", "Hook Test", NativeFunctions.MessageBoxType.Ok); + + if (ImGui.Button("Dispose")) + { + this.messageBoxMinHook?.Dispose(); + this.messageBoxMinHook = null; + } + + if (ImGui.Button("Test")) + _ = NativeFunctions.MessageBoxW(IntPtr.Zero, "Hi", "Hello", NativeFunctions.MessageBoxType.Ok); + + if (this.messageBoxMinHook != null) + ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled); + } + catch (Exception ex) + { + Log.Error(ex, "MinHook error caught"); + } + } + + private async Task TestTaskInTaskDelay() + { + await Task.Delay(5000); + } + +#pragma warning disable 1998 + private async Task TestTaskInTaskSleep() +#pragma warning restore 1998 + { + Thread.Sleep(5000); + } + + private void Load() + { + var dataManager = Service.Get(); + + if (dataManager.IsDataReady) + { + this.serverOpString = JsonConvert.SerializeObject(dataManager.ServerOpCodes, Formatting.Indented); + this.wasReady = true; + } + } + + private void PrintGameObject(GameObject actor, string tag) + { + var actorString = + $"{actor.Address.ToInt64():X}:{actor.ObjectId:X}[{tag}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation} - Target: {actor.TargetObjectId:X}\n"; + + if (actor is Npc npc) + actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n"; + + if (actor is Character chara) + { + actorString += + $" Level: {chara.Level} ClassJob: {(this.resolveGameData ? chara.ClassJob.GameData.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + } + + if (actor is PlayerCharacter pc) + { + actorString += + $" HomeWorld: {(this.resolveGameData ? pc.HomeWorld.GameData.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(this.resolveGameData ? pc.CurrentWorld.GameData.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + } + + ImGui.TextUnformatted(actorString); + ImGui.SameLine(); + if (ImGui.Button($"C##{this.copyButtonIndex++}")) + { + ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X")); + } + } } diff --git a/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs b/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs index 17fa7850a..e95c510d3 100644 --- a/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs +++ b/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs @@ -4,46 +4,45 @@ using CheapLoc; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// Class responsible for drawing a notifier on screen that gamepad mode is active. +/// +internal class GamepadModeNotifierWindow : Window { /// - /// Class responsible for drawing a notifier on screen that gamepad mode is active. + /// Initializes a new instance of the class. /// - internal class GamepadModeNotifierWindow : Window + public GamepadModeNotifierWindow() + : base( + "###DalamudGamepadModeNotifier", + ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs + | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav + | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings, + true) { - /// - /// Initializes a new instance of the class. - /// - public GamepadModeNotifierWindow() - : base( - "###DalamudGamepadModeNotifier", - ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs - | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav - | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings, - true) - { - this.Size = Vector2.Zero; - this.SizeCondition = ImGuiCond.Always; - this.IsOpen = false; + this.Size = Vector2.Zero; + this.SizeCondition = ImGuiCond.Always; + this.IsOpen = false; - this.RespectCloseHotkey = false; - } + this.RespectCloseHotkey = false; + } - /// - /// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode. - /// - public override void Draw() - { - var drawList = ImGui.GetBackgroundDrawList(); - drawList.PushClipRectFullScreen(); - drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A); - drawList.AddText( - Vector2.One, - 0xFFFFFFFF, - Loc.Localize( - "DalamudGamepadModeNotifierText", - "Gamepad mode is ON. Press L1+L3 to deactivate, press R3 to toggle PluginInstaller.")); - drawList.PopClipRect(); - } + /// + /// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode. + /// + public override void Draw() + { + var drawList = ImGui.GetBackgroundDrawList(); + drawList.PushClipRectFullScreen(); + drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A); + drawList.AddText( + Vector2.One, + 0xFFFFFFFF, + Loc.Localize( + "DalamudGamepadModeNotifierText", + "Gamepad mode is ON. Press L1+L3 to deactivate, press R3 to toggle PluginInstaller.")); + drawList.PopClipRect(); } } diff --git a/Dalamud/Interface/Internal/Windows/IMEWindow.cs b/Dalamud/Interface/Internal/Windows/IMEWindow.cs index 5c936d4e9..3ae92560a 100644 --- a/Dalamud/Interface/Internal/Windows/IMEWindow.cs +++ b/Dalamud/Interface/Internal/Windows/IMEWindow.cs @@ -5,65 +5,64 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// A window for displaying IME details. +/// +internal class IMEWindow : Window { + private const int ImePageSize = 9; + /// - /// A window for displaying IME details. + /// Initializes a new instance of the class. /// - internal class IMEWindow : Window + public IMEWindow() + : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize) { - private const int ImePageSize = 9; + this.Size = new Vector2(100, 200); + this.SizeCondition = ImGuiCond.FirstUseEver; - /// - /// Initializes a new instance of the class. - /// - public IMEWindow() - : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize) + this.RespectCloseHotkey = false; + } + + /// + public override void Draw() + { + var ime = Service.GetNullable(); + + if (ime == null || !ime.IsEnabled) { - this.Size = new Vector2(100, 200); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.RespectCloseHotkey = false; + ImGui.Text("IME is unavailable."); + return; } - /// - public override void Draw() + ImGui.Text(ime.ImmComp); + + ImGui.Separator(); + + var native = ime.ImmCandNative; + for (var i = 0; i < ime.ImmCand.Count; i++) { - var ime = Service.GetNullable(); + var selected = i == (native.Selection % ImePageSize); - if (ime == null || !ime.IsEnabled) - { - ImGui.Text("IME is unavailable."); - return; - } + if (selected) + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); - ImGui.Text(ime.ImmComp); + ImGui.Text($"{i + 1}. {ime.ImmCand[i]}"); - ImGui.Separator(); - - var native = ime.ImmCandNative; - for (var i = 0; i < ime.ImmCand.Count; i++) - { - var selected = i == (native.Selection % ImePageSize); - - if (selected) - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); - - ImGui.Text($"{i + 1}. {ime.ImmCand[i]}"); - - if (selected) - ImGui.PopStyleColor(); - } - - var totalIndex = native.Selection + 1; - var totalSize = native.Count; - - var pageStart = native.PageStart; - var pageIndex = (pageStart / ImePageSize) + 1; - var pageCount = (totalSize / ImePageSize) + 1; - - ImGui.Separator(); - ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"); + if (selected) + ImGui.PopStyleColor(); } + + var totalIndex = native.Selection + 1; + var totalSize = native.Count; + + var pageStart = native.PageStart; + var pageIndex = (pageStart / ImePageSize) + 1; + var pageCount = (totalSize / ImePageSize) + 1; + + ImGui.Separator(); + ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"); } } diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 6313a1c43..c27b9db5a 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -14,394 +14,330 @@ using Dalamud.Utility; using ImGuiScene; using Serilog; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// A cache for plugin icons and images. +/// +internal class PluginImageCache : IDisposable { /// - /// A cache for plugin icons and images. + /// Maximum plugin image width. /// - internal class PluginImageCache : IDisposable + public const int PluginImageWidth = 730; + + /// + /// Maximum plugin image height. + /// + public const int PluginImageHeight = 380; + + /// + /// Maximum plugin icon width. + /// + public const int PluginIconWidth = 512; + + /// + /// Maximum plugin height. + /// + public const int PluginIconHeight = 512; + + // TODO: Change back to master after release + private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/{0}/{1}/images/{2}"; + + private BlockingCollection> downloadQueue = new(); + private CancellationTokenSource downloadToken = new(); + + private Dictionary pluginIconMap = new(); + private Dictionary pluginImagesMap = new(); + + /// + /// Initializes a new instance of the class. + /// + public PluginImageCache() { - /// - /// Maximum plugin image width. - /// - public const int PluginImageWidth = 730; + var dalamud = Service.Get(); + var interfaceManager = Service.Get(); - /// - /// Maximum plugin image height. - /// - public const int PluginImageHeight = 380; + this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png")); + this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png")); + this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png")); - /// - /// Maximum plugin icon width. - /// - public const int PluginIconWidth = 512; + var task = new Task( + () => this.DownloadTask(this.downloadToken.Token), + this.downloadToken.Token, + TaskCreationOptions.LongRunning); + task.Start(); + } - /// - /// Maximum plugin height. - /// - public const int PluginIconHeight = 512; + /// + /// Gets the default plugin icon. + /// + public TextureWrap DefaultIcon { get; } - // TODO: Change back to master after release - private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/{0}/{1}/images/{2}"; + /// + /// Gets the plugin trouble icon overlay. + /// + public TextureWrap TroubleIcon { get; } - private BlockingCollection> downloadQueue = new(); - private CancellationTokenSource downloadToken = new(); + /// + /// Gets the plugin update icon overlay. + /// + public TextureWrap UpdateIcon { get; } - private Dictionary pluginIconMap = new(); - private Dictionary pluginImagesMap = new(); + /// + public void Dispose() + { + this.DefaultIcon?.Dispose(); + this.TroubleIcon?.Dispose(); + this.UpdateIcon?.Dispose(); - /// - /// Initializes a new instance of the class. - /// - public PluginImageCache() + this.downloadToken?.Cancel(); + this.downloadToken?.Dispose(); + this.downloadQueue?.CompleteAdding(); + this.downloadQueue?.Dispose(); + + foreach (var icon in this.pluginIconMap.Values) { - var dalamud = Service.Get(); - var interfaceManager = Service.Get(); - - this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png")); - this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png")); - this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png")); - - var task = new Task( - () => this.DownloadTask(this.downloadToken.Token), - this.downloadToken.Token, - TaskCreationOptions.LongRunning); - task.Start(); + icon?.Dispose(); } - /// - /// Gets the default plugin icon. - /// - public TextureWrap DefaultIcon { get; } - - /// - /// Gets the plugin trouble icon overlay. - /// - public TextureWrap TroubleIcon { get; } - - /// - /// Gets the plugin update icon overlay. - /// - public TextureWrap UpdateIcon { get; } - - /// - public void Dispose() + foreach (var images in this.pluginImagesMap.Values) { - this.DefaultIcon?.Dispose(); - this.TroubleIcon?.Dispose(); - this.UpdateIcon?.Dispose(); - - this.downloadToken?.Cancel(); - this.downloadToken?.Dispose(); - this.downloadQueue?.CompleteAdding(); - this.downloadQueue?.Dispose(); - - foreach (var icon in this.pluginIconMap.Values) + foreach (var image in images) { - icon?.Dispose(); + image?.Dispose(); } - - foreach (var images in this.pluginImagesMap.Values) - { - foreach (var image in images) - { - image?.Dispose(); - } - } - - this.pluginIconMap.Clear(); - this.pluginImagesMap.Clear(); } - /// - /// Clear the cache of downloaded icons. - /// - public void ClearIconCache() + this.pluginIconMap.Clear(); + this.pluginImagesMap.Clear(); + } + + /// + /// Clear the cache of downloaded icons. + /// + public void ClearIconCache() + { + this.pluginIconMap.Clear(); + this.pluginImagesMap.Clear(); + } + + /// + /// Try to get the icon associated with the internal name of a plugin. + /// Uses the name within the manifest to search. + /// + /// The installed plugin, if available. + /// The plugin manifest. + /// If the plugin was third party sourced. + /// Cached image textures, or an empty array. + /// True if an entry exists, may be null if currently downloading. + public bool TryGetIcon(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap? iconTexture) + { + if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture)) + return true; + + iconTexture = null; + this.pluginIconMap.Add(manifest.InternalName, iconTexture); + + if (!this.downloadQueue.IsCompleted) { - this.pluginIconMap.Clear(); - this.pluginImagesMap.Clear(); + this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty)); } - /// - /// Try to get the icon associated with the internal name of a plugin. - /// Uses the name within the manifest to search. - /// - /// The installed plugin, if available. - /// The plugin manifest. - /// If the plugin was third party sourced. - /// Cached image textures, or an empty array. - /// True if an entry exists, may be null if currently downloading. - public bool TryGetIcon(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap? iconTexture) + return false; + } + + /// + /// Try to get any images associated with the internal name of a plugin. + /// Uses the name within the manifest to search. + /// + /// The installed plugin, if available. + /// The plugin manifest. + /// If the plugin was third party sourced. + /// Cached image textures, or an empty array. + /// True if the image array exists, may be empty if currently downloading. + public bool TryGetImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap?[] imageTextures) + { + if (this.pluginImagesMap.TryGetValue(manifest.InternalName, out imageTextures)) + return true; + + imageTextures = Array.Empty(); + this.pluginImagesMap.Add(manifest.InternalName, imageTextures); + + if (!this.downloadQueue.IsCompleted) { - if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture)) - return true; - - iconTexture = null; - this.pluginIconMap.Add(manifest.InternalName, iconTexture); - - if (!this.downloadQueue.IsCompleted) - { - this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty)); - } - - return false; + this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty)); } - /// - /// Try to get any images associated with the internal name of a plugin. - /// Uses the name within the manifest to search. - /// - /// The installed plugin, if available. - /// The plugin manifest. - /// If the plugin was third party sourced. - /// Cached image textures, or an empty array. - /// True if the image array exists, may be empty if currently downloading. - public bool TryGetImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap?[] imageTextures) + return false; + } + + private async void DownloadTask(CancellationToken token) + { + while (true) { - if (this.pluginImagesMap.TryGetValue(manifest.InternalName, out imageTextures)) - return true; - - imageTextures = Array.Empty(); - this.pluginImagesMap.Add(manifest.InternalName, imageTextures); - - if (!this.downloadQueue.IsCompleted) + try { - this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty)); - } - - return false; - } - - private async void DownloadTask(CancellationToken token) - { - while (true) - { - try - { - if (token.IsCancellationRequested) - return; - - if (!this.downloadQueue.TryTake(out var task, -1, token)) - return; - - await task.Invoke(); - } - catch (OperationCanceledException) - { - // Shutdown signal. - break; - } - catch (Exception ex) - { - Log.Error(ex, "An unhandled exception occurred in the plugin image downloader"); - } - } - - Log.Debug("Plugin image downloader has shutdown"); - } - - private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) - { - var interfaceManager = Service.Get(); - var pluginManager = Service.Get(); - - static bool TryLoadIcon(byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap icon) - { - icon = interfaceManager.LoadImage(bytes); - - if (icon == null) - { - Log.Error($"Could not load icon for {manifest.InternalName} at {loc}"); - return false; - } - - if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight) - { - Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight})."); - return false; - } - - if (icon.Height != icon.Width) - { - Log.Error($"Icon for {manifest.InternalName} at {loc} was not square."); - return false; - } - - return true; - } - - if (plugin != null && plugin.IsDev) - { - var file = this.GetPluginIconFileInfo(plugin); - if (file != null) - { - Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}"); - - var bytes = await File.ReadAllBytesAsync(file.FullName); - if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon)) - return; - - this.pluginIconMap[manifest.InternalName] = icon; - Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk"); - - return; - } - - // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. - // So instead, set the value manually so we download from the urls specified. - isThirdParty = true; - } - - var useTesting = pluginManager.UseTesting(manifest); - var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting); - - if (!url.IsNullOrEmpty()) - { - Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}"); - - HttpResponseMessage data; - try - { - data = await Util.HttpClient.GetAsync(url); - } - catch (InvalidOperationException) - { - Log.Error($"Plugin icon for {manifest.InternalName} has an Invalid URI"); - return; - } - catch (Exception ex) - { - Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}"); - return; - } - - if (data.StatusCode == HttpStatusCode.NotFound) + if (token.IsCancellationRequested) return; - data.EnsureSuccessStatusCode(); + if (!this.downloadQueue.TryTake(out var task, -1, token)) + return; - var bytes = await data.Content.ReadAsByteArrayAsync(); - if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon)) + await task.Invoke(); + } + catch (OperationCanceledException) + { + // Shutdown signal. + break; + } + catch (Exception ex) + { + Log.Error(ex, "An unhandled exception occurred in the plugin image downloader"); + } + } + + Log.Debug("Plugin image downloader has shutdown"); + } + + private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) + { + var interfaceManager = Service.Get(); + var pluginManager = Service.Get(); + + static bool TryLoadIcon(byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap icon) + { + icon = interfaceManager.LoadImage(bytes); + + if (icon == null) + { + Log.Error($"Could not load icon for {manifest.InternalName} at {loc}"); + return false; + } + + if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight) + { + Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight})."); + return false; + } + + if (icon.Height != icon.Width) + { + Log.Error($"Icon for {manifest.InternalName} at {loc} was not square."); + return false; + } + + return true; + } + + if (plugin != null && plugin.IsDev) + { + var file = this.GetPluginIconFileInfo(plugin); + if (file != null) + { + Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}"); + + var bytes = await File.ReadAllBytesAsync(file.FullName); + if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon)) return; this.pluginIconMap[manifest.InternalName] = icon; - Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded"); + Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk"); return; } - Log.Verbose($"Plugin icon for {manifest.InternalName} is not available"); + // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. + // So instead, set the value manually so we download from the urls specified. + isThirdParty = true; } - private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) + var useTesting = pluginManager.UseTesting(manifest); + var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting); + + if (!url.IsNullOrEmpty()) { - var interfaceManager = Service.Get(); - var pluginManager = Service.Get(); + Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}"); - static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap image) + HttpResponseMessage data; + try { - image = interfaceManager.LoadImage(bytes); - - if (image == null) - { - Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}"); - return false; - } - - if (image.Width > PluginImageWidth || image.Height > PluginImageHeight) - { - Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight})."); - return false; - } - - return true; + data = await Util.HttpClient.GetAsync(url); + } + catch (InvalidOperationException) + { + Log.Error($"Plugin icon for {manifest.InternalName} has an Invalid URI"); + return; + } + catch (Exception ex) + { + Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}"); + return; } - if (plugin != null && plugin.IsDev) + if (data.StatusCode == HttpStatusCode.NotFound) + return; + + data.EnsureSuccessStatusCode(); + + var bytes = await data.Content.ReadAsByteArrayAsync(); + if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon)) + return; + + this.pluginIconMap[manifest.InternalName] = icon; + Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded"); + + return; + } + + Log.Verbose($"Plugin icon for {manifest.InternalName} is not available"); + } + + private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) + { + var interfaceManager = Service.Get(); + var pluginManager = Service.Get(); + + static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap image) + { + image = interfaceManager.LoadImage(bytes); + + if (image == null) { - var files = this.GetPluginImageFileInfos(plugin); - if (files != null) - { - var didAny = false; - var pluginImages = new TextureWrap[files.Count]; - for (var i = 0; i < files.Count; i++) - { - var file = files[i]; - - if (file == null) - continue; - - Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}"); - var bytes = await File.ReadAllBytesAsync(file.FullName); - - if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image)) - continue; - - Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk"); - pluginImages[i] = image; - - didAny = true; - } - - if (didAny) - { - Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk"); - - if (pluginImages.Contains(null)) - pluginImages = pluginImages.Where(image => image != null).ToArray(); - - this.pluginImagesMap[manifest.InternalName] = pluginImages; - - return; - } - } - - // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. - // So instead, set the value manually so we download from the urls specified. - isThirdParty = true; + Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}"); + return false; } - var useTesting = pluginManager.UseTesting(manifest); - var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting); - if (urls != null) + if (image.Width > PluginImageWidth || image.Height > PluginImageHeight) + { + Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight})."); + return false; + } + + return true; + } + + if (plugin != null && plugin.IsDev) + { + var files = this.GetPluginImageFileInfos(plugin); + if (files != null) { var didAny = false; - var pluginImages = new TextureWrap[urls.Count]; - for (var i = 0; i < urls.Count; i++) + var pluginImages = new TextureWrap[files.Count]; + for (var i = 0; i < files.Count; i++) { - var url = urls[i]; + var file = files[i]; - if (url.IsNullOrEmpty()) + if (file == null) continue; - Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}"); + Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}"); + var bytes = await File.ReadAllBytesAsync(file.FullName); - HttpResponseMessage data; - try - { - data = await Util.HttpClient.GetAsync(url); - } - catch (InvalidOperationException) - { - Log.Error($"Plugin image{i + 1} for {manifest.InternalName} has an Invalid URI"); - continue; - } - catch (Exception ex) - { - Log.Error(ex, $"An unexpected error occurred with image{i + 1} for {manifest.InternalName}"); - continue; - } - - if (data.StatusCode == HttpStatusCode.NotFound) + if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image)) continue; - data.EnsureSuccessStatusCode(); - - var bytes = await data.Content.ReadAsByteArrayAsync(); - if (!TryLoadImage(i, bytes, url, manifest, interfaceManager, out var image)) - continue; - - Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded"); + Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk"); pluginImages[i] = image; didAny = true; @@ -409,7 +345,7 @@ namespace Dalamud.Interface.Internal.Windows if (didAny) { - Log.Verbose($"Plugin images for {manifest.InternalName} downloaded"); + Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk"); if (pluginImages.Contains(null)) pluginImages = pluginImages.Where(image => image != null).ToArray(); @@ -420,67 +356,130 @@ namespace Dalamud.Interface.Internal.Windows } } - Log.Verbose($"Images for {manifest.InternalName} are not available"); + // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. + // So instead, set the value manually so we download from the urls specified. + isThirdParty = true; } - private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting) + var useTesting = pluginManager.UseTesting(manifest); + var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting); + if (urls != null) { - if (isThirdParty) - return manifest.IconUrl; - - return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png"); - } - - private List? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting) - { - if (isThirdParty) + var didAny = false; + var pluginImages = new TextureWrap[urls.Count]; + for (var i = 0; i < urls.Count; i++) { - if (manifest.ImageUrls?.Count > 5) + var url = urls[i]; + + if (url.IsNullOrEmpty()) + continue; + + Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}"); + + HttpResponseMessage data; + try { - Log.Warning($"Plugin {manifest.InternalName} has too many images"); - return manifest.ImageUrls.Take(5).ToList(); + data = await Util.HttpClient.GetAsync(url); } - - return manifest.ImageUrls; - } - - var output = new List(); - for (var i = 1; i <= 5; i++) - { - output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); - } - - return output; - } - - private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin) - { - var pluginDir = plugin.DllFile.Directory; - - var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png")); - if (devUrl.Exists) - return devUrl; - - return null; - } - - private List GetPluginImageFileInfos(LocalPlugin? plugin) - { - var pluginDir = plugin.DllFile.Directory; - var output = new List(); - for (var i = 1; i <= 5; i++) - { - var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png")); - if (devUrl.Exists) + catch (InvalidOperationException) { - output.Add(devUrl); + Log.Error($"Plugin image{i + 1} for {manifest.InternalName} has an Invalid URI"); + continue; + } + catch (Exception ex) + { + Log.Error(ex, $"An unexpected error occurred with image{i + 1} for {manifest.InternalName}"); continue; } - output.Add(null); + if (data.StatusCode == HttpStatusCode.NotFound) + continue; + + data.EnsureSuccessStatusCode(); + + var bytes = await data.Content.ReadAsByteArrayAsync(); + if (!TryLoadImage(i, bytes, url, manifest, interfaceManager, out var image)) + continue; + + Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded"); + pluginImages[i] = image; + + didAny = true; } - return output; + if (didAny) + { + Log.Verbose($"Plugin images for {manifest.InternalName} downloaded"); + + if (pluginImages.Contains(null)) + pluginImages = pluginImages.Where(image => image != null).ToArray(); + + this.pluginImagesMap[manifest.InternalName] = pluginImages; + + return; + } } + + Log.Verbose($"Images for {manifest.InternalName} are not available"); + } + + private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting) + { + if (isThirdParty) + return manifest.IconUrl; + + return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png"); + } + + private List? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting) + { + if (isThirdParty) + { + if (manifest.ImageUrls?.Count > 5) + { + Log.Warning($"Plugin {manifest.InternalName} has too many images"); + return manifest.ImageUrls.Take(5).ToList(); + } + + return manifest.ImageUrls; + } + + var output = new List(); + for (var i = 1; i <= 5; i++) + { + output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); + } + + return output; + } + + private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin) + { + var pluginDir = plugin.DllFile.Directory; + + var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png")); + if (devUrl.Exists) + return devUrl; + + return null; + } + + private List GetPluginImageFileInfos(LocalPlugin? plugin) + { + var pluginDir = plugin.DllFile.Directory; + var output = new List(); + for (var i = 1; i <= 5; i++) + { + var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png")); + if (devUrl.Exists) + { + output.Add(devUrl); + continue; + } + + output.Add(null); + } + + return output; } } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs index 9c300e438..abe569ec8 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs @@ -25,1784 +25,817 @@ using Dalamud.Utility; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// Class responsible for drawing the plugin installer. +/// +internal class PluginInstallerWindow : Window, IDisposable { + private static readonly ModuleLog Log = new("PLUGINW"); + + private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f); + private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f); + + private readonly PluginCategoryManager categoryManager = new(); + private readonly PluginImageCache imageCache = new(); + + #region Image Tester State + + private string[] testerImagePaths = new string[5]; + private string testerIconPath = string.Empty; + + private TextureWrap?[] testerImages; + private TextureWrap? testerIcon; + + private bool testerError = false; + private bool testerUpdateAvailable = false; + + #endregion + + private bool errorModalDrawing = true; + private bool errorModalOnNextFrame = false; + private string errorModalMessage = string.Empty; + + private bool feedbackModalDrawing = true; + private bool feedbackModalOnNextFrame = false; + private string feedbackModalBody = string.Empty; + private string feedbackModalContact = string.Empty; + private bool feedbackModalIncludeException = false; + private PluginManifest? feedbackPlugin = null; + + private int updatePluginCount = 0; + private List? updatedPlugins; + + private List pluginListAvailable = new(); + private List pluginListInstalled = new(); + private List pluginListUpdatable = new(); + private bool hasDevPlugins = false; + + private string searchText = string.Empty; + + private PluginSortKind sortKind = PluginSortKind.Alphabetical; + private string filterText = Locs.SortBy_Alphabetical; + + private OperationStatus installStatus = OperationStatus.Idle; + private OperationStatus updateStatus = OperationStatus.Idle; + + private List openPluginCollapsibles = new(); + /// - /// Class responsible for drawing the plugin installer. + /// Initializes a new instance of the class. /// - internal class PluginInstallerWindow : Window, IDisposable + public PluginInstallerWindow() + : base( + Locs.WindowTitle + (Service.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller", + ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar) { - private static readonly ModuleLog Log = new("PLUGINW"); + this.IsOpen = true; - private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f); - private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f); + this.Size = new Vector2(830, 570); + this.SizeCondition = ImGuiCond.FirstUseEver; - private readonly PluginCategoryManager categoryManager = new(); - private readonly PluginImageCache imageCache = new(); - - #region Image Tester State - - private string[] testerImagePaths = new string[5]; - private string testerIconPath = string.Empty; - - private TextureWrap?[] testerImages; - private TextureWrap? testerIcon; - - private bool testerError = false; - private bool testerUpdateAvailable = false; - - #endregion - - private bool errorModalDrawing = true; - private bool errorModalOnNextFrame = false; - private string errorModalMessage = string.Empty; - - private bool feedbackModalDrawing = true; - private bool feedbackModalOnNextFrame = false; - private string feedbackModalBody = string.Empty; - private string feedbackModalContact = string.Empty; - private bool feedbackModalIncludeException = false; - private PluginManifest? feedbackPlugin = null; - - private int updatePluginCount = 0; - private List? updatedPlugins; - - private List pluginListAvailable = new(); - private List pluginListInstalled = new(); - private List pluginListUpdatable = new(); - private bool hasDevPlugins = false; - - private string searchText = string.Empty; - - private PluginSortKind sortKind = PluginSortKind.Alphabetical; - private string filterText = Locs.SortBy_Alphabetical; - - private OperationStatus installStatus = OperationStatus.Idle; - private OperationStatus updateStatus = OperationStatus.Idle; - - private List openPluginCollapsibles = new(); - - /// - /// Initializes a new instance of the class. - /// - public PluginInstallerWindow() - : base( - Locs.WindowTitle + (Service.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller", - ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar) + this.SizeConstraints = new WindowSizeConstraints { - this.IsOpen = true; + MinimumSize = this.Size.Value, + MaximumSize = new Vector2(5000, 5000), + }; - this.Size = new Vector2(830, 570); - this.SizeCondition = ImGuiCond.FirstUseEver; + var pluginManager = Service.Get(); - this.SizeConstraints = new WindowSizeConstraints - { - MinimumSize = this.Size.Value, - MaximumSize = new Vector2(5000, 5000), - }; + // For debugging + if (pluginManager.PluginsReady) + this.OnInstalledPluginsChanged(); - var pluginManager = Service.Get(); + pluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged; + pluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged; - // For debugging - if (pluginManager.PluginsReady) - this.OnInstalledPluginsChanged(); - - pluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged; - pluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged; - - for (var i = 0; i < this.testerImagePaths.Length; i++) - { - this.testerImagePaths[i] = string.Empty; - } + for (var i = 0; i < this.testerImagePaths.Length; i++) + { + this.testerImagePaths[i] = string.Empty; } + } - private enum OperationStatus + private enum OperationStatus + { + Idle, + InProgress, + Complete, + } + + private enum PluginSortKind + { + Alphabetical, + DownloadCount, + LastUpdate, + NewOrNot, + } + + /// + public void Dispose() + { + var pluginManager = Service.Get(); + + pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged; + pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged; + + this.imageCache?.Dispose(); + } + + /// + public override void OnOpen() + { + var pluginManager = Service.Get(); + + _ = pluginManager.ReloadPluginMastersAsync(); + + this.updatePluginCount = 0; + this.updatedPlugins = null; + + this.searchText = string.Empty; + this.sortKind = PluginSortKind.Alphabetical; + this.filterText = Locs.SortBy_Alphabetical; + } + + /// + public override void OnClose() + { + Service.Get().Save(); + } + + /// + public override void Draw() + { + this.DrawHeader(); + this.DrawPluginCategories(); + this.DrawFooter(); + this.DrawErrorModal(); + this.DrawFeedbackModal(); + } + + /// + /// Clear the icon and image caches, forcing a fresh download. + /// + public void ClearIconCache() + { + this.imageCache.ClearIconCache(); + } + + private void DrawHeader() + { + var style = ImGui.GetStyle(); + var windowSize = ImGui.GetWindowContentRegionMax(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); + + var searchInputWidth = 240 * ImGuiHelpers.GlobalScale; + + var sortByText = Locs.SortBy_Label; + var sortByTextWidth = ImGui.CalcTextSize(sortByText).X; + var sortSelectables = new (string Localization, PluginSortKind SortKind)[] { - Idle, - InProgress, - Complete, - } - - private enum PluginSortKind - { - Alphabetical, - DownloadCount, - LastUpdate, - NewOrNot, - } - - /// - public void Dispose() - { - var pluginManager = Service.Get(); - - pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged; - pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged; - - this.imageCache?.Dispose(); - } - - /// - public override void OnOpen() - { - var pluginManager = Service.Get(); - - _ = pluginManager.ReloadPluginMastersAsync(); - - this.updatePluginCount = 0; - this.updatedPlugins = null; - - this.searchText = string.Empty; - this.sortKind = PluginSortKind.Alphabetical; - this.filterText = Locs.SortBy_Alphabetical; - } - - /// - public override void OnClose() - { - Service.Get().Save(); - } - - /// - public override void Draw() - { - this.DrawHeader(); - this.DrawPluginCategories(); - this.DrawFooter(); - this.DrawErrorModal(); - this.DrawFeedbackModal(); - } - - /// - /// Clear the icon and image caches, forcing a fresh download. - /// - public void ClearIconCache() - { - this.imageCache.ClearIconCache(); - } - - private void DrawHeader() - { - var style = ImGui.GetStyle(); - var windowSize = ImGui.GetWindowContentRegionMax(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); - - var searchInputWidth = 240 * ImGuiHelpers.GlobalScale; - - var sortByText = Locs.SortBy_Label; - var sortByTextWidth = ImGui.CalcTextSize(sortByText).X; - var sortSelectables = new (string Localization, PluginSortKind SortKind)[] - { (Locs.SortBy_Alphabetical, PluginSortKind.Alphabetical), (Locs.SortBy_DownloadCounts, PluginSortKind.DownloadCount), (Locs.SortBy_LastUpdate, PluginSortKind.LastUpdate), (Locs.SortBy_NewOrNot, PluginSortKind.NewOrNot), - }; - var longestSelectableWidth = sortSelectables.Select(t => ImGui.CalcTextSize(t.Localization).X).Max(); - var selectableWidth = longestSelectableWidth + (style.FramePadding.X * 2); // This does not include the label - var sortSelectWidth = selectableWidth + sortByTextWidth + style.ItemInnerSpacing.X; // Item spacing between the selectable and the label + }; + var longestSelectableWidth = sortSelectables.Select(t => ImGui.CalcTextSize(t.Localization).X).Max(); + var selectableWidth = longestSelectableWidth + (style.FramePadding.X * 2); // This does not include the label + var sortSelectWidth = selectableWidth + sortByTextWidth + style.ItemInnerSpacing.X; // Item spacing between the selectable and the label - var headerText = Locs.Header_Hint; - var headerTextSize = ImGui.CalcTextSize(headerText); - ImGui.Text(headerText); + var headerText = Locs.Header_Hint; + var headerTextSize = ImGui.CalcTextSize(headerText); + ImGui.Text(headerText); + ImGui.SameLine(); + + // Shift down a little to align with the middle of the header text + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2); + + ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth); + ImGui.SetNextItemWidth(searchInputWidth); + if (ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100)) + { + this.UpdateCategoriesOnSearchChange(); + } + + ImGui.SameLine(); + ImGui.SetCursorPosX(windowSize.X - sortSelectWidth); + ImGui.SetNextItemWidth(selectableWidth); + if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton)) + { + foreach (var selectable in sortSelectables) + { + if (ImGui.Selectable(selectable.Localization)) + { + this.sortKind = selectable.SortKind; + this.filterText = selectable.Localization; + + this.ResortPlugins(); + } + } + + ImGui.EndCombo(); + } + } + + private void DrawFooter() + { + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + var windowSize = ImGui.GetWindowContentRegionMax(); + var placeholderButtonSize = ImGuiHelpers.GetButtonSize("placeholder"); + + ImGui.Separator(); + + ImGui.SetCursorPosY(windowSize.Y - placeholderButtonSize.Y); + + this.DrawUpdatePluginsButton(); + + ImGui.SameLine(); + if (ImGui.Button(Locs.FooterButton_Settings)) + { + Service.Get().OpenSettings(); + } + + // If any dev plugins are installed, allow a shortcut for the /xldev menu item + if (this.hasDevPlugins) + { ImGui.SameLine(); - - // Shift down a little to align with the middle of the header text - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2); - - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth); - ImGui.SetNextItemWidth(searchInputWidth); - if (ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100)) + if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) { - this.UpdateCategoriesOnSearchChange(); - } - - ImGui.SameLine(); - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth); - ImGui.SetNextItemWidth(selectableWidth); - if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton)) - { - foreach (var selectable in sortSelectables) - { - if (ImGui.Selectable(selectable.Localization)) - { - this.sortKind = selectable.SortKind; - this.filterText = selectable.Localization; - - this.ResortPlugins(); - } - } - - ImGui.EndCombo(); + pluginManager.ScanDevPlugins(); } } - private void DrawFooter() + var closeText = Locs.FooterButton_Close; + var closeButtonSize = ImGuiHelpers.GetButtonSize(closeText); + + ImGui.SameLine(windowSize.X - closeButtonSize.X - 20); + if (ImGui.Button(closeText)) { - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - var windowSize = ImGui.GetWindowContentRegionMax(); - var placeholderButtonSize = ImGuiHelpers.GetButtonSize("placeholder"); - - ImGui.Separator(); - - ImGui.SetCursorPosY(windowSize.Y - placeholderButtonSize.Y); - - this.DrawUpdatePluginsButton(); - - ImGui.SameLine(); - if (ImGui.Button(Locs.FooterButton_Settings)) - { - Service.Get().OpenSettings(); - } - - // If any dev plugins are installed, allow a shortcut for the /xldev menu item - if (this.hasDevPlugins) - { - ImGui.SameLine(); - if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) - { - pluginManager.ScanDevPlugins(); - } - } - - var closeText = Locs.FooterButton_Close; - var closeButtonSize = ImGuiHelpers.GetButtonSize(closeText); - - ImGui.SameLine(windowSize.X - closeButtonSize.X - 20); - if (ImGui.Button(closeText)) - { - this.IsOpen = false; - configuration.Save(); - } + this.IsOpen = false; + configuration.Save(); } + } - private void DrawUpdatePluginsButton() + private void DrawUpdatePluginsButton() + { + var pluginManager = Service.Get(); + var notifications = Service.Get(); + + var ready = pluginManager.PluginsReady && pluginManager.ReposReady; + + if (!ready || this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress) { - var pluginManager = Service.Get(); - var notifications = Service.Get(); - - var ready = pluginManager.PluginsReady && pluginManager.ReposReady; - - if (!ready || this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress) - { - ImGuiComponents.DisabledButton(Locs.FooterButton_UpdatePlugins); - } - else if (this.updateStatus == OperationStatus.Complete) - { - ImGui.Button(this.updatePluginCount > 0 - ? Locs.FooterButton_UpdateComplete(this.updatePluginCount) - : Locs.FooterButton_NoUpdates); - } - else - { - if (ImGui.Button(Locs.FooterButton_UpdatePlugins)) - { - this.updateStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.UpdatePluginsAsync()) - .ContinueWith(task => - { - this.updateStatus = OperationStatus.Complete; - - if (task.IsFaulted) - { - this.updatePluginCount = 0; - this.updatedPlugins = null; - this.DisplayErrorContinuation(task, Locs.ErrorModal_UpdaterFatal); - } - else - { - this.updatedPlugins = task.Result.Where(res => res.WasUpdated).ToList(); - this.updatePluginCount = this.updatedPlugins.Count; - - var errorPlugins = task.Result.Where(res => !res.WasUpdated).ToList(); - var errorPluginCount = errorPlugins.Count; - - if (errorPluginCount > 0) - { - var errorMessage = this.updatePluginCount > 0 - ? Locs.ErrorModal_UpdaterFailPartial(this.updatePluginCount, errorPluginCount) - : Locs.ErrorModal_UpdaterFail(errorPluginCount); - - var hintInsert = errorPlugins - .Aggregate(string.Empty, (current, pluginUpdateStatus) => $"{current}* {pluginUpdateStatus.InternalName}\n") - .TrimEnd(); - errorMessage += Locs.ErrorModal_HintBlame(hintInsert); - - this.DisplayErrorContinuation(task, errorMessage); - } - - if (this.updatePluginCount > 0) - { - pluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); - notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); - } - else if (this.updatePluginCount == 0) - { - notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info); - } - } - }); - } - } + ImGuiComponents.DisabledButton(Locs.FooterButton_UpdatePlugins); } - - private void DrawErrorModal() + else if (this.updateStatus == OperationStatus.Complete) { - var modalTitle = Locs.ErrorModal_Title; - - if (ImGui.BeginPopupModal(modalTitle, ref this.errorModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.Text(this.errorModalMessage); - ImGui.Spacing(); - - var buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); - - if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) - { - ImGui.CloseCurrentPopup(); - } - - ImGui.EndPopup(); - } - - if (this.errorModalOnNextFrame) - { - ImGui.OpenPopup(modalTitle); - this.errorModalOnNextFrame = false; - this.errorModalDrawing = true; - } + ImGui.Button(this.updatePluginCount > 0 + ? Locs.FooterButton_UpdateComplete(this.updatePluginCount) + : Locs.FooterButton_NoUpdates); } - - private void DrawFeedbackModal() + else { - var modalTitle = Locs.FeedbackModal_Title; - - if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + if (ImGui.Button(Locs.FooterButton_UpdatePlugins)) { - ImGui.Text(Locs.FeedbackModal_Text(this.feedbackPlugin.Name)); + this.updateStatus = OperationStatus.InProgress; - if (this.pluginListUpdatable.Any( - up => up.InstalledPlugin.Manifest.InternalName == this.feedbackPlugin?.InternalName)) - { - ImGui.TextColored(ImGuiColors.DalamudRed, Locs.FeedbackModal_HasUpdate); - } - - ImGui.Spacing(); - - ImGui.InputTextMultiline("###FeedbackContent", ref this.feedbackModalBody, 1000, new Vector2(400, 200)); - - ImGui.Spacing(); - - ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); - - ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException); - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint); - - ImGui.Spacing(); - - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_Hint); - - var buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); - - if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) - { - if (this.feedbackPlugin != null) - { - Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException)) - .ContinueWith( - t => - { - var notif = Service.Get(); - if (t.IsCanceled || t.IsFaulted) - notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error); - else - notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success); - }); - } - else - { - Log.Error("FeedbackPlugin was null."); - } - - ImGui.CloseCurrentPopup(); - } - - ImGui.EndPopup(); - } - - if (this.feedbackModalOnNextFrame) - { - ImGui.OpenPopup(modalTitle); - this.feedbackModalOnNextFrame = false; - this.feedbackModalDrawing = true; - this.feedbackModalBody = string.Empty; - this.feedbackModalContact = string.Empty; - this.feedbackModalIncludeException = false; - } - } - - /* - private void DrawPluginTabBar() - { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); - - if (ImGui.BeginTabBar("PluginsTabBar", ImGuiTabBarFlags.NoTooltip)) - { - this.DrawPluginTab(Locs.TabTitle_AvailablePlugins, this.DrawAvailablePluginList); - this.DrawPluginTab(Locs.TabTitle_InstalledPlugins, this.DrawInstalledPluginList); - - if (this.hasDevPlugins) - { - this.DrawPluginTab(Locs.TabTitle_InstalledDevPlugins, this.DrawInstalledDevPluginList); - this.DrawPluginTab("Image/Icon Tester", this.DrawImageTester); - } - } - - ImGui.PopStyleVar(); - } - */ - - /* - private void DrawPluginTab(string title, Action drawPluginList) - { - if (ImGui.BeginTabItem(title)) - { - ImGui.BeginChild($"Scrolling{title}", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); - - var ready = this.DrawPluginListLoading(); - - if (ready) - { - drawPluginList(); - } - - ImGui.EndChild(); - - ImGui.EndTabItem(); - } - } - */ - - private void DrawAvailablePluginList() - { - var pluginList = this.pluginListAvailable; - - if (pluginList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible); - return; - } - - var filteredManifests = pluginList - .Where(rm => !this.IsManifestFiltered(rm)) - .ToList(); - - if (filteredManifests.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); - return; - } - - // get list to show and reset category dirty flag - var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests); - - var i = 0; - foreach (var manifest in categoryManifestsList) - { - var remoteManifest = manifest as RemotePluginManifest; - var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest); - - ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}"); - if (isInstalled) - { - this.DrawInstalledPlugin(plugin, i++, true); - } - else - { - this.DrawAvailablePlugin(remoteManifest, i++); - } - - ImGui.PopID(); - } - } - - private void DrawInstalledPluginList() - { - var pluginList = this.pluginListInstalled; - - if (pluginList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); - return; - } - - var filteredList = pluginList - .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) - .ToList(); - - if (filteredList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); - return; - } - - var i = 0; - foreach (var plugin in filteredList) - { - this.DrawInstalledPlugin(plugin, i++); - } - } - - private void DrawInstalledDevPluginList() - { - var pluginList = this.pluginListInstalled - .Where(plugin => plugin.IsDev) - .ToList(); - - if (pluginList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); - return; - } - - var filteredList = pluginList - .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) - .ToList(); - - if (filteredList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); - return; - } - - var i = 0; - foreach (var plugin in filteredList) - { - this.DrawInstalledPlugin(plugin, i++); - } - } - - private void DrawPluginCategories() - { - var useContentHeight = -40f; // button height + spacing - var useMenuWidth = 180f; // works fine as static value, table can be resized by user - - var useContentWidth = ImGui.GetContentRegionAvail().X; - - if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale))) - { - ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0)); - if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV)) - { - ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextRow(); - - ImGui.TableNextColumn(); - this.DrawPluginCategorySelectors(); - - ImGui.TableNextColumn(); - if (ImGui.BeginChild("ScrollingPlugins", new Vector2(-1, 0), false, ImGuiWindowFlags.NoBackground)) - { - this.DrawPluginCategoryContent(); - } - - ImGui.EndChild(); - ImGui.EndTable(); - } - - ImGui.PopStyleVar(); - ImGui.EndChild(); - } - } - - private void DrawPluginCategorySelectors() - { - var colorSearchHighlight = Vector4.One; - unsafe - { - var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight); - if (colorPtr != null) - { - colorSearchHighlight = *colorPtr; - } - } - - for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++) - { - var groupInfo = this.categoryManager.GroupList[groupIdx]; - var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins; - if (!canShowGroup) - { - continue; - } - - ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx); - if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None)) - { - if (this.categoryManager.CurrentGroupIdx != groupIdx) - { - this.categoryManager.CurrentGroupIdx = groupIdx; - } - - ImGui.Indent(); - var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight()); - for (var categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++) - { - var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]); - - var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId); - if (hasSearchHighlight) - { - ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight); - } - - if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize)) - { - this.categoryManager.CurrentCategoryIdx = categoryIdx; - } - - if (hasSearchHighlight) - { - ImGui.PopStyleColor(); - } - } - - ImGui.Unindent(); - - if (groupIdx != this.categoryManager.GroupList.Length - 1) - { - ImGuiHelpers.ScaledDummy(5); - } - } - } - } - - private void DrawPluginCategoryContent() - { - var ready = this.DrawPluginListLoading(); - if (!this.categoryManager.IsSelectionValid || !ready) - { - return; - } - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); - - var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx]; - if (this.categoryManager.IsContentDirty) - { - // reset opened list of collapsibles when switching between categories - this.openPluginCollapsibles.Clear(); - - // do NOT reset dirty flag when Available group is selected, it will be handled by DrawAvailablePluginList() - if (groupInfo.GroupKind != PluginCategoryManager.GroupKind.Available) - { - this.categoryManager.ResetContentDirty(); - } - } - - if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.DevTools) - { - // this one is never sorted and remains in hardcoded order from group ctor - switch (this.categoryManager.CurrentCategoryIdx) - { - case 0: - this.DrawInstalledDevPluginList(); - break; - - case 1: - this.DrawImageTester(); - break; - - default: - // umm, there's nothing else, keep handled set and just skip drawing... - break; - } - } - else if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.Installed) - { - this.DrawInstalledPluginList(); - } - else - { - this.DrawAvailablePluginList(); - } - - ImGui.PopStyleVar(); - } - - private void DrawImageTester() - { - var sectionSize = ImGuiHelpers.GlobalScale * 66; - var startCursor = ImGui.GetCursorPos(); - - ImGui.PushStyleColor(ImGuiCol.Button, true ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); - - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - - ImGui.Button($"###pluginTesterCollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize)); - - ImGui.PopStyleVar(); - - ImGui.PopStyleColor(3); - - ImGui.SetCursorPos(startCursor); - - var hasIcon = this.testerIcon != null; - - var iconTex = this.imageCache.DefaultIcon; - if (hasIcon) iconTex = this.testerIcon; - - var iconSize = ImGuiHelpers.ScaledVector2(64, 64); - - var cursorBeforeImage = ImGui.GetCursorPos(); - ImGui.Image(iconTex.ImGuiHandle, iconSize); - ImGui.SameLine(); - - if (this.testerError) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); - ImGui.SameLine(); - } - else if (this.testerUpdateAvailable) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); - ImGui.SameLine(); - } - - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - var cursor = ImGui.GetCursorPos(); - // Name - ImGui.Text("My Cool Plugin"); - - // Download count - var downloadCountText = Locs.PluginBody_AuthorWithDownloadCount("Plugin Enjoyer", 69420); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); - - cursor.Y += ImGui.GetTextLineHeightWithSpacing(); - ImGui.SetCursorPos(cursor); - - // Description - ImGui.TextWrapped("This plugin does very many great things."); - - startCursor.Y += sectionSize; - ImGui.SetCursorPos(startCursor); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Indent(); - - // Description - ImGui.TextWrapped("This is a description.\nIt has multiple lines.\nTruly descriptive."); - - ImGuiHelpers.ScaledDummy(5); - - // Controls - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; - - var versionString = "1.0.0.0"; - - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); - } - else - { - var buttonText = Locs.PluginButton_InstallVersion(versionString); - ImGui.Button($"{buttonText}##{buttonText}testing"); - } - - this.DrawVisitRepoUrlButton("https://google.com"); - - if (this.testerImages != null) - { - ImGuiHelpers.ScaledDummy(5); - - const float thumbFactor = 2.7f; - - var scrollBarSize = 15; - ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, scrollBarSize); - ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Vector4.Zero); - - var width = ImGui.GetWindowWidth(); - - if (ImGui.BeginChild( - "pluginTestingImageScrolling", - new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), - false, - ImGuiWindowFlags.HorizontalScrollbar | - ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.NoBackground)) - { - if (this.testerImages != null && this.testerImages is { Length: > 0 }) - { - for (var i = 0; i < this.testerImages.Length; i++) - { - var popupId = $"pluginTestingImage{i}"; - var image = this.testerImages[i]; - if (image == null) - continue; - - ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - if (ImGui.BeginPopup(popupId)) - { - if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) - ImGui.CloseCurrentPopup(); - - ImGui.EndPopup(); - } - - ImGui.PopStyleVar(3); - - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - float xAct = image.Width; - float yAct = image.Height; - float xMax = PluginImageCache.PluginImageWidth; - float yMax = PluginImageCache.PluginImageHeight; - - // scale image if undersized - if (xAct < xMax && yAct < yMax) - { - var scale = Math.Min(xMax / xAct, yMax / yAct); - xAct *= scale; - yAct *= scale; - } - - var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); - if (ImGui.ImageButton(image.ImGuiHandle, size)) - ImGui.OpenPopup(popupId); - - ImGui.PopStyleVar(); - - if (i < this.testerImages.Length - 1) - { - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - } - } - } - } - - ImGui.EndChild(); - - ImGui.PopStyleVar(); - ImGui.PopStyleColor(); - - ImGui.Unindent(); - } - - ImGuiHelpers.ScaledDummy(20); - - ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); - ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000); - ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000); - ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000); - ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000); - ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000); - - var im = Service.Get(); - if (ImGui.Button("Load")) - { - try - { - if (this.testerIcon != null) - { - this.testerIcon.Dispose(); - this.testerIcon = null; - } - - if (!this.testerIconPath.IsNullOrEmpty()) - { - this.testerIcon = im.LoadImage(this.testerIconPath); - } - - this.testerImages = new TextureWrap[this.testerImagePaths.Length]; - - for (var i = 0; i < this.testerImagePaths.Length; i++) - { - if (this.testerImagePaths[i].IsNullOrEmpty()) - continue; - - if (this.testerImages[i] != null) - { - this.testerImages[i].Dispose(); - this.testerImages[i] = null; - } - - this.testerImages[i] = im.LoadImage(this.testerImagePaths[i]); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not load plugin images for testing."); - } - } - - ImGui.Checkbox("Failed", ref this.testerError); - ImGui.Checkbox("Has Update", ref this.testerUpdateAvailable); - } - - private bool DrawPluginListLoading() - { - var pluginManager = Service.Get(); - - if (pluginManager.SafeMode) - { - ImGui.Text(Locs.TabBody_SafeMode); - return false; - } - - var ready = pluginManager.PluginsReady && pluginManager.ReposReady; - - if (!ready) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_LoadingPlugins); - } - - var failedRepos = pluginManager.Repos - .Where(repo => repo.State == PluginRepositoryState.Fail) - .ToArray(); - - if (failedRepos.Length > 0) - { - var failText = Locs.TabBody_DownloadFailed; - var aggFailText = failedRepos - .Select(repo => $"{failText} ({repo.PluginMasterUrl})") - .Aggregate((s1, s2) => $"{s1}\n{s2}"); - - ImGui.TextColored(ImGuiColors.DalamudRed, aggFailText); - } - - return ready; - } - - private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index) - { - ImGui.Separator(); - - var isOpen = this.openPluginCollapsibles.Contains(index); - - var sectionSize = ImGuiHelpers.GlobalScale * 66; - var startCursor = ImGui.GetCursorPos(); - - ImGui.PushStyleColor(ImGuiCol.Button, isOpen ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); - - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - - if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize))) - { - if (isOpen) - { - this.openPluginCollapsibles.Remove(index); - } - else - { - this.openPluginCollapsibles.Add(index); - } - - isOpen = !isOpen; - } - - drawContextMenuAction?.Invoke(); - - ImGui.PopStyleVar(); - - ImGui.PopStyleColor(3); - - ImGui.SetCursorPos(startCursor); - - var iconTex = this.imageCache.DefaultIcon; - var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); - if (hasIcon && cachedIconTex != null) - { - iconTex = cachedIconTex; - } - - var iconSize = ImGuiHelpers.ScaledVector2(64, 64); - - var cursorBeforeImage = ImGui.GetCursorPos(); - ImGui.Image(iconTex.ImGuiHandle, iconSize); - ImGui.SameLine(); - - if (updateAvailable) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); - ImGui.SameLine(); - } - else if (trouble) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); - ImGui.SameLine(); - } - - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - var cursor = ImGui.GetCursorPos(); - - // Name - ImGui.Text(label); - - // Download count - var downloadCountText = manifest.DownloadCount > 0 - ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) - : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); - - if (isNew) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.TankBlue, Locs.PluginTitleMod_New); - } - - cursor.Y += ImGui.GetTextLineHeightWithSpacing(); - ImGui.SetCursorPos(cursor); - - // Outdated warning - if (plugin is { IsOutdated: true, IsBanned: false }) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_Outdated); - ImGui.PopStyleColor(); - } - - // Banned warning - if (plugin is { IsBanned: true }) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(plugin.BanReason.IsNullOrEmpty() - ? Locs.PluginBody_Banned - : Locs.PluginBody_BannedReason(plugin.BanReason)); - - ImGui.PopStyleColor(); - } - - // Description - if (plugin is null or { IsOutdated: false, IsBanned: false }) - { - if (!string.IsNullOrWhiteSpace(manifest.Punchline)) - { - ImGui.TextWrapped(manifest.Punchline); - } - else if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - const int punchlineLen = 200; - var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0]; - - ImGui.TextWrapped(firstLine.Length < punchlineLen - ? firstLine - : firstLine[..punchlineLen]); - } - } - - startCursor.Y += sectionSize; - ImGui.SetCursorPos(startCursor); - - return isOpen; - } - - private void DrawAvailablePlugin(RemotePluginManifest manifest, int index) - { - var configuration = Service.Get(); - var notifications = Service.Get(); - var pluginManager = Service.Get(); - - var useTesting = pluginManager.UseTesting(manifest); - var wasSeen = this.WasPluginSeen(manifest.InternalName); - - // Check for valid versions - if ((useTesting && manifest.TestingAssemblyVersion == null) || manifest.AssemblyVersion == null) - { - // Without a valid version, quit - return; - } - - // Name - var label = manifest.Name; - - // Testing - if (useTesting) - { - label += Locs.PluginTitleMod_TestingVersion; - } - - ImGui.PushID($"available{index}{manifest.InternalName}"); - - var isThirdParty = manifest.SourceRepo.IsThirdParty; - if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index)) - { - if (!wasSeen) - configuration.SeenPluginInternalName.Add(manifest.InternalName); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Indent(); - - // Installable from - if (manifest.SourceRepo.IsThirdParty) - { - var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.SourceRepo.PluginMasterUrl); - ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); - - ImGuiHelpers.ScaledDummy(2); - } - - // Description - if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - ImGui.TextWrapped(manifest.Description); - } - - ImGuiHelpers.ScaledDummy(5); - - // Controls - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; - - var versionString = useTesting - ? $"{manifest.TestingAssemblyVersion}" - : $"{manifest.AssemblyVersion}"; - - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); - } - else - { - var buttonText = Locs.PluginButton_InstallVersion(versionString); - if (ImGui.Button($"{buttonText}##{buttonText}{index}")) - { - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer)) - .ContinueWith(task => - { - // There is no need to set as Complete for an individual plugin installation - this.installStatus = OperationStatus.Idle; - if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name))) - { - if (task.Result.State == PluginState.Loaded) - { - notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success); - } - else - { - notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error); - this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name)); - } - } - }); - } - } - - this.DrawVisitRepoUrlButton(manifest.RepoUrl); - - if (!manifest.SourceRepo.IsThirdParty) - { - this.DrawSendFeedbackButton(manifest); - } - - ImGuiHelpers.ScaledDummy(5); - - if (this.DrawPluginImages(null, manifest, isThirdParty, index)) - ImGuiHelpers.ScaledDummy(5); - - ImGui.Unindent(); - } - - ImGui.PopID(); - } - - private void DrawAvailablePluginContextMenu(PluginManifest manifest) - { - var configuration = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); - - if (ImGui.BeginPopupContextItem("ItemContextMenu")) - { - if (ImGui.Selectable(Locs.PluginContext_MarkAllSeen)) - { - configuration.SeenPluginInternalName.AddRange(this.pluginListAvailable.Select(x => x.InternalName)); - configuration.Save(); - pluginManager.RefilterPluginMasters(); - } - - if (ImGui.Selectable(Locs.PluginContext_HidePlugin)) - { - Log.Debug($"Adding {manifest.InternalName} to hidden plugins"); - configuration.HiddenPluginInternalName.Add(manifest.InternalName); - configuration.Save(); - pluginManager.RefilterPluginMasters(); - } - - if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfig)) - { - Log.Debug($"Deleting config for {manifest.InternalName}"); - - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => - { - pluginManager.PluginConfigs.Delete(manifest.InternalName); - - var path = Path.Combine(startInfo.PluginDirectory, manifest.InternalName); - if (Directory.Exists(path)) - Directory.Delete(path, true); - }) - .ContinueWith(task => - { - this.installStatus = OperationStatus.Idle; - - this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(manifest.InternalName)); - }); - } - - ImGui.EndPopup(); - } - } - - private void DrawInstalledPlugin(LocalPlugin plugin, int index, bool showInstalled = false) - { - var configuration = Service.Get(); - var commandManager = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); - - var trouble = false; - - // Name - var label = plugin.Manifest.Name; - - // Testing - if (plugin.Manifest.Testing) - { - label += Locs.PluginTitleMod_TestingVersion; - } - - // Freshly installed - if (showInstalled) - { - label += Locs.PluginTitleMod_Installed; - } - - // Disabled - if (plugin.IsDisabled) - { - label += Locs.PluginTitleMod_Disabled; - trouble = true; - } - - // Load error - if (plugin.State == PluginState.LoadError) - { - label += Locs.PluginTitleMod_LoadError; - trouble = true; - } - - // Unload error - if (plugin.State == PluginState.UnloadError) - { - label += Locs.PluginTitleMod_UnloadError; - trouble = true; - } - - var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin); - // Update available - if (availablePluginUpdate != default) - { - label += Locs.PluginTitleMod_HasUpdate; - } - - // Freshly updated - var thisWasUpdated = false; - if (this.updatedPlugins != null && !plugin.IsDev) - { - var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName); - if (update != default) - { - if (update.WasUpdated) - { - thisWasUpdated = true; - label += Locs.PluginTitleMod_Updated; - } - else - { - label += Locs.PluginTitleMod_UpdateFailed; - } - } - } - - // Outdated API level - if (plugin.IsOutdated) - { - label += Locs.PluginTitleMod_OutdatedError; - trouble = true; - } - - // Banned - if (plugin.IsBanned) - { - label += Locs.PluginTitleMod_BannedError; - trouble = true; - } - - ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); - - if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index)) - { - if (!this.WasPluginSeen(plugin.Manifest.InternalName)) - configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName); - - var manifest = plugin.Manifest; - - ImGui.Indent(); - - // Name - ImGui.Text(manifest.Name); - - // Download count - var downloadText = plugin.IsDev - ? Locs.PluginBody_AuthorWithoutDownloadCount(manifest.Author) - : manifest.DownloadCount > 0 - ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) - : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText); - - var isThirdParty = manifest.IsThirdParty; - var canFeedback = !isThirdParty && !plugin.IsDev && plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel; - - // Installed from - if (plugin.IsDev) - { - var fileText = Locs.PluginBody_DevPluginPath(plugin.DllFile.FullName); - ImGui.TextColored(ImGuiColors.DalamudGrey3, fileText); - } - else if (isThirdParty) - { - var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.InstalledFromUrl); - ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); - } - - // Description - if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - ImGui.TextWrapped(manifest.Description); - } - - // Available commands (if loaded) - if (plugin.IsLoaded) - { - var commands = commandManager.Commands - .Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName) - .ToArray(); - - if (commands.Any()) - { - ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f)); - foreach (var command in commands) - { - ImGui.TextWrapped($"{command.Key} → {command.Value.HelpMessage}"); - } - } - } - - // Controls - this.DrawPluginControlButton(plugin); - this.DrawDevPluginButtons(plugin); - this.DrawDeletePluginButton(plugin); - this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl); - - if (canFeedback) - { - this.DrawSendFeedbackButton(plugin.Manifest); - } - - if (availablePluginUpdate != default) - this.DrawUpdateSinglePluginButton(availablePluginUpdate); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.AssemblyVersion}"); - - if (plugin.IsDev) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin); - } - - ImGuiHelpers.ScaledDummy(5); - - if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) - ImGuiHelpers.ScaledDummy(5); - - ImGui.Unindent(); - } - - if (thisWasUpdated && !plugin.Manifest.Changelog.IsNullOrEmpty()) - { - ImGuiHelpers.ScaledDummy(5); - - ImGui.PushStyleColor(ImGuiCol.ChildBg, this.changelogBgColor); - ImGui.PushStyleColor(ImGuiCol.Text, this.changelogTextColor); - - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(7, 5)); - - if (ImGui.BeginChild("##changelog", new Vector2(-1, 100), true, ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.AlwaysAutoResize)) - { - ImGui.Text("Changelog:"); - ImGuiHelpers.ScaledDummy(2); - ImGui.TextWrapped(plugin.Manifest.Changelog); - } - - ImGui.EndChild(); - - ImGui.PopStyleVar(); - ImGui.PopStyleColor(2); - } - - ImGui.PopID(); - } - - private void DrawInstalledPluginContextMenu(LocalPlugin plugin) - { - var pluginManager = Service.Get(); - - if (ImGui.BeginPopupContextItem("InstalledItemContextMenu")) - { - if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfigReload)) - { - Log.Debug($"Deleting config for {plugin.Manifest.InternalName}"); - - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.DeleteConfiguration(plugin)) - .ContinueWith(task => - { - this.installStatus = OperationStatus.Idle; - - this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name)); - }); - } - - ImGui.EndPopup(); - } - } - - private void DrawPluginControlButton(LocalPlugin plugin) - { - var configuration = Service.Get(); - var notifications = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); - - // Disable everything if the updater is running or another plugin is operating - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; - - // Disable everything if the plugin is outdated - disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned; - - if (plugin.State == PluginState.InProgress) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_Working); - } - else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) - { - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_Disable); - } - else - { - if (ImGui.Button(Locs.PluginButton_Disable)) - { - Task.Run(() => - { - var unloadTask = Task.Run(() => plugin.Unload()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); - - unloadTask.Wait(); - if (!unloadTask.Result) - return; - - var disableTask = Task.Run(() => plugin.Disable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); - - disableTask.Wait(); - if (!disableTask.Result) - return; - - if (!plugin.IsDev) - { - pluginManager.RemovePlugin(plugin); - } - - notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); - }); - } - } - - if (plugin.State == PluginState.Loaded) - { - // Only if the plugin isn't broken. - this.DrawOpenPluginSettingsButton(plugin); - } - } - else if (plugin.State == PluginState.Unloaded) - { - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_Load); - } - else - { - if (ImGui.Button(Locs.PluginButton_Load)) - { - Task.Run(() => - { - var enableTask = Task.Run(() => plugin.Enable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name)); - - enableTask.Wait(); - if (!enableTask.Result) - return; - - var loadTask = Task.Run(() => plugin.Load(PluginLoadReason.Installer)) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); - - loadTask.Wait(); - if (!loadTask.Result) - return; - }); - } - } - } - else if (plugin.State == PluginState.UnloadError) - { - ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); - } - } - - private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update) - { - var pluginManager = Service.Get(); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) - { - this.installStatus = OperationStatus.InProgress; - - Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false)) + Task.Run(() => pluginManager.UpdatePluginsAsync()) .ContinueWith(task => { - // There is no need to set as Complete for an individual plugin installation - this.installStatus = OperationStatus.Idle; + this.updateStatus = OperationStatus.Complete; - var errorMessage = Locs.ErrorModal_SingleUpdateFail(update.UpdateManifest.Name); - this.DisplayErrorContinuation(task, errorMessage); - - if (!task.Result.WasUpdated) + if (task.IsFaulted) { - this.ShowErrorModal(errorMessage); + this.updatePluginCount = 0; + this.updatedPlugins = null; + this.DisplayErrorContinuation(task, Locs.ErrorModal_UpdaterFatal); + } + else + { + this.updatedPlugins = task.Result.Where(res => res.WasUpdated).ToList(); + this.updatePluginCount = this.updatedPlugins.Count; + + var errorPlugins = task.Result.Where(res => !res.WasUpdated).ToList(); + var errorPluginCount = errorPlugins.Count; + + if (errorPluginCount > 0) + { + var errorMessage = this.updatePluginCount > 0 + ? Locs.ErrorModal_UpdaterFailPartial(this.updatePluginCount, errorPluginCount) + : Locs.ErrorModal_UpdaterFail(errorPluginCount); + + var hintInsert = errorPlugins + .Aggregate(string.Empty, (current, pluginUpdateStatus) => $"{current}* {pluginUpdateStatus.InternalName}\n") + .TrimEnd(); + errorMessage += Locs.ErrorModal_HintBlame(hintInsert); + + this.DisplayErrorContinuation(task, errorMessage); + } + + if (this.updatePluginCount > 0) + { + pluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); + notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); + } + else if (this.updatePluginCount == 0) + { + notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info); + } } }); } + } + } - if (ImGui.IsItemHovered()) + private void DrawErrorModal() + { + var modalTitle = Locs.ErrorModal_Title; + + if (ImGui.BeginPopupModal(modalTitle, ref this.errorModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(this.errorModalMessage); + ImGui.Spacing(); + + var buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); + + if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) { - var updateVersion = update.UseTesting - ? update.UpdateManifest.TestingAssemblyVersion - : update.UpdateManifest.AssemblyVersion; - ImGui.SetTooltip(Locs.PluginButtonToolTip_UpdateSingle(updateVersion.ToString())); + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + + if (this.errorModalOnNextFrame) + { + ImGui.OpenPopup(modalTitle); + this.errorModalOnNextFrame = false; + this.errorModalDrawing = true; + } + } + + private void DrawFeedbackModal() + { + var modalTitle = Locs.FeedbackModal_Title; + + if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(Locs.FeedbackModal_Text(this.feedbackPlugin.Name)); + + if (this.pluginListUpdatable.Any( + up => up.InstalledPlugin.Manifest.InternalName == this.feedbackPlugin?.InternalName)) + { + ImGui.TextColored(ImGuiColors.DalamudRed, Locs.FeedbackModal_HasUpdate); + } + + ImGui.Spacing(); + + ImGui.InputTextMultiline("###FeedbackContent", ref this.feedbackModalBody, 1000, new Vector2(400, 200)); + + ImGui.Spacing(); + + ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); + + ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException); + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint); + + ImGui.Spacing(); + + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_Hint); + + var buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); + + if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) + { + if (this.feedbackPlugin != null) + { + Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException)) + .ContinueWith( + t => + { + var notif = Service.Get(); + if (t.IsCanceled || t.IsFaulted) + notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error); + else + notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success); + }); + } + else + { + Log.Error("FeedbackPlugin was null."); + } + + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + + if (this.feedbackModalOnNextFrame) + { + ImGui.OpenPopup(modalTitle); + this.feedbackModalOnNextFrame = false; + this.feedbackModalDrawing = true; + this.feedbackModalBody = string.Empty; + this.feedbackModalContact = string.Empty; + this.feedbackModalIncludeException = false; + } + } + + /* + private void DrawPluginTabBar() + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); + + if (ImGui.BeginTabBar("PluginsTabBar", ImGuiTabBarFlags.NoTooltip)) + { + this.DrawPluginTab(Locs.TabTitle_AvailablePlugins, this.DrawAvailablePluginList); + this.DrawPluginTab(Locs.TabTitle_InstalledPlugins, this.DrawInstalledPluginList); + + if (this.hasDevPlugins) + { + this.DrawPluginTab(Locs.TabTitle_InstalledDevPlugins, this.DrawInstalledDevPluginList); + this.DrawPluginTab("Image/Icon Tester", this.DrawImageTester); } } - private void DrawOpenPluginSettingsButton(LocalPlugin plugin) + ImGui.PopStyleVar(); + } + */ + + /* + private void DrawPluginTab(string title, Action drawPluginList) + { + if (ImGui.BeginTabItem(title)) { - if (plugin.DalamudInterface?.UiBuilder?.HasConfigUi ?? false) + ImGui.BeginChild($"Scrolling{title}", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + + var ready = this.DrawPluginListLoading(); + + if (ready) { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) + drawPluginList(); + } + + ImGui.EndChild(); + + ImGui.EndTabItem(); + } + } + */ + + private void DrawAvailablePluginList() + { + var pluginList = this.pluginListAvailable; + + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible); + return; + } + + var filteredManifests = pluginList + .Where(rm => !this.IsManifestFiltered(rm)) + .ToList(); + + if (filteredManifests.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; + } + + // get list to show and reset category dirty flag + var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests); + + var i = 0; + foreach (var manifest in categoryManifestsList) + { + var remoteManifest = manifest as RemotePluginManifest; + var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest); + + ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}"); + if (isInstalled) + { + this.DrawInstalledPlugin(plugin, i++, true); + } + else + { + this.DrawAvailablePlugin(remoteManifest, i++); + } + + ImGui.PopID(); + } + } + + private void DrawInstalledPluginList() + { + var pluginList = this.pluginListInstalled; + + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; + } + + var filteredList = pluginList + .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) + .ToList(); + + if (filteredList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; + } + + var i = 0; + foreach (var plugin in filteredList) + { + this.DrawInstalledPlugin(plugin, i++); + } + } + + private void DrawInstalledDevPluginList() + { + var pluginList = this.pluginListInstalled + .Where(plugin => plugin.IsDev) + .ToList(); + + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; + } + + var filteredList = pluginList + .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) + .ToList(); + + if (filteredList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; + } + + var i = 0; + foreach (var plugin in filteredList) + { + this.DrawInstalledPlugin(plugin, i++); + } + } + + private void DrawPluginCategories() + { + var useContentHeight = -40f; // button height + spacing + var useMenuWidth = 180f; // works fine as static value, table can be resized by user + + var useContentWidth = ImGui.GetContentRegionAvail().X; + + if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale))) + { + ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0)); + if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV)) + { + ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + this.DrawPluginCategorySelectors(); + + ImGui.TableNextColumn(); + if (ImGui.BeginChild("ScrollingPlugins", new Vector2(-1, 0), false, ImGuiWindowFlags.NoBackground)) { - try - { - plugin.DalamudInterface.UiBuilder.OpenConfig(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error during OpenConfigUi: {plugin.Name}"); - } + this.DrawPluginCategoryContent(); } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration); - } + ImGui.EndChild(); + ImGui.EndTable(); + } + + ImGui.PopStyleVar(); + ImGui.EndChild(); + } + } + + private void DrawPluginCategorySelectors() + { + var colorSearchHighlight = Vector4.One; + unsafe + { + var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight); + if (colorPtr != null) + { + colorSearchHighlight = *colorPtr; } } - private void DrawSendFeedbackButton(PluginManifest manifest) + for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++) { + var groupInfo = this.categoryManager.GroupList[groupIdx]; + var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins; + if (!canShowGroup) + { + continue; + } + + ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx); + if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None)) + { + if (this.categoryManager.CurrentGroupIdx != groupIdx) + { + this.categoryManager.CurrentGroupIdx = groupIdx; + } + + ImGui.Indent(); + var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight()); + for (var categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++) + { + var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]); + + var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId); + if (hasSearchHighlight) + { + ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight); + } + + if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize)) + { + this.categoryManager.CurrentCategoryIdx = categoryIdx; + } + + if (hasSearchHighlight) + { + ImGui.PopStyleColor(); + } + } + + ImGui.Unindent(); + + if (groupIdx != this.categoryManager.GroupList.Length - 1) + { + ImGuiHelpers.ScaledDummy(5); + } + } + } + } + + private void DrawPluginCategoryContent() + { + var ready = this.DrawPluginListLoading(); + if (!this.categoryManager.IsSelectionValid || !ready) + { + return; + } + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); + + var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx]; + if (this.categoryManager.IsContentDirty) + { + // reset opened list of collapsibles when switching between categories + this.openPluginCollapsibles.Clear(); + + // do NOT reset dirty flag when Available group is selected, it will be handled by DrawAvailablePluginList() + if (groupInfo.GroupKind != PluginCategoryManager.GroupKind.Available) + { + this.categoryManager.ResetContentDirty(); + } + } + + if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.DevTools) + { + // this one is never sorted and remains in hardcoded order from group ctor + switch (this.categoryManager.CurrentCategoryIdx) + { + case 0: + this.DrawInstalledDevPluginList(); + break; + + case 1: + this.DrawImageTester(); + break; + + default: + // umm, there's nothing else, keep handled set and just skip drawing... + break; + } + } + else if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.Installed) + { + this.DrawInstalledPluginList(); + } + else + { + this.DrawAvailablePluginList(); + } + + ImGui.PopStyleVar(); + } + + private void DrawImageTester() + { + var sectionSize = ImGuiHelpers.GlobalScale * 66; + var startCursor = ImGui.GetCursorPos(); + + ImGui.PushStyleColor(ImGuiCol.Button, true ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); + + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); + + ImGui.Button($"###pluginTesterCollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize)); + + ImGui.PopStyleVar(); + + ImGui.PopStyleColor(3); + + ImGui.SetCursorPos(startCursor); + + var hasIcon = this.testerIcon != null; + + var iconTex = this.imageCache.DefaultIcon; + if (hasIcon) iconTex = this.testerIcon; + + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); + + var cursorBeforeImage = ImGui.GetCursorPos(); + ImGui.Image(iconTex.ImGuiHandle, iconSize); + ImGui.SameLine(); + + if (this.testerError) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment)) - { - this.feedbackPlugin = manifest; - this.feedbackModalOnNextFrame = true; - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.FeedbackModal_Title); - } } - - private void DrawDevPluginButtons(LocalPlugin localPlugin) + else if (this.testerUpdateAvailable) { - var configuration = Service.Get(); - - if (localPlugin is LocalDevPlugin plugin) - { - // https://colorswall.com/palette/2868/ - var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF; - var redColor = new Vector4(0xD9, 0x53, 0x4F, 0xFF) / 0xFF; - - // Load on boot - ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff)) - { - plugin.StartOnBoot ^= true; - configuration.Save(); - } - - ImGui.PopStyleColor(2); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_StartOnBoot); - } - - // Automatic reload - ImGui.PushStyleColor(ImGuiCol.Button, plugin.AutomaticReload ? greenColor : redColor); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.AutomaticReload ? greenColor : redColor); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.SyncAlt)) - { - plugin.AutomaticReload ^= true; - configuration.Save(); - } - - ImGui.PopStyleColor(2); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading); - } - } - } - - private void DrawDeletePluginButton(LocalPlugin plugin) - { - var unloaded = plugin.State == PluginState.Unloaded; - var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned); - - if (!showButton) - return; - - var pluginManager = Service.Get(); - + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt)) - { - try - { - plugin.DllFile.Delete(); - pluginManager.RemovePlugin(plugin); - } - catch (Exception ex) - { - Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); - - this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name); - this.errorModalDrawing = true; - this.errorModalOnNextFrame = true; - } - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin); - } } - private void DrawVisitRepoUrlButton(string? repoUrl) - { - if (!string.IsNullOrEmpty(repoUrl) && repoUrl.StartsWith("https://")) - { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Globe)) - { - try - { - _ = Process.Start(new ProcessStartInfo() - { - FileName = repoUrl, - UseShellExecute = true, - }); - } - catch (Exception ex) - { - Log.Error(ex, $"Could not open repoUrl: {repoUrl}"); - } - } + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_VisitPluginUrl); - } + var cursor = ImGui.GetCursorPos(); + // Name + ImGui.Text("My Cool Plugin"); + + // Download count + var downloadCountText = Locs.PluginBody_AuthorWithDownloadCount("Plugin Enjoyer", 69420); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); + + cursor.Y += ImGui.GetTextLineHeightWithSpacing(); + ImGui.SetCursorPos(cursor); + + // Description + ImGui.TextWrapped("This plugin does very many great things."); + + startCursor.Y += sectionSize; + ImGui.SetCursorPos(startCursor); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Indent(); + + // Description + ImGui.TextWrapped("This is a description.\nIt has multiple lines.\nTruly descriptive."); + + ImGuiHelpers.ScaledDummy(5); + + // Controls + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; + + var versionString = "1.0.0.0"; + + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); + } + else + { + var buttonText = Locs.PluginButton_InstallVersion(versionString); + ImGui.Button($"{buttonText}##{buttonText}testing"); } - private bool DrawPluginImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, int index) + this.DrawVisitRepoUrlButton("https://google.com"); + + if (this.testerImages != null) { - var hasImages = this.imageCache.TryGetImages(plugin, manifest, isThirdParty, out var imageTextures); - if (!hasImages || imageTextures.Length == 0) - return false; + ImGuiHelpers.ScaledDummy(5); const float thumbFactor = 2.7f; @@ -1812,55 +845,64 @@ namespace Dalamud.Interface.Internal.Windows var width = ImGui.GetWindowWidth(); - if (ImGui.BeginChild($"plugin{index}ImageScrolling", new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground)) + if (ImGui.BeginChild( + "pluginTestingImageScrolling", + new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), + false, + ImGuiWindowFlags.HorizontalScrollbar | + ImGuiWindowFlags.NoScrollWithMouse | + ImGuiWindowFlags.NoBackground)) { - for (var i = 0; i < imageTextures.Length; i++) + if (this.testerImages != null && this.testerImages is { Length: > 0 }) { - var image = imageTextures[i]; - if (image == null) - continue; - - ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - var popupId = $"plugin{index}image{i}"; - if (ImGui.BeginPopup(popupId)) + for (var i = 0; i < this.testerImages.Length; i++) { - if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) - ImGui.CloseCurrentPopup(); + var popupId = $"pluginTestingImage{i}"; + var image = this.testerImages[i]; + if (image == null) + continue; - ImGui.EndPopup(); - } + ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - ImGui.PopStyleVar(3); + if (ImGui.BeginPopup(popupId)) + { + if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) + ImGui.CloseCurrentPopup(); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + ImGui.EndPopup(); + } - float xAct = image.Width; - float yAct = image.Height; - float xMax = PluginImageCache.PluginImageWidth; - float yMax = PluginImageCache.PluginImageHeight; + ImGui.PopStyleVar(3); - // scale image if undersized - if (xAct < xMax && yAct < yMax) - { - var scale = Math.Min(xMax / xAct, yMax / yAct); - xAct *= scale; - yAct *= scale; - } + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); - if (ImGui.ImageButton(image.ImGuiHandle, size)) - ImGui.OpenPopup(popupId); + float xAct = image.Width; + float yAct = image.Height; + float xMax = PluginImageCache.PluginImageWidth; + float yMax = PluginImageCache.PluginImageHeight; - ImGui.PopStyleVar(); + // scale image if undersized + if (xAct < xMax && yAct < yMax) + { + var scale = Math.Min(xMax / xAct, yMax / yAct); + xAct *= scale; + yAct *= scale; + } - if (i < imageTextures.Length - 1) - { - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); + var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); + if (ImGui.ImageButton(image.ImGuiHandle, size)) + ImGui.OpenPopup(popupId); + + ImGui.PopStyleVar(); + + if (i < this.testerImages.Length - 1) + { + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + } } } } @@ -1870,422 +912,1379 @@ namespace Dalamud.Interface.Internal.Windows ImGui.PopStyleVar(); ImGui.PopStyleColor(); - return true; + ImGui.Unindent(); } - private bool IsManifestFiltered(PluginManifest manifest) + ImGuiHelpers.ScaledDummy(20); + + ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); + ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000); + ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000); + ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000); + ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000); + ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000); + + var im = Service.Get(); + if (ImGui.Button("Load")) { - var searchString = this.searchText.ToLowerInvariant(); - var hasSearchString = !string.IsNullOrWhiteSpace(searchString); - - return hasSearchString && !( - manifest.Name.ToLowerInvariant().Contains(searchString) || - manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase) || - (manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase))); - } - - private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(RemotePluginManifest manifest) - { - var plugin = this.pluginListInstalled.FirstOrDefault(plugin => plugin.Manifest.InternalName == manifest.InternalName); - var isInstalled = plugin != default; - - return (isInstalled, plugin); - } - - private void OnAvailablePluginsChanged() - { - var pluginManager = Service.Get(); - - // By removing installed plugins only when the available plugin list changes (basically when the window is - // opened), plugins that have been newly installed remain in the available plugin list as installed. - this.pluginListAvailable = pluginManager.AvailablePlugins - .Where(manifest => !this.IsManifestInstalled(manifest).IsInstalled) - .ToList(); - this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); - this.ResortPlugins(); - - this.UpdateCategoriesOnPluginsChange(); - } - - private void OnInstalledPluginsChanged() - { - var pluginManager = Service.Get(); - - this.pluginListInstalled = pluginManager.InstalledPlugins.ToList(); - this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); - this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev); - this.ResortPlugins(); - - this.UpdateCategoriesOnPluginsChange(); - } - - private void ResortPlugins() - { - switch (this.sortKind) + try { - case PluginSortKind.Alphabetical: - this.pluginListAvailable.Sort((p1, p2) => p1.Name.CompareTo(p2.Name)); - this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); - break; - case PluginSortKind.DownloadCount: - this.pluginListAvailable.Sort((p1, p2) => p2.DownloadCount.CompareTo(p1.DownloadCount)); - this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.DownloadCount.CompareTo(p1.Manifest.DownloadCount)); - break; - case PluginSortKind.LastUpdate: - this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate)); - this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.LastUpdate.CompareTo(p1.Manifest.LastUpdate)); - break; - case PluginSortKind.NewOrNot: - this.pluginListAvailable.Sort((p1, p2) => this.WasPluginSeen(p1.InternalName) - .CompareTo(this.WasPluginSeen(p2.InternalName))); - this.pluginListInstalled.Sort((p1, p2) => this.WasPluginSeen(p1.Manifest.InternalName) - .CompareTo(this.WasPluginSeen(p2.Manifest.InternalName))); - break; - default: - throw new InvalidEnumArgumentException("Unknown plugin sort type."); - } - } - - private bool WasPluginSeen(string internalName) => - Service.Get().SeenPluginInternalName.Contains(internalName); - - /// - /// A continuation task that displays any errors received into the error modal. - /// - /// The previous task. - /// An error message to be displayed. - /// A value indicating whether to continue with the next task. - private bool DisplayErrorContinuation(Task task, object state) - { - if (task.IsFaulted) - { - var errorModalMessage = state as string; - - foreach (var ex in task.Exception.InnerExceptions) + if (this.testerIcon != null) { - if (ex is PluginException) - { - Log.Error(ex, "Plugin installer threw an error"); -#if DEBUG - if (!string.IsNullOrEmpty(ex.Message)) - errorModalMessage += $"\n\n{ex.Message}"; -#endif - } - else - { - Log.Error(ex, "Plugin installer threw an unexpected error"); -#if DEBUG - if (!string.IsNullOrEmpty(ex.Message)) - errorModalMessage += $"\n\n{ex.Message}"; -#endif - } + this.testerIcon.Dispose(); + this.testerIcon = null; } - this.ShowErrorModal(errorModalMessage); + if (!this.testerIconPath.IsNullOrEmpty()) + { + this.testerIcon = im.LoadImage(this.testerIconPath); + } - return false; + this.testerImages = new TextureWrap[this.testerImagePaths.Length]; + + for (var i = 0; i < this.testerImagePaths.Length; i++) + { + if (this.testerImagePaths[i].IsNullOrEmpty()) + continue; + + if (this.testerImages[i] != null) + { + this.testerImages[i].Dispose(); + this.testerImages[i] = null; + } + + this.testerImages[i] = im.LoadImage(this.testerImagePaths[i]); + } } - - return true; - } - - private void ShowErrorModal(string message) - { - this.errorModalMessage = message; - this.errorModalDrawing = true; - this.errorModalOnNextFrame = true; - } - - private void UpdateCategoriesOnSearchChange() - { - if (string.IsNullOrEmpty(this.searchText)) + catch (Exception ex) { - this.categoryManager.SetCategoryHighlightsForPlugins(null); + Log.Error(ex, "Could not load plugin images for testing."); + } + } + + ImGui.Checkbox("Failed", ref this.testerError); + ImGui.Checkbox("Has Update", ref this.testerUpdateAvailable); + } + + private bool DrawPluginListLoading() + { + var pluginManager = Service.Get(); + + if (pluginManager.SafeMode) + { + ImGui.Text(Locs.TabBody_SafeMode); + return false; + } + + var ready = pluginManager.PluginsReady && pluginManager.ReposReady; + + if (!ready) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_LoadingPlugins); + } + + var failedRepos = pluginManager.Repos + .Where(repo => repo.State == PluginRepositoryState.Fail) + .ToArray(); + + if (failedRepos.Length > 0) + { + var failText = Locs.TabBody_DownloadFailed; + var aggFailText = failedRepos + .Select(repo => $"{failText} ({repo.PluginMasterUrl})") + .Aggregate((s1, s2) => $"{s1}\n{s2}"); + + ImGui.TextColored(ImGuiColors.DalamudRed, aggFailText); + } + + return ready; + } + + private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index) + { + ImGui.Separator(); + + var isOpen = this.openPluginCollapsibles.Contains(index); + + var sectionSize = ImGuiHelpers.GlobalScale * 66; + var startCursor = ImGui.GetCursorPos(); + + ImGui.PushStyleColor(ImGuiCol.Button, isOpen ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); + + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); + + if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize))) + { + if (isOpen) + { + this.openPluginCollapsibles.Remove(index); } else { - var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm)); - this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch); + this.openPluginCollapsibles.Add(index); + } + + isOpen = !isOpen; + } + + drawContextMenuAction?.Invoke(); + + ImGui.PopStyleVar(); + + ImGui.PopStyleColor(3); + + ImGui.SetCursorPos(startCursor); + + var iconTex = this.imageCache.DefaultIcon; + var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); + if (hasIcon && cachedIconTex != null) + { + iconTex = cachedIconTex; + } + + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); + + var cursorBeforeImage = ImGui.GetCursorPos(); + ImGui.Image(iconTex.ImGuiHandle, iconSize); + ImGui.SameLine(); + + if (updateAvailable) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); + ImGui.SameLine(); + } + else if (trouble) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); + ImGui.SameLine(); + } + + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + var cursor = ImGui.GetCursorPos(); + + // Name + ImGui.Text(label); + + // Download count + var downloadCountText = manifest.DownloadCount > 0 + ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) + : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); + + if (isNew) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.TankBlue, Locs.PluginTitleMod_New); + } + + cursor.Y += ImGui.GetTextLineHeightWithSpacing(); + ImGui.SetCursorPos(cursor); + + // Outdated warning + if (plugin is { IsOutdated: true, IsBanned: false }) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(Locs.PluginBody_Outdated); + ImGui.PopStyleColor(); + } + + // Banned warning + if (plugin is { IsBanned: true }) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(plugin.BanReason.IsNullOrEmpty() + ? Locs.PluginBody_Banned + : Locs.PluginBody_BannedReason(plugin.BanReason)); + + ImGui.PopStyleColor(); + } + + // Description + if (plugin is null or { IsOutdated: false, IsBanned: false }) + { + if (!string.IsNullOrWhiteSpace(manifest.Punchline)) + { + ImGui.TextWrapped(manifest.Punchline); + } + else if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + const int punchlineLen = 200; + var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0]; + + ImGui.TextWrapped(firstLine.Length < punchlineLen + ? firstLine + : firstLine[..punchlineLen]); } } - private void UpdateCategoriesOnPluginsChange() + startCursor.Y += sectionSize; + ImGui.SetCursorPos(startCursor); + + return isOpen; + } + + private void DrawAvailablePlugin(RemotePluginManifest manifest, int index) + { + var configuration = Service.Get(); + var notifications = Service.Get(); + var pluginManager = Service.Get(); + + var useTesting = pluginManager.UseTesting(manifest); + var wasSeen = this.WasPluginSeen(manifest.InternalName); + + // Check for valid versions + if ((useTesting && manifest.TestingAssemblyVersion == null) || manifest.AssemblyVersion == null) { - this.categoryManager.BuildCategories(this.pluginListAvailable); - this.UpdateCategoriesOnSearchChange(); + // Without a valid version, quit + return; } - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")] - private static class Locs + // Name + var label = manifest.Name; + + // Testing + if (useTesting) { - #region Window Title - - public static string WindowTitle => Loc.Localize("InstallerHeader", "Plugin Installer"); - - public static string WindowTitleMod_Testing => Loc.Localize("InstallerHeaderTesting", " (TESTING)"); - - #endregion - - #region Header - - public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove in-game plugins.\nThey are made by third-party developers."); - - public static string Header_SearchPlaceholder => Loc.Localize("InstallerSearch", "Search"); - - #endregion - - #region SortBy - - public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical"); - - public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count"); - - public static string SortBy_LastUpdate => Loc.Localize("InstallerLastUpdate", "Last Update"); - - public static string SortBy_NewOrNot => Loc.Localize("InstallerNewOrNot", "New or not"); - - public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By"); - - #endregion - - #region Tabs - - public static string TabTitle_AvailablePlugins => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); - - public static string TabTitle_InstalledPlugins => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); - - public static string TabTitle_InstalledDevPlugins => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); - - #endregion - - #region Tab body - - public static string TabBody_LoadingPlugins => Loc.Localize("InstallerLoading", "Loading plugins..."); - - public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed."); - - public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins."); - - #endregion - - #region Search text - - public static string TabBody_SearchNoMatching => Loc.Localize("InstallerNoMatching", "No plugins were found matching your search."); - - public static string TabBody_SearchNoCompatible => Loc.Localize("InstallerNoCompatible", "No compatible plugins were found :( Please restart your game and try again."); - - public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the Available Plugins tab."); - - #endregion - - #region Plugin title text - - public static string PluginTitleMod_Installed => Loc.Localize("InstallerInstalled", " (installed)"); - - public static string PluginTitleMod_Disabled => Loc.Localize("InstallerDisabled", " (disabled)"); - - public static string PluginTitleMod_Unloaded => Loc.Localize("InstallerUnloaded", " (unloaded)"); - - public static string PluginTitleMod_HasUpdate => Loc.Localize("InstallerHasUpdate", " (has update)"); - - public static string PluginTitleMod_Updated => Loc.Localize("InstallerUpdated", " (updated)"); - - public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)"); - - public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)"); - - public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)"); - - public static string PluginTitleMod_UnloadError => Loc.Localize("InstallerUnloadError", " (unload error)"); - - public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)"); - - public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (banned)"); - - public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); - - #endregion - - #region Plugin context menu - - public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen"); - - public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer"); - - public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin"); - - public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin settings & reload"); - - #endregion - - #region Plugin body - - public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author); - - public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0}, {1} downloads").Format(author, count); - - public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}, download count unavailable").Format(author); - - public static string PluginBody_DevPluginPath(string path) => Loc.Localize("InstallerDevPluginPath", "From {0}").Format(path); - - public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url); - - public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder."); - - public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder."); - - public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author."); - - public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin version is banned due to incompatibilities and not available at the moment. Please wait for it to be updated by its author."); - - public static string PluginBody_BannedReason(string message) => - Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin is banned: {0}").Format(message); - - #endregion - - #region Plugin buttons - - public static string PluginButton_InstallVersion(string version) => Loc.Localize("InstallerInstall", "Install v{0}").Format(version); - - public static string PluginButton_Working => Loc.Localize("InstallerWorking", "Working"); - - public static string PluginButton_Disable => Loc.Localize("InstallerDisable", "Disable"); - - public static string PluginButton_Load => Loc.Localize("InstallerLoad", "Load"); - - public static string PluginButton_Unload => Loc.Localize("InstallerUnload", "Unload"); - - #endregion - - #region Plugin button tooltips - - public static string PluginButtonToolTip_OpenConfiguration => Loc.Localize("InstallerOpenConfig", "Open Configuration"); - - public static string PluginButtonToolTip_StartOnBoot => Loc.Localize("InstallerStartOnBoot", "Start on boot"); - - public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading"); - - public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin"); - - public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL"); - - public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version); - - public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again."); - - #endregion - - #region Notifications - - public static string Notifications_PluginInstalledTitle => Loc.Localize("NotificationsPluginInstalledTitle", "Plugin installed!"); - - public static string Notifications_PluginInstalled(string name) => Loc.Localize("NotificationsPluginInstalled", "'{0}' was successfully installed.").Format(name); - - public static string Notifications_PluginNotInstalledTitle => Loc.Localize("NotificationsPluginNotInstalledTitle", "Plugin not installed!"); - - public static string Notifications_PluginNotInstalled(string name) => Loc.Localize("NotificationsPluginNotInstalled", "'{0}' failed to install.").Format(name); - - public static string Notifications_NoUpdatesFoundTitle => Loc.Localize("NotificationsNoUpdatesFoundTitle", "No updates found!"); - - public static string Notifications_NoUpdatesFound => Loc.Localize("NotificationsNoUpdatesFound", "No updates were found."); - - public static string Notifications_UpdatesInstalledTitle => Loc.Localize("NotificationsUpdatesInstalledTitle", "Updates installed!"); - - public static string Notifications_UpdatesInstalled(int count) => Loc.Localize("NotificationsUpdatesInstalled", "Updates for {0} of your plugins were installed.").Format(count); - - public static string Notifications_PluginDisabledTitle => Loc.Localize("NotificationsPluginDisabledTitle", "Plugin disabled!"); - - public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name); - - #endregion - - #region Footer - - public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins"); - - public static string FooterButton_InProgress => Loc.Localize("InstallerInProgress", "Install in progress..."); - - public static string FooterButton_NoUpdates => Loc.Localize("InstallerNoUpdates", "No updates found!"); - - public static string FooterButton_UpdateComplete(int count) => Loc.Localize("InstallerUpdateComplete", "{0} plugins updated!").Format(count); - - public static string FooterButton_Settings => Loc.Localize("InstallerSettings", "Settings"); - - public static string FooterButton_ScanDevPlugins => Loc.Localize("InstallerScanDevPlugins", "Scan Dev Plugins"); - - public static string FooterButton_Close => Loc.Localize("InstallerClose", "Close"); - - #endregion - - #region Error modal - - public static string ErrorModal_Title => Loc.Localize("InstallerError", "Installer Error"); - - public static string ErrorModal_InstallContactAuthor => Loc.Localize( - "InstallerContactAuthor", - "Please restart your game and try again. If this error occurs again, please contact the plugin author."); - - public static string ErrorModal_InstallFail(string name) => Loc.Localize("InstallerInstallFail", "Failed to install plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_SingleUpdateFail(string name) => Loc.Localize("InstallerSingleUpdateFail", "Failed to update plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_DeleteConfigFail(string name) => Loc.Localize("InstallerDeleteConfigFail", "Failed to reset the plugin {0}.\n\nThe plugin may not support this action. You can try deleting the configuration manually while the game is shut down - please see the FAQ.").Format(name); - - public static string ErrorModal_EnableFail(string name) => Loc.Localize("InstallerEnableFail", "Failed to enable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_DisableFail(string name) => Loc.Localize("InstallerDisableFail", "Failed to disable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_UnloadFail(string name) => Loc.Localize("InstallerUnloadFail", "Failed to unload plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_LoadFail(string name) => Loc.Localize("InstallerLoadFail", "Failed to load plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_DeleteFail(string name) => Loc.Localize("InstallerDeleteFail", "Failed to delete plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - - public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain."); - - public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount); - - public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount); - - public static string ErrorModal_HintBlame(string plugins) => Loc.Localize("InstallerErrorPluginInfo", "\n\nThe following plugins caused these issues:\n\n{0}\nYou may try removing these plugins manually and reinstalling them.").Format(plugins); - - // public static string ErrorModal_Hint => Loc.Localize("InstallerErrorHint", "The plugin installer ran into an issue or the plugin is incompatible.\nPlease restart the game and report this error on our discord."); - - #endregion - - #region Feedback Modal - - public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback"); - - public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.").Format(pluginName); - - public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs."); - - public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); - - public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message"); - - public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong."); - - public static string FeedbackModal_Hint => Loc.Localize("InstallerFeedbackHint", "All plugin developers will be able to see your feedback.\nPlease never include any personal or revealing information.\nIf you chose to include the last error message, information like your Windows username may be included.\n\nThe collected feedback is not stored on our end and immediately relayed to Discord."); - - public static string FeedbackModal_NotificationSuccess => Loc.Localize("InstallerFeedbackNotificationSuccess", "Your feedback was sent successfully!"); - - public static string FeedbackModal_NotificationError => Loc.Localize("InstallerFeedbackNotificationError", "Your feedback could not be sent."); - - #endregion - - #region Plugin Update chatbox - - public static string PluginUpdateHeader_Chatbox => Loc.Localize("DalamudPluginUpdates", "Updates:"); - - #endregion - - #region Error modal buttons - - public static string ErrorModalButton_Ok => Loc.Localize("OK", "OK"); - - #endregion + label += Locs.PluginTitleMod_TestingVersion; + } + + ImGui.PushID($"available{index}{manifest.InternalName}"); + + var isThirdParty = manifest.SourceRepo.IsThirdParty; + if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index)) + { + if (!wasSeen) + configuration.SeenPluginInternalName.Add(manifest.InternalName); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Indent(); + + // Installable from + if (manifest.SourceRepo.IsThirdParty) + { + var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.SourceRepo.PluginMasterUrl); + ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); + + ImGuiHelpers.ScaledDummy(2); + } + + // Description + if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + ImGui.TextWrapped(manifest.Description); + } + + ImGuiHelpers.ScaledDummy(5); + + // Controls + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; + + var versionString = useTesting + ? $"{manifest.TestingAssemblyVersion}" + : $"{manifest.AssemblyVersion}"; + + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); + } + else + { + var buttonText = Locs.PluginButton_InstallVersion(versionString); + if (ImGui.Button($"{buttonText}##{buttonText}{index}")) + { + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer)) + .ContinueWith(task => + { + // There is no need to set as Complete for an individual plugin installation + this.installStatus = OperationStatus.Idle; + if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name))) + { + if (task.Result.State == PluginState.Loaded) + { + notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success); + } + else + { + notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error); + this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name)); + } + } + }); + } + } + + this.DrawVisitRepoUrlButton(manifest.RepoUrl); + + if (!manifest.SourceRepo.IsThirdParty) + { + this.DrawSendFeedbackButton(manifest); + } + + ImGuiHelpers.ScaledDummy(5); + + if (this.DrawPluginImages(null, manifest, isThirdParty, index)) + ImGuiHelpers.ScaledDummy(5); + + ImGui.Unindent(); + } + + ImGui.PopID(); + } + + private void DrawAvailablePluginContextMenu(PluginManifest manifest) + { + var configuration = Service.Get(); + var pluginManager = Service.Get(); + var startInfo = Service.Get(); + + if (ImGui.BeginPopupContextItem("ItemContextMenu")) + { + if (ImGui.Selectable(Locs.PluginContext_MarkAllSeen)) + { + configuration.SeenPluginInternalName.AddRange(this.pluginListAvailable.Select(x => x.InternalName)); + configuration.Save(); + pluginManager.RefilterPluginMasters(); + } + + if (ImGui.Selectable(Locs.PluginContext_HidePlugin)) + { + Log.Debug($"Adding {manifest.InternalName} to hidden plugins"); + configuration.HiddenPluginInternalName.Add(manifest.InternalName); + configuration.Save(); + pluginManager.RefilterPluginMasters(); + } + + if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfig)) + { + Log.Debug($"Deleting config for {manifest.InternalName}"); + + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => + { + pluginManager.PluginConfigs.Delete(manifest.InternalName); + + var path = Path.Combine(startInfo.PluginDirectory, manifest.InternalName); + if (Directory.Exists(path)) + Directory.Delete(path, true); + }) + .ContinueWith(task => + { + this.installStatus = OperationStatus.Idle; + + this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(manifest.InternalName)); + }); + } + + ImGui.EndPopup(); } } + + private void DrawInstalledPlugin(LocalPlugin plugin, int index, bool showInstalled = false) + { + var configuration = Service.Get(); + var commandManager = Service.Get(); + var pluginManager = Service.Get(); + var startInfo = Service.Get(); + + var trouble = false; + + // Name + var label = plugin.Manifest.Name; + + // Testing + if (plugin.Manifest.Testing) + { + label += Locs.PluginTitleMod_TestingVersion; + } + + // Freshly installed + if (showInstalled) + { + label += Locs.PluginTitleMod_Installed; + } + + // Disabled + if (plugin.IsDisabled) + { + label += Locs.PluginTitleMod_Disabled; + trouble = true; + } + + // Load error + if (plugin.State == PluginState.LoadError) + { + label += Locs.PluginTitleMod_LoadError; + trouble = true; + } + + // Unload error + if (plugin.State == PluginState.UnloadError) + { + label += Locs.PluginTitleMod_UnloadError; + trouble = true; + } + + var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin); + // Update available + if (availablePluginUpdate != default) + { + label += Locs.PluginTitleMod_HasUpdate; + } + + // Freshly updated + var thisWasUpdated = false; + if (this.updatedPlugins != null && !plugin.IsDev) + { + var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName); + if (update != default) + { + if (update.WasUpdated) + { + thisWasUpdated = true; + label += Locs.PluginTitleMod_Updated; + } + else + { + label += Locs.PluginTitleMod_UpdateFailed; + } + } + } + + // Outdated API level + if (plugin.IsOutdated) + { + label += Locs.PluginTitleMod_OutdatedError; + trouble = true; + } + + // Banned + if (plugin.IsBanned) + { + label += Locs.PluginTitleMod_BannedError; + trouble = true; + } + + ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); + + if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index)) + { + if (!this.WasPluginSeen(plugin.Manifest.InternalName)) + configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName); + + var manifest = plugin.Manifest; + + ImGui.Indent(); + + // Name + ImGui.Text(manifest.Name); + + // Download count + var downloadText = plugin.IsDev + ? Locs.PluginBody_AuthorWithoutDownloadCount(manifest.Author) + : manifest.DownloadCount > 0 + ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) + : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText); + + var isThirdParty = manifest.IsThirdParty; + var canFeedback = !isThirdParty && !plugin.IsDev && plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel; + + // Installed from + if (plugin.IsDev) + { + var fileText = Locs.PluginBody_DevPluginPath(plugin.DllFile.FullName); + ImGui.TextColored(ImGuiColors.DalamudGrey3, fileText); + } + else if (isThirdParty) + { + var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.InstalledFromUrl); + ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); + } + + // Description + if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + ImGui.TextWrapped(manifest.Description); + } + + // Available commands (if loaded) + if (plugin.IsLoaded) + { + var commands = commandManager.Commands + .Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName) + .ToArray(); + + if (commands.Any()) + { + ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f)); + foreach (var command in commands) + { + ImGui.TextWrapped($"{command.Key} → {command.Value.HelpMessage}"); + } + } + } + + // Controls + this.DrawPluginControlButton(plugin); + this.DrawDevPluginButtons(plugin); + this.DrawDeletePluginButton(plugin); + this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl); + + if (canFeedback) + { + this.DrawSendFeedbackButton(plugin.Manifest); + } + + if (availablePluginUpdate != default) + this.DrawUpdateSinglePluginButton(availablePluginUpdate); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.AssemblyVersion}"); + + if (plugin.IsDev) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin); + } + + ImGuiHelpers.ScaledDummy(5); + + if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) + ImGuiHelpers.ScaledDummy(5); + + ImGui.Unindent(); + } + + if (thisWasUpdated && !plugin.Manifest.Changelog.IsNullOrEmpty()) + { + ImGuiHelpers.ScaledDummy(5); + + ImGui.PushStyleColor(ImGuiCol.ChildBg, this.changelogBgColor); + ImGui.PushStyleColor(ImGuiCol.Text, this.changelogTextColor); + + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(7, 5)); + + if (ImGui.BeginChild("##changelog", new Vector2(-1, 100), true, ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.Text("Changelog:"); + ImGuiHelpers.ScaledDummy(2); + ImGui.TextWrapped(plugin.Manifest.Changelog); + } + + ImGui.EndChild(); + + ImGui.PopStyleVar(); + ImGui.PopStyleColor(2); + } + + ImGui.PopID(); + } + + private void DrawInstalledPluginContextMenu(LocalPlugin plugin) + { + var pluginManager = Service.Get(); + + if (ImGui.BeginPopupContextItem("InstalledItemContextMenu")) + { + if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfigReload)) + { + Log.Debug($"Deleting config for {plugin.Manifest.InternalName}"); + + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => pluginManager.DeleteConfiguration(plugin)) + .ContinueWith(task => + { + this.installStatus = OperationStatus.Idle; + + this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name)); + }); + } + + ImGui.EndPopup(); + } + } + + private void DrawPluginControlButton(LocalPlugin plugin) + { + var configuration = Service.Get(); + var notifications = Service.Get(); + var pluginManager = Service.Get(); + var startInfo = Service.Get(); + + // Disable everything if the updater is running or another plugin is operating + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; + + // Disable everything if the plugin is outdated + disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned; + + if (plugin.State == PluginState.InProgress) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_Working); + } + else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) + { + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_Disable); + } + else + { + if (ImGui.Button(Locs.PluginButton_Disable)) + { + Task.Run(() => + { + var unloadTask = Task.Run(() => plugin.Unload()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); + + unloadTask.Wait(); + if (!unloadTask.Result) + return; + + var disableTask = Task.Run(() => plugin.Disable()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); + + disableTask.Wait(); + if (!disableTask.Result) + return; + + if (!plugin.IsDev) + { + pluginManager.RemovePlugin(plugin); + } + + notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); + }); + } + } + + if (plugin.State == PluginState.Loaded) + { + // Only if the plugin isn't broken. + this.DrawOpenPluginSettingsButton(plugin); + } + } + else if (plugin.State == PluginState.Unloaded) + { + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_Load); + } + else + { + if (ImGui.Button(Locs.PluginButton_Load)) + { + Task.Run(() => + { + var enableTask = Task.Run(() => plugin.Enable()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name)); + + enableTask.Wait(); + if (!enableTask.Result) + return; + + var loadTask = Task.Run(() => plugin.Load(PluginLoadReason.Installer)) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); + + loadTask.Wait(); + if (!loadTask.Result) + return; + }); + } + } + } + else if (plugin.State == PluginState.UnloadError) + { + ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); + } + } + + private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update) + { + var pluginManager = Service.Get(); + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) + { + this.installStatus = OperationStatus.InProgress; + + Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false)) + .ContinueWith(task => + { + // There is no need to set as Complete for an individual plugin installation + this.installStatus = OperationStatus.Idle; + + var errorMessage = Locs.ErrorModal_SingleUpdateFail(update.UpdateManifest.Name); + this.DisplayErrorContinuation(task, errorMessage); + + if (!task.Result.WasUpdated) + { + this.ShowErrorModal(errorMessage); + } + }); + } + + if (ImGui.IsItemHovered()) + { + var updateVersion = update.UseTesting + ? update.UpdateManifest.TestingAssemblyVersion + : update.UpdateManifest.AssemblyVersion; + ImGui.SetTooltip(Locs.PluginButtonToolTip_UpdateSingle(updateVersion.ToString())); + } + } + + private void DrawOpenPluginSettingsButton(LocalPlugin plugin) + { + if (plugin.DalamudInterface?.UiBuilder?.HasConfigUi ?? false) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) + { + try + { + plugin.DalamudInterface.UiBuilder.OpenConfig(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error during OpenConfigUi: {plugin.Name}"); + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration); + } + } + } + + private void DrawSendFeedbackButton(PluginManifest manifest) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment)) + { + this.feedbackPlugin = manifest; + this.feedbackModalOnNextFrame = true; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.FeedbackModal_Title); + } + } + + private void DrawDevPluginButtons(LocalPlugin localPlugin) + { + var configuration = Service.Get(); + + if (localPlugin is LocalDevPlugin plugin) + { + // https://colorswall.com/palette/2868/ + var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF; + var redColor = new Vector4(0xD9, 0x53, 0x4F, 0xFF) / 0xFF; + + // Load on boot + ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff)) + { + plugin.StartOnBoot ^= true; + configuration.Save(); + } + + ImGui.PopStyleColor(2); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_StartOnBoot); + } + + // Automatic reload + ImGui.PushStyleColor(ImGuiCol.Button, plugin.AutomaticReload ? greenColor : redColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.AutomaticReload ? greenColor : redColor); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.SyncAlt)) + { + plugin.AutomaticReload ^= true; + configuration.Save(); + } + + ImGui.PopStyleColor(2); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading); + } + } + } + + private void DrawDeletePluginButton(LocalPlugin plugin) + { + var unloaded = plugin.State == PluginState.Unloaded; + var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned); + + if (!showButton) + return; + + var pluginManager = Service.Get(); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt)) + { + try + { + plugin.DllFile.Delete(); + pluginManager.RemovePlugin(plugin); + } + catch (Exception ex) + { + Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); + + this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name); + this.errorModalDrawing = true; + this.errorModalOnNextFrame = true; + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin); + } + } + + private void DrawVisitRepoUrlButton(string? repoUrl) + { + if (!string.IsNullOrEmpty(repoUrl) && repoUrl.StartsWith("https://")) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Globe)) + { + try + { + _ = Process.Start(new ProcessStartInfo() + { + FileName = repoUrl, + UseShellExecute = true, + }); + } + catch (Exception ex) + { + Log.Error(ex, $"Could not open repoUrl: {repoUrl}"); + } + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_VisitPluginUrl); + } + } + + private bool DrawPluginImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, int index) + { + var hasImages = this.imageCache.TryGetImages(plugin, manifest, isThirdParty, out var imageTextures); + if (!hasImages || imageTextures.Length == 0) + return false; + + const float thumbFactor = 2.7f; + + var scrollBarSize = 15; + ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, scrollBarSize); + ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Vector4.Zero); + + var width = ImGui.GetWindowWidth(); + + if (ImGui.BeginChild($"plugin{index}ImageScrolling", new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground)) + { + for (var i = 0; i < imageTextures.Length; i++) + { + var image = imageTextures[i]; + if (image == null) + continue; + + ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + + var popupId = $"plugin{index}image{i}"; + if (ImGui.BeginPopup(popupId)) + { + if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) + ImGui.CloseCurrentPopup(); + + ImGui.EndPopup(); + } + + ImGui.PopStyleVar(3); + + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + + float xAct = image.Width; + float yAct = image.Height; + float xMax = PluginImageCache.PluginImageWidth; + float yMax = PluginImageCache.PluginImageHeight; + + // scale image if undersized + if (xAct < xMax && yAct < yMax) + { + var scale = Math.Min(xMax / xAct, yMax / yAct); + xAct *= scale; + yAct *= scale; + } + + var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); + if (ImGui.ImageButton(image.ImGuiHandle, size)) + ImGui.OpenPopup(popupId); + + ImGui.PopStyleVar(); + + if (i < imageTextures.Length - 1) + { + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + } + } + } + + ImGui.EndChild(); + + ImGui.PopStyleVar(); + ImGui.PopStyleColor(); + + return true; + } + + private bool IsManifestFiltered(PluginManifest manifest) + { + var searchString = this.searchText.ToLowerInvariant(); + var hasSearchString = !string.IsNullOrWhiteSpace(searchString); + + return hasSearchString && !( + manifest.Name.ToLowerInvariant().Contains(searchString) || + manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase) || + (manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase))); + } + + private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(RemotePluginManifest manifest) + { + var plugin = this.pluginListInstalled.FirstOrDefault(plugin => plugin.Manifest.InternalName == manifest.InternalName); + var isInstalled = plugin != default; + + return (isInstalled, plugin); + } + + private void OnAvailablePluginsChanged() + { + var pluginManager = Service.Get(); + + // By removing installed plugins only when the available plugin list changes (basically when the window is + // opened), plugins that have been newly installed remain in the available plugin list as installed. + this.pluginListAvailable = pluginManager.AvailablePlugins + .Where(manifest => !this.IsManifestInstalled(manifest).IsInstalled) + .ToList(); + this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); + this.ResortPlugins(); + + this.UpdateCategoriesOnPluginsChange(); + } + + private void OnInstalledPluginsChanged() + { + var pluginManager = Service.Get(); + + this.pluginListInstalled = pluginManager.InstalledPlugins.ToList(); + this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); + this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev); + this.ResortPlugins(); + + this.UpdateCategoriesOnPluginsChange(); + } + + private void ResortPlugins() + { + switch (this.sortKind) + { + case PluginSortKind.Alphabetical: + this.pluginListAvailable.Sort((p1, p2) => p1.Name.CompareTo(p2.Name)); + this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); + break; + case PluginSortKind.DownloadCount: + this.pluginListAvailable.Sort((p1, p2) => p2.DownloadCount.CompareTo(p1.DownloadCount)); + this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.DownloadCount.CompareTo(p1.Manifest.DownloadCount)); + break; + case PluginSortKind.LastUpdate: + this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate)); + this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.LastUpdate.CompareTo(p1.Manifest.LastUpdate)); + break; + case PluginSortKind.NewOrNot: + this.pluginListAvailable.Sort((p1, p2) => this.WasPluginSeen(p1.InternalName) + .CompareTo(this.WasPluginSeen(p2.InternalName))); + this.pluginListInstalled.Sort((p1, p2) => this.WasPluginSeen(p1.Manifest.InternalName) + .CompareTo(this.WasPluginSeen(p2.Manifest.InternalName))); + break; + default: + throw new InvalidEnumArgumentException("Unknown plugin sort type."); + } + } + + private bool WasPluginSeen(string internalName) => + Service.Get().SeenPluginInternalName.Contains(internalName); + + /// + /// A continuation task that displays any errors received into the error modal. + /// + /// The previous task. + /// An error message to be displayed. + /// A value indicating whether to continue with the next task. + private bool DisplayErrorContinuation(Task task, object state) + { + if (task.IsFaulted) + { + var errorModalMessage = state as string; + + foreach (var ex in task.Exception.InnerExceptions) + { + if (ex is PluginException) + { + Log.Error(ex, "Plugin installer threw an error"); +#if DEBUG + if (!string.IsNullOrEmpty(ex.Message)) + errorModalMessage += $"\n\n{ex.Message}"; +#endif + } + else + { + Log.Error(ex, "Plugin installer threw an unexpected error"); +#if DEBUG + if (!string.IsNullOrEmpty(ex.Message)) + errorModalMessage += $"\n\n{ex.Message}"; +#endif + } + } + + this.ShowErrorModal(errorModalMessage); + + return false; + } + + return true; + } + + private void ShowErrorModal(string message) + { + this.errorModalMessage = message; + this.errorModalDrawing = true; + this.errorModalOnNextFrame = true; + } + + private void UpdateCategoriesOnSearchChange() + { + if (string.IsNullOrEmpty(this.searchText)) + { + this.categoryManager.SetCategoryHighlightsForPlugins(null); + } + else + { + var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm)); + this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch); + } + } + + private void UpdateCategoriesOnPluginsChange() + { + this.categoryManager.BuildCategories(this.pluginListAvailable); + this.UpdateCategoriesOnSearchChange(); + } + + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")] + private static class Locs + { + #region Window Title + + public static string WindowTitle => Loc.Localize("InstallerHeader", "Plugin Installer"); + + public static string WindowTitleMod_Testing => Loc.Localize("InstallerHeaderTesting", " (TESTING)"); + + #endregion + + #region Header + + public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove in-game plugins.\nThey are made by third-party developers."); + + public static string Header_SearchPlaceholder => Loc.Localize("InstallerSearch", "Search"); + + #endregion + + #region SortBy + + public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical"); + + public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count"); + + public static string SortBy_LastUpdate => Loc.Localize("InstallerLastUpdate", "Last Update"); + + public static string SortBy_NewOrNot => Loc.Localize("InstallerNewOrNot", "New or not"); + + public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By"); + + #endregion + + #region Tabs + + public static string TabTitle_AvailablePlugins => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); + + public static string TabTitle_InstalledPlugins => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); + + public static string TabTitle_InstalledDevPlugins => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); + + #endregion + + #region Tab body + + public static string TabBody_LoadingPlugins => Loc.Localize("InstallerLoading", "Loading plugins..."); + + public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed."); + + public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins."); + + #endregion + + #region Search text + + public static string TabBody_SearchNoMatching => Loc.Localize("InstallerNoMatching", "No plugins were found matching your search."); + + public static string TabBody_SearchNoCompatible => Loc.Localize("InstallerNoCompatible", "No compatible plugins were found :( Please restart your game and try again."); + + public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the Available Plugins tab."); + + #endregion + + #region Plugin title text + + public static string PluginTitleMod_Installed => Loc.Localize("InstallerInstalled", " (installed)"); + + public static string PluginTitleMod_Disabled => Loc.Localize("InstallerDisabled", " (disabled)"); + + public static string PluginTitleMod_Unloaded => Loc.Localize("InstallerUnloaded", " (unloaded)"); + + public static string PluginTitleMod_HasUpdate => Loc.Localize("InstallerHasUpdate", " (has update)"); + + public static string PluginTitleMod_Updated => Loc.Localize("InstallerUpdated", " (updated)"); + + public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)"); + + public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)"); + + public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)"); + + public static string PluginTitleMod_UnloadError => Loc.Localize("InstallerUnloadError", " (unload error)"); + + public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)"); + + public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (banned)"); + + public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); + + #endregion + + #region Plugin context menu + + public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen"); + + public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer"); + + public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin"); + + public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin settings & reload"); + + #endregion + + #region Plugin body + + public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author); + + public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0}, {1} downloads").Format(author, count); + + public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}, download count unavailable").Format(author); + + public static string PluginBody_DevPluginPath(string path) => Loc.Localize("InstallerDevPluginPath", "From {0}").Format(path); + + public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url); + + public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder."); + + public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder."); + + public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author."); + + public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin version is banned due to incompatibilities and not available at the moment. Please wait for it to be updated by its author."); + + public static string PluginBody_BannedReason(string message) => + Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin is banned: {0}").Format(message); + + #endregion + + #region Plugin buttons + + public static string PluginButton_InstallVersion(string version) => Loc.Localize("InstallerInstall", "Install v{0}").Format(version); + + public static string PluginButton_Working => Loc.Localize("InstallerWorking", "Working"); + + public static string PluginButton_Disable => Loc.Localize("InstallerDisable", "Disable"); + + public static string PluginButton_Load => Loc.Localize("InstallerLoad", "Load"); + + public static string PluginButton_Unload => Loc.Localize("InstallerUnload", "Unload"); + + #endregion + + #region Plugin button tooltips + + public static string PluginButtonToolTip_OpenConfiguration => Loc.Localize("InstallerOpenConfig", "Open Configuration"); + + public static string PluginButtonToolTip_StartOnBoot => Loc.Localize("InstallerStartOnBoot", "Start on boot"); + + public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading"); + + public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin"); + + public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL"); + + public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version); + + public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again."); + + #endregion + + #region Notifications + + public static string Notifications_PluginInstalledTitle => Loc.Localize("NotificationsPluginInstalledTitle", "Plugin installed!"); + + public static string Notifications_PluginInstalled(string name) => Loc.Localize("NotificationsPluginInstalled", "'{0}' was successfully installed.").Format(name); + + public static string Notifications_PluginNotInstalledTitle => Loc.Localize("NotificationsPluginNotInstalledTitle", "Plugin not installed!"); + + public static string Notifications_PluginNotInstalled(string name) => Loc.Localize("NotificationsPluginNotInstalled", "'{0}' failed to install.").Format(name); + + public static string Notifications_NoUpdatesFoundTitle => Loc.Localize("NotificationsNoUpdatesFoundTitle", "No updates found!"); + + public static string Notifications_NoUpdatesFound => Loc.Localize("NotificationsNoUpdatesFound", "No updates were found."); + + public static string Notifications_UpdatesInstalledTitle => Loc.Localize("NotificationsUpdatesInstalledTitle", "Updates installed!"); + + public static string Notifications_UpdatesInstalled(int count) => Loc.Localize("NotificationsUpdatesInstalled", "Updates for {0} of your plugins were installed.").Format(count); + + public static string Notifications_PluginDisabledTitle => Loc.Localize("NotificationsPluginDisabledTitle", "Plugin disabled!"); + + public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name); + + #endregion + + #region Footer + + public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins"); + + public static string FooterButton_InProgress => Loc.Localize("InstallerInProgress", "Install in progress..."); + + public static string FooterButton_NoUpdates => Loc.Localize("InstallerNoUpdates", "No updates found!"); + + public static string FooterButton_UpdateComplete(int count) => Loc.Localize("InstallerUpdateComplete", "{0} plugins updated!").Format(count); + + public static string FooterButton_Settings => Loc.Localize("InstallerSettings", "Settings"); + + public static string FooterButton_ScanDevPlugins => Loc.Localize("InstallerScanDevPlugins", "Scan Dev Plugins"); + + public static string FooterButton_Close => Loc.Localize("InstallerClose", "Close"); + + #endregion + + #region Error modal + + public static string ErrorModal_Title => Loc.Localize("InstallerError", "Installer Error"); + + public static string ErrorModal_InstallContactAuthor => Loc.Localize( + "InstallerContactAuthor", + "Please restart your game and try again. If this error occurs again, please contact the plugin author."); + + public static string ErrorModal_InstallFail(string name) => Loc.Localize("InstallerInstallFail", "Failed to install plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_SingleUpdateFail(string name) => Loc.Localize("InstallerSingleUpdateFail", "Failed to update plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_DeleteConfigFail(string name) => Loc.Localize("InstallerDeleteConfigFail", "Failed to reset the plugin {0}.\n\nThe plugin may not support this action. You can try deleting the configuration manually while the game is shut down - please see the FAQ.").Format(name); + + public static string ErrorModal_EnableFail(string name) => Loc.Localize("InstallerEnableFail", "Failed to enable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_DisableFail(string name) => Loc.Localize("InstallerDisableFail", "Failed to disable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_UnloadFail(string name) => Loc.Localize("InstallerUnloadFail", "Failed to unload plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_LoadFail(string name) => Loc.Localize("InstallerLoadFail", "Failed to load plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_DeleteFail(string name) => Loc.Localize("InstallerDeleteFail", "Failed to delete plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + + public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain."); + + public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount); + + public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount); + + public static string ErrorModal_HintBlame(string plugins) => Loc.Localize("InstallerErrorPluginInfo", "\n\nThe following plugins caused these issues:\n\n{0}\nYou may try removing these plugins manually and reinstalling them.").Format(plugins); + + // public static string ErrorModal_Hint => Loc.Localize("InstallerErrorHint", "The plugin installer ran into an issue or the plugin is incompatible.\nPlease restart the game and report this error on our discord."); + + #endregion + + #region Feedback Modal + + public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback"); + + public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.").Format(pluginName); + + public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs."); + + public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); + + public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message"); + + public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong."); + + public static string FeedbackModal_Hint => Loc.Localize("InstallerFeedbackHint", "All plugin developers will be able to see your feedback.\nPlease never include any personal or revealing information.\nIf you chose to include the last error message, information like your Windows username may be included.\n\nThe collected feedback is not stored on our end and immediately relayed to Discord."); + + public static string FeedbackModal_NotificationSuccess => Loc.Localize("InstallerFeedbackNotificationSuccess", "Your feedback was sent successfully!"); + + public static string FeedbackModal_NotificationError => Loc.Localize("InstallerFeedbackNotificationError", "Your feedback could not be sent."); + + #endregion + + #region Plugin Update chatbox + + public static string PluginUpdateHeader_Chatbox => Loc.Localize("DalamudPluginUpdates", "Updates:"); + + #endregion + + #region Error modal buttons + + public static string ErrorModalButton_Ok => Loc.Localize("OK", "OK"); + + #endregion + } } diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index 257b67857..986f01b7b 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -11,264 +11,263 @@ using Dalamud.Plugin.Internal; using Dalamud.Plugin.Internal.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// This window displays plugin statistics for troubleshooting. +/// +internal class PluginStatWindow : Window { + private bool showDalamudHooks; + /// - /// This window displays plugin statistics for troubleshooting. + /// Initializes a new instance of the class. /// - internal class PluginStatWindow : Window + public PluginStatWindow() + : base("Plugin Statistics###DalamudPluginStatWindow") { - private bool showDalamudHooks; + this.RespectCloseHotkey = false; + } - /// - /// Initializes a new instance of the class. - /// - public PluginStatWindow() - : base("Plugin Statistics###DalamudPluginStatWindow") + /// + public override void Draw() + { + var pluginManager = Service.Get(); + + ImGui.BeginTabBar("Stat Tabs"); + + if (ImGui.BeginTabItem("Draw times")) { - this.RespectCloseHotkey = false; - } + var doStats = UiBuilder.DoStats; - /// - public override void Draw() - { - var pluginManager = Service.Get(); - - ImGui.BeginTabBar("Stat Tabs"); - - if (ImGui.BeginTabItem("Draw times")) + if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats)) { - var doStats = UiBuilder.DoStats; - - if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats)) - { - UiBuilder.DoStats = doStats; - } - - if (doStats) - { - ImGui.SameLine(); - if (ImGui.Button("Reset")) - { - foreach (var plugin in pluginManager.InstalledPlugins) - { - plugin.DalamudInterface.UiBuilder.LastDrawTime = -1; - plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1; - plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear(); - } - } - - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 180f); - ImGui.SetColumnWidth(1, 100f); - ImGui.SetColumnWidth(2, 100f); - ImGui.SetColumnWidth(3, 100f); - - ImGui.Text("Plugin"); - ImGui.NextColumn(); - - ImGui.Text("Last"); - ImGui.NextColumn(); - - ImGui.Text("Longest"); - ImGui.NextColumn(); - - ImGui.Text("Average"); - ImGui.NextColumn(); - - ImGui.Separator(); - - foreach (var plugin in pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded)) - { - ImGui.Text(plugin.Manifest.Name); - ImGui.NextColumn(); - - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); - ImGui.NextColumn(); - - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); - ImGui.NextColumn(); - - if (plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0) - { - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms"); - } - else - { - ImGui.Text("-"); - } - - ImGui.NextColumn(); - } - - ImGui.Columns(1); - } - - ImGui.EndTabItem(); + UiBuilder.DoStats = doStats; } - if (ImGui.BeginTabItem("Framework times")) + if (doStats) { - var doStats = Framework.StatsEnabled; - - if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) + ImGui.SameLine(); + if (ImGui.Button("Reset")) { - Framework.StatsEnabled = doStats; + foreach (var plugin in pluginManager.InstalledPlugins) + { + plugin.DalamudInterface.UiBuilder.LastDrawTime = -1; + plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1; + plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear(); + } } - if (doStats) - { - ImGui.SameLine(); - if (ImGui.Button("Reset")) - { - Framework.StatsHistory.Clear(); - } - - ImGui.Columns(4); - - ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300); - ImGui.SetColumnWidth(1, 100f); - ImGui.SetColumnWidth(2, 100f); - ImGui.SetColumnWidth(3, 100f); - - ImGui.Text("Method"); - ImGui.NextColumn(); - - ImGui.Text("Last"); - ImGui.NextColumn(); - - ImGui.Text("Longest"); - ImGui.NextColumn(); - - ImGui.Text("Average"); - ImGui.NextColumn(); - - ImGui.Separator(); - ImGui.Separator(); - - foreach (var handlerHistory in Framework.StatsHistory) - { - if (handlerHistory.Value.Count == 0) - continue; - - ImGui.SameLine(); - - ImGui.Text($"{handlerHistory.Key}"); - ImGui.NextColumn(); - - ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); - ImGui.NextColumn(); - - ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); - ImGui.NextColumn(); - - ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); - ImGui.NextColumn(); - - ImGui.Separator(); - } - - ImGui.Columns(0); - } - - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem("Hooks")) - { ImGui.Columns(4); - - ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330); - ImGui.SetColumnWidth(1, 180f); + ImGui.SetColumnWidth(0, 180f); + ImGui.SetColumnWidth(1, 100f); ImGui.SetColumnWidth(2, 100f); ImGui.SetColumnWidth(3, 100f); - ImGui.Text("Detour Method"); - ImGui.SameLine(); - - ImGui.Text(" "); - ImGui.SameLine(); - - ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks); + ImGui.Text("Plugin"); ImGui.NextColumn(); - ImGui.Text("Address"); + ImGui.Text("Last"); ImGui.NextColumn(); - ImGui.Text("Status"); + ImGui.Text("Longest"); ImGui.NextColumn(); - ImGui.Text("Backend"); + ImGui.Text("Average"); ImGui.NextColumn(); - ImGui.Separator(); ImGui.Separator(); - foreach (var trackedHook in HookManager.TrackedHooks) + foreach (var plugin in pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded)) { - try + ImGui.Text(plugin.Manifest.Name); + ImGui.NextColumn(); + + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); + ImGui.NextColumn(); + + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); + ImGui.NextColumn(); + + if (plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0) { - if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) - continue; - - ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); - ImGui.TextDisabled(trackedHook.Assembly.FullName); - ImGui.NextColumn(); - if (!trackedHook.Hook.IsDisposed) - { - ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}"); - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); - } - - var processMemoryOffset = trackedHook.InProcessMemory; - if (processMemoryOffset.HasValue) - { - ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}"); - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); - } - } - } - - ImGui.NextColumn(); - - if (trackedHook.Hook.IsDisposed) - { - ImGui.Text("Disposed"); - } - else - { - ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); - } - - ImGui.NextColumn(); - - ImGui.Text(trackedHook.Hook.BackendName); - - ImGui.NextColumn(); + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms"); } - catch (Exception ex) + else { - ImGui.Text(ex.Message); - ImGui.NextColumn(); - while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn(); + ImGui.Text("-"); } + ImGui.NextColumn(); + } + + ImGui.Columns(1); + } + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Framework times")) + { + var doStats = Framework.StatsEnabled; + + if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) + { + Framework.StatsEnabled = doStats; + } + + if (doStats) + { + ImGui.SameLine(); + if (ImGui.Button("Reset")) + { + Framework.StatsHistory.Clear(); + } + + ImGui.Columns(4); + + ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300); + ImGui.SetColumnWidth(1, 100f); + ImGui.SetColumnWidth(2, 100f); + ImGui.SetColumnWidth(3, 100f); + + ImGui.Text("Method"); + ImGui.NextColumn(); + + ImGui.Text("Last"); + ImGui.NextColumn(); + + ImGui.Text("Longest"); + ImGui.NextColumn(); + + ImGui.Text("Average"); + ImGui.NextColumn(); + + ImGui.Separator(); + ImGui.Separator(); + + foreach (var handlerHistory in Framework.StatsHistory) + { + if (handlerHistory.Value.Count == 0) + continue; + + ImGui.SameLine(); + + ImGui.Text($"{handlerHistory.Key}"); + ImGui.NextColumn(); + + ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); + ImGui.NextColumn(); + + ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); + ImGui.NextColumn(); + + ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); + ImGui.NextColumn(); + ImGui.Separator(); } - ImGui.Columns(); + ImGui.Columns(0); } - if (ImGui.IsWindowAppearing()) - { - HookManager.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed); - } - - ImGui.EndTabBar(); + ImGui.EndTabItem(); } + + if (ImGui.BeginTabItem("Hooks")) + { + ImGui.Columns(4); + + ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330); + ImGui.SetColumnWidth(1, 180f); + ImGui.SetColumnWidth(2, 100f); + ImGui.SetColumnWidth(3, 100f); + + ImGui.Text("Detour Method"); + ImGui.SameLine(); + + ImGui.Text(" "); + ImGui.SameLine(); + + ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks); + ImGui.NextColumn(); + + ImGui.Text("Address"); + ImGui.NextColumn(); + + ImGui.Text("Status"); + ImGui.NextColumn(); + + ImGui.Text("Backend"); + ImGui.NextColumn(); + + ImGui.Separator(); + ImGui.Separator(); + + foreach (var trackedHook in HookManager.TrackedHooks) + { + try + { + if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) + continue; + + ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); + ImGui.TextDisabled(trackedHook.Assembly.FullName); + ImGui.NextColumn(); + if (!trackedHook.Hook.IsDisposed) + { + ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); + } + + var processMemoryOffset = trackedHook.InProcessMemory; + if (processMemoryOffset.HasValue) + { + ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); + } + } + } + + ImGui.NextColumn(); + + if (trackedHook.Hook.IsDisposed) + { + ImGui.Text("Disposed"); + } + else + { + ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); + } + + ImGui.NextColumn(); + + ImGui.Text(trackedHook.Hook.BackendName); + + ImGui.NextColumn(); + } + catch (Exception ex) + { + ImGui.Text(ex.Message); + ImGui.NextColumn(); + while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn(); + } + + ImGui.Separator(); + } + + ImGui.Columns(); + } + + if (ImGui.IsWindowAppearing()) + { + HookManager.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed); + } + + ImGui.EndTabBar(); } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs index a8cb3e4a1..242e93a49 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs @@ -2,47 +2,46 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the Actor Table. +/// +internal class ActorTableAgingStep : IAgingStep { - /// - /// Test setup for the Actor Table. - /// - internal class ActorTableAgingStep : IAgingStep + private int index = 0; + + /// + public string Name => "Test ActorTable"; + + /// + public SelfTestStepResult RunStep() { - private int index = 0; + var objectTable = Service.Get(); - /// - public string Name => "Test ActorTable"; + ImGui.Text("Checking actor table..."); - /// - public SelfTestStepResult RunStep() + if (this.index == objectTable.Length - 1) { - var objectTable = Service.Get(); + return SelfTestStepResult.Pass; + } - ImGui.Text("Checking actor table..."); - - if (this.index == objectTable.Length - 1) - { - return SelfTestStepResult.Pass; - } - - var actor = objectTable[this.index]; - this.index++; - - if (actor == null) - { - return SelfTestStepResult.Waiting; - } - - Util.ShowObject(actor); + var actor = objectTable[this.index]; + this.index++; + if (actor == null) + { return SelfTestStepResult.Waiting; } - /// - public void CleanUp() - { - // ignored - } + Util.ShowObject(actor); + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs index 02613dee2..c7b6213d4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs @@ -3,71 +3,70 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for Chat. +/// +internal class ChatAgingStep : IAgingStep { - /// - /// Test setup for Chat. - /// - internal class ChatAgingStep : IAgingStep + private int step = 0; + private bool subscribed = false; + private bool hasPassed = false; + + /// + public string Name => "Test Chat"; + + /// + public SelfTestStepResult RunStep() { - private int step = 0; - private bool subscribed = false; - private bool hasPassed = false; + var chatGui = Service.Get(); - /// - public string Name => "Test Chat"; - - /// - public SelfTestStepResult RunStep() + switch (this.step) { - var chatGui = Service.Get(); + case 0: + chatGui.Print("Testing!"); + this.step++; - switch (this.step) - { - case 0: - chatGui.Print("Testing!"); - this.step++; + break; - break; + case 1: + ImGui.Text("Type \"/e DALAMUD\" in chat..."); - case 1: - ImGui.Text("Type \"/e DALAMUD\" in chat..."); + if (!this.subscribed) + { + this.subscribed = true; + chatGui.ChatMessage += this.ChatOnOnChatMessage; + } - if (!this.subscribed) - { - this.subscribed = true; - chatGui.ChatMessage += this.ChatOnOnChatMessage; - } + if (this.hasPassed) + { + chatGui.ChatMessage -= this.ChatOnOnChatMessage; + this.subscribed = false; + return SelfTestStepResult.Pass; + } - if (this.hasPassed) - { - chatGui.ChatMessage -= this.ChatOnOnChatMessage; - this.subscribed = false; - return SelfTestStepResult.Pass; - } - - break; - } - - return SelfTestStepResult.Waiting; + break; } - /// - public void CleanUp() - { - var chatGui = Service.Get(); + return SelfTestStepResult.Waiting; + } - chatGui.ChatMessage -= this.ChatOnOnChatMessage; - this.subscribed = false; - } + /// + public void CleanUp() + { + var chatGui = Service.Get(); - private void ChatOnOnChatMessage( - XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled) + chatGui.ChatMessage -= this.ChatOnOnChatMessage; + this.subscribed = false; + } + + private void ChatOnOnChatMessage( + XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled) + { + if (type == XivChatType.Echo && message.TextValue == "DALAMUD") { - if (type == XivChatType.Echo && message.TextValue == "DALAMUD") - { - this.hasPassed = true; - } + this.hasPassed = true; } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs index 501920343..fee692ab8 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs @@ -2,36 +2,35 @@ using Dalamud.Game.ClientState.Conditions; using ImGuiNET; using Serilog; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for Condition. +/// +internal class ConditionAgingStep : IAgingStep { - /// - /// Test setup for Condition. - /// - internal class ConditionAgingStep : IAgingStep + /// + public string Name => "Test Condition"; + + /// + public SelfTestStepResult RunStep() { - /// - public string Name => "Test Condition"; + var condition = Service.Get(); - /// - public SelfTestStepResult RunStep() + if (!condition.Any()) { - var condition = Service.Get(); - - if (!condition.Any()) - { - Log.Error("No condition flags present."); - return SelfTestStepResult.Fail; - } - - ImGui.Text("Please jump..."); - - return condition[ConditionFlag.Jumping] ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + Log.Error("No condition flags present."); + return SelfTestStepResult.Fail; } - /// - public void CleanUp() - { - // ignored - } + ImGui.Text("Please jump..."); + + return condition[ConditionFlag.Jumping] ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs index 487a6e572..d301cb1ff 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs @@ -1,70 +1,69 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for Territory Change. +/// +internal class EnterTerritoryAgingStep : IAgingStep { + private readonly ushort territory; + private readonly string terriName; + private bool subscribed = false; + private bool hasPassed = false; + /// - /// Test setup for Territory Change. + /// Initializes a new instance of the class. /// - internal class EnterTerritoryAgingStep : IAgingStep + /// The territory to check for. + /// Name to show. + public EnterTerritoryAgingStep(ushort terri, string name) { - private readonly ushort territory; - private readonly string terriName; - private bool subscribed = false; - private bool hasPassed = false; + this.terriName = name; + this.territory = terri; + } - /// - /// Initializes a new instance of the class. - /// - /// The territory to check for. - /// Name to show. - public EnterTerritoryAgingStep(ushort terri, string name) + /// + public string Name => $"Enter Terri: {this.terriName}"; + + /// + public SelfTestStepResult RunStep() + { + var clientState = Service.Get(); + + ImGui.TextUnformatted(this.Name); + + if (!this.subscribed) { - this.terriName = name; - this.territory = terri; + clientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; + this.subscribed = true; } - /// - public string Name => $"Enter Terri: {this.terriName}"; - - /// - public SelfTestStepResult RunStep() + if (this.hasPassed) { - var clientState = Service.Get(); - - ImGui.TextUnformatted(this.Name); - - if (!this.subscribed) - { - clientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; - this.subscribed = true; - } - - if (this.hasPassed) - { - clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; - this.subscribed = false; - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - var clientState = Service.Get(); - clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; this.subscribed = false; + return SelfTestStepResult.Pass; } - private void ClientStateOnTerritoryChanged(object sender, ushort e) + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + var clientState = Service.Get(); + + clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; + this.subscribed = false; + } + + private void ClientStateOnTerritoryChanged(object sender, ushort e) + { + if (e == this.territory) { - if (e == this.territory) - { - this.hasPassed = true; - } + this.hasPassed = true; } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs index b037d06ca..a8fe60aa9 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs @@ -2,47 +2,46 @@ using Dalamud.Game.ClientState.Fates; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the Fate Table. +/// +internal class FateTableAgingStep : IAgingStep { - /// - /// Test setup for the Fate Table. - /// - internal class FateTableAgingStep : IAgingStep + private int index = 0; + + /// + public string Name => "Test FateTable"; + + /// + public SelfTestStepResult RunStep() { - private int index = 0; + var fateTable = Service.Get(); - /// - public string Name => "Test FateTable"; + ImGui.Text("Checking fate table..."); - /// - public SelfTestStepResult RunStep() + if (this.index == fateTable.Length - 1) { - var fateTable = Service.Get(); + return SelfTestStepResult.Pass; + } - ImGui.Text("Checking fate table..."); - - if (this.index == fateTable.Length - 1) - { - return SelfTestStepResult.Pass; - } - - var actor = fateTable[this.index]; - this.index++; - - if (actor == null) - { - return SelfTestStepResult.Waiting; - } - - Util.ShowObject(actor); + var actor = fateTable[this.index]; + this.index++; + if (actor == null) + { return SelfTestStepResult.Waiting; } - /// - public void CleanUp() - { - // ignored - } + Util.ShowObject(actor); + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs index 320e0d55c..31ba8e5d6 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs @@ -1,37 +1,36 @@ using Dalamud.Game.ClientState.GamePad; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the Gamepad State. +/// +internal class GamepadStateAgingStep : IAgingStep { - /// - /// Test setup for the Gamepad State. - /// - internal class GamepadStateAgingStep : IAgingStep + /// + public string Name => "Test GamePadState"; + + /// + public SelfTestStepResult RunStep() { - /// - public string Name => "Test GamePadState"; + var gamepadState = Service.Get(); - /// - public SelfTestStepResult RunStep() + ImGui.Text("Hold down North, East, L1"); + + if (gamepadState.Pressed(GamepadButtons.North) == 1 + && gamepadState.Pressed(GamepadButtons.East) == 1 + && gamepadState.Pressed(GamepadButtons.L1) == 1) { - var gamepadState = Service.Get(); - - ImGui.Text("Hold down North, East, L1"); - - if (gamepadState.Pressed(GamepadButtons.North) == 1 - && gamepadState.Pressed(GamepadButtons.East) == 1 - && gamepadState.Pressed(GamepadButtons.L1) == 1) - { - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; + return SelfTestStepResult.Pass; } - /// - public void CleanUp() - { - // ignored - } + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs index 7a63d6ba7..43798968e 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs @@ -1,35 +1,34 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test dedicated to handling of Access Violations. +/// +internal class HandledExceptionAgingStep : IAgingStep { - /// - /// Test dedicated to handling of Access Violations. - /// - internal class HandledExceptionAgingStep : IAgingStep + /// + public string Name => "Test Handled Exception"; + + /// + public SelfTestStepResult RunStep() { - /// - public string Name => "Test Handled Exception"; - - /// - public SelfTestStepResult RunStep() + try { - try - { - Marshal.ReadByte(IntPtr.Zero); - } - catch (AccessViolationException) - { - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Fail; + Marshal.ReadByte(IntPtr.Zero); + } + catch (AccessViolationException) + { + return SelfTestStepResult.Pass; } - /// - public void CleanUp() - { - // ignored - } + return SelfTestStepResult.Fail; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs index 713d0ebc4..b4b069487 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs @@ -1,58 +1,57 @@ using Dalamud.Game.Gui; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the Hover events. +/// +internal class HoverAgingStep : IAgingStep { - /// - /// Test setup for the Hover events. - /// - internal class HoverAgingStep : IAgingStep + private bool clearedItem = false; + private bool clearedAction = false; + + /// + public string Name => "Test Hover"; + + /// + public SelfTestStepResult RunStep() { - private bool clearedItem = false; - private bool clearedAction = false; + var gameGui = Service.Get(); - /// - public string Name => "Test Hover"; - - /// - public SelfTestStepResult RunStep() + if (!this.clearedItem) { - var gameGui = Service.Get(); + ImGui.Text("Hover WHM soul crystal..."); - if (!this.clearedItem) + if (gameGui.HoveredItem == 4547) { - ImGui.Text("Hover WHM soul crystal..."); - - if (gameGui.HoveredItem == 4547) - { - this.clearedItem = true; - } + this.clearedItem = true; } - - if (!this.clearedAction) - { - ImGui.Text("Hover \"Open Linkshells\" action..."); - - if (gameGui.HoveredAction != null && - gameGui.HoveredAction.ActionKind == HoverActionKind.MainCommand && - gameGui.HoveredAction.ActionID == 28) - { - this.clearedAction = true; - } - } - - if (this.clearedItem && this.clearedAction) - { - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; } - /// - public void CleanUp() + if (!this.clearedAction) { - // ignored + ImGui.Text("Hover \"Open Linkshells\" action..."); + + if (gameGui.HoveredAction != null && + gameGui.HoveredAction.ActionKind == HoverActionKind.MainCommand && + gameGui.HoveredAction.ActionID == 28) + { + this.clearedAction = true; + } } + + if (this.clearedItem && this.clearedAction) + { + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs index 6315f9d05..680961344 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs @@ -1,24 +1,23 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Interface for test implementations. +/// +internal interface IAgingStep { /// - /// Interface for test implementations. + /// Gets the name of the test. /// - internal interface IAgingStep - { - /// - /// Gets the name of the test. - /// - public string Name { get; } + public string Name { get; } - /// - /// Run the test step, once per frame it is active. - /// - /// The result of this frame, test is discarded once a result other than is returned. - public SelfTestStepResult RunStep(); + /// + /// Run the test step, once per frame it is active. + /// + /// The result of this frame, test is discarded once a result other than is returned. + public SelfTestStepResult RunStep(); - /// - /// Clean up this test. - /// - public void CleanUp(); - } + /// + /// Clean up this test. + /// + public void CleanUp(); } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs index 20b645b15..c1ae9289a 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs @@ -1,39 +1,38 @@ using Dalamud.Game.ClientState.Keys; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the Key State. +/// +internal class KeyStateAgingStep : IAgingStep { - /// - /// Test setup for the Key State. - /// - internal class KeyStateAgingStep : IAgingStep + /// + public string Name => "Test KeyState"; + + /// + public SelfTestStepResult RunStep() { - /// - public string Name => "Test KeyState"; + var keyState = Service.Get(); - /// - public SelfTestStepResult RunStep() + ImGui.Text("Hold down D,A,L,M,U"); + + if (keyState[VirtualKey.D] + && keyState[VirtualKey.A] + && keyState[VirtualKey.L] + && keyState[VirtualKey.M] + && keyState[VirtualKey.U]) { - var keyState = Service.Get(); - - ImGui.Text("Hold down D,A,L,M,U"); - - if (keyState[VirtualKey.D] - && keyState[VirtualKey.A] - && keyState[VirtualKey.L] - && keyState[VirtualKey.M] - && keyState[VirtualKey.U]) - { - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; + return SelfTestStepResult.Pass; } - /// - public void CleanUp() - { - // ignored - } + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs index 47a7f9bc9..c1dba389f 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs @@ -3,57 +3,56 @@ using System; using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the login events. +/// +internal class LoginEventAgingStep : IAgingStep { - /// - /// Test setup for the login events. - /// - internal class LoginEventAgingStep : IAgingStep + private bool subscribed = false; + private bool hasPassed = false; + + /// + public string Name => "Test Log-In"; + + /// + public SelfTestStepResult RunStep() { - private bool subscribed = false; - private bool hasPassed = false; + var clientState = Service.Get(); - /// - public string Name => "Test Log-In"; + ImGui.Text("Log in now..."); - /// - public SelfTestStepResult RunStep() + if (!this.subscribed) { - var clientState = Service.Get(); - - ImGui.Text("Log in now..."); - - if (!this.subscribed) - { - clientState.Login += this.ClientStateOnOnLogin; - this.subscribed = true; - } - - if (this.hasPassed) - { - clientState.Login -= this.ClientStateOnOnLogin; - this.subscribed = false; - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; + clientState.Login += this.ClientStateOnOnLogin; + this.subscribed = true; } - /// - public void CleanUp() + if (this.hasPassed) { - var clientState = Service.Get(); - - if (this.subscribed) - { - clientState.Login -= this.ClientStateOnOnLogin; - this.subscribed = false; - } + clientState.Login -= this.ClientStateOnOnLogin; + this.subscribed = false; + return SelfTestStepResult.Pass; } - private void ClientStateOnOnLogin(object sender, EventArgs e) + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + var clientState = Service.Get(); + + if (this.subscribed) { - this.hasPassed = true; + clientState.Login -= this.ClientStateOnOnLogin; + this.subscribed = false; } } + + private void ClientStateOnOnLogin(object sender, EventArgs e) + { + this.hasPassed = true; + } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs index 8c216eb19..2cb7b30f9 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs @@ -3,57 +3,56 @@ using System; using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for the login events. +/// +internal class LogoutEventAgingStep : IAgingStep { - /// - /// Test setup for the login events. - /// - internal class LogoutEventAgingStep : IAgingStep + private bool subscribed = false; + private bool hasPassed = false; + + /// + public string Name => "Test Log-In"; + + /// + public SelfTestStepResult RunStep() { - private bool subscribed = false; - private bool hasPassed = false; + var clientState = Service.Get(); - /// - public string Name => "Test Log-In"; + ImGui.Text("Log out now..."); - /// - public SelfTestStepResult RunStep() + if (!this.subscribed) { - var clientState = Service.Get(); - - ImGui.Text("Log out now..."); - - if (!this.subscribed) - { - clientState.Logout += this.ClientStateOnOnLogout; - this.subscribed = true; - } - - if (this.hasPassed) - { - clientState.Logout -= this.ClientStateOnOnLogout; - this.subscribed = false; - return SelfTestStepResult.Pass; - } - - return SelfTestStepResult.Waiting; + clientState.Logout += this.ClientStateOnOnLogout; + this.subscribed = true; } - /// - public void CleanUp() + if (this.hasPassed) { - var clientState = Service.Get(); - - if (this.subscribed) - { - clientState.Logout -= this.ClientStateOnOnLogout; - this.subscribed = false; - } + clientState.Logout -= this.ClientStateOnOnLogout; + this.subscribed = false; + return SelfTestStepResult.Pass; } - private void ClientStateOnOnLogout(object sender, EventArgs e) + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + var clientState = Service.Get(); + + if (this.subscribed) { - this.hasPassed = true; + clientState.Logout -= this.ClientStateOnOnLogout; + this.subscribed = false; } } + + private void ClientStateOnOnLogout(object sender, EventArgs e) + { + this.hasPassed = true; + } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs index e1ab91978..a07b21e54 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs @@ -5,38 +5,37 @@ using Dalamud.Data; using Dalamud.Utility; using Lumina.Excel; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for Lumina. +/// +/// ExcelRow to run test on. +internal class LuminaAgingStep : IAgingStep + where T : ExcelRow { - /// - /// Test setup for Lumina. - /// - /// ExcelRow to run test on. - internal class LuminaAgingStep : IAgingStep - where T : ExcelRow + private int step = 0; + private List rows; + + /// + public string Name => "Test Lumina"; + + /// + public SelfTestStepResult RunStep() { - private int step = 0; - private List rows; + var dataManager = Service.Get(); - /// - public string Name => "Test Lumina"; + this.rows ??= dataManager.GetExcelSheet().ToList(); - /// - public SelfTestStepResult RunStep() - { - var dataManager = Service.Get(); + Util.ShowObject(this.rows[this.step]); - this.rows ??= dataManager.GetExcelSheet().ToList(); + this.step++; + return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } - Util.ShowObject(this.rows[this.step]); - - this.step++; - return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored - } + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs index 6e8edbe29..eea015ad8 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs @@ -2,57 +2,56 @@ using Dalamud.Game.Gui.PartyFinder; using Dalamud.Game.Gui.PartyFinder.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for Party Finder events. +/// +internal class PartyFinderAgingStep : IAgingStep { - /// - /// Test setup for Party Finder events. - /// - internal class PartyFinderAgingStep : IAgingStep + private bool subscribed = false; + private bool hasPassed = false; + + /// + public string Name => "Test Party Finder"; + + /// + public SelfTestStepResult RunStep() { - private bool subscribed = false; - private bool hasPassed = false; + var partyFinderGui = Service.Get(); - /// - public string Name => "Test Party Finder"; - - /// - public SelfTestStepResult RunStep() + if (!this.subscribed) { - var partyFinderGui = Service.Get(); - - if (!this.subscribed) - { - partyFinderGui.ReceiveListing += this.PartyFinderOnReceiveListing; - this.subscribed = true; - } - - if (this.hasPassed) - { - partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; - this.subscribed = false; - return SelfTestStepResult.Pass; - } - - ImGui.Text("Open Party Finder"); - - return SelfTestStepResult.Waiting; + partyFinderGui.ReceiveListing += this.PartyFinderOnReceiveListing; + this.subscribed = true; } - /// - public void CleanUp() + if (this.hasPassed) { - var partyFinderGui = Service.Get(); - - if (this.subscribed) - { - partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; - this.subscribed = false; - } + partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; + this.subscribed = false; + return SelfTestStepResult.Pass; } - private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) + ImGui.Text("Open Party Finder"); + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + var partyFinderGui = Service.Get(); + + if (this.subscribed) { - this.hasPassed = true; + partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; + this.subscribed = false; } } + + private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) + { + this.hasPassed = true; + } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs index 6866cdd52..97cf9d604 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs @@ -3,74 +3,73 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for targets. +/// +internal class TargetAgingStep : IAgingStep { - /// - /// Test setup for targets. - /// - internal class TargetAgingStep : IAgingStep + private int step = 0; + + /// + public string Name => "Test Target"; + + /// + public SelfTestStepResult RunStep() { - private int step = 0; + var targetManager = Service.Get(); - /// - public string Name => "Test Target"; - - /// - public SelfTestStepResult RunStep() + switch (this.step) { - var targetManager = Service.Get(); + case 0: + targetManager.ClearTarget(); + targetManager.ClearFocusTarget(); - switch (this.step) - { - case 0: - targetManager.ClearTarget(); - targetManager.ClearFocusTarget(); + this.step++; + break; + + case 1: + ImGui.Text("Target a player..."); + + var cTarget = targetManager.Target; + if (cTarget is PlayerCharacter) + { this.step++; + } - break; + break; - case 1: - ImGui.Text("Target a player..."); + case 2: + ImGui.Text("Focus-Target a Battle NPC..."); - var cTarget = targetManager.Target; - if (cTarget is PlayerCharacter) - { - this.step++; - } + var fTarget = targetManager.FocusTarget; + if (fTarget is BattleNpc) + { + this.step++; + } - break; + break; - case 2: - ImGui.Text("Focus-Target a Battle NPC..."); + case 3: + ImGui.Text("Soft-Target an EventObj..."); - var fTarget = targetManager.FocusTarget; - if (fTarget is BattleNpc) - { - this.step++; - } + var sTarget = targetManager.FocusTarget; + if (sTarget is EventObj) + { + return SelfTestStepResult.Pass; + } - break; - - case 3: - ImGui.Text("Soft-Target an EventObj..."); - - var sTarget = targetManager.FocusTarget; - if (sTarget is EventObj) - { - return SelfTestStepResult.Pass; - } - - break; - } - - return SelfTestStepResult.Waiting; + break; } - /// - public void CleanUp() - { - // ignored - } + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs index 239f8650e..f02eafc99 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs @@ -1,30 +1,29 @@ using Dalamud.Game.Gui.Toast; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test setup for toasts. +/// +internal class ToastAgingStep : IAgingStep { - /// - /// Test setup for toasts. - /// - internal class ToastAgingStep : IAgingStep + /// + public string Name => "Test Toasts"; + + /// + public SelfTestStepResult RunStep() { - /// - public string Name => "Test Toasts"; + var toastGui = Service.Get(); - /// - public SelfTestStepResult RunStep() - { - var toastGui = Service.Get(); + toastGui.ShowNormal("Normal Toast"); + toastGui.ShowError("Error Toast"); - toastGui.ShowNormal("Normal Toast"); - toastGui.ShowError("Error Toast"); + return SelfTestStepResult.Pass; + } - return SelfTestStepResult.Pass; - } - - /// - public void CleanUp() - { - // ignored - } + /// + public void CleanUp() + { + // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs index 57e7e99ac..54aeee145 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs @@ -1,38 +1,37 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; + +/// +/// Test that waits N frames. +/// +internal class WaitFramesAgingStep : IAgingStep { + private readonly int frames; + private int cFrames; + /// - /// Test that waits N frames. + /// Initializes a new instance of the class. /// - internal class WaitFramesAgingStep : IAgingStep + /// Amount of frames to wait. + public WaitFramesAgingStep(int frames) { - private readonly int frames; - private int cFrames; + this.frames = frames; + this.cFrames = frames; + } - /// - /// Initializes a new instance of the class. - /// - /// Amount of frames to wait. - public WaitFramesAgingStep(int frames) - { - this.frames = frames; - this.cFrames = frames; - } + /// + public string Name => $"Wait {this.cFrames} frames"; - /// - public string Name => $"Wait {this.cFrames} frames"; + /// + public SelfTestStepResult RunStep() + { + this.cFrames--; - /// - public SelfTestStepResult RunStep() - { - this.cFrames--; + return this.cFrames <= 0 ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } - return this.cFrames <= 0 ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - this.cFrames = this.frames; - } + /// + public void CleanUp() + { + this.cFrames = this.frames; } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs index 70dddcab9..36842c934 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest +namespace Dalamud.Interface.Internal.Windows.SelfTest; + +/// +/// Enum declaring result states of tests. +/// +internal enum SelfTestStepResult { /// - /// Enum declaring result states of tests. + /// Test was not ran. /// - internal enum SelfTestStepResult - { - /// - /// Test was not ran. - /// - NotRan, + NotRan, - /// - /// Test is waiting for completion. - /// - Waiting, + /// + /// Test is waiting for completion. + /// + Waiting, - /// - /// Test has failed. - /// - Fail, + /// + /// Test has failed. + /// + Fail, - /// - /// Test has passed. - /// - Pass, - } + /// + /// Test has passed. + /// + Pass, } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index d97fad7bc..47afef69f 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -11,246 +11,245 @@ using Dalamud.Logging.Internal; using ImGuiNET; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Interface.Internal.Windows.SelfTest +namespace Dalamud.Interface.Internal.Windows.SelfTest; + +/// +/// Window for the Self-Test logic. +/// +internal class SelfTestWindow : Window { + private static readonly ModuleLog Log = new("AGING"); + + private readonly List steps = + new() + { + new LoginEventAgingStep(), + new WaitFramesAgingStep(1000), + new EnterTerritoryAgingStep(148, "Central Shroud"), + new ActorTableAgingStep(), + new FateTableAgingStep(), + new ConditionAgingStep(), + new ToastAgingStep(), + new TargetAgingStep(), + new KeyStateAgingStep(), + new GamepadStateAgingStep(), + new ChatAgingStep(), + new HoverAgingStep(), + new LuminaAgingStep(), + new PartyFinderAgingStep(), + new HandledExceptionAgingStep(), + new LogoutEventAgingStep(), + }; + + private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); + + private bool selfTestRunning = false; + private int currentStep = 0; + + private DateTimeOffset lastTestStart; + /// - /// Window for the Self-Test logic. + /// Initializes a new instance of the class. /// - internal class SelfTestWindow : Window + public SelfTestWindow() + : base("Dalamud Self-Test", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) { - private static readonly ModuleLog Log = new("AGING"); + this.Size = new Vector2(800, 800); + this.SizeCondition = ImGuiCond.FirstUseEver; - private readonly List steps = - new() - { - new LoginEventAgingStep(), - new WaitFramesAgingStep(1000), - new EnterTerritoryAgingStep(148, "Central Shroud"), - new ActorTableAgingStep(), - new FateTableAgingStep(), - new ConditionAgingStep(), - new ToastAgingStep(), - new TargetAgingStep(), - new KeyStateAgingStep(), - new GamepadStateAgingStep(), - new ChatAgingStep(), - new HoverAgingStep(), - new LuminaAgingStep(), - new PartyFinderAgingStep(), - new HandledExceptionAgingStep(), - new LogoutEventAgingStep(), - }; + this.RespectCloseHotkey = false; + } - private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); - - private bool selfTestRunning = false; - private int currentStep = 0; - - private DateTimeOffset lastTestStart; - - /// - /// Initializes a new instance of the class. - /// - public SelfTestWindow() - : base("Dalamud Self-Test", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) + /// + public override void Draw() + { + if (this.selfTestRunning) { - this.Size = new Vector2(800, 800); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.RespectCloseHotkey = false; - } - - /// - public override void Draw() - { - if (this.selfTestRunning) + if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) { - if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) - { - this.StopTests(); - } - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward)) - { - this.stepResults.Add((SelfTestStepResult.NotRan, null)); - this.currentStep++; - this.lastTestStart = DateTimeOffset.Now; - - if (this.currentStep >= this.steps.Count) - { - this.StopTests(); - } - } - } - else - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) - { - this.selfTestRunning = true; - this.currentStep = 0; - this.stepResults.Clear(); - this.lastTestStart = DateTimeOffset.Now; - } + this.StopTests(); } ImGui.SameLine(); - ImGui.TextUnformatted($"Step: {this.currentStep} / {this.steps.Count}"); - - ImGuiHelpers.ScaledDummy(10); - - this.DrawResultTable(); - - ImGuiHelpers.ScaledDummy(10); - - if (this.currentStep >= this.steps.Count) + if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward)) { - if (this.selfTestRunning) + this.stepResults.Add((SelfTestStepResult.NotRan, null)); + this.currentStep++; + this.lastTestStart = DateTimeOffset.Now; + + if (this.currentStep >= this.steps.Count) { this.StopTests(); } - - if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail)) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!"); - } - else - { - ImGui.TextColored(ImGuiColors.HealerGreen, "All checks passed!"); - } - - return; } - - if (!this.selfTestRunning) + } + else + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) { - return; - } - - ImGui.Separator(); - - var step = this.steps[this.currentStep]; - ImGui.TextUnformatted($"Current: {step.Name}"); - - ImGuiHelpers.ScaledDummy(10); - - SelfTestStepResult result; - try - { - result = step.RunStep(); - } - catch (Exception ex) - { - Log.Error(ex, $"Step failed: {step.Name}"); - result = SelfTestStepResult.Fail; - } - - ImGui.Separator(); - - if (result != SelfTestStepResult.Waiting) - { - var duration = DateTimeOffset.Now - this.lastTestStart; - this.currentStep++; - this.stepResults.Add((result, duration)); - + this.selfTestRunning = true; + this.currentStep = 0; + this.stepResults.Clear(); this.lastTestStart = DateTimeOffset.Now; } } - private void DrawResultTable() + ImGui.SameLine(); + + ImGui.TextUnformatted($"Step: {this.currentStep} / {this.steps.Count}"); + + ImGuiHelpers.ScaledDummy(10); + + this.DrawResultTable(); + + ImGuiHelpers.ScaledDummy(10); + + if (this.currentStep >= this.steps.Count) { - if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders)) + if (this.selfTestRunning) { - ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f); - ImGui.TableSetupColumn("Name"); - ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f); - ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f); - - ImGui.TableHeadersRow(); - - for (var i = 0; i < this.steps.Count; i++) - { - var step = this.steps[i]; - ImGui.TableNextRow(); - - ImGui.TableSetColumnIndex(0); - ImGui.Text(i.ToString()); - - ImGui.TableSetColumnIndex(1); - ImGui.Text(step.Name); - - ImGui.TableSetColumnIndex(2); - ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont); - if (this.stepResults.Count > i) - { - var result = this.stepResults[i]; - - switch (result.Result) - { - case SelfTestStepResult.Pass: - ImGui.TextColored(ImGuiColors.HealerGreen, "PASS"); - break; - case SelfTestStepResult.Fail: - ImGui.TextColored(ImGuiColors.DalamudRed, "FAIL"); - break; - default: - ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); - break; - } - } - else - { - if (this.selfTestRunning && this.currentStep == i) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT"); - } - else - { - ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); - } - } - - ImGui.PopFont(); - - ImGui.TableSetColumnIndex(3); - if (this.stepResults.Count > i) - { - var (_, duration) = this.stepResults[i]; - - if (duration.HasValue) - { - ImGui.TextUnformatted(duration.Value.ToString("g")); - } - } - else - { - if (this.selfTestRunning && this.currentStep == i) - { - ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g")); - } - } - } - - ImGui.EndTable(); + this.StopTests(); } + + if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail)) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!"); + } + else + { + ImGui.TextColored(ImGuiColors.HealerGreen, "All checks passed!"); + } + + return; } - private void StopTests() + if (!this.selfTestRunning) { - this.selfTestRunning = false; + return; + } - foreach (var agingStep in this.steps) + ImGui.Separator(); + + var step = this.steps[this.currentStep]; + ImGui.TextUnformatted($"Current: {step.Name}"); + + ImGuiHelpers.ScaledDummy(10); + + SelfTestStepResult result; + try + { + result = step.RunStep(); + } + catch (Exception ex) + { + Log.Error(ex, $"Step failed: {step.Name}"); + result = SelfTestStepResult.Fail; + } + + ImGui.Separator(); + + if (result != SelfTestStepResult.Waiting) + { + var duration = DateTimeOffset.Now - this.lastTestStart; + this.currentStep++; + this.stepResults.Add((result, duration)); + + this.lastTestStart = DateTimeOffset.Now; + } + } + + private void DrawResultTable() + { + if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders)) + { + ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f); + ImGui.TableSetupColumn("Name"); + ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f); + ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f); + + ImGui.TableHeadersRow(); + + for (var i = 0; i < this.steps.Count; i++) { - try + var step = this.steps[i]; + ImGui.TableNextRow(); + + ImGui.TableSetColumnIndex(0); + ImGui.Text(i.ToString()); + + ImGui.TableSetColumnIndex(1); + ImGui.Text(step.Name); + + ImGui.TableSetColumnIndex(2); + ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont); + if (this.stepResults.Count > i) { - agingStep.CleanUp(); + var result = this.stepResults[i]; + + switch (result.Result) + { + case SelfTestStepResult.Pass: + ImGui.TextColored(ImGuiColors.HealerGreen, "PASS"); + break; + case SelfTestStepResult.Fail: + ImGui.TextColored(ImGuiColors.DalamudRed, "FAIL"); + break; + default: + ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); + break; + } } - catch (Exception ex) + else { - Log.Error(ex, $"Could not clean up AgingStep: {agingStep.Name}"); + if (this.selfTestRunning && this.currentStep == i) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT"); + } + else + { + ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); + } } + + ImGui.PopFont(); + + ImGui.TableSetColumnIndex(3); + if (this.stepResults.Count > i) + { + var (_, duration) = this.stepResults[i]; + + if (duration.HasValue) + { + ImGui.TextUnformatted(duration.Value.ToString("g")); + } + } + else + { + if (this.selfTestRunning && this.currentStep == i) + { + ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g")); + } + } + } + + ImGui.EndTable(); + } + } + + private void StopTests() + { + this.selfTestRunning = false; + + foreach (var agingStep in this.steps) + { + try + { + agingStep.CleanUp(); + } + catch (Exception ex) + { + Log.Error(ex, $"Could not clean up AgingStep: {agingStep.Name}"); } } } diff --git a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs index e33ac4e4c..b4fe6ba1a 100644 --- a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs @@ -6,7 +6,6 @@ using System.Numerics; using System.Threading.Tasks; using CheapLoc; -using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Game.Text; using Dalamud.Interface.Colors; @@ -15,717 +14,716 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin.Internal; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows +namespace Dalamud.Interface.Internal.Windows; + +/// +/// The window that allows for general configuration of Dalamud itself. +/// +internal class SettingsWindow : Window { + private const float MinScale = 0.3f; + private const float MaxScale = 2.0f; + + private readonly string[] languages; + private readonly string[] locLanguages; + private int langIndex; + + private XivChatType dalamudMessagesChatType; + + private bool doCfTaskBarFlash; + private bool doCfChatMessage; + + private float globalUiScale; + private bool doToggleUiHide; + private bool doToggleUiHideDuringCutscenes; + private bool doToggleUiHideDuringGpose; + private bool doDocking; + private bool doViewport; + private bool doGamepad; + private bool doFocus; + + private List thirdRepoList; + private bool thirdRepoListChanged; + private string thirdRepoTempUrl = string.Empty; + private string thirdRepoAddError = string.Empty; + + private List devPluginLocations; + private bool devPluginLocationsChanged; + private string devPluginTempLocation = string.Empty; + private string devPluginLocationAddError = string.Empty; + + private bool printPluginsWelcomeMsg; + private bool autoUpdatePlugins; + private bool doButtonsSystemMenu; + private bool disableRmtFiltering; + + #region Experimental + + private bool doPluginTest; + + #endregion + /// - /// The window that allows for general configuration of Dalamud itself. + /// Initializes a new instance of the class. /// - internal class SettingsWindow : Window + public SettingsWindow() + : base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse) { - private const float MinScale = 0.3f; - private const float MaxScale = 2.0f; + var configuration = Service.Get(); - private readonly string[] languages; - private readonly string[] locLanguages; - private int langIndex; + this.Size = new Vector2(740, 550); + this.SizeCondition = ImGuiCond.FirstUseEver; - private XivChatType dalamudMessagesChatType; + this.dalamudMessagesChatType = configuration.GeneralChatType; - private bool doCfTaskBarFlash; - private bool doCfChatMessage; + this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; + this.doCfChatMessage = configuration.DutyFinderChatMessage; - private float globalUiScale; - private bool doToggleUiHide; - private bool doToggleUiHideDuringCutscenes; - private bool doToggleUiHideDuringGpose; - private bool doDocking; - private bool doViewport; - private bool doGamepad; - private bool doFocus; + this.globalUiScale = configuration.GlobalUiScale; + this.doToggleUiHide = configuration.ToggleUiHide; + this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes; + this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose; - private List thirdRepoList; - private bool thirdRepoListChanged; - private string thirdRepoTempUrl = string.Empty; - private string thirdRepoAddError = string.Empty; + this.doDocking = configuration.IsDocking; + this.doViewport = !configuration.IsDisableViewport; + this.doGamepad = configuration.IsGamepadNavigationEnabled; + this.doFocus = configuration.IsFocusManagementEnabled; - private List devPluginLocations; - private bool devPluginLocationsChanged; - private string devPluginTempLocation = string.Empty; - private string devPluginLocationAddError = string.Empty; + this.doPluginTest = configuration.DoPluginTest; + this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); + this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); - private bool printPluginsWelcomeMsg; - private bool autoUpdatePlugins; - private bool doButtonsSystemMenu; - private bool disableRmtFiltering; + this.printPluginsWelcomeMsg = configuration.PrintPluginsWelcomeMsg; + this.autoUpdatePlugins = configuration.AutoUpdatePlugins; + this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu; + this.disableRmtFiltering = configuration.DisableRmtFiltering; - #region Experimental - - private bool doPluginTest; - - #endregion - - /// - /// Initializes a new instance of the class. - /// - public SettingsWindow() - : base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse) + this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); + try { - var configuration = Service.Get(); - - this.Size = new Vector2(740, 550); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.dalamudMessagesChatType = configuration.GeneralChatType; - - this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; - this.doCfChatMessage = configuration.DutyFinderChatMessage; - - this.globalUiScale = configuration.GlobalUiScale; - this.doToggleUiHide = configuration.ToggleUiHide; - this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes; - this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose; - - this.doDocking = configuration.IsDocking; - this.doViewport = !configuration.IsDisableViewport; - this.doGamepad = configuration.IsGamepadNavigationEnabled; - this.doFocus = configuration.IsFocusManagementEnabled; - - this.doPluginTest = configuration.DoPluginTest; - this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); - this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); - - this.printPluginsWelcomeMsg = configuration.PrintPluginsWelcomeMsg; - this.autoUpdatePlugins = configuration.AutoUpdatePlugins; - this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu; - this.disableRmtFiltering = configuration.DisableRmtFiltering; - - this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); - try + if (string.IsNullOrEmpty(configuration.LanguageOverride)) { - if (string.IsNullOrEmpty(configuration.LanguageOverride)) - { - var currentUiLang = CultureInfo.CurrentUICulture; + var currentUiLang = CultureInfo.CurrentUICulture; - if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x)) - this.langIndex = Array.IndexOf(this.languages, currentUiLang.TwoLetterISOLanguageName); - else - this.langIndex = 0; + if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x)) + this.langIndex = Array.IndexOf(this.languages, currentUiLang.TwoLetterISOLanguageName); + else + this.langIndex = 0; + } + else + { + this.langIndex = Array.IndexOf(this.languages, configuration.LanguageOverride); + } + } + catch (Exception) + { + this.langIndex = 0; + } + + try + { + var locLanguagesList = new List(); + string locLanguage; + foreach (var language in this.languages) + { + if (language != "ko") + { + locLanguage = CultureInfo.GetCultureInfo(language).NativeName; + locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]); } else { - this.langIndex = Array.IndexOf(this.languages, configuration.LanguageOverride); + locLanguagesList.Add("Korean"); } } - catch (Exception) + + this.locLanguages = locLanguagesList.ToArray(); + } + catch (Exception) + { + this.locLanguages = this.languages; // Languages not localized, only codes. + } + } + + /// + public override void OnOpen() + { + this.thirdRepoListChanged = false; + this.devPluginLocationsChanged = false; + } + + /// + public override void OnClose() + { + var configuration = Service.Get(); + + ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; + this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); + this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); + } + + /// + public override void Draw() + { + var windowSize = ImGui.GetWindowSize(); + ImGui.BeginChild("scrolling", new Vector2(windowSize.X - 5 - (5 * ImGuiHelpers.GlobalScale), windowSize.Y - 35 - (35 * ImGuiHelpers.GlobalScale)), false, ImGuiWindowFlags.HorizontalScrollbar); + + if (ImGui.BeginTabBar("SetTabBar")) + { + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General"))) { - this.langIndex = 0; + this.DrawGeneralTab(); + ImGui.EndTabItem(); } - try + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel"))) { - var locLanguagesList = new List(); - string locLanguage; - foreach (var language in this.languages) + this.DrawLookAndFeelTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental"))) + { + this.DrawExperimentalTab(); + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + + ImGui.EndChild(); + + this.DrawSaveCloseButtons(); + } + + /// + /// Transform byte count to human readable format. + /// + /// Number of bytes. + /// Human readable version. + private static string FormatBytes(long bytes) + { + string[] suffix = { "B", "KB", "MB", "GB", "TB" }; + int i; + double dblSByte = bytes; + for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) + { + dblSByte = bytes / 1024.0; + } + + return $"{dblSByte:0.##} {suffix[i]}"; + } + + private void DrawGeneralTab() + { + ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language")); + ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Text(Loc.Localize("DalamudSettingsChannel", "General Chat Channel")); + if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString())) + { + foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast()) + { + if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType)) { - if (language != "ko") - { - locLanguage = CultureInfo.GetCultureInfo(language).NativeName; - locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]); - } - else - { - locLanguagesList.Add("Korean"); - } + this.dalamudMessagesChatType = type; } - - this.locLanguages = locLanguagesList.ToArray(); - } - catch (Exception) - { - this.locLanguages = this.languages; // Languages not localized, only codes. - } - } - - /// - public override void OnOpen() - { - this.thirdRepoListChanged = false; - this.devPluginLocationsChanged = false; - } - - /// - public override void OnClose() - { - var configuration = Service.Get(); - - ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; - this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); - this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); - } - - /// - public override void Draw() - { - var windowSize = ImGui.GetWindowSize(); - ImGui.BeginChild("scrolling", new Vector2(windowSize.X - 5 - (5 * ImGuiHelpers.GlobalScale), windowSize.Y - 35 - (35 * ImGuiHelpers.GlobalScale)), false, ImGuiWindowFlags.HorizontalScrollbar); - - if (ImGui.BeginTabBar("SetTabBar")) - { - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General"))) - { - this.DrawGeneralTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel"))) - { - this.DrawLookAndFeelTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental"))) - { - this.DrawExperimentalTab(); - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); } - ImGui.EndChild(); - - this.DrawSaveCloseButtons(); + ImGui.EndCombo(); } - /// - /// Transform byte count to human readable format. - /// - /// Number of bytes. - /// Human readable version. - private static string FormatBytes(long bytes) - { - string[] suffix = { "B", "KB", "MB", "GB", "TB" }; - int i; - double dblSByte = bytes; - for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) - { - dblSByte = bytes / 1024.0; - } + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages.")); - return $"{dblSByte:0.##} {suffix[i]}"; + ImGuiHelpers.ScaledDummy(5); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsDutyFinderMessage", "Chatlog message on duty pop"), ref this.doCfChatMessage); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDutyFinderMessageHint", "Send a message in FFXIV chat when a duty is ready.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"), ref this.printPluginsWelcomeMsg); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePlugins", "Auto-update plugins"), ref this.autoUpdatePlugins); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePluginsMsgHint", "Automatically update plugins when logging in with a character.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"), ref this.doButtonsSystemMenu); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable dalamud's built-in RMT ad filtering.")); + } + + private void DrawLookAndFeelTab() + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); + ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global UI Scale")); + ImGui.SameLine(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); + if (ImGui.Button("Reset")) + { + this.globalUiScale = 1.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; } - private void DrawGeneralTab() + if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref this.globalUiScale, 0.005f, MinScale, MaxScale, "%.2f")) + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale all XIVLauncher UI elements - useful for 4K displays.")); + + ImGuiHelpers.ScaledDummy(10, 16); + + if (ImGui.Button(Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor"))) { - ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language")); - ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Text(Loc.Localize("DalamudSettingsChannel", "General Chat Channel")); - if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString())) - { - foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast()) - { - if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType)) - { - this.dalamudMessagesChatType = type; - } - } - - ImGui.EndCombo(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsDutyFinderMessage", "Chatlog message on duty pop"), ref this.doCfChatMessage); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDutyFinderMessageHint", "Send a message in FFXIV chat when a duty is ready.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"), ref this.printPluginsWelcomeMsg); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePlugins", "Auto-update plugins"), ref this.autoUpdatePlugins); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePluginsMsgHint", "Automatically update plugins when logging in with a character.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"), ref this.doButtonsSystemMenu); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable dalamud's built-in RMT ad filtering.")); + Service.Get().OpenStyleEditor(); } - private void DrawLookAndFeelTab() + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsStyleEditorHint", "Modify the look & feel of Dalamud windows.")); + + ImGuiHelpers.ScaledDummy(10, 16); + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"), ref this.doToggleUiHide); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringCutscenes", "Hide plugin UI during cutscenes"), ref this.doToggleUiHideDuringCutscenes); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringCutscenesHint", "Hide any open windows by plugins during cutscenes.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringGpose", "Hide plugin UI while gpose is active"), ref this.doToggleUiHideDuringGpose); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringGposeHint", "Hide any open windows by plugins while gpose is active.")); + + ImGuiHelpers.ScaledDummy(10, 16); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleFocusManagement", "Use escape to close Dalamud windows"), ref this.doFocus); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleFocusManagementHint", "This will cause Dalamud windows to behave like in-game windows when pressing escape.\nThey will close one after another until all are closed. May not work for all plugins.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleViewports", "Enable multi-monitor windows"), ref this.doViewport); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleViewportsHint", "This will allow you move plugin windows onto other monitors.\nWill only work in Borderless Window or Windowed mode.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Control plugins via gamepad"), ref this.doGamepad); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and plugin navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled.")); + } + + private void DrawExperimentalTab() + { + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + #region Plugin testing + + ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for plugins.")); + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks.")); + + #endregion + + ImGuiHelpers.ScaledDummy(12); + + #region Hidden plugins + + if (ImGui.Button(Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"))) { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); - ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global UI Scale")); + configuration.HiddenPluginInternalName.Clear(); + pluginManager.RefilterPluginMasters(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer.")); + + #endregion + + ImGuiHelpers.ScaledDummy(12); + + this.DrawCustomReposSection(); + + ImGuiHelpers.ScaledDummy(12); + + this.DrawDevPluginLocationsSection(); + + ImGuiHelpers.ScaledDummy(12); + + ImGui.TextColored(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + FormatBytes(GC.GetTotalMemory(false))); + } + + private void DrawCustomReposSection() + { + ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories")); + if (this.thirdRepoListChanged) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); - if (ImGui.Button("Reset")) - { - this.globalUiScale = 1.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - } - - if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref this.globalUiScale, 0.005f, MinScale, MaxScale, "%.2f")) - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale all XIVLauncher UI elements - useful for 4K displays.")); - - ImGuiHelpers.ScaledDummy(10, 16); - - if (ImGui.Button(Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor"))) - { - Service.Get().OpenStyleEditor(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsStyleEditorHint", "Modify the look & feel of Dalamud windows.")); - - ImGuiHelpers.ScaledDummy(10, 16); - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"), ref this.doToggleUiHide); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringCutscenes", "Hide plugin UI during cutscenes"), ref this.doToggleUiHideDuringCutscenes); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringCutscenesHint", "Hide any open windows by plugins during cutscenes.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringGpose", "Hide plugin UI while gpose is active"), ref this.doToggleUiHideDuringGpose); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringGposeHint", "Hide any open windows by plugins while gpose is active.")); - - ImGuiHelpers.ScaledDummy(10, 16); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleFocusManagement", "Use escape to close Dalamud windows"), ref this.doFocus); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleFocusManagementHint", "This will cause Dalamud windows to behave like in-game windows when pressing escape.\nThey will close one after another until all are closed. May not work for all plugins.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleViewports", "Enable multi-monitor windows"), ref this.doViewport); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleViewportsHint", "This will allow you move plugin windows onto other monitors.\nWill only work in Borderless Window or Windowed mode.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Control plugins via gamepad"), ref this.doGamepad); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and plugin navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled.")); + ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); + ImGui.PopStyleColor(); } - private void DrawExperimentalTab() + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingCustomRepoHint", "Add custom plugin repositories.")); + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning", "We cannot take any responsibility for third-party plugins and repositories.\nTake care when installing third-party plugins from untrusted sources.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Columns(4); + ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); + + ImGui.Separator(); + + ImGui.Text("#"); + ImGui.NextColumn(); + ImGui.Text("URL"); + ImGui.NextColumn(); + ImGui.Text("Enabled"); + ImGui.NextColumn(); + ImGui.Text(string.Empty); + ImGui.NextColumn(); + + ImGui.Separator(); + + ImGui.Text("0"); + ImGui.NextColumn(); + ImGui.Text("XIVLauncher"); + ImGui.NextColumn(); + ImGui.NextColumn(); + ImGui.NextColumn(); + ImGui.Separator(); + + ThirdPartyRepoSettings repoToRemove = null; + + var repoNumber = 1; + foreach (var thirdRepoSetting in this.thirdRepoList) { - var configuration = Service.Get(); - var pluginManager = Service.Get(); + var isEnabled = thirdRepoSetting.IsEnabled; - #region Plugin testing - - ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for plugins.")); - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks.")); - - #endregion - - ImGuiHelpers.ScaledDummy(12); - - #region Hidden plugins - - if (ImGui.Button(Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"))) - { - configuration.HiddenPluginInternalName.Clear(); - pluginManager.RefilterPluginMasters(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer.")); - - #endregion - - ImGuiHelpers.ScaledDummy(12); - - this.DrawCustomReposSection(); - - ImGuiHelpers.ScaledDummy(12); - - this.DrawDevPluginLocationsSection(); - - ImGuiHelpers.ScaledDummy(12); - - ImGui.TextColored(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + FormatBytes(GC.GetTotalMemory(false))); - } - - private void DrawCustomReposSection() - { - ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories")); - if (this.thirdRepoListChanged) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); - ImGui.SameLine(); - ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); - ImGui.PopStyleColor(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingCustomRepoHint", "Add custom plugin repositories.")); - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning", "We cannot take any responsibility for third-party plugins and repositories.\nTake care when installing third-party plugins from untrusted sources.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); - - ImGui.Separator(); - - ImGui.Text("#"); - ImGui.NextColumn(); - ImGui.Text("URL"); - ImGui.NextColumn(); - ImGui.Text("Enabled"); - ImGui.NextColumn(); - ImGui.Text(string.Empty); - ImGui.NextColumn(); - - ImGui.Separator(); - - ImGui.Text("0"); - ImGui.NextColumn(); - ImGui.Text("XIVLauncher"); - ImGui.NextColumn(); - ImGui.NextColumn(); - ImGui.NextColumn(); - ImGui.Separator(); - - ThirdPartyRepoSettings repoToRemove = null; - - var repoNumber = 1; - foreach (var thirdRepoSetting in this.thirdRepoList) - { - var isEnabled = thirdRepoSetting.IsEnabled; - - ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}"); - - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2)); - ImGui.Text(repoNumber.ToString()); - ImGui.NextColumn(); - - ImGui.SetNextItemWidth(-1); - var url = thirdRepoSetting.Url; - if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) - { - var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url); - if (thirdRepoSetting.Url == url) - { - // no change. - } - else if (contains && thirdRepoSetting.Url != url) - { - this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); - } - else - { - thirdRepoSetting.Url = url; - this.thirdRepoListChanged = url != thirdRepoSetting.Url; - } - } - - ImGui.NextColumn(); - - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); - ImGui.Checkbox("##thirdRepoCheck", ref isEnabled); - ImGui.NextColumn(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) - { - repoToRemove = thirdRepoSetting; - } - - ImGui.PopID(); - - ImGui.NextColumn(); - ImGui.Separator(); - - thirdRepoSetting.IsEnabled = isEnabled; - - repoNumber++; - } - - if (repoToRemove != null) - { - this.thirdRepoList.Remove(repoToRemove); - this.thirdRepoListChanged = true; - } + ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}"); ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2)); ImGui.Text(repoNumber.ToString()); ImGui.NextColumn(); + ImGui.SetNextItemWidth(-1); - ImGui.InputText("##thirdRepoUrlInput", ref this.thirdRepoTempUrl, 300); - ImGui.NextColumn(); - // Enabled button - ImGui.NextColumn(); - if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + var url = thirdRepoSetting.Url; + if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) { - this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd(); - if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) + var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url); + if (thirdRepoSetting.Url == url) + { + // no change. + } + else if (contains && thirdRepoSetting.Url != url) { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); } else { - this.thirdRepoList.Add(new ThirdPartyRepoSettings - { - Url = this.thirdRepoTempUrl, - IsEnabled = true, - }); - this.thirdRepoListChanged = true; - this.thirdRepoTempUrl = string.Empty; + thirdRepoSetting.Url = url; + this.thirdRepoListChanged = url != thirdRepoSetting.Url; } } - ImGui.Columns(1); + ImGui.NextColumn(); - if (!string.IsNullOrEmpty(this.thirdRepoAddError)) + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); + ImGui.Checkbox("##thirdRepoCheck", ref isEnabled); + ImGui.NextColumn(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) { - ImGui.TextColored(new Vector4(1, 0, 0, 1), this.thirdRepoAddError); + repoToRemove = thirdRepoSetting; + } + + ImGui.PopID(); + + ImGui.NextColumn(); + ImGui.Separator(); + + thirdRepoSetting.IsEnabled = isEnabled; + + repoNumber++; + } + + if (repoToRemove != null) + { + this.thirdRepoList.Remove(repoToRemove); + this.thirdRepoListChanged = true; + } + + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2)); + ImGui.Text(repoNumber.ToString()); + ImGui.NextColumn(); + ImGui.SetNextItemWidth(-1); + ImGui.InputText("##thirdRepoUrlInput", ref this.thirdRepoTempUrl, 300); + ImGui.NextColumn(); + // Enabled button + ImGui.NextColumn(); + if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd(); + if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) + { + this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); + Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + } + else + { + this.thirdRepoList.Add(new ThirdPartyRepoSettings + { + Url = this.thirdRepoTempUrl, + IsEnabled = true, + }); + this.thirdRepoListChanged = true; + this.thirdRepoTempUrl = string.Empty; } } - private void DrawDevPluginLocationsSection() + ImGui.Columns(1); + + if (!string.IsNullOrEmpty(this.thirdRepoAddError)) { - ImGui.Text(Loc.Localize("DalamudSettingsDevPluginLocation", "Dev Plugin Locations")); - if (this.devPluginLocationsChanged) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); - ImGui.SameLine(); - ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); - ImGui.PopStyleColor(); - } + ImGui.TextColored(new Vector4(1, 0, 0, 1), this.thirdRepoAddError); + } + } - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path.")); + private void DrawDevPluginLocationsSection() + { + ImGui.Text(Loc.Localize("DalamudSettingsDevPluginLocation", "Dev Plugin Locations")); + if (this.devPluginLocationsChanged) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + ImGui.SameLine(); + ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); + ImGui.PopStyleColor(); + } - ImGuiHelpers.ScaledDummy(5); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path.")); - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); + ImGuiHelpers.ScaledDummy(5); - ImGui.Separator(); + ImGui.Columns(4); + ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); - ImGui.Text("#"); - ImGui.NextColumn(); - ImGui.Text("Path"); - ImGui.NextColumn(); - ImGui.Text("Enabled"); - ImGui.NextColumn(); - ImGui.Text(string.Empty); - ImGui.NextColumn(); + ImGui.Separator(); - ImGui.Separator(); + ImGui.Text("#"); + ImGui.NextColumn(); + ImGui.Text("Path"); + ImGui.NextColumn(); + ImGui.Text("Enabled"); + ImGui.NextColumn(); + ImGui.Text(string.Empty); + ImGui.NextColumn(); - DevPluginLocationSettings locationToRemove = null; + ImGui.Separator(); - var locNumber = 1; - foreach (var devPluginLocationSetting in this.devPluginLocations) - { - var isEnabled = devPluginLocationSetting.IsEnabled; + DevPluginLocationSettings locationToRemove = null; - ImGui.PushID($"devPluginLocation_{devPluginLocationSetting.Path}"); + var locNumber = 1; + foreach (var devPluginLocationSetting in this.devPluginLocations) + { + var isEnabled = devPluginLocationSetting.IsEnabled; - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2)); - ImGui.Text(locNumber.ToString()); - ImGui.NextColumn(); - - ImGui.SetNextItemWidth(-1); - var path = devPluginLocationSetting.Path; - if (ImGui.InputText($"##devPluginLocationInput", ref path, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) - { - var contains = this.devPluginLocations.Select(loc => loc.Path).Contains(path); - if (devPluginLocationSetting.Path == path) - { - // no change. - } - else if (contains && devPluginLocationSetting.Path != path) - { - this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); - Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); - } - else - { - devPluginLocationSetting.Path = path; - this.devPluginLocationsChanged = path != devPluginLocationSetting.Path; - } - } - - ImGui.NextColumn(); - - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); - ImGui.Checkbox("##devPluginLocationCheck", ref isEnabled); - ImGui.NextColumn(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) - { - locationToRemove = devPluginLocationSetting; - } - - ImGui.PopID(); - - ImGui.NextColumn(); - ImGui.Separator(); - - devPluginLocationSetting.IsEnabled = isEnabled; - - locNumber++; - } - - if (locationToRemove != null) - { - this.devPluginLocations.Remove(locationToRemove); - this.devPluginLocationsChanged = true; - } + ImGui.PushID($"devPluginLocation_{devPluginLocationSetting.Path}"); ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2)); ImGui.Text(locNumber.ToString()); ImGui.NextColumn(); + ImGui.SetNextItemWidth(-1); - ImGui.InputText("##devPluginLocationInput", ref this.devPluginTempLocation, 300); - ImGui.NextColumn(); - // Enabled button - ImGui.NextColumn(); - if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + var path = devPluginLocationSetting.Path; + if (ImGui.InputText($"##devPluginLocationInput", ref path, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) { - if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) + var contains = this.devPluginLocations.Select(loc => loc.Path).Contains(path); + if (devPluginLocationSetting.Path == path) + { + // no change. + } + else if (contains && devPluginLocationSetting.Path != path) { this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); } else { - this.devPluginLocations.Add(new DevPluginLocationSettings - { - Path = this.devPluginTempLocation, - IsEnabled = true, - }); - this.devPluginLocationsChanged = true; - this.devPluginTempLocation = string.Empty; + devPluginLocationSetting.Path = path; + this.devPluginLocationsChanged = path != devPluginLocationSetting.Path; } } - ImGui.Columns(1); + ImGui.NextColumn(); - if (!string.IsNullOrEmpty(this.devPluginLocationAddError)) + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); + ImGui.Checkbox("##devPluginLocationCheck", ref isEnabled); + ImGui.NextColumn(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) { - ImGui.TextColored(new Vector4(1, 0, 0, 1), this.devPluginLocationAddError); + locationToRemove = devPluginLocationSetting; } + + ImGui.PopID(); + + ImGui.NextColumn(); + ImGui.Separator(); + + devPluginLocationSetting.IsEnabled = isEnabled; + + locNumber++; } - private void DrawSaveCloseButtons() + if (locationToRemove != null) { - var buttonSave = false; - var buttonClose = false; - - var pluginManager = Service.Get(); - - if (ImGui.Button(Loc.Localize("Save", "Save"))) - buttonSave = true; - - ImGui.SameLine(); - - if (ImGui.Button(Loc.Localize("Close", "Close"))) - buttonClose = true; - - ImGui.SameLine(); - - if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) - buttonSave = buttonClose = true; - - if (buttonSave) - { - this.Save(); - - if (this.thirdRepoListChanged) - { - _ = pluginManager.SetPluginReposFromConfigAsync(true); - this.thirdRepoListChanged = false; - } - - if (this.devPluginLocationsChanged) - { - pluginManager.ScanDevPlugins(); - this.devPluginLocationsChanged = false; - } - } - - if (buttonClose) - { - this.IsOpen = false; - } + this.devPluginLocations.Remove(locationToRemove); + this.devPluginLocationsChanged = true; } - private void Save() + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2)); + ImGui.Text(locNumber.ToString()); + ImGui.NextColumn(); + ImGui.SetNextItemWidth(-1); + ImGui.InputText("##devPluginLocationInput", ref this.devPluginTempLocation, 300); + ImGui.NextColumn(); + // Enabled button + ImGui.NextColumn(); + if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - var configuration = Service.Get(); - var localization = Service.Get(); - - localization.SetupWithLangCode(this.languages[this.langIndex]); - configuration.LanguageOverride = this.languages[this.langIndex]; - - configuration.GeneralChatType = this.dalamudMessagesChatType; - - configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; - configuration.DutyFinderChatMessage = this.doCfChatMessage; - - configuration.GlobalUiScale = this.globalUiScale; - configuration.ToggleUiHide = this.doToggleUiHide; - configuration.ToggleUiHideDuringCutscenes = this.doToggleUiHideDuringCutscenes; - configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose; - - configuration.IsDocking = this.doDocking; - configuration.IsGamepadNavigationEnabled = this.doGamepad; - configuration.IsFocusManagementEnabled = this.doFocus; - - // This is applied every frame in InterfaceManager::CheckViewportState() - configuration.IsDisableViewport = !this.doViewport; - - // Apply docking flag - if (!configuration.IsDocking) + if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; + this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); + Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); } else { - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; + this.devPluginLocations.Add(new DevPluginLocationSettings + { + Path = this.devPluginTempLocation, + IsEnabled = true, + }); + this.devPluginLocationsChanged = true; + this.devPluginTempLocation = string.Empty; } + } - // NOTE (Chiv) Toggle gamepad navigation via setting - if (!configuration.IsGamepadNavigationEnabled) - { - ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; - } - else - { - ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; - } + ImGui.Columns(1); - configuration.DoPluginTest = this.doPluginTest; - configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList(); - configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList(); - - configuration.PrintPluginsWelcomeMsg = this.printPluginsWelcomeMsg; - configuration.AutoUpdatePlugins = this.autoUpdatePlugins; - configuration.DoButtonsSystemMenu = this.doButtonsSystemMenu; - configuration.DisableRmtFiltering = this.disableRmtFiltering; - - configuration.Save(); - - _ = Service.Get().ReloadPluginMastersAsync(); + if (!string.IsNullOrEmpty(this.devPluginLocationAddError)) + { + ImGui.TextColored(new Vector4(1, 0, 0, 1), this.devPluginLocationAddError); } } + + private void DrawSaveCloseButtons() + { + var buttonSave = false; + var buttonClose = false; + + var pluginManager = Service.Get(); + + if (ImGui.Button(Loc.Localize("Save", "Save"))) + buttonSave = true; + + ImGui.SameLine(); + + if (ImGui.Button(Loc.Localize("Close", "Close"))) + buttonClose = true; + + ImGui.SameLine(); + + if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) + buttonSave = buttonClose = true; + + if (buttonSave) + { + this.Save(); + + if (this.thirdRepoListChanged) + { + _ = pluginManager.SetPluginReposFromConfigAsync(true); + this.thirdRepoListChanged = false; + } + + if (this.devPluginLocationsChanged) + { + pluginManager.ScanDevPlugins(); + this.devPluginLocationsChanged = false; + } + } + + if (buttonClose) + { + this.IsOpen = false; + } + } + + private void Save() + { + var configuration = Service.Get(); + var localization = Service.Get(); + + localization.SetupWithLangCode(this.languages[this.langIndex]); + configuration.LanguageOverride = this.languages[this.langIndex]; + + configuration.GeneralChatType = this.dalamudMessagesChatType; + + configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; + configuration.DutyFinderChatMessage = this.doCfChatMessage; + + configuration.GlobalUiScale = this.globalUiScale; + configuration.ToggleUiHide = this.doToggleUiHide; + configuration.ToggleUiHideDuringCutscenes = this.doToggleUiHideDuringCutscenes; + configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose; + + configuration.IsDocking = this.doDocking; + configuration.IsGamepadNavigationEnabled = this.doGamepad; + configuration.IsFocusManagementEnabled = this.doFocus; + + // This is applied every frame in InterfaceManager::CheckViewportState() + configuration.IsDisableViewport = !this.doViewport; + + // Apply docking flag + if (!configuration.IsDocking) + { + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; + } + else + { + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; + } + + // NOTE (Chiv) Toggle gamepad navigation via setting + if (!configuration.IsGamepadNavigationEnabled) + { + ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; + } + else + { + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; + } + + configuration.DoPluginTest = this.doPluginTest; + configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList(); + configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList(); + + configuration.PrintPluginsWelcomeMsg = this.printPluginsWelcomeMsg; + configuration.AutoUpdatePlugins = this.autoUpdatePlugins; + configuration.DoButtonsSystemMenu = this.doButtonsSystemMenu; + configuration.DisableRmtFiltering = this.disableRmtFiltering; + + configuration.Save(); + + _ = Service.Get().ReloadPluginMastersAsync(); + } } diff --git a/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs b/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs index 5b2c3d90c..8ad4e98e6 100644 --- a/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs +++ b/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs @@ -15,388 +15,387 @@ using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Serilog; -namespace Dalamud.Interface.Internal.Windows.StyleEditor +namespace Dalamud.Interface.Internal.Windows.StyleEditor; + +/// +/// Window for the Dalamud style editor. +/// +public class StyleEditorWindow : Window { + private ImGuiColorEditFlags alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; + + private int currentSel = 0; + private string initialStyle = string.Empty; + private bool didSave = false; + + private string renameText = string.Empty; + private bool renameModalDrawing = false; + /// - /// Window for the Dalamud style editor. + /// Initializes a new instance of the class. /// - public class StyleEditorWindow : Window + public StyleEditorWindow() + : base("Dalamud Style Editor") { - private ImGuiColorEditFlags alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; - - private int currentSel = 0; - private string initialStyle = string.Empty; - private bool didSave = false; - - private string renameText = string.Empty; - private bool renameModalDrawing = false; - - /// - /// Initializes a new instance of the class. - /// - public StyleEditorWindow() - : base("Dalamud Style Editor") + this.IsOpen = true; + this.SizeConstraints = new WindowSizeConstraints { - this.IsOpen = true; - this.SizeConstraints = new WindowSizeConstraints - { - MinimumSize = new Vector2(890, 560), - MaximumSize = new Vector2(10000, 10000), - }; - } + MinimumSize = new Vector2(890, 560), + MaximumSize = new Vector2(10000, 10000), + }; + } - /// - public override void OnOpen() - { - this.didSave = false; + /// + public override void OnOpen() + { + this.didSave = false; - var config = Service.Get(); - config.SavedStyles ??= new List(); - this.currentSel = config.SavedStyles.FindIndex(x => x.Name == config.ChosenStyle); + var config = Service.Get(); + config.SavedStyles ??= new List(); + this.currentSel = config.SavedStyles.FindIndex(x => x.Name == config.ChosenStyle); - this.initialStyle = config.ChosenStyle; + this.initialStyle = config.ChosenStyle; - base.OnOpen(); - } + base.OnOpen(); + } - /// - public override void OnClose() - { - if (!this.didSave) - { - var config = Service.Get(); - var newStyle = config.SavedStyles.FirstOrDefault(x => x.Name == this.initialStyle); - newStyle?.Apply(); - } - - base.OnClose(); - } - - /// - public override void Draw() + /// + public override void OnClose() + { + if (!this.didSave) { var config = Service.Get(); - var renameModalTitle = Loc.Localize("RenameStyleModalTitle", "Rename Style"); - - var workStyle = config.SavedStyles[this.currentSel]; - workStyle.BuiltInColors ??= StyleModelV1.DalamudStandard.BuiltInColors; - - var appliedThisFrame = false; - - var styleAry = config.SavedStyles.Select(x => x.Name).ToArray(); - ImGui.Text(Loc.Localize("StyleEditorChooseStyle", "Choose Style:")); - if (ImGui.Combo("###styleChooserCombo", ref this.currentSel, styleAry, styleAry.Length)) - { - var newStyle = config.SavedStyles[this.currentSel]; - newStyle.Apply(); - appliedThisFrame = true; - } - - if (ImGui.Button(Loc.Localize("StyleEditorAddNew", "Add new style"))) - { - this.SaveStyle(); - - var newStyle = StyleModelV1.DalamudStandard; - newStyle.Name = GetRandomName(); - config.SavedStyles.Add(newStyle); - - this.currentSel = config.SavedStyles.Count - 1; - - newStyle.Apply(); - appliedThisFrame = true; - - config.Save(); - } - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash) && this.currentSel != 0) - { - this.currentSel--; - var newStyle = config.SavedStyles[this.currentSel]; - newStyle.Apply(); - appliedThisFrame = true; - - config.SavedStyles.RemoveAt(this.currentSel + 1); - - config.Save(); - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorDeleteStyle", "Delete current style")); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) && this.currentSel != 0) - { - var newStyle = config.SavedStyles[this.currentSel]; - this.renameText = newStyle.Name; - - this.renameModalDrawing = true; - ImGui.OpenPopup(renameModalTitle); - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorRenameStyle", "Rename style")); - - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport)) - { - var selectedStyle = config.SavedStyles[this.currentSel]; - var newStyle = StyleModelV1.Get(); - newStyle.Name = selectedStyle.Name; - ImGui.SetClipboardText(newStyle.Serialize()); - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorCopy", "Copy style to clipboard for sharing")); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport)) - { - this.SaveStyle(); - - var styleJson = ImGui.GetClipboardText(); - - try - { - var newStyle = StyleModel.Deserialize(styleJson); - - newStyle.Name ??= GetRandomName(); - - if (config.SavedStyles.Any(x => x.Name == newStyle.Name)) - { - newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)"; - } - - config.SavedStyles.Add(newStyle); - newStyle.Apply(); - appliedThisFrame = true; - - this.currentSel = config.SavedStyles.Count - 1; - - config.Save(); - } - catch (Exception ex) - { - Log.Error(ex, "Could not import style"); - } - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorImport", "Import style from clipboard")); - - ImGui.Separator(); - - ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.50f); - - if (this.currentSel < 2) - { - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("StyleEditorNotAllowed", "You cannot edit built-in styles. Please add a new style first.")); - } - else if (appliedThisFrame) - { - ImGui.Text(Loc.Localize("StyleEditorApplying", "Applying style...")); - } - else if (ImGui.BeginTabBar("StyleEditorTabs")) - { - var style = ImGui.GetStyle(); - - if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables"))) - { - ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); - - ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); - ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f"); - ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); - ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - ImGui.Text("Borders"); - ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.Text("Rounding"); - ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f"); - ImGui.Text("Alignment"); - ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1; - if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0")) - style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1); - ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui.SameLine(); - ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content."); - ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui.SameLine(); - ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content."); - ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui.EndTabItem(); - - ImGui.EndChild(); - - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors"))) - { - ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); - - if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None)) - this.alphaFlags = ImGuiColorEditFlags.None; - ImGui.SameLine(); - if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview)) - this.alphaFlags = ImGuiColorEditFlags.AlphaPreview; - ImGui.SameLine(); - if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf)) - this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; - ImGui.SameLine(); - - ImGuiComponents.HelpMarker( - "In the color list:\n" + - "Left-click on color square to open color picker,\n" + - "Right-click to open edit options menu."); - - foreach (var imGuiCol in Enum.GetValues()) - { - if (imGuiCol == ImGuiCol.COUNT) - continue; - - ImGui.PushID(imGuiCol.ToString()); - - ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags); - - ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); - ImGui.TextUnformatted(imGuiCol.ToString()); - - ImGui.PopID(); - } - - ImGui.Separator(); - - foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - ImGui.PushID(property.Name); - - var colorVal = property.GetValue(workStyle.BuiltInColors); - if (colorVal == null) - { - colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors); - property.SetValue(workStyle.BuiltInColors, colorVal); - } - - var color = (Vector4)colorVal; - - if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags)) - { - property.SetValue(workStyle.BuiltInColors, color); - workStyle.BuiltInColors?.Apply(); - } - - ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); - ImGui.TextUnformatted(property.Name); - - ImGui.PopID(); - } - - ImGui.EndChild(); - - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); - } - - ImGui.PopItemWidth(); - - ImGui.Separator(); - - if (ImGui.Button(Loc.Localize("Close", "Close"))) - { - this.IsOpen = false; - } - - ImGui.SameLine(); - - if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) - { - this.SaveStyle(); - - config.ChosenStyle = config.SavedStyles[this.currentSel].Name; - Log.Verbose("ChosenStyle = {ChosenStyle}", config.ChosenStyle); - - this.didSave = true; - - this.IsOpen = false; - } - - if (ImGui.BeginPopupModal(renameModalTitle, ref this.renameModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.Text(Loc.Localize("StyleEditorEnterName", "Please enter the new name for this style.")); - ImGui.Spacing(); - - ImGui.InputText("###renameModalInput", ref this.renameText, 255); - - const float buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); - - if (ImGui.Button("OK", new Vector2(buttonWidth, 40))) - { - config.SavedStyles[this.currentSel].Name = this.renameText; - config.Save(); - - ImGui.CloseCurrentPopup(); - } - - ImGui.EndPopup(); - } + var newStyle = config.SavedStyles.FirstOrDefault(x => x.Name == this.initialStyle); + newStyle?.Apply(); } - private static string GetRandomName() + base.OnClose(); + } + + /// + public override void Draw() + { + var config = Service.Get(); + var renameModalTitle = Loc.Localize("RenameStyleModalTitle", "Rename Style"); + + var workStyle = config.SavedStyles[this.currentSel]; + workStyle.BuiltInColors ??= StyleModelV1.DalamudStandard.BuiltInColors; + + var appliedThisFrame = false; + + var styleAry = config.SavedStyles.Select(x => x.Name).ToArray(); + ImGui.Text(Loc.Localize("StyleEditorChooseStyle", "Choose Style:")); + if (ImGui.Combo("###styleChooserCombo", ref this.currentSel, styleAry, styleAry.Length)) { - var data = Service.Get(); - var names = data.GetExcelSheet(ClientLanguage.English); - var rng = new Random(); - - return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString; - } - - private void SaveStyle() - { - if (this.currentSel < 2) - return; - - var config = Service.Get(); - - var newStyle = StyleModelV1.Get(); - newStyle.Name = config.SavedStyles[this.currentSel].Name; - config.SavedStyles[this.currentSel] = newStyle; + var newStyle = config.SavedStyles[this.currentSel]; newStyle.Apply(); + appliedThisFrame = true; + } + + if (ImGui.Button(Loc.Localize("StyleEditorAddNew", "Add new style"))) + { + this.SaveStyle(); + + var newStyle = StyleModelV1.DalamudStandard; + newStyle.Name = GetRandomName(); + config.SavedStyles.Add(newStyle); + + this.currentSel = config.SavedStyles.Count - 1; + + newStyle.Apply(); + appliedThisFrame = true; config.Save(); } + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash) && this.currentSel != 0) + { + this.currentSel--; + var newStyle = config.SavedStyles[this.currentSel]; + newStyle.Apply(); + appliedThisFrame = true; + + config.SavedStyles.RemoveAt(this.currentSel + 1); + + config.Save(); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorDeleteStyle", "Delete current style")); + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) && this.currentSel != 0) + { + var newStyle = config.SavedStyles[this.currentSel]; + this.renameText = newStyle.Name; + + this.renameModalDrawing = true; + ImGui.OpenPopup(renameModalTitle); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorRenameStyle", "Rename style")); + + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport)) + { + var selectedStyle = config.SavedStyles[this.currentSel]; + var newStyle = StyleModelV1.Get(); + newStyle.Name = selectedStyle.Name; + ImGui.SetClipboardText(newStyle.Serialize()); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorCopy", "Copy style to clipboard for sharing")); + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport)) + { + this.SaveStyle(); + + var styleJson = ImGui.GetClipboardText(); + + try + { + var newStyle = StyleModel.Deserialize(styleJson); + + newStyle.Name ??= GetRandomName(); + + if (config.SavedStyles.Any(x => x.Name == newStyle.Name)) + { + newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)"; + } + + config.SavedStyles.Add(newStyle); + newStyle.Apply(); + appliedThisFrame = true; + + this.currentSel = config.SavedStyles.Count - 1; + + config.Save(); + } + catch (Exception ex) + { + Log.Error(ex, "Could not import style"); + } + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorImport", "Import style from clipboard")); + + ImGui.Separator(); + + ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.50f); + + if (this.currentSel < 2) + { + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("StyleEditorNotAllowed", "You cannot edit built-in styles. Please add a new style first.")); + } + else if (appliedThisFrame) + { + ImGui.Text(Loc.Localize("StyleEditorApplying", "Applying style...")); + } + else if (ImGui.BeginTabBar("StyleEditorTabs")) + { + var style = ImGui.GetStyle(); + + if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables"))) + { + ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + + ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui.Text("Borders"); + ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.Text("Rounding"); + ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f"); + ImGui.Text("Alignment"); + ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1; + if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0")) + style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1); + ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui.SameLine(); + ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content."); + ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui.SameLine(); + ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content."); + ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); + ImGui.SameLine(); + ImGuiComponents.HelpMarker( + "Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + ImGui.EndTabItem(); + + ImGui.EndChild(); + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors"))) + { + ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + + if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None)) + this.alphaFlags = ImGuiColorEditFlags.None; + ImGui.SameLine(); + if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview)) + this.alphaFlags = ImGuiColorEditFlags.AlphaPreview; + ImGui.SameLine(); + if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf)) + this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; + ImGui.SameLine(); + + ImGuiComponents.HelpMarker( + "In the color list:\n" + + "Left-click on color square to open color picker,\n" + + "Right-click to open edit options menu."); + + foreach (var imGuiCol in Enum.GetValues()) + { + if (imGuiCol == ImGuiCol.COUNT) + continue; + + ImGui.PushID(imGuiCol.ToString()); + + ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags); + + ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); + ImGui.TextUnformatted(imGuiCol.ToString()); + + ImGui.PopID(); + } + + ImGui.Separator(); + + foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + ImGui.PushID(property.Name); + + var colorVal = property.GetValue(workStyle.BuiltInColors); + if (colorVal == null) + { + colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors); + property.SetValue(workStyle.BuiltInColors, colorVal); + } + + var color = (Vector4)colorVal; + + if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags)) + { + property.SetValue(workStyle.BuiltInColors, color); + workStyle.BuiltInColors?.Apply(); + } + + ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); + ImGui.TextUnformatted(property.Name); + + ImGui.PopID(); + } + + ImGui.EndChild(); + + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + + ImGui.PopItemWidth(); + + ImGui.Separator(); + + if (ImGui.Button(Loc.Localize("Close", "Close"))) + { + this.IsOpen = false; + } + + ImGui.SameLine(); + + if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) + { + this.SaveStyle(); + + config.ChosenStyle = config.SavedStyles[this.currentSel].Name; + Log.Verbose("ChosenStyle = {ChosenStyle}", config.ChosenStyle); + + this.didSave = true; + + this.IsOpen = false; + } + + if (ImGui.BeginPopupModal(renameModalTitle, ref this.renameModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(Loc.Localize("StyleEditorEnterName", "Please enter the new name for this style.")); + ImGui.Spacing(); + + ImGui.InputText("###renameModalInput", ref this.renameText, 255); + + const float buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); + + if (ImGui.Button("OK", new Vector2(buttonWidth, 40))) + { + config.SavedStyles[this.currentSel].Name = this.renameText; + config.Save(); + + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + } + + private static string GetRandomName() + { + var data = Service.Get(); + var names = data.GetExcelSheet(ClientLanguage.English); + var rng = new Random(); + + return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString; + } + + private void SaveStyle() + { + if (this.currentSel < 2) + return; + + var config = Service.Get(); + + var newStyle = StyleModelV1.Get(); + newStyle.Name = config.SavedStyles[this.currentSel].Name; + config.SavedStyles[this.currentSel] = newStyle; + newStyle.Apply(); + + config.Save(); } } diff --git a/Dalamud/Interface/Style/DalamudColors.cs b/Dalamud/Interface/Style/DalamudColors.cs index a674ee4b2..0d39e056a 100644 --- a/Dalamud/Interface/Style/DalamudColors.cs +++ b/Dalamud/Interface/Style/DalamudColors.cs @@ -1,97 +1,93 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Interface.Colors; using Newtonsoft.Json; -namespace Dalamud.Interface.Style +namespace Dalamud.Interface.Style; +#pragma warning disable SA1600 + +public class DalamudColors { - #pragma warning disable SA1600 + [JsonProperty("a")] + public Vector4? DalamudRed { get; set; } - public class DalamudColors + [JsonProperty("b")] + public Vector4? DalamudGrey { get; set; } + + [JsonProperty("c")] + public Vector4? DalamudGrey2 { get; set; } + + [JsonProperty("d")] + public Vector4? DalamudGrey3 { get; set; } + + [JsonProperty("e")] + public Vector4? DalamudWhite { get; set; } + + [JsonProperty("f")] + public Vector4? DalamudWhite2 { get; set; } + + [JsonProperty("g")] + public Vector4? DalamudOrange { get; set; } + + [JsonProperty("h")] + public Vector4? TankBlue { get; set; } + + [JsonProperty("i")] + public Vector4? HealerGreen { get; set; } + + [JsonProperty("j")] + public Vector4? DPSRed { get; set; } + + public void Apply() + { + if (this.DalamudRed.HasValue) { - [JsonProperty("a")] - public Vector4? DalamudRed { get; set; } - - [JsonProperty("b")] - public Vector4? DalamudGrey { get; set; } - - [JsonProperty("c")] - public Vector4? DalamudGrey2 { get; set; } - - [JsonProperty("d")] - public Vector4? DalamudGrey3 { get; set; } - - [JsonProperty("e")] - public Vector4? DalamudWhite { get; set; } - - [JsonProperty("f")] - public Vector4? DalamudWhite2 { get; set; } - - [JsonProperty("g")] - public Vector4? DalamudOrange { get; set; } - - [JsonProperty("h")] - public Vector4? TankBlue { get; set; } - - [JsonProperty("i")] - public Vector4? HealerGreen { get; set; } - - [JsonProperty("j")] - public Vector4? DPSRed { get; set; } - - public void Apply() - { - if (this.DalamudRed.HasValue) - { - ImGuiColors.DalamudRed = this.DalamudRed.Value; - } - - if (this.DalamudGrey.HasValue) - { - ImGuiColors.DalamudGrey = this.DalamudGrey.Value; - } - - if (this.DalamudGrey2.HasValue) - { - ImGuiColors.DalamudGrey2 = this.DalamudGrey2.Value; - } - - if (this.DalamudGrey3.HasValue) - { - ImGuiColors.DalamudGrey3 = this.DalamudGrey3.Value; - } - - if (this.DalamudWhite.HasValue) - { - ImGuiColors.DalamudWhite = this.DalamudWhite.Value; - } - - if (this.DalamudWhite2.HasValue) - { - ImGuiColors.DalamudWhite2 = this.DalamudWhite2.Value; - } - - if (this.DalamudOrange.HasValue) - { - ImGuiColors.DalamudOrange = this.DalamudOrange.Value; - } - - if (this.TankBlue.HasValue) - { - ImGuiColors.TankBlue = this.TankBlue.Value; - } - - if (this.HealerGreen.HasValue) - { - ImGuiColors.HealerGreen = this.HealerGreen.Value; - } - - if (this.DPSRed.HasValue) - { - ImGuiColors.DPSRed = this.DPSRed.Value; - } - } + ImGuiColors.DalamudRed = this.DalamudRed.Value; } -#pragma warning restore SA1600 + if (this.DalamudGrey.HasValue) + { + ImGuiColors.DalamudGrey = this.DalamudGrey.Value; + } + + if (this.DalamudGrey2.HasValue) + { + ImGuiColors.DalamudGrey2 = this.DalamudGrey2.Value; + } + + if (this.DalamudGrey3.HasValue) + { + ImGuiColors.DalamudGrey3 = this.DalamudGrey3.Value; + } + + if (this.DalamudWhite.HasValue) + { + ImGuiColors.DalamudWhite = this.DalamudWhite.Value; + } + + if (this.DalamudWhite2.HasValue) + { + ImGuiColors.DalamudWhite2 = this.DalamudWhite2.Value; + } + + if (this.DalamudOrange.HasValue) + { + ImGuiColors.DalamudOrange = this.DalamudOrange.Value; + } + + if (this.TankBlue.HasValue) + { + ImGuiColors.TankBlue = this.TankBlue.Value; + } + + if (this.HealerGreen.HasValue) + { + ImGuiColors.HealerGreen = this.HealerGreen.Value; + } + + if (this.DPSRed.HasValue) + { + ImGuiColors.DPSRed = this.DPSRed.Value; + } + } } diff --git a/Dalamud/Interface/Style/StyleModel.cs b/Dalamud/Interface/Style/StyleModel.cs index 978ef78dc..8047a9a42 100644 --- a/Dalamud/Interface/Style/StyleModel.cs +++ b/Dalamud/Interface/Style/StyleModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -8,121 +8,120 @@ using Dalamud.Utility; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Interface.Style +namespace Dalamud.Interface.Style; + +/// +/// Superclass for all versions of the Dalamud style model. +/// +public abstract class StyleModel { /// - /// Superclass for all versions of the Dalamud style model. + /// Gets or sets the name of the style model. /// - public abstract class StyleModel + [JsonProperty("name")] + public string Name { get; set; } = "Unknown"; + + /// + /// Gets or sets class representing Dalamud-builtin . + /// + [JsonProperty("dol")] + public DalamudColors? BuiltInColors { get; set; } + + /// + /// Gets or sets version number of this model. + /// + [JsonProperty("ver")] + public int Version { get; set; } + + /// + /// Get a StyleModel based on the current Dalamud style, with the current version. + /// + /// The current style. + public static StyleModel GetFromCurrent() => StyleModelV1.Get(); + + /// + /// Get the current style model, as per configuration. + /// + /// The current style, as per configuration. + public static StyleModel? GetConfiguredStyle() { - /// - /// Gets or sets the name of the style model. - /// - [JsonProperty("name")] - public string Name { get; set; } = "Unknown"; - - /// - /// Gets or sets class representing Dalamud-builtin . - /// - [JsonProperty("dol")] - public DalamudColors? BuiltInColors { get; set; } - - /// - /// Gets or sets version number of this model. - /// - [JsonProperty("ver")] - public int Version { get; set; } - - /// - /// Get a StyleModel based on the current Dalamud style, with the current version. - /// - /// The current style. - public static StyleModel GetFromCurrent() => StyleModelV1.Get(); - - /// - /// Get the current style model, as per configuration. - /// - /// The current style, as per configuration. - public static StyleModel? GetConfiguredStyle() - { - var configuration = Service.Get(); - return configuration.SavedStyles?.FirstOrDefault(x => x.Name == configuration.ChosenStyle); - } - - /// - /// Get an enumerable of all saved styles. - /// - /// Enumerable of saved styles. - public static IEnumerable? GetConfiguredStyles() => Service.Get().SavedStyles; - - /// - /// Deserialize a style model. - /// - /// The serialized model. - /// The deserialized model. - /// Thrown in case the version of the model is not known. - public static StyleModel? Deserialize(string model) - { - var json = Util.DecompressString(Convert.FromBase64String(model.Substring(3))); - - if (model.StartsWith(StyleModelV1.SerializedPrefix)) - return JsonConvert.DeserializeObject(json); - - throw new ArgumentException("Was not a compressed style model."); - } - - /// - /// [TEMPORARY] Transfer old non-polymorphic style models to the new format. - /// - public static void TransferOldModels() - { - var configuration = Service.Get(); - - if (configuration.SavedStylesOld == null) - return; - - configuration.SavedStyles = new List(); - configuration.SavedStyles.AddRange(configuration.SavedStylesOld); - - Log.Information("Transferred {0} styles", configuration.SavedStyles.Count); - - configuration.SavedStylesOld = null; - configuration.Save(); - } - - /// - /// Serialize this style model. - /// - /// Serialized style model as string. - /// Thrown when the version of the style model is unknown. - public string Serialize() - { - string prefix; - switch (this) - { - case StyleModelV1: - prefix = StyleModelV1.SerializedPrefix; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - return prefix + Convert.ToBase64String(Util.CompressString(JsonConvert.SerializeObject(this))); - } - - /// - /// Apply this style model to ImGui. - /// - public abstract void Apply(); - - /// - /// Push this StyleModel into the ImGui style/color stack. - /// - public abstract void Push(); - - /// - /// Pop this style model from the ImGui style/color stack. - /// - public abstract void Pop(); + var configuration = Service.Get(); + return configuration.SavedStyles?.FirstOrDefault(x => x.Name == configuration.ChosenStyle); } + + /// + /// Get an enumerable of all saved styles. + /// + /// Enumerable of saved styles. + public static IEnumerable? GetConfiguredStyles() => Service.Get().SavedStyles; + + /// + /// Deserialize a style model. + /// + /// The serialized model. + /// The deserialized model. + /// Thrown in case the version of the model is not known. + public static StyleModel? Deserialize(string model) + { + var json = Util.DecompressString(Convert.FromBase64String(model.Substring(3))); + + if (model.StartsWith(StyleModelV1.SerializedPrefix)) + return JsonConvert.DeserializeObject(json); + + throw new ArgumentException("Was not a compressed style model."); + } + + /// + /// [TEMPORARY] Transfer old non-polymorphic style models to the new format. + /// + public static void TransferOldModels() + { + var configuration = Service.Get(); + + if (configuration.SavedStylesOld == null) + return; + + configuration.SavedStyles = new List(); + configuration.SavedStyles.AddRange(configuration.SavedStylesOld); + + Log.Information("Transferred {0} styles", configuration.SavedStyles.Count); + + configuration.SavedStylesOld = null; + configuration.Save(); + } + + /// + /// Serialize this style model. + /// + /// Serialized style model as string. + /// Thrown when the version of the style model is unknown. + public string Serialize() + { + string prefix; + switch (this) + { + case StyleModelV1: + prefix = StyleModelV1.SerializedPrefix; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return prefix + Convert.ToBase64String(Util.CompressString(JsonConvert.SerializeObject(this))); + } + + /// + /// Apply this style model to ImGui. + /// + public abstract void Apply(); + + /// + /// Push this StyleModel into the ImGui style/color stack. + /// + public abstract void Push(); + + /// + /// Pop this style model from the ImGui style/color stack. + /// + public abstract void Pop(); } diff --git a/Dalamud/Interface/Style/StyleModelV1.cs b/Dalamud/Interface/Style/StyleModelV1.cs index 3792df4f4..676c68f64 100644 --- a/Dalamud/Interface/Style/StyleModelV1.cs +++ b/Dalamud/Interface/Style/StyleModelV1.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Numerics; @@ -6,58 +6,58 @@ using Dalamud.Interface.Colors; using ImGuiNET; using Newtonsoft.Json; -namespace Dalamud.Interface.Style +namespace Dalamud.Interface.Style; + +/// +/// Version one of the Dalamud style model. +/// +public class StyleModelV1 : StyleModel { /// - /// Version one of the Dalamud style model. + /// Initializes a new instance of the class. /// - public class StyleModelV1 : StyleModel + private StyleModelV1() { - /// - /// Initializes a new instance of the class. - /// - private StyleModelV1() - { - this.Colors = new Dictionary(); - this.Name = "Unknown"; - } + this.Colors = new Dictionary(); + this.Name = "Unknown"; + } - /// - /// Gets the standard Dalamud look. - /// - public static StyleModelV1 DalamudStandard => new() - { - Name = "Dalamud Standard", + /// + /// Gets the standard Dalamud look. + /// + public static StyleModelV1 DalamudStandard => new() + { + Name = "Dalamud Standard", - Alpha = 1, - WindowPadding = new Vector2(8, 8), - WindowRounding = 4, - WindowBorderSize = 0, - WindowTitleAlign = new Vector2(0, 0.5f), - WindowMenuButtonPosition = ImGuiDir.Right, - ChildRounding = 0, - ChildBorderSize = 1, - PopupRounding = 0, - FramePadding = new Vector2(4, 3), - FrameRounding = 4, - FrameBorderSize = 0, - ItemSpacing = new Vector2(8, 4), - ItemInnerSpacing = new Vector2(4, 4), - CellPadding = new Vector2(4, 2), - TouchExtraPadding = new Vector2(0, 0), - IndentSpacing = 21, - ScrollbarSize = 16, - ScrollbarRounding = 9, - GrabMinSize = 13, - GrabRounding = 3, - LogSliderDeadzone = 4, - TabRounding = 4, - TabBorderSize = 0, - ButtonTextAlign = new Vector2(0.5f, 0.5f), - SelectableTextAlign = new Vector2(0, 0), - DisplaySafeAreaPadding = new Vector2(3, 3), + Alpha = 1, + WindowPadding = new Vector2(8, 8), + WindowRounding = 4, + WindowBorderSize = 0, + WindowTitleAlign = new Vector2(0, 0.5f), + WindowMenuButtonPosition = ImGuiDir.Right, + ChildRounding = 0, + ChildBorderSize = 1, + PopupRounding = 0, + FramePadding = new Vector2(4, 3), + FrameRounding = 4, + FrameBorderSize = 0, + ItemSpacing = new Vector2(8, 4), + ItemInnerSpacing = new Vector2(4, 4), + CellPadding = new Vector2(4, 2), + TouchExtraPadding = new Vector2(0, 0), + IndentSpacing = 21, + ScrollbarSize = 16, + ScrollbarRounding = 9, + GrabMinSize = 13, + GrabRounding = 3, + LogSliderDeadzone = 4, + TabRounding = 4, + TabBorderSize = 0, + ButtonTextAlign = new Vector2(0.5f, 0.5f), + SelectableTextAlign = new Vector2(0, 0), + DisplaySafeAreaPadding = new Vector2(3, 3), - Colors = new Dictionary + Colors = new Dictionary { { "Text", new Vector4(1, 1, 1, 1) }, { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1) }, @@ -116,57 +116,57 @@ namespace Dalamud.Interface.Style { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, }, - BuiltInColors = new DalamudColors - { - DalamudRed = new Vector4(1f, 0f, 0f, 1f), - DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), - DalamudWhite = new Vector4(1f, 1f, 1f, 1f), - DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), - DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), - TankBlue = new Vector4(0f, 0.6f, 1f, 1f), - HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), - DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), - }, - }; - - /// - /// Gets the standard Dalamud look. - /// - public static StyleModelV1 DalamudClassic => new() + BuiltInColors = new DalamudColors { - Name = "Dalamud Classic", + DalamudRed = new Vector4(1f, 0f, 0f, 1f), + DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), + DalamudWhite = new Vector4(1f, 1f, 1f, 1f), + DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), + DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), + TankBlue = new Vector4(0f, 0.6f, 1f, 1f), + HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), + DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), + }, + }; - Alpha = 1, - WindowPadding = new Vector2(8, 8), - WindowRounding = 4, - WindowBorderSize = 0, - WindowTitleAlign = new Vector2(0, 0.5f), - WindowMenuButtonPosition = ImGuiDir.Right, - ChildRounding = 0, - ChildBorderSize = 1, - PopupRounding = 0, - FramePadding = new Vector2(4, 3), - FrameRounding = 4, - FrameBorderSize = 0, - ItemSpacing = new Vector2(8, 4), - ItemInnerSpacing = new Vector2(4, 4), - CellPadding = new Vector2(4, 2), - TouchExtraPadding = new Vector2(0, 0), - IndentSpacing = 21, - ScrollbarSize = 16, - ScrollbarRounding = 9, - GrabMinSize = 10, - GrabRounding = 3, - LogSliderDeadzone = 4, - TabRounding = 4, - TabBorderSize = 0, - ButtonTextAlign = new Vector2(0.5f, 0.5f), - SelectableTextAlign = new Vector2(0, 0), - DisplaySafeAreaPadding = new Vector2(3, 3), + /// + /// Gets the standard Dalamud look. + /// + public static StyleModelV1 DalamudClassic => new() + { + Name = "Dalamud Classic", - Colors = new Dictionary + Alpha = 1, + WindowPadding = new Vector2(8, 8), + WindowRounding = 4, + WindowBorderSize = 0, + WindowTitleAlign = new Vector2(0, 0.5f), + WindowMenuButtonPosition = ImGuiDir.Right, + ChildRounding = 0, + ChildBorderSize = 1, + PopupRounding = 0, + FramePadding = new Vector2(4, 3), + FrameRounding = 4, + FrameBorderSize = 0, + ItemSpacing = new Vector2(8, 4), + ItemInnerSpacing = new Vector2(4, 4), + CellPadding = new Vector2(4, 2), + TouchExtraPadding = new Vector2(0, 0), + IndentSpacing = 21, + ScrollbarSize = 16, + ScrollbarRounding = 9, + GrabMinSize = 10, + GrabRounding = 3, + LogSliderDeadzone = 4, + TabRounding = 4, + TabBorderSize = 0, + ButtonTextAlign = new Vector2(0.5f, 0.5f), + SelectableTextAlign = new Vector2(0, 0), + DisplaySafeAreaPadding = new Vector2(3, 3), + + Colors = new Dictionary { { "Text", new Vector4(1f, 1f, 1f, 1f) }, { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1f) }, @@ -225,241 +225,240 @@ namespace Dalamud.Interface.Style { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, }, - BuiltInColors = new DalamudColors - { - DalamudRed = new Vector4(1f, 0f, 0f, 1f), - DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), - DalamudWhite = new Vector4(1f, 1f, 1f, 1f), - DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), - DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), - TankBlue = new Vector4(0f, 0.6f, 1f, 1f), - HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), - DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), - }, - }; + BuiltInColors = new DalamudColors + { + DalamudRed = new Vector4(1f, 0f, 0f, 1f), + DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), + DalamudWhite = new Vector4(1f, 1f, 1f, 1f), + DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), + DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), + TankBlue = new Vector4(0f, 0.6f, 1f, 1f), + HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), + DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), + }, + }; - /// - /// Gets the version prefix for this version. - /// - public static string SerializedPrefix => "DS1"; + /// + /// Gets the version prefix for this version. + /// + public static string SerializedPrefix => "DS1"; #pragma warning disable SA1600 - [JsonProperty("a")] - public float Alpha { get; set; } + [JsonProperty("a")] + public float Alpha { get; set; } - [JsonProperty("b")] - public Vector2 WindowPadding { get; set; } + [JsonProperty("b")] + public Vector2 WindowPadding { get; set; } - [JsonProperty("c")] - public float WindowRounding { get; set; } + [JsonProperty("c")] + public float WindowRounding { get; set; } - [JsonProperty("d")] - public float WindowBorderSize { get; set; } + [JsonProperty("d")] + public float WindowBorderSize { get; set; } - [JsonProperty("e")] - public Vector2 WindowTitleAlign { get; set; } + [JsonProperty("e")] + public Vector2 WindowTitleAlign { get; set; } - [JsonProperty("f")] - public ImGuiDir WindowMenuButtonPosition { get; set; } + [JsonProperty("f")] + public ImGuiDir WindowMenuButtonPosition { get; set; } - [JsonProperty("g")] - public float ChildRounding { get; set; } + [JsonProperty("g")] + public float ChildRounding { get; set; } - [JsonProperty("h")] - public float ChildBorderSize { get; set; } + [JsonProperty("h")] + public float ChildBorderSize { get; set; } - [JsonProperty("i")] - public float PopupRounding { get; set; } + [JsonProperty("i")] + public float PopupRounding { get; set; } - [JsonProperty("j")] - public Vector2 FramePadding { get; set; } + [JsonProperty("j")] + public Vector2 FramePadding { get; set; } - [JsonProperty("k")] - public float FrameRounding { get; set; } + [JsonProperty("k")] + public float FrameRounding { get; set; } - [JsonProperty("l")] - public float FrameBorderSize { get; set; } + [JsonProperty("l")] + public float FrameBorderSize { get; set; } - [JsonProperty("m")] - public Vector2 ItemSpacing { get; set; } + [JsonProperty("m")] + public Vector2 ItemSpacing { get; set; } - [JsonProperty("n")] - public Vector2 ItemInnerSpacing { get; set; } + [JsonProperty("n")] + public Vector2 ItemInnerSpacing { get; set; } - [JsonProperty("o")] - public Vector2 CellPadding { get; set; } + [JsonProperty("o")] + public Vector2 CellPadding { get; set; } - [JsonProperty("p")] - public Vector2 TouchExtraPadding { get; set; } + [JsonProperty("p")] + public Vector2 TouchExtraPadding { get; set; } - [JsonProperty("q")] - public float IndentSpacing { get; set; } + [JsonProperty("q")] + public float IndentSpacing { get; set; } - [JsonProperty("r")] - public float ScrollbarSize { get; set; } + [JsonProperty("r")] + public float ScrollbarSize { get; set; } - [JsonProperty("s")] - public float ScrollbarRounding { get; set; } + [JsonProperty("s")] + public float ScrollbarRounding { get; set; } - [JsonProperty("t")] - public float GrabMinSize { get; set; } + [JsonProperty("t")] + public float GrabMinSize { get; set; } - [JsonProperty("u")] - public float GrabRounding { get; set; } + [JsonProperty("u")] + public float GrabRounding { get; set; } - [JsonProperty("v")] - public float LogSliderDeadzone { get; set; } + [JsonProperty("v")] + public float LogSliderDeadzone { get; set; } - [JsonProperty("w")] - public float TabRounding { get; set; } + [JsonProperty("w")] + public float TabRounding { get; set; } - [JsonProperty("x")] - public float TabBorderSize { get; set; } + [JsonProperty("x")] + public float TabBorderSize { get; set; } - [JsonProperty("y")] - public Vector2 ButtonTextAlign { get; set; } + [JsonProperty("y")] + public Vector2 ButtonTextAlign { get; set; } - [JsonProperty("z")] - public Vector2 SelectableTextAlign { get; set; } + [JsonProperty("z")] + public Vector2 SelectableTextAlign { get; set; } - [JsonProperty("aa")] - public Vector2 DisplaySafeAreaPadding { get; set; } + [JsonProperty("aa")] + public Vector2 DisplaySafeAreaPadding { get; set; } #pragma warning restore SA1600 - /// - /// Gets or sets a dictionary mapping ImGui color names to colors. - /// - [JsonProperty("col")] - public Dictionary Colors { get; set; } + /// + /// Gets or sets a dictionary mapping ImGui color names to colors. + /// + [JsonProperty("col")] + public Dictionary Colors { get; set; } - /// - /// Get a instance via ImGui. - /// - /// The newly created instance. - public static StyleModelV1 Get() + /// + /// Get a instance via ImGui. + /// + /// The newly created instance. + public static StyleModelV1 Get() + { + var model = new StyleModelV1(); + var style = ImGui.GetStyle(); + + model.Alpha = style.Alpha; + model.WindowPadding = style.WindowPadding; + model.WindowRounding = style.WindowRounding; + model.WindowBorderSize = style.WindowBorderSize; + model.WindowTitleAlign = style.WindowTitleAlign; + model.WindowMenuButtonPosition = style.WindowMenuButtonPosition; + model.ChildRounding = style.ChildRounding; + model.ChildBorderSize = style.ChildBorderSize; + model.PopupRounding = style.PopupRounding; + model.FramePadding = style.FramePadding; + model.FrameRounding = style.FrameRounding; + model.FrameBorderSize = style.FrameBorderSize; + model.ItemSpacing = style.ItemSpacing; + model.ItemInnerSpacing = style.ItemInnerSpacing; + model.CellPadding = style.CellPadding; + model.TouchExtraPadding = style.TouchExtraPadding; + model.IndentSpacing = style.IndentSpacing; + model.ScrollbarSize = style.ScrollbarSize; + model.ScrollbarRounding = style.ScrollbarRounding; + model.GrabMinSize = style.GrabMinSize; + model.GrabRounding = style.GrabRounding; + model.LogSliderDeadzone = style.LogSliderDeadzone; + model.TabRounding = style.TabRounding; + model.TabBorderSize = style.TabBorderSize; + model.ButtonTextAlign = style.ButtonTextAlign; + model.SelectableTextAlign = style.SelectableTextAlign; + model.DisplaySafeAreaPadding = style.DisplaySafeAreaPadding; + + model.Colors = new Dictionary(); + + foreach (var imGuiCol in Enum.GetValues()) { - var model = new StyleModelV1(); - var style = ImGui.GetStyle(); - - model.Alpha = style.Alpha; - model.WindowPadding = style.WindowPadding; - model.WindowRounding = style.WindowRounding; - model.WindowBorderSize = style.WindowBorderSize; - model.WindowTitleAlign = style.WindowTitleAlign; - model.WindowMenuButtonPosition = style.WindowMenuButtonPosition; - model.ChildRounding = style.ChildRounding; - model.ChildBorderSize = style.ChildBorderSize; - model.PopupRounding = style.PopupRounding; - model.FramePadding = style.FramePadding; - model.FrameRounding = style.FrameRounding; - model.FrameBorderSize = style.FrameBorderSize; - model.ItemSpacing = style.ItemSpacing; - model.ItemInnerSpacing = style.ItemInnerSpacing; - model.CellPadding = style.CellPadding; - model.TouchExtraPadding = style.TouchExtraPadding; - model.IndentSpacing = style.IndentSpacing; - model.ScrollbarSize = style.ScrollbarSize; - model.ScrollbarRounding = style.ScrollbarRounding; - model.GrabMinSize = style.GrabMinSize; - model.GrabRounding = style.GrabRounding; - model.LogSliderDeadzone = style.LogSliderDeadzone; - model.TabRounding = style.TabRounding; - model.TabBorderSize = style.TabBorderSize; - model.ButtonTextAlign = style.ButtonTextAlign; - model.SelectableTextAlign = style.SelectableTextAlign; - model.DisplaySafeAreaPadding = style.DisplaySafeAreaPadding; - - model.Colors = new Dictionary(); - - foreach (var imGuiCol in Enum.GetValues()) + if (imGuiCol == ImGuiCol.COUNT) { - if (imGuiCol == ImGuiCol.COUNT) - { - continue; - } - - model.Colors[imGuiCol.ToString()] = style.Colors[(int)imGuiCol]; + continue; } - model.BuiltInColors = new DalamudColors - { - DalamudRed = ImGuiColors.DalamudRed, - DalamudGrey = ImGuiColors.DalamudGrey, - DalamudGrey2 = ImGuiColors.DalamudGrey2, - DalamudGrey3 = ImGuiColors.DalamudGrey3, - DalamudWhite = ImGuiColors.DalamudWhite, - DalamudWhite2 = ImGuiColors.DalamudWhite2, - DalamudOrange = ImGuiColors.DalamudOrange, - TankBlue = ImGuiColors.TankBlue, - HealerGreen = ImGuiColors.HealerGreen, - DPSRed = ImGuiColors.DPSRed, - }; - - return model; + model.Colors[imGuiCol.ToString()] = style.Colors[(int)imGuiCol]; } - /// - /// Apply this StyleModel via ImGui. - /// - public override void Apply() + model.BuiltInColors = new DalamudColors { - var style = ImGui.GetStyle(); + DalamudRed = ImGuiColors.DalamudRed, + DalamudGrey = ImGuiColors.DalamudGrey, + DalamudGrey2 = ImGuiColors.DalamudGrey2, + DalamudGrey3 = ImGuiColors.DalamudGrey3, + DalamudWhite = ImGuiColors.DalamudWhite, + DalamudWhite2 = ImGuiColors.DalamudWhite2, + DalamudOrange = ImGuiColors.DalamudOrange, + TankBlue = ImGuiColors.TankBlue, + HealerGreen = ImGuiColors.HealerGreen, + DPSRed = ImGuiColors.DPSRed, + }; - style.Alpha = this.Alpha; - style.WindowPadding = this.WindowPadding; - style.WindowRounding = this.WindowRounding; - style.WindowBorderSize = this.WindowBorderSize; - style.WindowTitleAlign = this.WindowTitleAlign; - style.WindowMenuButtonPosition = this.WindowMenuButtonPosition; - style.ChildRounding = this.ChildRounding; - style.ChildBorderSize = this.ChildBorderSize; - style.PopupRounding = this.PopupRounding; - style.FramePadding = this.FramePadding; - style.FrameRounding = this.FrameRounding; - style.FrameBorderSize = this.FrameBorderSize; - style.ItemSpacing = this.ItemSpacing; - style.ItemInnerSpacing = this.ItemInnerSpacing; - style.CellPadding = this.CellPadding; - style.TouchExtraPadding = this.TouchExtraPadding; - style.IndentSpacing = this.IndentSpacing; - style.ScrollbarSize = this.ScrollbarSize; - style.ScrollbarRounding = this.ScrollbarRounding; - style.GrabMinSize = this.GrabMinSize; - style.GrabRounding = this.GrabRounding; - style.LogSliderDeadzone = this.LogSliderDeadzone; - style.TabRounding = this.TabRounding; - style.TabBorderSize = this.TabBorderSize; - style.ButtonTextAlign = this.ButtonTextAlign; - style.SelectableTextAlign = this.SelectableTextAlign; - style.DisplaySafeAreaPadding = this.DisplaySafeAreaPadding; + return model; + } - foreach (var imGuiCol in Enum.GetValues()) + /// + /// Apply this StyleModel via ImGui. + /// + public override void Apply() + { + var style = ImGui.GetStyle(); + + style.Alpha = this.Alpha; + style.WindowPadding = this.WindowPadding; + style.WindowRounding = this.WindowRounding; + style.WindowBorderSize = this.WindowBorderSize; + style.WindowTitleAlign = this.WindowTitleAlign; + style.WindowMenuButtonPosition = this.WindowMenuButtonPosition; + style.ChildRounding = this.ChildRounding; + style.ChildBorderSize = this.ChildBorderSize; + style.PopupRounding = this.PopupRounding; + style.FramePadding = this.FramePadding; + style.FrameRounding = this.FrameRounding; + style.FrameBorderSize = this.FrameBorderSize; + style.ItemSpacing = this.ItemSpacing; + style.ItemInnerSpacing = this.ItemInnerSpacing; + style.CellPadding = this.CellPadding; + style.TouchExtraPadding = this.TouchExtraPadding; + style.IndentSpacing = this.IndentSpacing; + style.ScrollbarSize = this.ScrollbarSize; + style.ScrollbarRounding = this.ScrollbarRounding; + style.GrabMinSize = this.GrabMinSize; + style.GrabRounding = this.GrabRounding; + style.LogSliderDeadzone = this.LogSliderDeadzone; + style.TabRounding = this.TabRounding; + style.TabBorderSize = this.TabBorderSize; + style.ButtonTextAlign = this.ButtonTextAlign; + style.SelectableTextAlign = this.SelectableTextAlign; + style.DisplaySafeAreaPadding = this.DisplaySafeAreaPadding; + + foreach (var imGuiCol in Enum.GetValues()) + { + if (imGuiCol == ImGuiCol.COUNT) { - if (imGuiCol == ImGuiCol.COUNT) - { - continue; - } - - style.Colors[(int)imGuiCol] = this.Colors[imGuiCol.ToString()]; + continue; } - this.BuiltInColors?.Apply(); + style.Colors[(int)imGuiCol] = this.Colors[imGuiCol.ToString()]; } - /// - public override void Push() - { - throw new NotImplementedException(); - } + this.BuiltInColors?.Apply(); + } - /// - public override void Pop() - { - throw new NotImplementedException(); - } + /// + public override void Push() + { + throw new NotImplementedException(); + } + + /// + public override void Pop() + { + throw new NotImplementedException(); } } diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 7811ccd00..3991ed8e5 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -13,316 +13,315 @@ using ImGuiScene; using Serilog; using SharpDX.Direct3D11; -namespace Dalamud.Interface +namespace Dalamud.Interface; + +/// +/// This class represents the Dalamud UI that is drawn on top of the game. +/// It can be used to draw custom windows and overlays. +/// +public sealed class UiBuilder : IDisposable { + private readonly Stopwatch stopwatch; + private readonly string namespaceName; + + private bool hasErrorWindow; + /// - /// This class represents the Dalamud UI that is drawn on top of the game. - /// It can be used to draw custom windows and overlays. + /// Initializes a new instance of the class and registers it. + /// You do not have to call this manually. /// - public sealed class UiBuilder : IDisposable + /// The plugin namespace. + internal UiBuilder(string namespaceName) { - private readonly Stopwatch stopwatch; - private readonly string namespaceName; + this.stopwatch = new Stopwatch(); + this.namespaceName = namespaceName; - private bool hasErrorWindow; + var interfaceManager = Service.Get(); + interfaceManager.Draw += this.OnDraw; + interfaceManager.BuildFonts += this.OnBuildFonts; + interfaceManager.ResizeBuffers += this.OnResizeBuffers; + } - /// - /// Initializes a new instance of the class and registers it. - /// You do not have to call this manually. - /// - /// The plugin namespace. - internal UiBuilder(string namespaceName) - { - this.stopwatch = new Stopwatch(); - this.namespaceName = namespaceName; + /// + /// The event that gets called when Dalamud is ready to draw your windows or overlays. + /// When it is called, you can use static ImGui calls. + /// + public event Action Draw; - var interfaceManager = Service.Get(); - interfaceManager.Draw += this.OnDraw; - interfaceManager.BuildFonts += this.OnBuildFonts; - interfaceManager.ResizeBuffers += this.OnResizeBuffers; - } + /// + /// The event that is called when the game's DirectX device is requesting you to resize your buffers. + /// + public event Action ResizeBuffers; - /// - /// The event that gets called when Dalamud is ready to draw your windows or overlays. - /// When it is called, you can use static ImGui calls. - /// - public event Action Draw; + /// + /// Event that is fired when the plugin should open its configuration interface. + /// + public event Action OpenConfigUi; - /// - /// The event that is called when the game's DirectX device is requesting you to resize your buffers. - /// - public event Action ResizeBuffers; + /// + /// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.
+ /// Any ImFontPtr objects that you store can be invalidated when fonts are rebuilt + /// (at any time), so you should both reload your custom fonts and restore those + /// pointers inside this handler.
+ /// PLEASE remove this handler inside Dispose, or when you no longer need your fonts! + ///
+ public event Action BuildFonts; - /// - /// Event that is fired when the plugin should open its configuration interface. - /// - public event Action OpenConfigUi; + /// + /// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons. + /// + public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont; - /// - /// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.
- /// Any ImFontPtr objects that you store can be invalidated when fonts are rebuilt - /// (at any time), so you should both reload your custom fonts and restore those - /// pointers inside this handler.
- /// PLEASE remove this handler inside Dispose, or when you no longer need your fonts! - ///
- public event Action BuildFonts; + /// + /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid in 17pt. + /// + public static ImFontPtr IconFont => InterfaceManager.IconFont; - /// - /// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons. - /// - public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont; + /// + /// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt. + /// + public static ImFontPtr MonoFont => InterfaceManager.MonoFont; - /// - /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid in 17pt. - /// - public static ImFontPtr IconFont => InterfaceManager.IconFont; + /// + /// Gets the game's active Direct3D device. + /// + public Device Device => Service.Get().Device; - /// - /// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt. - /// - public static ImFontPtr MonoFont => InterfaceManager.MonoFont; + /// + /// Gets the game's main window handle. + /// + public IntPtr WindowHandlePtr => Service.Get().WindowHandlePtr; - /// - /// Gets the game's active Direct3D device. - /// - public Device Device => Service.Get().Device; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. + /// + public bool DisableAutomaticUiHide { get; set; } = false; - /// - /// Gets the game's main window handle. - /// - public IntPtr WindowHandlePtr => Service.Get().WindowHandlePtr; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the user toggles the UI. + /// + public bool DisableUserUiHide { get; set; } = false; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. - /// - public bool DisableAutomaticUiHide { get; set; } = false; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically during cutscenes. + /// + public bool DisableCutsceneUiHide { get; set; } = false; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the user toggles the UI. - /// - public bool DisableUserUiHide { get; set; } = false; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically while gpose is active. + /// + public bool DisableGposeUiHide { get; set; } = false; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically during cutscenes. - /// - public bool DisableCutsceneUiHide { get; set; } = false; + /// + /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. + /// + public bool OverrideGameCursor + { + get => Service.Get().OverrideGameCursor; + set => Service.Get().OverrideGameCursor = value; + } - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically while gpose is active. - /// - public bool DisableGposeUiHide { get; set; } = false; + /// + /// Gets the count of Draw calls made since plugin creation. + /// + public ulong FrameCount { get; private set; } = 0; - /// - /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. - /// - public bool OverrideGameCursor - { - get => Service.Get().OverrideGameCursor; - set => Service.Get().OverrideGameCursor = value; - } - - /// - /// Gets the count of Draw calls made since plugin creation. - /// - public ulong FrameCount { get; private set; } = 0; - - /// - /// Gets or sets a value indicating whether statistics about UI draw time should be collected. - /// + /// + /// Gets or sets a value indicating whether statistics about UI draw time should be collected. + /// #if DEBUG - internal static bool DoStats { get; set; } = true; + internal static bool DoStats { get; set; } = true; #else internal static bool DoStats { get; set; } = false; #endif - /// - /// Gets a value indicating whether this UiBuilder has a configuration UI registered. - /// - internal bool HasConfigUi => this.OpenConfigUi != null; + /// + /// Gets a value indicating whether this UiBuilder has a configuration UI registered. + /// + internal bool HasConfigUi => this.OpenConfigUi != null; - /// - /// Gets or sets the time this plugin took to draw on the last frame. - /// - internal long LastDrawTime { get; set; } = -1; + /// + /// Gets or sets the time this plugin took to draw on the last frame. + /// + internal long LastDrawTime { get; set; } = -1; - /// - /// Gets or sets the longest amount of time this plugin ever took to draw. - /// - internal long MaxDrawTime { get; set; } = -1; + /// + /// Gets or sets the longest amount of time this plugin ever took to draw. + /// + internal long MaxDrawTime { get; set; } = -1; - /// - /// Gets or sets a history of the last draw times, used to calculate an average. - /// - internal List DrawTimeHistory { get; set; } = new List(); + /// + /// Gets or sets a history of the last draw times, used to calculate an average. + /// + internal List DrawTimeHistory { get; set; } = new List(); - private bool CutsceneActive + private bool CutsceneActive + { + get { - get - { - var condition = Service.Get(); - return condition[ConditionFlag.OccupiedInCutSceneEvent] - || condition[ConditionFlag.WatchingCutscene78]; - } - } - - private bool GposeActive - { - get - { - var condition = Service.Get(); - return condition[ConditionFlag.WatchingCutscene]; - } - } - - /// - /// Loads an image from the specified file. - /// - /// The full filepath to the image. - /// A object wrapping the created image. Use inside ImGui.Image(). - public TextureWrap LoadImage(string filePath) - => Service.Get().LoadImage(filePath); - - /// - /// Loads an image from a byte stream, such as a png downloaded into memory. - /// - /// A byte array containing the raw image data. - /// A object wrapping the created image. Use inside ImGui.Image(). - public TextureWrap LoadImage(byte[] imageData) - => Service.Get().LoadImage(imageData); - - /// - /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . - /// - /// A byte array containing the raw pixel data. - /// The width of the image contained in . - /// The height of the image contained in . - /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. - /// A object wrapping the created image. Use inside ImGui.Image(). - public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - => Service.Get().LoadImageRaw(imageData, width, height, numChannels); - - /// - /// Call this to queue a rebuild of the font atlas.
- /// This will invoke any handlers and ensure that any loaded fonts are - /// ready to be used on the next UI frame. - ///
- public void RebuildFonts() - { - Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName); - Service.Get().RebuildFonts(); - } - - /// - /// Add a notification to the notification queue. - /// - /// The content of the notification. - /// The title of the notification. - /// The type of the notification. - /// The time the notification should be displayed for. - public void AddNotification( - string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000) => - Service.Get().AddNotification(content, title, type, msDelay); - - /// - /// Unregister the UiBuilder. Do not call this in plugin code. - /// - public void Dispose() - { - var interfaceManager = Service.Get(); - - interfaceManager.Draw -= this.OnDraw; - interfaceManager.BuildFonts -= this.OnBuildFonts; - interfaceManager.ResizeBuffers -= this.OnResizeBuffers; - } - - /// - /// Open the registered configuration UI, if it exists. - /// - internal void OpenConfig() - { - this.OpenConfigUi?.Invoke(); - } - - private void OnDraw() - { - var configuration = Service.Get(); - var gameGui = Service.Get(); - var interfaceManager = Service.Get(); - - if ((gameGui.GameUiHidden && configuration.ToggleUiHide && !(this.DisableUserUiHide || this.DisableAutomaticUiHide)) || - (this.CutsceneActive && configuration.ToggleUiHideDuringCutscenes && !(this.DisableCutsceneUiHide || this.DisableAutomaticUiHide)) || - (this.GposeActive && configuration.ToggleUiHideDuringGpose && !(this.DisableGposeUiHide || this.DisableAutomaticUiHide))) - return; - - if (!interfaceManager.FontsReady) - return; - - ImGui.PushID(this.namespaceName); - if (DoStats) - { - this.stopwatch.Restart(); - } - - if (this.hasErrorWindow && ImGui.Begin($"{this.namespaceName} Error", ref this.hasErrorWindow, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize)) - { - ImGui.Text($"The plugin {this.namespaceName} ran into an error.\nContact the plugin developer for support.\n\nPlease try restarting your game."); - ImGui.Spacing(); - - if (ImGui.Button("OK")) - { - this.hasErrorWindow = false; - } - - ImGui.End(); - } - - ImGuiManagedAsserts.ImGuiContextSnapshot snapshot = null; - if (this.Draw != null) - { - snapshot = ImGuiManagedAsserts.GetSnapshot(); - } - - try - { - this.Draw?.Invoke(); - } - catch (Exception ex) - { - Log.Error(ex, "[{0}] UiBuilder OnBuildUi caught exception", this.namespaceName); - this.Draw = null; - this.OpenConfigUi = null; - - this.hasErrorWindow = true; - } - - // Only if Draw was successful - if (this.Draw != null) - { - ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); - } - - this.FrameCount++; - - if (DoStats) - { - this.stopwatch.Stop(); - this.LastDrawTime = this.stopwatch.ElapsedTicks; - this.MaxDrawTime = Math.Max(this.LastDrawTime, this.MaxDrawTime); - this.DrawTimeHistory.Add(this.LastDrawTime); - while (this.DrawTimeHistory.Count > 100) this.DrawTimeHistory.RemoveAt(0); - } - - ImGui.PopID(); - } - - private void OnBuildFonts() - { - this.BuildFonts?.Invoke(); - } - - private void OnResizeBuffers() - { - this.ResizeBuffers?.Invoke(); + var condition = Service.Get(); + return condition[ConditionFlag.OccupiedInCutSceneEvent] + || condition[ConditionFlag.WatchingCutscene78]; } } + + private bool GposeActive + { + get + { + var condition = Service.Get(); + return condition[ConditionFlag.WatchingCutscene]; + } + } + + /// + /// Loads an image from the specified file. + /// + /// The full filepath to the image. + /// A object wrapping the created image. Use inside ImGui.Image(). + public TextureWrap LoadImage(string filePath) + => Service.Get().LoadImage(filePath); + + /// + /// Loads an image from a byte stream, such as a png downloaded into memory. + /// + /// A byte array containing the raw image data. + /// A object wrapping the created image. Use inside ImGui.Image(). + public TextureWrap LoadImage(byte[] imageData) + => Service.Get().LoadImage(imageData); + + /// + /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . + /// + /// A byte array containing the raw pixel data. + /// The width of the image contained in . + /// The height of the image contained in . + /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. + /// A object wrapping the created image. Use inside ImGui.Image(). + public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) + => Service.Get().LoadImageRaw(imageData, width, height, numChannels); + + /// + /// Call this to queue a rebuild of the font atlas.
+ /// This will invoke any handlers and ensure that any loaded fonts are + /// ready to be used on the next UI frame. + ///
+ public void RebuildFonts() + { + Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName); + Service.Get().RebuildFonts(); + } + + /// + /// Add a notification to the notification queue. + /// + /// The content of the notification. + /// The title of the notification. + /// The type of the notification. + /// The time the notification should be displayed for. + public void AddNotification( + string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000) => + Service.Get().AddNotification(content, title, type, msDelay); + + /// + /// Unregister the UiBuilder. Do not call this in plugin code. + /// + public void Dispose() + { + var interfaceManager = Service.Get(); + + interfaceManager.Draw -= this.OnDraw; + interfaceManager.BuildFonts -= this.OnBuildFonts; + interfaceManager.ResizeBuffers -= this.OnResizeBuffers; + } + + /// + /// Open the registered configuration UI, if it exists. + /// + internal void OpenConfig() + { + this.OpenConfigUi?.Invoke(); + } + + private void OnDraw() + { + var configuration = Service.Get(); + var gameGui = Service.Get(); + var interfaceManager = Service.Get(); + + if ((gameGui.GameUiHidden && configuration.ToggleUiHide && !(this.DisableUserUiHide || this.DisableAutomaticUiHide)) || + (this.CutsceneActive && configuration.ToggleUiHideDuringCutscenes && !(this.DisableCutsceneUiHide || this.DisableAutomaticUiHide)) || + (this.GposeActive && configuration.ToggleUiHideDuringGpose && !(this.DisableGposeUiHide || this.DisableAutomaticUiHide))) + return; + + if (!interfaceManager.FontsReady) + return; + + ImGui.PushID(this.namespaceName); + if (DoStats) + { + this.stopwatch.Restart(); + } + + if (this.hasErrorWindow && ImGui.Begin($"{this.namespaceName} Error", ref this.hasErrorWindow, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize)) + { + ImGui.Text($"The plugin {this.namespaceName} ran into an error.\nContact the plugin developer for support.\n\nPlease try restarting your game."); + ImGui.Spacing(); + + if (ImGui.Button("OK")) + { + this.hasErrorWindow = false; + } + + ImGui.End(); + } + + ImGuiManagedAsserts.ImGuiContextSnapshot snapshot = null; + if (this.Draw != null) + { + snapshot = ImGuiManagedAsserts.GetSnapshot(); + } + + try + { + this.Draw?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, "[{0}] UiBuilder OnBuildUi caught exception", this.namespaceName); + this.Draw = null; + this.OpenConfigUi = null; + + this.hasErrorWindow = true; + } + + // Only if Draw was successful + if (this.Draw != null) + { + ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); + } + + this.FrameCount++; + + if (DoStats) + { + this.stopwatch.Stop(); + this.LastDrawTime = this.stopwatch.ElapsedTicks; + this.MaxDrawTime = Math.Max(this.LastDrawTime, this.MaxDrawTime); + this.DrawTimeHistory.Add(this.LastDrawTime); + while (this.DrawTimeHistory.Count > 100) this.DrawTimeHistory.RemoveAt(0); + } + + ImGui.PopID(); + } + + private void OnBuildFonts() + { + this.BuildFonts?.Invoke(); + } + + private void OnResizeBuffers() + { + this.ResizeBuffers?.Invoke(); + } } diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index f173f8256..8e1c65d26 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -4,298 +4,297 @@ using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Keys; using ImGuiNET; -namespace Dalamud.Interface.Windowing +namespace Dalamud.Interface.Windowing; + +/// +/// Base class you can use to implement an ImGui window for use with the built-in . +/// +public abstract class Window { + private static bool wasEscPressedLastFrame = false; + + private bool internalLastIsOpen = false; + private bool internalIsOpen = false; + /// - /// Base class you can use to implement an ImGui window for use with the built-in . + /// Initializes a new instance of the class. /// - public abstract class Window + /// The name/ID of this window. + /// If you have multiple windows with the same name, you will need to + /// append an unique ID to it by specifying it after "###" behind the window title. + /// + /// The of this window. + /// Whether or not this window should be limited to the main game window. + protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) { - private static bool wasEscPressedLastFrame = false; + this.WindowName = name; + this.Flags = flags; + this.ForceMainWindow = forceMainWindow; + } - private bool internalLastIsOpen = false; - private bool internalIsOpen = false; + /// + /// Gets or sets the namespace of the window. + /// + public string? Namespace { get; set; } - /// - /// Initializes a new instance of the class. - /// - /// The name/ID of this window. - /// If you have multiple windows with the same name, you will need to - /// append an unique ID to it by specifying it after "###" behind the window title. - /// - /// The of this window. - /// Whether or not this window should be limited to the main game window. - protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) + /// + /// Gets or sets the name of the window. + /// If you have multiple windows with the same name, you will need to + /// append an unique ID to it by specifying it after "###" behind the window title. + /// + public string WindowName { get; set; } + + /// + /// Gets a value indicating whether the window is focused. + /// + public bool IsFocused { get; private set; } + + /// + /// Gets or sets a value indicating whether this window is to be closed with a hotkey, like Escape, and keep game addons open in turn if it is closed. + /// + public bool RespectCloseHotkey { get; set; } = true; + + /// + /// Gets or sets the position of this window. + /// + public Vector2? Position { get; set; } + + /// + /// Gets or sets the condition that defines when the position of this window is set. + /// + public ImGuiCond PositionCondition { get; set; } + + /// + /// Gets or sets the size of the window. + /// + public Vector2? Size { get; set; } + + /// + /// Gets or sets the condition that defines when the size of this window is set. + /// + public ImGuiCond SizeCondition { get; set; } + + /// + /// Gets or sets the size constraints of the window. + /// + public WindowSizeConstraints? SizeConstraints { get; set; } + + /// + /// Gets or sets a value indicating whether or not this window is collapsed. + /// + public bool? Collapsed { get; set; } + + /// + /// Gets or sets the condition that defines when the collapsed state of this window is set. + /// + public ImGuiCond CollapsedCondition { get; set; } + + /// + /// Gets or sets the window flags. + /// + public ImGuiWindowFlags Flags { get; set; } + + /// + /// Gets or sets a value indicating whether or not this ImGui window will be forced to stay inside the main game window. + /// + public bool ForceMainWindow { get; set; } + + /// + /// Gets or sets this window's background alpha value. + /// + public float? BgAlpha { get; set; } + + /// + /// Gets or sets a value indicating whether or not this window will stay open. + /// + public bool IsOpen + { + get => this.internalIsOpen; + set => this.internalIsOpen = value; + } + + /// + /// Toggle window is open state. + /// + public void Toggle() + { + this.IsOpen ^= true; + } + + /// + /// Code to be executed before conditionals are applied and the window is drawn. + /// + public virtual void PreDraw() + { + } + + /// + /// Code to be executed after the window is drawn. + /// + public virtual void PostDraw() + { + } + + /// + /// Code to be executed every time the window renders. + /// + /// + /// In this method, implement your drawing code. + /// You do NOT need to ImGui.Begin your window. + /// + public abstract void Draw(); + + /// + /// Code to be executed when the window is opened. + /// + public virtual void OnOpen() + { + } + + /// + /// Code to be executed when the window is closed. + /// + public virtual void OnClose() + { + } + + /// + /// Draw the window via ImGui. + /// + internal void DrawInternal() + { + if (!this.IsOpen) { - this.WindowName = name; - this.Flags = flags; - this.ForceMainWindow = forceMainWindow; - } - - /// - /// Gets or sets the namespace of the window. - /// - public string? Namespace { get; set; } - - /// - /// Gets or sets the name of the window. - /// If you have multiple windows with the same name, you will need to - /// append an unique ID to it by specifying it after "###" behind the window title. - /// - public string WindowName { get; set; } - - /// - /// Gets a value indicating whether the window is focused. - /// - public bool IsFocused { get; private set; } - - /// - /// Gets or sets a value indicating whether this window is to be closed with a hotkey, like Escape, and keep game addons open in turn if it is closed. - /// - public bool RespectCloseHotkey { get; set; } = true; - - /// - /// Gets or sets the position of this window. - /// - public Vector2? Position { get; set; } - - /// - /// Gets or sets the condition that defines when the position of this window is set. - /// - public ImGuiCond PositionCondition { get; set; } - - /// - /// Gets or sets the size of the window. - /// - public Vector2? Size { get; set; } - - /// - /// Gets or sets the condition that defines when the size of this window is set. - /// - public ImGuiCond SizeCondition { get; set; } - - /// - /// Gets or sets the size constraints of the window. - /// - public WindowSizeConstraints? SizeConstraints { get; set; } - - /// - /// Gets or sets a value indicating whether or not this window is collapsed. - /// - public bool? Collapsed { get; set; } - - /// - /// Gets or sets the condition that defines when the collapsed state of this window is set. - /// - public ImGuiCond CollapsedCondition { get; set; } - - /// - /// Gets or sets the window flags. - /// - public ImGuiWindowFlags Flags { get; set; } - - /// - /// Gets or sets a value indicating whether or not this ImGui window will be forced to stay inside the main game window. - /// - public bool ForceMainWindow { get; set; } - - /// - /// Gets or sets this window's background alpha value. - /// - public float? BgAlpha { get; set; } - - /// - /// Gets or sets a value indicating whether or not this window will stay open. - /// - public bool IsOpen - { - get => this.internalIsOpen; - set => this.internalIsOpen = value; - } - - /// - /// Toggle window is open state. - /// - public void Toggle() - { - this.IsOpen ^= true; - } - - /// - /// Code to be executed before conditionals are applied and the window is drawn. - /// - public virtual void PreDraw() - { - } - - /// - /// Code to be executed after the window is drawn. - /// - public virtual void PostDraw() - { - } - - /// - /// Code to be executed every time the window renders. - /// - /// - /// In this method, implement your drawing code. - /// You do NOT need to ImGui.Begin your window. - /// - public abstract void Draw(); - - /// - /// Code to be executed when the window is opened. - /// - public virtual void OnOpen() - { - } - - /// - /// Code to be executed when the window is closed. - /// - public virtual void OnClose() - { - } - - /// - /// Draw the window via ImGui. - /// - internal void DrawInternal() - { - if (!this.IsOpen) - { - if (this.internalIsOpen != this.internalLastIsOpen) - { - this.internalLastIsOpen = this.internalIsOpen; - this.OnClose(); - - this.IsFocused = false; - } - - return; - } - - var hasNamespace = !string.IsNullOrEmpty(this.Namespace); - - if (hasNamespace) - ImGui.PushID(this.Namespace); - - this.PreDraw(); - this.ApplyConditionals(); - - if (this.ForceMainWindow) - ImGuiHelpers.ForceNextWindowMainViewport(); - - if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) + if (this.internalIsOpen != this.internalLastIsOpen) { this.internalLastIsOpen = this.internalIsOpen; - this.OnOpen(); + this.OnClose(); + + this.IsFocused = false; } - var wasFocused = this.IsFocused; - if (wasFocused) - { - var style = ImGui.GetStyle(); - var focusedHeaderColor = style.Colors[(int)ImGuiCol.TitleBgActive]; - ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, focusedHeaderColor); - } - - if (ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags)) - { - // Draw the actual window contents - this.Draw(); - } - - if (wasFocused) - { - ImGui.PopStyleColor(); - } - - this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); - - var escapeDown = Service.Get()[VirtualKey.ESCAPE]; - var isAllowed = Service.Get().IsFocusManagementEnabled; - if (escapeDown && this.IsFocused && isAllowed && !wasEscPressedLastFrame && this.RespectCloseHotkey) - { - this.IsOpen = false; - wasEscPressedLastFrame = true; - } - else if (!escapeDown && wasEscPressedLastFrame) - { - wasEscPressedLastFrame = false; - } - - ImGui.End(); - - this.PostDraw(); - - if (hasNamespace) - ImGui.PopID(); + return; } - // private void CheckState() - // { - // if (this.internalLastIsOpen != this.internalIsOpen) - // { - // if (this.internalIsOpen) - // { - // this.OnOpen(); - // } - // else - // { - // this.OnClose(); - // } - // } - // } + var hasNamespace = !string.IsNullOrEmpty(this.Namespace); - private void ApplyConditionals() + if (hasNamespace) + ImGui.PushID(this.Namespace); + + this.PreDraw(); + this.ApplyConditionals(); + + if (this.ForceMainWindow) + ImGuiHelpers.ForceNextWindowMainViewport(); + + if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) { - if (this.Position.HasValue) - { - var pos = this.Position.Value; - - if (this.ForceMainWindow) - pos += ImGuiHelpers.MainViewport.Pos; - - ImGui.SetNextWindowPos(pos, this.PositionCondition); - } - - if (this.Size.HasValue) - { - ImGui.SetNextWindowSize(this.Size.Value * ImGuiHelpers.GlobalScale, this.SizeCondition); - } - - if (this.Collapsed.HasValue) - { - ImGui.SetNextWindowCollapsed(this.Collapsed.Value, this.CollapsedCondition); - } - - if (this.SizeConstraints.HasValue) - { - ImGui.SetNextWindowSizeConstraints(this.SizeConstraints.Value.MinimumSize * ImGuiHelpers.GlobalScale, this.SizeConstraints.Value.MaximumSize * ImGuiHelpers.GlobalScale); - } - - if (this.BgAlpha.HasValue) - { - ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); - } + this.internalLastIsOpen = this.internalIsOpen; + this.OnOpen(); } - /// - /// Structure detailing the size constraints of a window. - /// - public struct WindowSizeConstraints + var wasFocused = this.IsFocused; + if (wasFocused) { - /// - /// Gets or sets the minimum size of the window. - /// - public Vector2 MinimumSize { get; set; } + var style = ImGui.GetStyle(); + var focusedHeaderColor = style.Colors[(int)ImGuiCol.TitleBgActive]; + ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, focusedHeaderColor); + } - /// - /// Gets or sets the maximum size of the window. - /// - public Vector2 MaximumSize { get; set; } + if (ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags)) + { + // Draw the actual window contents + this.Draw(); + } + + if (wasFocused) + { + ImGui.PopStyleColor(); + } + + this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); + + var escapeDown = Service.Get()[VirtualKey.ESCAPE]; + var isAllowed = Service.Get().IsFocusManagementEnabled; + if (escapeDown && this.IsFocused && isAllowed && !wasEscPressedLastFrame && this.RespectCloseHotkey) + { + this.IsOpen = false; + wasEscPressedLastFrame = true; + } + else if (!escapeDown && wasEscPressedLastFrame) + { + wasEscPressedLastFrame = false; + } + + ImGui.End(); + + this.PostDraw(); + + if (hasNamespace) + ImGui.PopID(); + } + + // private void CheckState() + // { + // if (this.internalLastIsOpen != this.internalIsOpen) + // { + // if (this.internalIsOpen) + // { + // this.OnOpen(); + // } + // else + // { + // this.OnClose(); + // } + // } + // } + + private void ApplyConditionals() + { + if (this.Position.HasValue) + { + var pos = this.Position.Value; + + if (this.ForceMainWindow) + pos += ImGuiHelpers.MainViewport.Pos; + + ImGui.SetNextWindowPos(pos, this.PositionCondition); + } + + if (this.Size.HasValue) + { + ImGui.SetNextWindowSize(this.Size.Value * ImGuiHelpers.GlobalScale, this.SizeCondition); + } + + if (this.Collapsed.HasValue) + { + ImGui.SetNextWindowCollapsed(this.Collapsed.Value, this.CollapsedCondition); + } + + if (this.SizeConstraints.HasValue) + { + ImGui.SetNextWindowSizeConstraints(this.SizeConstraints.Value.MinimumSize * ImGuiHelpers.GlobalScale, this.SizeConstraints.Value.MaximumSize * ImGuiHelpers.GlobalScale); + } + + if (this.BgAlpha.HasValue) + { + ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); } } + + /// + /// Structure detailing the size constraints of a window. + /// + public struct WindowSizeConstraints + { + /// + /// Gets or sets the minimum size of the window. + /// + public Vector2 MinimumSize { get; set; } + + /// + /// Gets or sets the maximum size of the window. + /// + public Vector2 MaximumSize { get; set; } + } } diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index 16dfcdf5c..18739763a 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -6,134 +6,133 @@ using Dalamud.Interface.Internal.ManagedAsserts; using ImGuiNET; using Serilog; -namespace Dalamud.Interface.Windowing +namespace Dalamud.Interface.Windowing; + +/// +/// Class running a WindowSystem using implementations to simplify ImGui windowing. +/// +public class WindowSystem { + private static DateTimeOffset lastAnyFocus; + + private readonly List windows = new(); + + private string lastFocusedWindowName = string.Empty; + /// - /// Class running a WindowSystem using implementations to simplify ImGui windowing. + /// Initializes a new instance of the class. /// - public class WindowSystem + /// The name/ID-space of this . + public WindowSystem(string? imNamespace = null) { - private static DateTimeOffset lastAnyFocus; + this.Namespace = imNamespace; + } - private readonly List windows = new(); + /// + /// Gets a value indicating whether any contains any + /// that has focus and is not marked to be excluded from consideration. + /// + public static bool HasAnyWindowSystemFocus { get; internal set; } = false; - private string lastFocusedWindowName = string.Empty; + /// + /// Gets the name of the currently focused window system that is redirecting normal escape functionality. + /// + public static string FocusedWindowSystemNamespace { get; internal set; } = string.Empty; - /// - /// Initializes a new instance of the class. - /// - /// The name/ID-space of this . - public WindowSystem(string? imNamespace = null) + /// + /// Gets the timespan since the last time any window was focused. + /// + public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus; + + /// + /// Gets a value indicating whether any window in this has focus and is + /// not marked to be excluded from consideration. + /// + public bool HasAnyFocus { get; private set; } + + /// + /// Gets or sets the name/ID-space of this . + /// + public string? Namespace { get; set; } + + /// + /// Add a window to this . + /// + /// The window to add. + public void AddWindow(Window window) + { + if (this.windows.Any(x => x.WindowName == window.WindowName)) + throw new ArgumentException("A window with this name/ID already exists."); + + this.windows.Add(window); + } + + /// + /// Remove a window from this . + /// + /// The window to remove. + public void RemoveWindow(Window window) + { + if (!this.windows.Contains(window)) + throw new ArgumentException("This window is not registered on this WindowSystem."); + + this.windows.Remove(window); + } + + /// + /// Remove all windows from this . + /// + public void RemoveAllWindows() => this.windows.Clear(); + + /// + /// Draw all registered windows using ImGui. + /// + public void Draw() + { + var hasNamespace = !string.IsNullOrEmpty(this.Namespace); + + if (hasNamespace) + ImGui.PushID(this.Namespace); + + foreach (var window in this.windows) { - this.Namespace = imNamespace; - } - - /// - /// Gets a value indicating whether any contains any - /// that has focus and is not marked to be excluded from consideration. - /// - public static bool HasAnyWindowSystemFocus { get; internal set; } = false; - - /// - /// Gets the name of the currently focused window system that is redirecting normal escape functionality. - /// - public static string FocusedWindowSystemNamespace { get; internal set; } = string.Empty; - - /// - /// Gets the timespan since the last time any window was focused. - /// - public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus; - - /// - /// Gets a value indicating whether any window in this has focus and is - /// not marked to be excluded from consideration. - /// - public bool HasAnyFocus { get; private set; } - - /// - /// Gets or sets the name/ID-space of this . - /// - public string? Namespace { get; set; } - - /// - /// Add a window to this . - /// - /// The window to add. - public void AddWindow(Window window) - { - if (this.windows.Any(x => x.WindowName == window.WindowName)) - throw new ArgumentException("A window with this name/ID already exists."); - - this.windows.Add(window); - } - - /// - /// Remove a window from this . - /// - /// The window to remove. - public void RemoveWindow(Window window) - { - if (!this.windows.Contains(window)) - throw new ArgumentException("This window is not registered on this WindowSystem."); - - this.windows.Remove(window); - } - - /// - /// Remove all windows from this . - /// - public void RemoveAllWindows() => this.windows.Clear(); - - /// - /// Draw all registered windows using ImGui. - /// - public void Draw() - { - var hasNamespace = !string.IsNullOrEmpty(this.Namespace); - - if (hasNamespace) - ImGui.PushID(this.Namespace); - - foreach (var window in this.windows) - { #if DEBUG - // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); + // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif - var snapshot = ImGuiManagedAsserts.GetSnapshot(); + var snapshot = ImGuiManagedAsserts.GetSnapshot(); - window.DrawInternal(); + window.DrawInternal(); - var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; - ImGuiManagedAsserts.ReportProblems(source, snapshot); - } - - var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); - this.HasAnyFocus = focusedWindow != default; - - if (this.HasAnyFocus) - { - if (this.lastFocusedWindowName != focusedWindow.WindowName) - { - Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.WindowName}\" has focus now"); - this.lastFocusedWindowName = focusedWindow.WindowName; - } - - HasAnyWindowSystemFocus = true; - FocusedWindowSystemNamespace = this.Namespace; - - lastAnyFocus = DateTimeOffset.Now; - } - else - { - if (this.lastFocusedWindowName != string.Empty) - { - Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{this.lastFocusedWindowName}\" lost focus"); - this.lastFocusedWindowName = string.Empty; - } - } - - if (hasNamespace) - ImGui.PopID(); + var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; + ImGuiManagedAsserts.ReportProblems(source, snapshot); } + + var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); + this.HasAnyFocus = focusedWindow != default; + + if (this.HasAnyFocus) + { + if (this.lastFocusedWindowName != focusedWindow.WindowName) + { + Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.WindowName}\" has focus now"); + this.lastFocusedWindowName = focusedWindow.WindowName; + } + + HasAnyWindowSystemFocus = true; + FocusedWindowSystemNamespace = this.Namespace; + + lastAnyFocus = DateTimeOffset.Now; + } + else + { + if (this.lastFocusedWindowName != string.Empty) + { + Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{this.lastFocusedWindowName}\" lost focus"); + this.lastFocusedWindowName = string.Empty; + } + } + + if (hasNamespace) + ImGui.PopID(); } } diff --git a/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs b/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs index f6e6c1001..db69e17aa 100644 --- a/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs +++ b/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs @@ -1,25 +1,24 @@ using System; -namespace Dalamud.IoC.Internal +namespace Dalamud.IoC.Internal; + +/// +/// This attribute represents the current version of a module that is loaded in the Service Locator. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] +internal class InterfaceVersionAttribute : Attribute { /// - /// This attribute represents the current version of a module that is loaded in the Service Locator. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] - internal class InterfaceVersionAttribute : Attribute + /// The current version. + public InterfaceVersionAttribute(string version) { - /// - /// Initializes a new instance of the class. - /// - /// The current version. - public InterfaceVersionAttribute(string version) - { - this.Version = new(version); - } - - /// - /// Gets the service version. - /// - public Version Version { get; } + this.Version = new(version); } + + /// + /// Gets the service version. + /// + public Version Version { get; } } diff --git a/Dalamud/IoC/Internal/ObjectInstance.cs b/Dalamud/IoC/Internal/ObjectInstance.cs index 7475fcfaf..5d232f36b 100644 --- a/Dalamud/IoC/Internal/ObjectInstance.cs +++ b/Dalamud/IoC/Internal/ObjectInstance.cs @@ -1,31 +1,30 @@ using System; using System.Reflection; -namespace Dalamud.IoC.Internal +namespace Dalamud.IoC.Internal; + +/// +/// An object instance registered in the . +/// +internal class ObjectInstance { /// - /// An object instance registered in the . + /// Initializes a new instance of the class. /// - internal class ObjectInstance + /// The underlying instance. + public ObjectInstance(object instance) { - /// - /// Initializes a new instance of the class. - /// - /// The underlying instance. - public ObjectInstance(object instance) - { - this.Instance = new WeakReference(instance); - this.Version = instance.GetType().GetCustomAttribute(); - } - - /// - /// Gets the current version of the instance, if it exists. - /// - public InterfaceVersionAttribute? Version { get; } - - /// - /// Gets a reference to the underlying instance. - /// - public WeakReference Instance { get; } + this.Instance = new WeakReference(instance); + this.Version = instance.GetType().GetCustomAttribute(); } + + /// + /// Gets the current version of the instance, if it exists. + /// + public InterfaceVersionAttribute? Version { get; } + + /// + /// Gets a reference to the underlying instance. + /// + public WeakReference Instance { get; } } diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index a211107bb..5ee15a1b6 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -6,230 +6,229 @@ using System.Runtime.Serialization; using Dalamud.Logging.Internal; -namespace Dalamud.IoC.Internal +namespace Dalamud.IoC.Internal; + +/// +/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. +/// +internal class ServiceContainer : IServiceProvider { + private static readonly ModuleLog Log = new("SERVICECONTAINER"); + + private readonly Dictionary instances = new(); + /// - /// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. + /// Register a singleton object of any type into the current IOC container. /// - internal class ServiceContainer : IServiceProvider + /// The existing instance to register in the container. + /// The interface to register. + public void RegisterSingleton(T instance) { - private static readonly ModuleLog Log = new("SERVICECONTAINER"); - - private readonly Dictionary instances = new(); - - /// - /// Register a singleton object of any type into the current IOC container. - /// - /// The existing instance to register in the container. - /// The interface to register. - public void RegisterSingleton(T instance) + if (instance == null) { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - this.instances[typeof(T)] = new(instance); + throw new ArgumentNullException(nameof(instance)); } - /// - /// Create an object. - /// - /// The type of object to create. - /// Scoped objects to be included in the constructor. - /// The created object. - public object? Create(Type objectType, params object[] scopedObjects) + this.instances[typeof(T)] = new(instance); + } + + /// + /// Create an object. + /// + /// The type of object to create. + /// Scoped objects to be included in the constructor. + /// The created object. + public object? Create(Type objectType, params object[] scopedObjects) + { + var ctor = this.FindApplicableCtor(objectType, scopedObjects); + if (ctor == null) { - var ctor = this.FindApplicableCtor(objectType, scopedObjects); - if (ctor == null) - { - Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName); - return null; - } - - // validate dependency versions (if they exist) - var parameters = ctor.GetParameters().Select(p => - { - var parameterType = p.ParameterType; - var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; - return (parameterType, requiredVersion); - }); - - var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType)); - - if (!versionCheck) - { - Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); - return null; - } - - var resolvedParams = parameters - .Select(p => - { - var service = this.GetService(p.parameterType, scopedObjects); - - if (service == null) - { - Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName); - } - - return service; - }) - .ToArray(); - - var hasNull = resolvedParams.Any(p => p == null); - if (hasNull) - { - Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName); - return null; - } - - var instance = FormatterServices.GetUninitializedObject(objectType); - - if (!this.InjectProperties(instance, scopedObjects)) - { - Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName); - return null; - } - - ctor.Invoke(instance, resolvedParams); - - return instance; + Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName); + return null; } - /// - /// Inject interfaces into public or static properties on the provided object. - /// The properties have to be marked with the . - /// The properties can be marked with the to lock down versions. - /// - /// The object instance. - /// Scoped objects. - /// Whether or not the injection was successful. - public bool InjectProperties(object instance, params object[] scopedObjects) + // validate dependency versions (if they exist) + var parameters = ctor.GetParameters().Select(p => { - var objectType = instance.GetType(); + var parameterType = p.ParameterType; + var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; + return (parameterType, requiredVersion); + }); - var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | - BindingFlags.NonPublic).Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()).Select( - propertyInfo => - { - var requiredVersion = propertyInfo.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; - return (propertyInfo, requiredVersion); - }).ToArray(); + var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType)); - var versionCheck = props.All(x => CheckInterfaceVersion(x.requiredVersion, x.propertyInfo.PropertyType)); + if (!versionCheck) + { + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); + return null; + } - if (!versionCheck) + var resolvedParams = parameters + .Select(p => { - Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); - return false; - } - - foreach (var prop in props) - { - var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects); + var service = this.GetService(p.parameterType, scopedObjects); if (service == null) { - Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName); - return false; + Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName); } - prop.propertyInfo.SetValue(instance, service); - } + return service; + }) + .ToArray(); - return true; + var hasNull = resolvedParams.Any(p => p == null); + if (hasNull) + { + Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName); + return null; } - /// - object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType); + var instance = FormatterServices.GetUninitializedObject(objectType); - private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType) + if (!this.InjectProperties(instance, scopedObjects)) { - // if there's no required version, ignore it - if (requiredVersion == null) - return true; + Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName); + return null; + } - // if there's no requested version, ignore it - var declVersion = parameterType.GetCustomAttribute(); - if (declVersion == null) - return true; + ctor.Invoke(instance, resolvedParams); - if (declVersion.Version == requiredVersion.Version) - return true; + return instance; + } - Log.Error( - "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", - requiredVersion.Version, - declVersion.Version, - parameterType.FullName); + /// + /// Inject interfaces into public or static properties on the provided object. + /// The properties have to be marked with the . + /// The properties can be marked with the to lock down versions. + /// + /// The object instance. + /// Scoped objects. + /// Whether or not the injection was successful. + public bool InjectProperties(object instance, params object[] scopedObjects) + { + var objectType = instance.GetType(); + var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | + BindingFlags.NonPublic).Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()).Select( + propertyInfo => + { + var requiredVersion = propertyInfo.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; + return (propertyInfo, requiredVersion); + }).ToArray(); + + var versionCheck = props.All(x => CheckInterfaceVersion(x.requiredVersion, x.propertyInfo.PropertyType)); + + if (!versionCheck) + { + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); return false; } - private object? GetService(Type serviceType, object[] scopedObjects) + foreach (var prop in props) { - var singletonService = this.GetService(serviceType); - if (singletonService != null) + var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects); + + if (service == null) { - return singletonService; + Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName); + return false; } - // resolve dependency from scoped objects - var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType); - if (scoped == default) - { - return null; - } - - return scoped; + prop.propertyInfo.SetValue(instance, service); } - private object? GetService(Type serviceType) - { - var hasInstance = this.instances.TryGetValue(serviceType, out var service); - if (hasInstance && service.Instance.IsAlive) - { - return service.Instance.Target; - } + return true; + } - return null; - } - - private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects) - { - // get a list of all the available types: scoped and singleton - var types = scopedObjects - .Select(o => o.GetType()) - .Union(this.instances.Keys) - .ToArray(); - - var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - foreach (var ctor in ctors) - { - if (this.ValidateCtor(ctor, types)) - { - return ctor; - } - } - - return null; - } - - private bool ValidateCtor(ConstructorInfo ctor, Type[] types) - { - var parameters = ctor.GetParameters(); - foreach (var parameter in parameters) - { - var contains = types.Contains(parameter.ParameterType); - if (!contains) - { - Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName); - return false; - } - } + /// + object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType); + private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType) + { + // if there's no required version, ignore it + if (requiredVersion == null) return true; + + // if there's no requested version, ignore it + var declVersion = parameterType.GetCustomAttribute(); + if (declVersion == null) + return true; + + if (declVersion.Version == requiredVersion.Version) + return true; + + Log.Error( + "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", + requiredVersion.Version, + declVersion.Version, + parameterType.FullName); + + return false; + } + + private object? GetService(Type serviceType, object[] scopedObjects) + { + var singletonService = this.GetService(serviceType); + if (singletonService != null) + { + return singletonService; } + + // resolve dependency from scoped objects + var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType); + if (scoped == default) + { + return null; + } + + return scoped; + } + + private object? GetService(Type serviceType) + { + var hasInstance = this.instances.TryGetValue(serviceType, out var service); + if (hasInstance && service.Instance.IsAlive) + { + return service.Instance.Target; + } + + return null; + } + + private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects) + { + // get a list of all the available types: scoped and singleton + var types = scopedObjects + .Select(o => o.GetType()) + .Union(this.instances.Keys) + .ToArray(); + + var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); + foreach (var ctor in ctors) + { + if (this.ValidateCtor(ctor, types)) + { + return ctor; + } + } + + return null; + } + + private bool ValidateCtor(ConstructorInfo ctor, Type[] types) + { + var parameters = ctor.GetParameters(); + foreach (var parameter in parameters) + { + var contains = types.Contains(parameter.ParameterType); + if (!contains) + { + Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName); + return false; + } + } + + return true; } } diff --git a/Dalamud/IoC/PluginInterfaceAttribute.cs b/Dalamud/IoC/PluginInterfaceAttribute.cs index 98d330117..1711a5e84 100644 --- a/Dalamud/IoC/PluginInterfaceAttribute.cs +++ b/Dalamud/IoC/PluginInterfaceAttribute.cs @@ -1,12 +1,11 @@ using System; -namespace Dalamud.IoC +namespace Dalamud.IoC; + +/// +/// This attribute indicates whether the decorated class should be exposed to plugins via IoC. +/// +[AttributeUsage(AttributeTargets.Class)] +public class PluginInterfaceAttribute : Attribute { - /// - /// This attribute indicates whether the decorated class should be exposed to plugins via IoC. - /// - [AttributeUsage(AttributeTargets.Class)] - public class PluginInterfaceAttribute : Attribute - { - } } diff --git a/Dalamud/IoC/PluginServiceAttribute.cs b/Dalamud/IoC/PluginServiceAttribute.cs index 80baceb55..341592cf1 100644 --- a/Dalamud/IoC/PluginServiceAttribute.cs +++ b/Dalamud/IoC/PluginServiceAttribute.cs @@ -1,12 +1,11 @@ -using System; +using System; -namespace Dalamud.IoC +namespace Dalamud.IoC; + +/// +/// This attribute indicates whether an applicable service should be injected into the plugin. +/// +[AttributeUsage(AttributeTargets.Property)] +public class PluginServiceAttribute : Attribute { - /// - /// This attribute indicates whether an applicable service should be injected into the plugin. - /// - [AttributeUsage(AttributeTargets.Property)] - public class PluginServiceAttribute : Attribute - { - } } diff --git a/Dalamud/IoC/RequiredVersionAttribute.cs b/Dalamud/IoC/RequiredVersionAttribute.cs index a456ef783..97aca56bb 100644 --- a/Dalamud/IoC/RequiredVersionAttribute.cs +++ b/Dalamud/IoC/RequiredVersionAttribute.cs @@ -1,25 +1,24 @@ using System; -namespace Dalamud.IoC +namespace Dalamud.IoC; + +/// +/// This attribute indicates the version of a service module that is required for the plugin to load. +/// +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] +public class RequiredVersionAttribute : Attribute { /// - /// This attribute indicates the version of a service module that is required for the plugin to load. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] - public class RequiredVersionAttribute : Attribute + /// The required version. + public RequiredVersionAttribute(string version) { - /// - /// Initializes a new instance of the class. - /// - /// The required version. - public RequiredVersionAttribute(string version) - { - this.Version = new(version); - } - - /// - /// Gets the required version. - /// - public Version Version { get; } + this.Version = new(version); } + + /// + /// Gets the required version. + /// + public Version Version { get; } } diff --git a/Dalamud/Localization.cs b/Dalamud/Localization.cs index e8bb28578..3188d5296 100644 --- a/Dalamud/Localization.cs +++ b/Dalamud/Localization.cs @@ -7,145 +7,144 @@ using System.Reflection; using CheapLoc; using Serilog; -namespace Dalamud +namespace Dalamud; + +/// +/// Class handling localization. +/// +public class Localization { /// - /// Class handling localization. + /// Array of language codes which have a valid translation in Dalamud. /// - public class Localization + public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru" }; + + private const string FallbackLangCode = "en"; + + private readonly string locResourceDirectory; + private readonly string locResourcePrefix; + private readonly bool useEmbedded; + private readonly Assembly assembly; + + /// + /// Initializes a new instance of the class. + /// + /// The working directory to load language files from. + /// The prefix on the loc resource file name (e.g. dalamud_). + /// Use embedded loc resource files. + public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false) { - /// - /// Array of language codes which have a valid translation in Dalamud. - /// - public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru" }; + this.locResourceDirectory = locResourceDirectory; + this.locResourcePrefix = locResourcePrefix; + this.useEmbedded = useEmbedded; + this.assembly = Assembly.GetCallingAssembly(); + } - private const string FallbackLangCode = "en"; + /// + /// Delegate for the event that occurs when the language is changed. + /// + /// The language code of the new language. + public delegate void LocalizationChangedDelegate(string langCode); - private readonly string locResourceDirectory; - private readonly string locResourcePrefix; - private readonly bool useEmbedded; - private readonly Assembly assembly; + /// + /// Event that occurs when the language is changed. + /// + public event LocalizationChangedDelegate LocalizationChanged; - /// - /// Initializes a new instance of the class. - /// - /// The working directory to load language files from. - /// The prefix on the loc resource file name (e.g. dalamud_). - /// Use embedded loc resource files. - public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false) + /// + /// Search the set-up localization data for the provided assembly for the given string key and return it. + /// If the key is not present, the fallback is shown. + /// The fallback is also required to create the string files to be localized. + /// + /// The string key to be returned. + /// The fallback string, usually your source language. + /// The localized string, fallback or string key if not found. + // TODO: This breaks loc export, since it's being called without string args. Fix in CheapLoc. + public static string Localize(string key, string fallBack) + { + return Loc.Localize(key, fallBack, Assembly.GetCallingAssembly()); + } + + /// + /// Set up the UI language with the users' local UI culture. + /// + public void SetupWithUiCulture() + { + try { - this.locResourceDirectory = locResourceDirectory; - this.locResourcePrefix = locResourcePrefix; - this.useEmbedded = useEmbedded; - this.assembly = Assembly.GetCallingAssembly(); - } + var currentUiLang = CultureInfo.CurrentUICulture; + Log.Information("Trying to set up Loc for culture {0}", currentUiLang.TwoLetterISOLanguageName); - /// - /// Delegate for the event that occurs when the language is changed. - /// - /// The language code of the new language. - public delegate void LocalizationChangedDelegate(string langCode); - - /// - /// Event that occurs when the language is changed. - /// - public event LocalizationChangedDelegate LocalizationChanged; - - /// - /// Search the set-up localization data for the provided assembly for the given string key and return it. - /// If the key is not present, the fallback is shown. - /// The fallback is also required to create the string files to be localized. - /// - /// The string key to be returned. - /// The fallback string, usually your source language. - /// The localized string, fallback or string key if not found. - // TODO: This breaks loc export, since it's being called without string args. Fix in CheapLoc. - public static string Localize(string key, string fallBack) - { - return Loc.Localize(key, fallBack, Assembly.GetCallingAssembly()); - } - - /// - /// Set up the UI language with the users' local UI culture. - /// - public void SetupWithUiCulture() - { - try + if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) { - var currentUiLang = CultureInfo.CurrentUICulture; - Log.Information("Trying to set up Loc for culture {0}", currentUiLang.TwoLetterISOLanguageName); - - if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) - { - this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName); - } - else - { - this.SetupWithFallbacks(); - } + this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName); } - catch (Exception ex) - { - Log.Error(ex, "Could not get language information. Setting up fallbacks."); - this.SetupWithFallbacks(); - } - } - - /// - /// Set up the UI language with "fallbacks"(original English text). - /// - public void SetupWithFallbacks() - { - this.LocalizationChanged?.Invoke(FallbackLangCode); - Loc.SetupWithFallbacks(this.assembly); - } - - /// - /// Set up the UI language with the provided language code. - /// - /// The language code to set up the UI language with. - public void SetupWithLangCode(string langCode) - { - if (langCode.ToLower() == FallbackLangCode) + else { this.SetupWithFallbacks(); - return; - } - - this.LocalizationChanged?.Invoke(langCode); - - try - { - Loc.Setup(this.ReadLocData(langCode), this.assembly); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load loc {0}. Setting up fallbacks.", langCode); - this.SetupWithFallbacks(); } } - - /// - /// Saves localizable JSON data in the current working directory for the provided assembly. - /// - public void ExportLocalizable() + catch (Exception ex) { - Loc.ExportLocalizableForAssembly(this.assembly); - } - - private string ReadLocData(string langCode) - { - if (this.useEmbedded) - { - var resourceStream = this.assembly.GetManifestResourceStream($"{this.locResourceDirectory}{this.locResourcePrefix}{langCode}.json"); - if (resourceStream == null) - return null; - - using var reader = new StreamReader(resourceStream); - return reader.ReadToEnd(); - } - - return File.ReadAllText(Path.Combine(this.locResourceDirectory, $"{this.locResourcePrefix}{langCode}.json")); + Log.Error(ex, "Could not get language information. Setting up fallbacks."); + this.SetupWithFallbacks(); } } + + /// + /// Set up the UI language with "fallbacks"(original English text). + /// + public void SetupWithFallbacks() + { + this.LocalizationChanged?.Invoke(FallbackLangCode); + Loc.SetupWithFallbacks(this.assembly); + } + + /// + /// Set up the UI language with the provided language code. + /// + /// The language code to set up the UI language with. + public void SetupWithLangCode(string langCode) + { + if (langCode.ToLower() == FallbackLangCode) + { + this.SetupWithFallbacks(); + return; + } + + this.LocalizationChanged?.Invoke(langCode); + + try + { + Loc.Setup(this.ReadLocData(langCode), this.assembly); + } + catch (Exception ex) + { + Log.Error(ex, "Could not load loc {0}. Setting up fallbacks.", langCode); + this.SetupWithFallbacks(); + } + } + + /// + /// Saves localizable JSON data in the current working directory for the provided assembly. + /// + public void ExportLocalizable() + { + Loc.ExportLocalizableForAssembly(this.assembly); + } + + private string ReadLocData(string langCode) + { + if (this.useEmbedded) + { + var resourceStream = this.assembly.GetManifestResourceStream($"{this.locResourceDirectory}{this.locResourcePrefix}{langCode}.json"); + if (resourceStream == null) + return null; + + using var reader = new StreamReader(resourceStream); + return reader.ReadToEnd(); + } + + return File.ReadAllText(Path.Combine(this.locResourceDirectory, $"{this.locResourcePrefix}{langCode}.json")); + } } diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs index 9ec951cad..fa763ba13 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -1,124 +1,123 @@ using System; -namespace Dalamud.Logging.Internal +namespace Dalamud.Logging.Internal; + +/// +/// Class offering various methods to allow for logging in Dalamud modules. +/// +internal class ModuleLog { + private readonly string moduleName; + /// - /// Class offering various methods to allow for logging in Dalamud modules. + /// Initializes a new instance of the class. + /// This class can be used to prefix logging messages with a Dalamud module name prefix. For example, "[PLUGINR] ...". /// - internal class ModuleLog + /// The module name. + public ModuleLog(string moduleName) { - private readonly string moduleName; - - /// - /// Initializes a new instance of the class. - /// This class can be used to prefix logging messages with a Dalamud module name prefix. For example, "[PLUGINR] ...". - /// - /// The module name. - public ModuleLog(string moduleName) - { - this.moduleName = moduleName; - } - - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Verbose(string messageTemplate, params object[] values) - => Serilog.Log.Verbose($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Verbose(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Verbose(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Debug(string messageTemplate, params object[] values) - => Serilog.Log.Debug($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Debug(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Debug(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated information message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Information(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated information message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Information(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Warning(string messageTemplate, params object[] values) - => Serilog.Log.Warning($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Warning(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Warning(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated error message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Error(string messageTemplate, params object[] values) - => Serilog.Log.Error($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated error message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Error(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Error(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Fatal(string messageTemplate, params object[] values) - => Serilog.Log.Fatal($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Fatal(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Fatal(exception, $"[{this.moduleName}] {messageTemplate}", values); + this.moduleName = moduleName; } + + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Verbose(string messageTemplate, params object[] values) + => Serilog.Log.Verbose($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Verbose(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Verbose(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Debug(string messageTemplate, params object[] values) + => Serilog.Log.Debug($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Debug(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Debug(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated information message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Information(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated information message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Information(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Warning(string messageTemplate, params object[] values) + => Serilog.Log.Warning($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Warning(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Warning(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated error message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Error(string messageTemplate, params object[] values) + => Serilog.Log.Error($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated error message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Error(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Error(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Fatal(string messageTemplate, params object[] values) + => Serilog.Log.Fatal($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Fatal(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Fatal(exception, $"[{this.moduleName}] {messageTemplate}", values); } diff --git a/Dalamud/Logging/Internal/SerilogEventSink.cs b/Dalamud/Logging/Internal/SerilogEventSink.cs index eb7c23d10..4c1aa3803 100644 --- a/Dalamud/Logging/Internal/SerilogEventSink.cs +++ b/Dalamud/Logging/Internal/SerilogEventSink.cs @@ -3,49 +3,48 @@ using System; using Serilog.Core; using Serilog.Events; -namespace Dalamud.Logging.Internal +namespace Dalamud.Logging.Internal; + +/// +/// Serilog event sink. +/// +internal class SerilogEventSink : ILogEventSink { + private static SerilogEventSink instance; + private readonly IFormatProvider formatProvider; + /// - /// Serilog event sink. + /// Initializes a new instance of the class. /// - internal class SerilogEventSink : ILogEventSink + /// Logging format provider. + private SerilogEventSink(IFormatProvider formatProvider) { - private static SerilogEventSink instance; - private readonly IFormatProvider formatProvider; + this.formatProvider = formatProvider; + } - /// - /// Initializes a new instance of the class. - /// - /// Logging format provider. - private SerilogEventSink(IFormatProvider formatProvider) + /// + /// Event on a log line being emitted. + /// + public event EventHandler<(string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception)>? LogLine; + + /// + /// Gets the default instance. + /// + public static SerilogEventSink Instance => instance ??= new SerilogEventSink(null); + + /// + /// Emit a log event. + /// + /// Log event to be emitted. + public void Emit(LogEvent logEvent) + { + var message = logEvent.RenderMessage(this.formatProvider); + + if (logEvent.Exception != null) { - this.formatProvider = formatProvider; + message += "\n" + logEvent.Exception; } - /// - /// Event on a log line being emitted. - /// - public event EventHandler<(string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception)>? LogLine; - - /// - /// Gets the default instance. - /// - public static SerilogEventSink Instance => instance ??= new SerilogEventSink(null); - - /// - /// Emit a log event. - /// - /// Log event to be emitted. - public void Emit(LogEvent logEvent) - { - var message = logEvent.RenderMessage(this.formatProvider); - - if (logEvent.Exception != null) - { - message += "\n" + logEvent.Exception; - } - - this.LogLine?.Invoke(this, (message, logEvent.Level, logEvent.Timestamp, logEvent.Exception)); - } + this.LogLine?.Invoke(this, (message, logEvent.Level, logEvent.Timestamp, logEvent.Exception)); } } diff --git a/Dalamud/Logging/Internal/TaskTracker.cs b/Dalamud/Logging/Internal/TaskTracker.cs index 3888d55db..4d5625dd0 100644 --- a/Dalamud/Logging/Internal/TaskTracker.cs +++ b/Dalamud/Logging/Internal/TaskTracker.cs @@ -8,223 +8,222 @@ using System.Threading.Tasks; using Dalamud.Game; using Serilog; -namespace Dalamud.Logging.Internal +namespace Dalamud.Logging.Internal; + +/// +/// Class responsible for tracking asynchronous tasks. +/// +internal class TaskTracker : IDisposable { + private static readonly ModuleLog Log = new("TT"); + private static readonly List TrackedTasksInternal = new(); + private static readonly ConcurrentQueue NewlyCreatedTasks = new(); + private static bool clearRequested = false; + + private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; + /// - /// Class responsible for tracking asynchronous tasks. + /// Initializes a new instance of the class. /// - internal class TaskTracker : IDisposable + public TaskTracker() { - private static readonly ModuleLog Log = new("TT"); - private static readonly List TrackedTasksInternal = new(); - private static readonly ConcurrentQueue NewlyCreatedTasks = new(); - private static bool clearRequested = false; + this.ApplyPatch(); - private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; + var framework = Service.Get(); + framework.Update += this.FrameworkOnUpdate; + } - /// - /// Initializes a new instance of the class. - /// - public TaskTracker() + /// + /// Gets a read-only list of tracked tasks. + /// + public static IReadOnlyList Tasks => TrackedTasksInternal.ToArray(); + + /// + /// Clear the list of tracked tasks. + /// + public static void Clear() => clearRequested = true; + + /// + /// Update the tracked data. + /// + public static void UpdateData() + { + if (clearRequested) { - this.ApplyPatch(); - - var framework = Service.Get(); - framework.Update += this.FrameworkOnUpdate; + TrackedTasksInternal.Clear(); + clearRequested = false; } - /// - /// Gets a read-only list of tracked tasks. - /// - public static IReadOnlyList Tasks => TrackedTasksInternal.ToArray(); - - /// - /// Clear the list of tracked tasks. - /// - public static void Clear() => clearRequested = true; - - /// - /// Update the tracked data. - /// - public static void UpdateData() + var i = 0; + var pruned = 0; + // Prune old tasks. Technically a memory "leak" that grows infinitely otherwise. + while (i < TrackedTasksInternal.Count) { - if (clearRequested) + var taskInfo = TrackedTasksInternal[i]; + if (taskInfo.IsCompleted && !taskInfo.IsBeingViewed && TrackedTasksInternal.Count > 1000) { - TrackedTasksInternal.Clear(); - clearRequested = false; + TrackedTasksInternal.RemoveAt(i); + pruned++; + continue; } - var i = 0; - var pruned = 0; - // Prune old tasks. Technically a memory "leak" that grows infinitely otherwise. - while (i < TrackedTasksInternal.Count) - { - var taskInfo = TrackedTasksInternal[i]; - if (taskInfo.IsCompleted && !taskInfo.IsBeingViewed && TrackedTasksInternal.Count > 1000) - { - TrackedTasksInternal.RemoveAt(i); - pruned++; - continue; - } - - i++; - } - - // if (pruned > 0) - // Log.Debug($"Pruned {pruned} tasks"); - - // Consume from a queue to prevent iteration errors - while (NewlyCreatedTasks.TryDequeue(out var newTask)) - { - TrackedTasksInternal.Add(newTask); - } - - // Update each task - for (i = 0; i < TrackedTasksInternal.Count; i++) - { - var taskInfo = TrackedTasksInternal[i]; - if (taskInfo.Task == null) - continue; - - taskInfo.IsCompleted = taskInfo.Task.IsCompleted; - taskInfo.IsFaulted = taskInfo.Task.IsFaulted; - taskInfo.IsCanceled = taskInfo.Task.IsCanceled; - taskInfo.IsCompletedSuccessfully = taskInfo.Task.IsCompletedSuccessfully; - taskInfo.Status = taskInfo.Task.Status; - - if (taskInfo.IsCompleted || taskInfo.IsFaulted || taskInfo.IsCanceled || - taskInfo.IsCompletedSuccessfully) - { - taskInfo.Exception = taskInfo.Task.Exception; - - taskInfo.Task = null; - taskInfo.FinishTime = DateTime.Now; - } - } + i++; } - /// - public void Dispose() - { - this.scheduleAndStartHook?.Dispose(); + // if (pruned > 0) + // Log.Debug($"Pruned {pruned} tasks"); - var framework = Service.Get(); - framework.Update -= this.FrameworkOnUpdate; + // Consume from a queue to prevent iteration errors + while (NewlyCreatedTasks.TryDequeue(out var newTask)) + { + TrackedTasksInternal.Add(newTask); } - private static bool AddToActiveTasksHook(Func orig, Task self) + // Update each task + for (i = 0; i < TrackedTasksInternal.Count; i++) { - orig(self); + var taskInfo = TrackedTasksInternal[i]; + if (taskInfo.Task == null) + continue; - var trace = new StackTrace(); - NewlyCreatedTasks.Enqueue(new TaskInfo + taskInfo.IsCompleted = taskInfo.Task.IsCompleted; + taskInfo.IsFaulted = taskInfo.Task.IsFaulted; + taskInfo.IsCanceled = taskInfo.Task.IsCanceled; + taskInfo.IsCompletedSuccessfully = taskInfo.Task.IsCompletedSuccessfully; + taskInfo.Status = taskInfo.Task.Status; + + if (taskInfo.IsCompleted || taskInfo.IsFaulted || taskInfo.IsCanceled || + taskInfo.IsCompletedSuccessfully) { - Task = self, - Id = self.Id, - StackTrace = trace, - }); + taskInfo.Exception = taskInfo.Task.Exception; - return true; - } - - private void FrameworkOnUpdate(Framework framework) - { - UpdateData(); - } - - private void ApplyPatch() - { - var targetType = typeof(Task); - - var debugField = targetType.GetField("s_asyncDebuggingEnabled", BindingFlags.Static | BindingFlags.NonPublic); - debugField.SetValue(null, true); - - Log.Information("s_asyncDebuggingEnabled: {0}", debugField.GetValue(null)); - - var targetMethod = targetType.GetMethod("AddToActiveTasks", BindingFlags.Static | BindingFlags.NonPublic); - var patchMethod = typeof(TaskTracker).GetMethod(nameof(AddToActiveTasksHook), BindingFlags.NonPublic | BindingFlags.Static); - - if (targetMethod == null) - { - Log.Error("AddToActiveTasks TargetMethod null!"); - return; + taskInfo.Task = null; + taskInfo.FinishTime = DateTime.Now; } - - if (patchMethod == null) - { - Log.Error("AddToActiveTasks PatchMethod null!"); - return; - } - - this.scheduleAndStartHook = new MonoMod.RuntimeDetour.Hook(targetMethod, patchMethod); - - Log.Information("AddToActiveTasks Hooked!"); - } - - /// - /// Class representing a tracked task. - /// - internal class TaskInfo - { - /// - /// Gets or sets the tracked task. - /// - public Task? Task { get; set; } - - /// - /// Gets or sets the ID of the task. - /// - public int Id { get; set; } - - /// - /// Gets or sets the stack trace of where the task was started. - /// - public StackTrace? StackTrace { get; set; } - - /// - /// Gets or sets a value indicating whether or not the task was completed. - /// - public bool IsCompleted { get; set; } - - /// - /// Gets or sets a value indicating whether or not the task faulted. - /// - public bool IsFaulted { get; set; } - - /// - /// Gets or sets a value indicating whether or not the task was canceled. - /// - public bool IsCanceled { get; set; } - - /// - /// Gets or sets a value indicating whether or not the task was completed successfully. - /// - public bool IsCompletedSuccessfully { get; set; } - - /// - /// Gets or sets a value indicating whether this task is being viewed. - /// - public bool IsBeingViewed { get; set; } - - /// - /// Gets or sets the status of the task. - /// - public TaskStatus Status { get; set; } - - /// - /// Gets the start time of the task. - /// - public DateTime StartTime { get; } = DateTime.Now; - - /// - /// Gets or sets the end time of the task. - /// - public DateTime FinishTime { get; set; } - - /// - /// Gets or sets the exception that occurred within the task. - /// - public AggregateException? Exception { get; set; } } } + + /// + public void Dispose() + { + this.scheduleAndStartHook?.Dispose(); + + var framework = Service.Get(); + framework.Update -= this.FrameworkOnUpdate; + } + + private static bool AddToActiveTasksHook(Func orig, Task self) + { + orig(self); + + var trace = new StackTrace(); + NewlyCreatedTasks.Enqueue(new TaskInfo + { + Task = self, + Id = self.Id, + StackTrace = trace, + }); + + return true; + } + + private void FrameworkOnUpdate(Framework framework) + { + UpdateData(); + } + + private void ApplyPatch() + { + var targetType = typeof(Task); + + var debugField = targetType.GetField("s_asyncDebuggingEnabled", BindingFlags.Static | BindingFlags.NonPublic); + debugField.SetValue(null, true); + + Log.Information("s_asyncDebuggingEnabled: {0}", debugField.GetValue(null)); + + var targetMethod = targetType.GetMethod("AddToActiveTasks", BindingFlags.Static | BindingFlags.NonPublic); + var patchMethod = typeof(TaskTracker).GetMethod(nameof(AddToActiveTasksHook), BindingFlags.NonPublic | BindingFlags.Static); + + if (targetMethod == null) + { + Log.Error("AddToActiveTasks TargetMethod null!"); + return; + } + + if (patchMethod == null) + { + Log.Error("AddToActiveTasks PatchMethod null!"); + return; + } + + this.scheduleAndStartHook = new MonoMod.RuntimeDetour.Hook(targetMethod, patchMethod); + + Log.Information("AddToActiveTasks Hooked!"); + } + + /// + /// Class representing a tracked task. + /// + internal class TaskInfo + { + /// + /// Gets or sets the tracked task. + /// + public Task? Task { get; set; } + + /// + /// Gets or sets the ID of the task. + /// + public int Id { get; set; } + + /// + /// Gets or sets the stack trace of where the task was started. + /// + public StackTrace? StackTrace { get; set; } + + /// + /// Gets or sets a value indicating whether or not the task was completed. + /// + public bool IsCompleted { get; set; } + + /// + /// Gets or sets a value indicating whether or not the task faulted. + /// + public bool IsFaulted { get; set; } + + /// + /// Gets or sets a value indicating whether or not the task was canceled. + /// + public bool IsCanceled { get; set; } + + /// + /// Gets or sets a value indicating whether or not the task was completed successfully. + /// + public bool IsCompletedSuccessfully { get; set; } + + /// + /// Gets or sets a value indicating whether this task is being viewed. + /// + public bool IsBeingViewed { get; set; } + + /// + /// Gets or sets the status of the task. + /// + public TaskStatus Status { get; set; } + + /// + /// Gets the start time of the task. + /// + public DateTime StartTime { get; } = DateTime.Now; + + /// + /// Gets or sets the end time of the task. + /// + public DateTime FinishTime { get; set; } + + /// + /// Gets or sets the exception that occurred within the task. + /// + public AggregateException? Exception { get; set; } + } } diff --git a/Dalamud/Logging/PluginLog.cs b/Dalamud/Logging/PluginLog.cs index a21363854..4b652f5c5 100644 --- a/Dalamud/Logging/PluginLog.cs +++ b/Dalamud/Logging/PluginLog.cs @@ -1,240 +1,239 @@ using System; using System.Reflection; -namespace Dalamud.Logging +namespace Dalamud.Logging; + +/// +/// Class offering various static methods to allow for logging in plugins. +/// +public static class PluginLog { + #region "Log" prefixed Serilog style methods + /// - /// Class offering various static methods to allow for logging in plugins. + /// Log a templated message to the in-game debug log. /// - public static class PluginLog - { - #region "Log" prefixed Serilog style methods + /// The message template. + /// Values to log. + public static void Log(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Log(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Log(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Log(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogVerbose(string messageTemplate, params object[] values) + => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogVerbose(string messageTemplate, params object[] values) - => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogVerbose(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogVerbose(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogDebug(string messageTemplate, params object[] values) + => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogDebug(string messageTemplate, params object[] values) - => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogDebug(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogDebug(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogInformation(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogInformation(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogInformation(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogInformation(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogWarning(string messageTemplate, params object[] values) + => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogWarning(string messageTemplate, params object[] values) - => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogWarning(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogWarning(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogError(string messageTemplate, params object[] values) + => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogError(string messageTemplate, params object[] values) - => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogError(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogError(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogFatal(string messageTemplate, params object[] values) + => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogFatal(string messageTemplate, params object[] values) - => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogFatal(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogFatal(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + #endregion - #endregion + #region Serilog style methods - #region Serilog style methods + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Verbose(string messageTemplate, params object[] values) + => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Verbose(string messageTemplate, params object[] values) - => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Verbose(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Verbose(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Debug(string messageTemplate, params object[] values) + => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Debug(string messageTemplate, params object[] values) - => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Debug(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Debug(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Information(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Information(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Information(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Information(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Warning(string messageTemplate, params object[] values) + => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Warning(string messageTemplate, params object[] values) - => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Warning(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Warning(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Error(string messageTemplate, params object[] values) + => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Error(string messageTemplate, params object[] values) - => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Error(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Error(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Fatal(string messageTemplate, params object[] values) + => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Fatal(string messageTemplate, params object[] values) - => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Fatal(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Fatal(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - - #endregion - } + #endregion } diff --git a/Dalamud/Memory/Exceptions/MemoryAllocationException.cs b/Dalamud/Memory/Exceptions/MemoryAllocationException.cs index e289c8782..61f124bad 100644 --- a/Dalamud/Memory/Exceptions/MemoryAllocationException.cs +++ b/Dalamud/Memory/Exceptions/MemoryAllocationException.cs @@ -1,47 +1,46 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions +namespace Dalamud.Memory.Exceptions; + +/// +/// An exception thrown when VirtualAlloc fails. +/// +public class MemoryAllocationException : MemoryException { /// - /// An exception thrown when VirtualAlloc fails. + /// Initializes a new instance of the class. /// - public class MemoryAllocationException : MemoryException + public MemoryAllocationException() { - /// - /// Initializes a new instance of the class. - /// - public MemoryAllocationException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryAllocationException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryAllocationException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryAllocationException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryAllocationException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryAllocationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryAllocationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/Dalamud/Memory/Exceptions/MemoryException.cs b/Dalamud/Memory/Exceptions/MemoryException.cs index 810e76404..6cb1b887c 100644 --- a/Dalamud/Memory/Exceptions/MemoryException.cs +++ b/Dalamud/Memory/Exceptions/MemoryException.cs @@ -1,47 +1,46 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions +namespace Dalamud.Memory.Exceptions; + +/// +/// The base exception when thrown from Dalamud.Memory. +/// +public abstract class MemoryException : Exception { /// - /// The base exception when thrown from Dalamud.Memory. + /// Initializes a new instance of the class. /// - public abstract class MemoryException : Exception + public MemoryException() { - /// - /// Initializes a new instance of the class. - /// - public MemoryException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/Dalamud/Memory/Exceptions/MemoryPermissionException.cs b/Dalamud/Memory/Exceptions/MemoryPermissionException.cs index e94ccc0ce..b4dddfc5f 100644 --- a/Dalamud/Memory/Exceptions/MemoryPermissionException.cs +++ b/Dalamud/Memory/Exceptions/MemoryPermissionException.cs @@ -1,47 +1,46 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions +namespace Dalamud.Memory.Exceptions; + +/// +/// An exception thrown when VirtualProtect fails. +/// +public class MemoryPermissionException : MemoryException { /// - /// An exception thrown when VirtualProtect fails. + /// Initializes a new instance of the class. /// - public class MemoryPermissionException : MemoryException + public MemoryPermissionException() { - /// - /// Initializes a new instance of the class. - /// - public MemoryPermissionException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryPermissionException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryPermissionException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryPermissionException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryPermissionException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryPermissionException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryPermissionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/Dalamud/Memory/Exceptions/MemoryReadException.cs b/Dalamud/Memory/Exceptions/MemoryReadException.cs index 7f3f4b1f2..ee02c5473 100644 --- a/Dalamud/Memory/Exceptions/MemoryReadException.cs +++ b/Dalamud/Memory/Exceptions/MemoryReadException.cs @@ -1,47 +1,46 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions +namespace Dalamud.Memory.Exceptions; + +/// +/// An exception thrown when ReadProcessMemory fails. +/// +public class MemoryReadException : MemoryException { /// - /// An exception thrown when ReadProcessMemory fails. + /// Initializes a new instance of the class. /// - public class MemoryReadException : MemoryException + public MemoryReadException() { - /// - /// Initializes a new instance of the class. - /// - public MemoryReadException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryReadException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryReadException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryReadException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryReadException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryReadException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryReadException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/Dalamud/Memory/Exceptions/MemoryWriteException.cs b/Dalamud/Memory/Exceptions/MemoryWriteException.cs index 5aadcee53..edbf06fdc 100644 --- a/Dalamud/Memory/Exceptions/MemoryWriteException.cs +++ b/Dalamud/Memory/Exceptions/MemoryWriteException.cs @@ -1,47 +1,46 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions +namespace Dalamud.Memory.Exceptions; + +/// +/// An exception thrown when WriteProcessMemory fails. +/// +public class MemoryWriteException : MemoryException { /// - /// An exception thrown when WriteProcessMemory fails. + /// Initializes a new instance of the class. /// - public class MemoryWriteException : MemoryException + public MemoryWriteException() { - /// - /// Initializes a new instance of the class. - /// - public MemoryWriteException() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryWriteException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryWriteException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryWriteException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryWriteException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryWriteException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryWriteException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/Dalamud/Memory/MemoryHelper.cs b/Dalamud/Memory/MemoryHelper.cs index 0a4b840ef..d2dffc59f 100644 --- a/Dalamud/Memory/MemoryHelper.cs +++ b/Dalamud/Memory/MemoryHelper.cs @@ -11,647 +11,646 @@ using static Dalamud.NativeFunctions; // Heavily inspired from Reloaded (https://github.com/Reloaded-Project/Reloaded.Memory) -namespace Dalamud.Memory +namespace Dalamud.Memory; + +/// +/// A simple class that provides read/write access to arbitrary memory. +/// +public static unsafe class MemoryHelper { + #region Read + /// - /// A simple class that provides read/write access to arbitrary memory. + /// Reads a generic type from a specified memory address. /// - public static unsafe class MemoryHelper + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The read in struct. + public static T Read(IntPtr memoryAddress) where T : unmanaged + => Read(memoryAddress, false); + + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// Set this to true to enable struct marshalling. + /// The read in struct. + public static T Read(IntPtr memoryAddress, bool marshal) { - #region Read + return marshal + ? Marshal.PtrToStructure(memoryAddress) + : Unsafe.Read((void*)memoryAddress); + } - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The read in struct. - public static T Read(IntPtr memoryAddress) where T : unmanaged - => Read(memoryAddress, false); + /// + /// Reads a byte array from a specified memory address. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// The read in byte array. + public static byte[] ReadRaw(IntPtr memoryAddress, int length) + { + var value = new byte[length]; + Marshal.Copy(memoryAddress, value, 0, value.Length); + return value; + } - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// Set this to true to enable struct marshalling. - /// The read in struct. - public static T Read(IntPtr memoryAddress, bool marshal) + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// The read in struct array. + public static T[] Read(IntPtr memoryAddress, int arrayLength) where T : unmanaged + => Read(memoryAddress, arrayLength, false); + + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// Set this to true to enable struct marshalling. + /// The read in struct array. + public static T[] Read(IntPtr memoryAddress, int arrayLength, bool marshal) + { + var structSize = SizeOf(marshal); + var value = new T[arrayLength]; + + for (var i = 0; i < arrayLength; i++) { - return marshal - ? Marshal.PtrToStructure(memoryAddress) - : Unsafe.Read((void*)memoryAddress); + var address = memoryAddress + (structSize * i); + Read(address, out T result, marshal); + value[i] = result; } - /// - /// Reads a byte array from a specified memory address. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// The read in byte array. - public static byte[] ReadRaw(IntPtr memoryAddress, int length) + return value; + } + + /// + /// Reads a null-terminated byte array from a specified memory address. + /// + /// The memory address to read from. + /// The read in byte array. + public static unsafe byte[] ReadRawNullTerminated(IntPtr memoryAddress) + { + var byteCount = 0; + while (*(byte*)(memoryAddress + byteCount) != 0x00) { - var value = new byte[length]; - Marshal.Copy(memoryAddress, value, 0, value.Length); - return value; + byteCount++; } - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// The read in struct array. - public static T[] Read(IntPtr memoryAddress, int arrayLength) where T : unmanaged - => Read(memoryAddress, arrayLength, false); + return ReadRaw(memoryAddress, byteCount); + } - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// Set this to true to enable struct marshalling. - /// The read in struct array. - public static T[] Read(IntPtr memoryAddress, int arrayLength, bool marshal) + #endregion + + #region Read(out) + + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// Local variable to receive the read in struct. + public static void Read(IntPtr memoryAddress, out T value) where T : unmanaged + => value = Read(memoryAddress); + + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// Local variable to receive the read in struct. + /// Set this to true to enable struct marshalling. + public static void Read(IntPtr memoryAddress, out T value, bool marshal) + => value = Read(memoryAddress, marshal); + + /// + /// Reads raw data from a specified memory address. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// Local variable to receive the read in bytes. + public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value) + => value = ReadRaw(memoryAddress, length); + + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// The read in struct array. + public static void Read(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged + => value = Read(memoryAddress, arrayLength); + + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// Set this to true to enable struct marshalling. + /// The read in struct array. + public static void Read(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value) + => value = Read(memoryAddress, arrayLength, marshal); + + #endregion + + #region ReadString + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The read in string. + public static string ReadStringNullTerminated(IntPtr memoryAddress) + => ReadStringNullTerminated(memoryAddress, Encoding.UTF8); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The read in string. + public static string ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding) + { + var buffer = ReadRawNullTerminated(memoryAddress); + return encoding.GetString(buffer); + } + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The maximum length of the string. + /// The read in string. + public static string ReadString(IntPtr memoryAddress, int maxLength) + => ReadString(memoryAddress, Encoding.UTF8, maxLength); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The maximum length of the string. + /// The read in string. + public static string ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength) + { + if (maxLength <= 0) + return string.Empty; + + ReadRaw(memoryAddress, maxLength, out var buffer); + + var data = encoding.GetString(buffer); + var eosPos = data.IndexOf('\0'); + return eosPos >= 0 ? data.Substring(0, eosPos) : data; + } + + /// + /// Read a null-terminated SeString from a specified memory address. + /// + /// The memory address to read from. + /// The read in string. + public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress) + { + var buffer = ReadRawNullTerminated(memoryAddress); + return SeString.Parse(buffer); + } + + /// + /// Read an SeString from a specified memory address. + /// + /// The memory address to read from. + /// The maximum length of the string. + /// The read in string. + public static SeString ReadSeString(IntPtr memoryAddress, int maxLength) + { + ReadRaw(memoryAddress, maxLength, out var buffer); + + var eos = Array.IndexOf(buffer, (byte)0); + if (eos < 0) { - var structSize = SizeOf(marshal); - var value = new T[arrayLength]; - - for (var i = 0; i < arrayLength; i++) - { - var address = memoryAddress + (structSize * i); - Read(address, out T result, marshal); - value[i] = result; - } - - return value; - } - - /// - /// Reads a null-terminated byte array from a specified memory address. - /// - /// The memory address to read from. - /// The read in byte array. - public static unsafe byte[] ReadRawNullTerminated(IntPtr memoryAddress) - { - var byteCount = 0; - while (*(byte*)(memoryAddress + byteCount) != 0x00) - { - byteCount++; - } - - return ReadRaw(memoryAddress, byteCount); - } - - #endregion - - #region Read(out) - - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// Local variable to receive the read in struct. - public static void Read(IntPtr memoryAddress, out T value) where T : unmanaged - => value = Read(memoryAddress); - - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// Local variable to receive the read in struct. - /// Set this to true to enable struct marshalling. - public static void Read(IntPtr memoryAddress, out T value, bool marshal) - => value = Read(memoryAddress, marshal); - - /// - /// Reads raw data from a specified memory address. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// Local variable to receive the read in bytes. - public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value) - => value = ReadRaw(memoryAddress, length); - - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// The read in struct array. - public static void Read(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged - => value = Read(memoryAddress, arrayLength); - - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// Set this to true to enable struct marshalling. - /// The read in struct array. - public static void Read(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value) - => value = Read(memoryAddress, arrayLength, marshal); - - #endregion - - #region ReadString - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The read in string. - public static string ReadStringNullTerminated(IntPtr memoryAddress) - => ReadStringNullTerminated(memoryAddress, Encoding.UTF8); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The read in string. - public static string ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding) - { - var buffer = ReadRawNullTerminated(memoryAddress); - return encoding.GetString(buffer); - } - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The maximum length of the string. - /// The read in string. - public static string ReadString(IntPtr memoryAddress, int maxLength) - => ReadString(memoryAddress, Encoding.UTF8, maxLength); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The maximum length of the string. - /// The read in string. - public static string ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength) - { - if (maxLength <= 0) - return string.Empty; - - ReadRaw(memoryAddress, maxLength, out var buffer); - - var data = encoding.GetString(buffer); - var eosPos = data.IndexOf('\0'); - return eosPos >= 0 ? data.Substring(0, eosPos) : data; - } - - /// - /// Read a null-terminated SeString from a specified memory address. - /// - /// The memory address to read from. - /// The read in string. - public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress) - { - var buffer = ReadRawNullTerminated(memoryAddress); return SeString.Parse(buffer); } - - /// - /// Read an SeString from a specified memory address. - /// - /// The memory address to read from. - /// The maximum length of the string. - /// The read in string. - public static SeString ReadSeString(IntPtr memoryAddress, int maxLength) + else { - ReadRaw(memoryAddress, maxLength, out var buffer); - - var eos = Array.IndexOf(buffer, (byte)0); - if (eos < 0) - { - return SeString.Parse(buffer); - } - else - { - var newBuffer = new byte[eos]; - Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos); - return SeString.Parse(newBuffer); - } + var newBuffer = new byte[eos]; + Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos); + return SeString.Parse(newBuffer); } - - /// - /// Read an SeString from a specified Utf8String structure. - /// - /// The memory address to read from. - /// The read in string. - public static unsafe SeString ReadSeString(Utf8String* utf8String) - { - if (utf8String == null) - return string.Empty; - - var ptr = utf8String->StringPtr; - if (ptr == null) - return string.Empty; - - var len = Math.Max(utf8String->BufUsed, utf8String->StringLength); - - return ReadSeString((IntPtr)ptr, (int)len); - } - - #endregion - - #region ReadString(out) - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The read in string. - public static void ReadStringNullTerminated(IntPtr memoryAddress, out string value) - => value = ReadStringNullTerminated(memoryAddress); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The read in string. - public static void ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding, out string value) - => value = ReadStringNullTerminated(memoryAddress, encoding); - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The read in string. - /// The maximum length of the string. - public static void ReadString(IntPtr memoryAddress, out string value, int maxLength) - => value = ReadString(memoryAddress, maxLength); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The maximum length of the string. - /// The read in string. - public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value) - => value = ReadString(memoryAddress, encoding, maxLength); - - /// - /// Read a null-terminated SeString from a specified memory address. - /// - /// The memory address to read from. - /// The read in SeString. - public static void ReadSeStringNullTerminated(IntPtr memoryAddress, out SeString value) - => value = ReadSeStringNullTerminated(memoryAddress); - - /// - /// Read an SeString from a specified memory address. - /// - /// The memory address to read from. - /// The maximum length of the string. - /// The read in SeString. - public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value) - => value = ReadSeString(memoryAddress, maxLength); - - /// - /// Read an SeString from a specified Utf8String structure. - /// - /// The memory address to read from. - /// The read in string. - public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value) - => value = ReadSeString(utf8String); - - #endregion - - #region Write - - /// - /// Writes a generic type to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The item to write to the address. - public static void Write(IntPtr memoryAddress, T item) where T : unmanaged - => Write(memoryAddress, item); - - /// - /// Writes a generic type to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The item to write to the address. - /// Set this to true to enable struct marshalling. - public static void Write(IntPtr memoryAddress, T item, bool marshal) - { - if (marshal) - Marshal.StructureToPtr(item, memoryAddress, false); - else - Unsafe.Write((void*)memoryAddress, item); - } - - /// - /// Writes raw data to a specified memory address. - /// - /// The memory address to read from. - /// The bytes to write to memoryAddress. - public static void WriteRaw(IntPtr memoryAddress, byte[] data) - { - Marshal.Copy(data, 0, memoryAddress, data.Length); - } - - /// - /// Writes a generic type array to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to write to. - /// The array of items to write to the address. - public static void Write(IntPtr memoryAddress, T[] items) where T : unmanaged - => Write(memoryAddress, items, false); - - /// - /// Writes a generic type array to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to write to. - /// The array of items to write to the address. - /// Set this to true to enable struct marshalling. - public static void Write(IntPtr memoryAddress, T[] items, bool marshal) - { - var structSize = SizeOf(marshal); - - for (var i = 0; i < items.Length; i++) - { - var address = memoryAddress + (structSize * i); - Write(address, items[i], marshal); - } - } - - #endregion - - #region WriteString - - /// - /// Write a UTF-8 encoded string to a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to encode or the applicable helper method. - /// - /// The memory address to write to. - /// The string to write. - public static void WriteString(IntPtr memoryAddress, string value) - => WriteString(memoryAddress, value, Encoding.UTF8); - - /// - /// Write a string with the given encoding to a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to encode or the applicable helper method. - /// - /// The memory address to write to. - /// The string to write. - /// The encoding to use. - public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding) - { - if (string.IsNullOrEmpty(value)) - return; - - var bytes = encoding.GetBytes(value + '\0'); - - WriteRaw(memoryAddress, bytes); - } - - /// - /// Write an SeString to a specified memory address. - /// - /// The memory address to write to. - /// The SeString to write. - public static void WriteSeString(IntPtr memoryAddress, SeString value) - { - if (value is null) - return; - - WriteRaw(memoryAddress, value.Encode()); - } - - #endregion - - #region ApiWrappers - - /// - /// Allocates fixed size of memory inside the target memory source via Windows API calls. - /// Returns the address of newly allocated memory. - /// - /// Amount of bytes to be allocated. - /// Address to the newly allocated memory. - public static IntPtr Allocate(int length) - { - var address = VirtualAlloc( - IntPtr.Zero, - (UIntPtr)length, - AllocationType.Commit | AllocationType.Reserve, - MemoryProtection.ExecuteReadWrite); - - if (address == IntPtr.Zero) - throw new MemoryAllocationException($"Unable to allocate {length} bytes."); - - return address; - } - - /// - /// Allocates fixed size of memory inside the target memory source via Windows API calls. - /// Returns the address of newly allocated memory. - /// - /// Amount of bytes to be allocated. - /// Address to the newly allocated memory. - public static void Allocate(int length, out IntPtr memoryAddress) - => memoryAddress = Allocate(length); - - /// - /// Frees memory previously allocated with Allocate via Windows API calls. - /// - /// The address of the memory to free. - /// True if the operation is successful. - public static bool Free(IntPtr memoryAddress) - { - return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release); - } - - /// - /// Changes the page permissions for a specified combination of address and length via Windows API calls. - /// - /// The memory address for which to change page permissions for. - /// The region size for which to change permissions for. - /// The new permissions to set. - /// The old page permissions. - public static MemoryProtection ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions) - { - var result = VirtualProtect(memoryAddress, (UIntPtr)length, newPermissions, out var oldPermissions); - - if (!result) - throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})"); - - var last = Marshal.GetLastWin32Error(); - if (last > 0) - throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (error={last})"); - - return oldPermissions; - } - - /// - /// Changes the page permissions for a specified combination of address and length via Windows API calls. - /// - /// The memory address for which to change page permissions for. - /// The region size for which to change permissions for. - /// The new permissions to set. - /// The old page permissions. - public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions) - => oldPermissions = ChangePermission(memoryAddress, length, newPermissions); - - /// - /// Changes the page permissions for a specified combination of address and element from which to deduce size via Windows API calls. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address for which to change page permissions for. - /// The struct element from which the region size to change permissions for will be calculated. - /// The new permissions to set. - /// Set to true to calculate the size of the struct after marshalling instead of before. - /// The old page permissions. - public static MemoryProtection ChangePermission(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal) - => ChangePermission(memoryAddress, SizeOf(marshal), newPermissions); - - /// - /// Reads raw data from a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// The read in bytes. - public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length) - { - var value = new byte[length]; - ReadProcessMemory(memoryAddress, ref value); - return value; - } - - /// - /// Reads raw data from a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// The read in bytes. - public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value) - => value = ReadProcessMemory(memoryAddress, length); - - /// - /// Reads raw data from a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to read from. - /// The read in bytes. - public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value) - { - var length = value.Length; - var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _); - - if (!result) - throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); - - var last = Marshal.GetLastWin32Error(); - if (last > 0) - throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); - } - - /// - /// Writes raw data to a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to write to. - /// The bytes to write to memoryAddress. - public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data) - { - var length = data.Length; - var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _); - - if (!result) - throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); - - var last = Marshal.GetLastWin32Error(); - if (last > 0) - throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); - } - - #endregion - - #region Sizing - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The size of the primitive or struct. - public static int SizeOf() - => SizeOf(false); - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// If set to true; will return the size of an element after marshalling. - /// The size of the primitive or struct. - public static int SizeOf(bool marshal) - => marshal ? Marshal.SizeOf() : Unsafe.SizeOf(); - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The number of array elements present. - /// The size of the primitive or struct array. - public static int SizeOf(int elementCount) where T : unmanaged - => SizeOf() * elementCount; - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The number of array elements present. - /// If set to true; will return the size of an element after marshalling. - /// The size of the primitive or struct array. - public static int SizeOf(int elementCount, bool marshal) - => SizeOf(marshal) * elementCount; - - #endregion } + + /// + /// Read an SeString from a specified Utf8String structure. + /// + /// The memory address to read from. + /// The read in string. + public static unsafe SeString ReadSeString(Utf8String* utf8String) + { + if (utf8String == null) + return string.Empty; + + var ptr = utf8String->StringPtr; + if (ptr == null) + return string.Empty; + + var len = Math.Max(utf8String->BufUsed, utf8String->StringLength); + + return ReadSeString((IntPtr)ptr, (int)len); + } + + #endregion + + #region ReadString(out) + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The read in string. + public static void ReadStringNullTerminated(IntPtr memoryAddress, out string value) + => value = ReadStringNullTerminated(memoryAddress); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The read in string. + public static void ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding, out string value) + => value = ReadStringNullTerminated(memoryAddress, encoding); + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The read in string. + /// The maximum length of the string. + public static void ReadString(IntPtr memoryAddress, out string value, int maxLength) + => value = ReadString(memoryAddress, maxLength); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The maximum length of the string. + /// The read in string. + public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value) + => value = ReadString(memoryAddress, encoding, maxLength); + + /// + /// Read a null-terminated SeString from a specified memory address. + /// + /// The memory address to read from. + /// The read in SeString. + public static void ReadSeStringNullTerminated(IntPtr memoryAddress, out SeString value) + => value = ReadSeStringNullTerminated(memoryAddress); + + /// + /// Read an SeString from a specified memory address. + /// + /// The memory address to read from. + /// The maximum length of the string. + /// The read in SeString. + public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value) + => value = ReadSeString(memoryAddress, maxLength); + + /// + /// Read an SeString from a specified Utf8String structure. + /// + /// The memory address to read from. + /// The read in string. + public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value) + => value = ReadSeString(utf8String); + + #endregion + + #region Write + + /// + /// Writes a generic type to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The item to write to the address. + public static void Write(IntPtr memoryAddress, T item) where T : unmanaged + => Write(memoryAddress, item); + + /// + /// Writes a generic type to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The item to write to the address. + /// Set this to true to enable struct marshalling. + public static void Write(IntPtr memoryAddress, T item, bool marshal) + { + if (marshal) + Marshal.StructureToPtr(item, memoryAddress, false); + else + Unsafe.Write((void*)memoryAddress, item); + } + + /// + /// Writes raw data to a specified memory address. + /// + /// The memory address to read from. + /// The bytes to write to memoryAddress. + public static void WriteRaw(IntPtr memoryAddress, byte[] data) + { + Marshal.Copy(data, 0, memoryAddress, data.Length); + } + + /// + /// Writes a generic type array to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to write to. + /// The array of items to write to the address. + public static void Write(IntPtr memoryAddress, T[] items) where T : unmanaged + => Write(memoryAddress, items, false); + + /// + /// Writes a generic type array to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to write to. + /// The array of items to write to the address. + /// Set this to true to enable struct marshalling. + public static void Write(IntPtr memoryAddress, T[] items, bool marshal) + { + var structSize = SizeOf(marshal); + + for (var i = 0; i < items.Length; i++) + { + var address = memoryAddress + (structSize * i); + Write(address, items[i], marshal); + } + } + + #endregion + + #region WriteString + + /// + /// Write a UTF-8 encoded string to a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to encode or the applicable helper method. + /// + /// The memory address to write to. + /// The string to write. + public static void WriteString(IntPtr memoryAddress, string value) + => WriteString(memoryAddress, value, Encoding.UTF8); + + /// + /// Write a string with the given encoding to a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to encode or the applicable helper method. + /// + /// The memory address to write to. + /// The string to write. + /// The encoding to use. + public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding) + { + if (string.IsNullOrEmpty(value)) + return; + + var bytes = encoding.GetBytes(value + '\0'); + + WriteRaw(memoryAddress, bytes); + } + + /// + /// Write an SeString to a specified memory address. + /// + /// The memory address to write to. + /// The SeString to write. + public static void WriteSeString(IntPtr memoryAddress, SeString value) + { + if (value is null) + return; + + WriteRaw(memoryAddress, value.Encode()); + } + + #endregion + + #region ApiWrappers + + /// + /// Allocates fixed size of memory inside the target memory source via Windows API calls. + /// Returns the address of newly allocated memory. + /// + /// Amount of bytes to be allocated. + /// Address to the newly allocated memory. + public static IntPtr Allocate(int length) + { + var address = VirtualAlloc( + IntPtr.Zero, + (UIntPtr)length, + AllocationType.Commit | AllocationType.Reserve, + MemoryProtection.ExecuteReadWrite); + + if (address == IntPtr.Zero) + throw new MemoryAllocationException($"Unable to allocate {length} bytes."); + + return address; + } + + /// + /// Allocates fixed size of memory inside the target memory source via Windows API calls. + /// Returns the address of newly allocated memory. + /// + /// Amount of bytes to be allocated. + /// Address to the newly allocated memory. + public static void Allocate(int length, out IntPtr memoryAddress) + => memoryAddress = Allocate(length); + + /// + /// Frees memory previously allocated with Allocate via Windows API calls. + /// + /// The address of the memory to free. + /// True if the operation is successful. + public static bool Free(IntPtr memoryAddress) + { + return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release); + } + + /// + /// Changes the page permissions for a specified combination of address and length via Windows API calls. + /// + /// The memory address for which to change page permissions for. + /// The region size for which to change permissions for. + /// The new permissions to set. + /// The old page permissions. + public static MemoryProtection ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions) + { + var result = VirtualProtect(memoryAddress, (UIntPtr)length, newPermissions, out var oldPermissions); + + if (!result) + throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})"); + + var last = Marshal.GetLastWin32Error(); + if (last > 0) + throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (error={last})"); + + return oldPermissions; + } + + /// + /// Changes the page permissions for a specified combination of address and length via Windows API calls. + /// + /// The memory address for which to change page permissions for. + /// The region size for which to change permissions for. + /// The new permissions to set. + /// The old page permissions. + public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions) + => oldPermissions = ChangePermission(memoryAddress, length, newPermissions); + + /// + /// Changes the page permissions for a specified combination of address and element from which to deduce size via Windows API calls. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address for which to change page permissions for. + /// The struct element from which the region size to change permissions for will be calculated. + /// The new permissions to set. + /// Set to true to calculate the size of the struct after marshalling instead of before. + /// The old page permissions. + public static MemoryProtection ChangePermission(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal) + => ChangePermission(memoryAddress, SizeOf(marshal), newPermissions); + + /// + /// Reads raw data from a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// The read in bytes. + public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length) + { + var value = new byte[length]; + ReadProcessMemory(memoryAddress, ref value); + return value; + } + + /// + /// Reads raw data from a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// The read in bytes. + public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value) + => value = ReadProcessMemory(memoryAddress, length); + + /// + /// Reads raw data from a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to read from. + /// The read in bytes. + public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value) + { + var length = value.Length; + var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _); + + if (!result) + throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); + + var last = Marshal.GetLastWin32Error(); + if (last > 0) + throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); + } + + /// + /// Writes raw data to a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to write to. + /// The bytes to write to memoryAddress. + public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data) + { + var length = data.Length; + var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _); + + if (!result) + throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); + + var last = Marshal.GetLastWin32Error(); + if (last > 0) + throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); + } + + #endregion + + #region Sizing + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The size of the primitive or struct. + public static int SizeOf() + => SizeOf(false); + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// If set to true; will return the size of an element after marshalling. + /// The size of the primitive or struct. + public static int SizeOf(bool marshal) + => marshal ? Marshal.SizeOf() : Unsafe.SizeOf(); + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The number of array elements present. + /// The size of the primitive or struct array. + public static int SizeOf(int elementCount) where T : unmanaged + => SizeOf() * elementCount; + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The number of array elements present. + /// If set to true; will return the size of an element after marshalling. + /// The size of the primitive or struct array. + public static int SizeOf(int elementCount, bool marshal) + => SizeOf(marshal) * elementCount; + + #endregion } diff --git a/Dalamud/Memory/MemoryProtection.cs b/Dalamud/Memory/MemoryProtection.cs index 289c5024d..019b656e8 100644 --- a/Dalamud/Memory/MemoryProtection.cs +++ b/Dalamud/Memory/MemoryProtection.cs @@ -2,116 +2,115 @@ using System; // This is a copy from NativeFunctions.MemoryProtection -namespace Dalamud.Memory +namespace Dalamud.Memory; + +/// +/// PAGE_* from memoryapi. +/// +[Flags] +public enum MemoryProtection { + // Copied from NativeFunctions to expose to the user. + /// - /// PAGE_* from memoryapi. + /// Enables execute access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. This flag is not supported by the CreateFileMapping function. /// - [Flags] - public enum MemoryProtection - { - // Copied from NativeFunctions to expose to the user. + Execute = 0x10, - /// - /// Enables execute access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. This flag is not supported by the CreateFileMapping function. - /// - Execute = 0x10, + /// + /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region + /// results in an access violation. + /// + ExecuteRead = 0x20, - /// - /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region - /// results in an access violation. - /// - ExecuteRead = 0x20, + /// + /// Enables execute, read-only, or read/write access to the committed region of pages. + /// + ExecuteReadWrite = 0x40, - /// - /// Enables execute, read-only, or read/write access to the committed region of pages. - /// - ExecuteReadWrite = 0x40, + /// + /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to + /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The + /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + ExecuteWriteCopy = 0x80, - /// - /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to - /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The - /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - ExecuteWriteCopy = 0x80, + /// + /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed + /// region results in an access violation. This flag is not supported by the CreateFileMapping function. + /// + NoAccess = 0x01, - /// - /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed - /// region results in an access violation. This flag is not supported by the CreateFileMapping function. - /// - NoAccess = 0x01, + /// + /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed + /// region results in an access violation. + /// + ReadOnly = 0x02, - /// - /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed - /// region results in an access violation. - /// - ReadOnly = 0x02, + /// + /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, + /// attempting to execute code in the committed region results in an access violation. + /// + ReadWrite = 0x04, - /// - /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, - /// attempting to execute code in the committed region results in an access violation. - /// - ReadWrite = 0x04, + /// + /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to + /// a committed copy-on-write page results in a private copy of the page being made for the process. The private + /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is + /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + WriteCopy = 0x08, - /// - /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to - /// a committed copy-on-write page results in a private copy of the page being made for the process. The private - /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is - /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - WriteCopy = 0x08, + /// + /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like + /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations + /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable + /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect + /// or CreateFileMapping functions. + /// + TargetsInvalid = 0x40000000, - /// - /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like - /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations - /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable - /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect - /// or CreateFileMapping functions. - /// - TargetsInvalid = 0x40000000, + /// + /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. + /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information + /// will be maintained while the page protection changes. This flag is only valid when the protection changes to + /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. + /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call + /// targets for CFG. + /// + TargetsNoUpdate = TargetsInvalid, - /// - /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. - /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information - /// will be maintained while the page protection changes. This flag is only valid when the protection changes to - /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. - /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call - /// targets for CFG. - /// - TargetsNoUpdate = TargetsInvalid, + /// + /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a + /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time + /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn + /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a + /// system service, the service typically returns a failure status indicator. This value cannot be used with + /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. + /// + Guard = 0x100, - /// - /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a - /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time - /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn - /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a - /// system service, the service typically returns a failure status indicator. This value cannot be used with - /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. - /// - Guard = 0x100, + /// + /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, + /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the + /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared + /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. + /// + NoCache = 0x200, - /// - /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, - /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the - /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared - /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. - /// - NoCache = 0x200, - - /// - /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, - /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory - /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access - /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. - /// - WriteCombine = 0x400, - } + /// + /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, + /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory + /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access + /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. + /// + WriteCombine = 0x400, } diff --git a/Dalamud/NativeFunctions.cs b/Dalamud/NativeFunctions.cs index d11c39464..c75db66a7 100644 --- a/Dalamud/NativeFunctions.cs +++ b/Dalamud/NativeFunctions.cs @@ -4,1928 +4,1927 @@ using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud +namespace Dalamud; + +/// +/// Native user32 functions. +/// +internal static partial class NativeFunctions { /// - /// Native user32 functions. + /// FLASHW_* from winuser. /// - internal static partial class NativeFunctions + public enum FlashWindow : uint { /// - /// FLASHW_* from winuser. + /// Stop flashing. The system restores the window to its original state. /// - public enum FlashWindow : uint - { - /// - /// Stop flashing. The system restores the window to its original state. - /// - Stop = 0, - - /// - /// Flash the window caption. - /// - Caption = 1, - - /// - /// Flash the taskbar button. - /// - Tray = 2, - - /// - /// Flash both the window caption and taskbar button. - /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. - /// - All = 3, - - /// - /// Flash continuously, until the FLASHW_STOP flag is set. - /// - Timer = 4, - - /// - /// Flash continuously until the window comes to the foreground. - /// - TimerNoFG = 12, - } + Stop = 0, /// - /// IDC_* from winuser. + /// Flash the window caption. /// - public enum CursorType - { - /// - /// Standard arrow and small hourglass. - /// - AppStarting = 32650, - - /// - /// Standard arrow. - /// - Arrow = 32512, - - /// - /// Crosshair. - /// - Cross = 32515, - - /// - /// Hand. - /// - Hand = 32649, - - /// - /// Arrow and question mark. - /// - Help = 32651, - - /// - /// I-beam. - /// - IBeam = 32513, - - /// - /// Obsolete for applications marked version 4.0 or later. - /// - Icon = 32641, - - /// - /// Slashed circle. - /// - No = 32648, - - /// - /// Obsolete for applications marked version 4.0 or later.Use IDC_SIZEALL. - /// - Size = 32640, - - /// - /// Four-pointed arrow pointing north, south, east, and west. - /// - SizeAll = 32646, - - /// - /// Double-pointed arrow pointing northeast and southwest. - /// - SizeNeSw = 32643, - - /// - /// Double-pointed arrow pointing north and south. - /// - SizeNS = 32645, - - /// - /// Double-pointed arrow pointing northwest and southeast. - /// - SizeNwSe = 32642, - - /// - /// Double-pointed arrow pointing west and east. - /// - SizeWE = 32644, - - /// - /// Vertical arrow. - /// - UpArrow = 32516, - - /// - /// Hourglass. - /// - Wait = 32514, - } + Caption = 1, /// - /// MB_* from winuser. + /// Flash the taskbar button. /// - public enum MessageBoxType : uint - { - /// - /// The default value for any of the various subtypes. - /// - DefaultValue = 0x0, - - // To indicate the buttons displayed in the message box, specify one of the following values. - - /// - /// The message box contains three push buttons: Abort, Retry, and Ignore. - /// - AbortRetryIgnore = 0x2, - - /// - /// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead - /// of MB_ABORTRETRYIGNORE. - /// - CancelTryContinue = 0x6, - - /// - /// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends - /// a WM_HELP message to the owner. - /// - Help = 0x4000, - - /// - /// The message box contains one push button: OK. This is the default. - /// - Ok = DefaultValue, - - /// - /// The message box contains two push buttons: OK and Cancel. - /// - OkCancel = 0x1, - - /// - /// The message box contains two push buttons: Retry and Cancel. - /// - RetryCancel = 0x5, - - /// - /// The message box contains two push buttons: Yes and No. - /// - YesNo = 0x4, - - /// - /// The message box contains three push buttons: Yes, No, and Cancel. - /// - YesNoCancel = 0x3, - - // To display an icon in the message box, specify one of the following values. - - /// - /// An exclamation-point icon appears in the message box. - /// - IconExclamation = 0x30, - - /// - /// An exclamation-point icon appears in the message box. - /// - IconWarning = IconExclamation, - - /// - /// An icon consisting of a lowercase letter i in a circle appears in the message box. - /// - IconInformation = 0x40, - - /// - /// An icon consisting of a lowercase letter i in a circle appears in the message box. - /// - IconAsterisk = IconInformation, - - /// - /// A question-mark icon appears in the message box. - /// The question-mark message icon is no longer recommended because it does not clearly represent a specific type - /// of message and because the phrasing of a message as a question could apply to any message type. In addition, - /// users can confuse the message symbol question mark with Help information. Therefore, do not use this question - /// mark message symbol in your message boxes. The system continues to support its inclusion only for backward - /// compatibility. - /// - IconQuestion = 0x20, - - /// - /// A stop-sign icon appears in the message box. - /// - IconStop = 0x10, - - /// - /// A stop-sign icon appears in the message box. - /// - IconError = IconStop, - - /// - /// A stop-sign icon appears in the message box. - /// - IconHand = IconStop, - - // To indicate the default button, specify one of the following values. - - /// - /// The first button is the default button. - /// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified. - /// - DefButton1 = DefaultValue, - - /// - /// The second button is the default button. - /// - DefButton2 = 0x100, - - /// - /// The third button is the default button. - /// - DefButton3 = 0x200, - - /// - /// The fourth button is the default button. - /// - DefButton4 = 0x300, - - // To indicate the modality of the dialog box, specify one of the following values. - - /// - /// The user must respond to the message box before continuing work in the window identified by the hWnd parameter. - /// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy - /// of windows in the application, the user may be able to move to other windows within the thread. All child windows - /// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the - /// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified. - /// - ApplModal = DefaultValue, - - /// - /// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style. - /// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate - /// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with - /// windows other than those associated with hWnd. - /// - SystemModal = 0x1000, - - /// - /// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the - /// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle - /// available but still needs to prevent input to other windows in the calling thread without suspending other threads. - /// - TaskModal = 0x2000, - - // To specify other options, use one or more of the following values. - - /// - /// Same as desktop of the interactive window station. For more information, see Window Stations. If the current - /// input desktop is not the default desktop, MessageBox does not return until the user switches to the default - /// desktop. - /// - DefaultDesktopOnly = 0x20000, - - /// - /// The text is right-justified. - /// - Right = 0x80000, - - /// - /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems. - /// - RtlReading = 0x100000, - - /// - /// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function - /// for the message box. - /// - SetForeground = 0x10000, - - /// - /// The message box is created with the WS_EX_TOPMOST window style. - /// - Topmost = 0x40000, - - /// - /// The caller is a service notifying the user of an event. The function displays a message box on the current active - /// desktop, even if there is no user logged on to the computer. - /// - ServiceNotification = 0x200000, - } + Tray = 2, /// - /// GWL_* from winuser. + /// Flash both the window caption and taskbar button. + /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. /// - public enum WindowLongType - { - /// - /// Sets a new extended window style. - /// - ExStyle = -20, - - /// - /// Sets a new application instance handle. - /// - HInstance = -6, - - /// - /// Sets a new identifier of the child window.The window cannot be a top-level window. - /// - Id = -12, - - /// - /// Sets a new window style. - /// - Style = -16, - - /// - /// Sets the user data associated with the window. This data is intended for use by the application that created the window. Its value is initially zero. - /// - UserData = -21, - - /// - /// Sets a new address for the window procedure. - /// - WndProc = -4, - - // The following values are also available when the hWnd parameter identifies a dialog box. - - // /// - // /// Sets the new pointer to the dialog box procedure. - // /// - // DWLP_DLGPROC = DWLP_MSGRESULT + sizeof(LRESULT), - - /// - /// Sets the return value of a message processed in the dialog box procedure. - /// - MsgResult = 0, - - // /// - // /// Sets new extra information that is private to the application, such as handles or pointers. - // /// - // DWLP_USER = DWLP_DLGPROC + sizeof(DLGPROC), - } + All = 3, /// - /// WM_* from winuser. - /// These are spread throughout multiple files, find the documentation manually if you need it. - /// https://gist.github.com/amgine/2395987. + /// Flash continuously, until the FLASHW_STOP flag is set. /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "No documentation available.")] - public enum WindowsMessage - { - WM_NULL = 0x0000, - WM_CREATE = 0x0001, - WM_DESTROY = 0x0002, - WM_MOVE = 0x0003, - WM_SIZE = 0x0005, - WM_ACTIVATE = 0x0006, - WM_SETFOCUS = 0x0007, - WM_KILLFOCUS = 0x0008, - WM_ENABLE = 0x000A, - WM_SETREDRAW = 0x000B, - WM_SETTEXT = 0x000C, - WM_GETTEXT = 0x000D, - WM_GETTEXTLENGTH = 0x000E, - WM_PAINT = 0x000F, - WM_CLOSE = 0x0010, - WM_QUERYENDSESSION = 0x0011, - WM_QUERYOPEN = 0x0013, - WM_ENDSESSION = 0x0016, - WM_QUIT = 0x0012, - WM_ERASEBKGND = 0x0014, - WM_SYSCOLORCHANGE = 0x0015, - WM_SHOWWINDOW = 0x0018, - WM_WININICHANGE = 0x001A, - WM_SETTINGCHANGE = WM_WININICHANGE, - WM_DEVMODECHANGE = 0x001B, - WM_ACTIVATEAPP = 0x001C, - WM_FONTCHANGE = 0x001D, - WM_TIMECHANGE = 0x001E, - WM_CANCELMODE = 0x001F, - WM_SETCURSOR = 0x0020, - WM_MOUSEACTIVATE = 0x0021, - WM_CHILDACTIVATE = 0x0022, - WM_QUEUESYNC = 0x0023, - WM_GETMINMAXINFO = 0x0024, - WM_PAINTICON = 0x0026, - WM_ICONERASEBKGND = 0x0027, - WM_NEXTDLGCTL = 0x0028, - WM_SPOOLERSTATUS = 0x002A, - WM_DRAWITEM = 0x002B, - WM_MEASUREITEM = 0x002C, - WM_DELETEITEM = 0x002D, - WM_VKEYTOITEM = 0x002E, - WM_CHARTOITEM = 0x002F, - WM_SETFONT = 0x0030, - WM_GETFONT = 0x0031, - WM_SETHOTKEY = 0x0032, - WM_GETHOTKEY = 0x0033, - WM_QUERYDRAGICON = 0x0037, - WM_COMPAREITEM = 0x0039, - WM_GETOBJECT = 0x003D, - WM_COMPACTING = 0x0041, - WM_COMMNOTIFY = 0x0044, - WM_WINDOWPOSCHANGING = 0x0046, - WM_WINDOWPOSCHANGED = 0x0047, - WM_POWER = 0x0048, - WM_COPYDATA = 0x004A, - WM_CANCELJOURNAL = 0x004B, - WM_NOTIFY = 0x004E, - WM_INPUTLANGCHANGEREQUEST = 0x0050, - WM_INPUTLANGCHANGE = 0x0051, - WM_TCARD = 0x0052, - WM_HELP = 0x0053, - WM_USERCHANGED = 0x0054, - WM_NOTIFYFORMAT = 0x0055, - WM_CONTEXTMENU = 0x007B, - WM_STYLECHANGING = 0x007C, - WM_STYLECHANGED = 0x007D, - WM_DISPLAYCHANGE = 0x007E, - WM_GETICON = 0x007F, - WM_SETICON = 0x0080, - WM_NCCREATE = 0x0081, - WM_NCDESTROY = 0x0082, - WM_NCCALCSIZE = 0x0083, - WM_NCHITTEST = 0x0084, - WM_NCPAINT = 0x0085, - WM_NCACTIVATE = 0x0086, - WM_GETDLGCODE = 0x0087, - WM_SYNCPAINT = 0x0088, - - WM_NCMOUSEMOVE = 0x00A0, - WM_NCLBUTTONDOWN = 0x00A1, - WM_NCLBUTTONUP = 0x00A2, - WM_NCLBUTTONDBLCLK = 0x00A3, - WM_NCRBUTTONDOWN = 0x00A4, - WM_NCRBUTTONUP = 0x00A5, - WM_NCRBUTTONDBLCLK = 0x00A6, - WM_NCMBUTTONDOWN = 0x00A7, - WM_NCMBUTTONUP = 0x00A8, - WM_NCMBUTTONDBLCLK = 0x00A9, - WM_NCXBUTTONDOWN = 0x00AB, - WM_NCXBUTTONUP = 0x00AC, - WM_NCXBUTTONDBLCLK = 0x00AD, - - WM_INPUT_DEVICE_CHANGE = 0x00FE, - WM_INPUT = 0x00FF, - - WM_KEYFIRST = 0x0100, - WM_KEYDOWN = WM_KEYFIRST, - WM_KEYUP = 0x0101, - WM_CHAR = 0x0102, - WM_DEADCHAR = 0x0103, - WM_SYSKEYDOWN = 0x0104, - WM_SYSKEYUP = 0x0105, - WM_SYSCHAR = 0x0106, - WM_SYSDEADCHAR = 0x0107, - WM_UNICHAR = 0x0109, - WM_KEYLAST = WM_UNICHAR, - - WM_IME_STARTCOMPOSITION = 0x010D, - WM_IME_ENDCOMPOSITION = 0x010E, - WM_IME_COMPOSITION = 0x010F, - WM_IME_KEYLAST = WM_IME_COMPOSITION, - - WM_INITDIALOG = 0x0110, - WM_COMMAND = 0x0111, - WM_SYSCOMMAND = 0x0112, - WM_TIMER = 0x0113, - WM_HSCROLL = 0x0114, - WM_VSCROLL = 0x0115, - WM_INITMENU = 0x0116, - WM_INITMENUPOPUP = 0x0117, - WM_MENUSELECT = 0x011F, - WM_MENUCHAR = 0x0120, - WM_ENTERIDLE = 0x0121, - WM_MENURBUTTONUP = 0x0122, - WM_MENUDRAG = 0x0123, - WM_MENUGETOBJECT = 0x0124, - WM_UNINITMENUPOPUP = 0x0125, - WM_MENUCOMMAND = 0x0126, - - WM_CHANGEUISTATE = 0x0127, - WM_UPDATEUISTATE = 0x0128, - WM_QUERYUISTATE = 0x0129, - - WM_CTLCOLORMSGBOX = 0x0132, - WM_CTLCOLOREDIT = 0x0133, - WM_CTLCOLORLISTBOX = 0x0134, - WM_CTLCOLORBTN = 0x0135, - WM_CTLCOLORDLG = 0x0136, - WM_CTLCOLORSCROLLBAR = 0x0137, - WM_CTLCOLORSTATIC = 0x0138, - MN_GETHMENU = 0x01E1, - - WM_MOUSEFIRST = 0x0200, - WM_MOUSEMOVE = WM_MOUSEFIRST, - WM_LBUTTONDOWN = 0x0201, - WM_LBUTTONUP = 0x0202, - WM_LBUTTONDBLCLK = 0x0203, - WM_RBUTTONDOWN = 0x0204, - WM_RBUTTONUP = 0x0205, - WM_RBUTTONDBLCLK = 0x0206, - WM_MBUTTONDOWN = 0x0207, - WM_MBUTTONUP = 0x0208, - WM_MBUTTONDBLCLK = 0x0209, - WM_MOUSEWHEEL = 0x020A, - WM_XBUTTONDOWN = 0x020B, - WM_XBUTTONUP = 0x020C, - WM_XBUTTONDBLCLK = 0x020D, - WM_MOUSEHWHEEL = 0x020E, - - WM_PARENTNOTIFY = 0x0210, - WM_ENTERMENULOOP = 0x0211, - WM_EXITMENULOOP = 0x0212, - - WM_NEXTMENU = 0x0213, - WM_SIZING = 0x0214, - WM_CAPTURECHANGED = 0x0215, - WM_MOVING = 0x0216, - - WM_POWERBROADCAST = 0x0218, - - WM_DEVICECHANGE = 0x0219, - - WM_MDICREATE = 0x0220, - WM_MDIDESTROY = 0x0221, - WM_MDIACTIVATE = 0x0222, - WM_MDIRESTORE = 0x0223, - WM_MDINEXT = 0x0224, - WM_MDIMAXIMIZE = 0x0225, - WM_MDITILE = 0x0226, - WM_MDICASCADE = 0x0227, - WM_MDIICONARRANGE = 0x0228, - WM_MDIGETACTIVE = 0x0229, - - WM_MDISETMENU = 0x0230, - WM_ENTERSIZEMOVE = 0x0231, - WM_EXITSIZEMOVE = 0x0232, - WM_DROPFILES = 0x0233, - WM_MDIREFRESHMENU = 0x0234, - - WM_IME_SETCONTEXT = 0x0281, - WM_IME_NOTIFY = 0x0282, - WM_IME_CONTROL = 0x0283, - WM_IME_COMPOSITIONFULL = 0x0284, - WM_IME_SELECT = 0x0285, - WM_IME_CHAR = 0x0286, - WM_IME_REQUEST = 0x0288, - WM_IME_KEYDOWN = 0x0290, - WM_IME_KEYUP = 0x0291, - - WM_MOUSEHOVER = 0x02A1, - WM_MOUSELEAVE = 0x02A3, - WM_NCMOUSEHOVER = 0x02A0, - WM_NCMOUSELEAVE = 0x02A2, - - WM_WTSSESSION_CHANGE = 0x02B1, - - WM_TABLET_FIRST = 0x02c0, - WM_TABLET_LAST = 0x02df, - - WM_CUT = 0x0300, - WM_COPY = 0x0301, - WM_PASTE = 0x0302, - WM_CLEAR = 0x0303, - WM_UNDO = 0x0304, - WM_RENDERFORMAT = 0x0305, - WM_RENDERALLFORMATS = 0x0306, - WM_DESTROYCLIPBOARD = 0x0307, - WM_DRAWCLIPBOARD = 0x0308, - WM_PAINTCLIPBOARD = 0x0309, - WM_VSCROLLCLIPBOARD = 0x030A, - WM_SIZECLIPBOARD = 0x030B, - WM_ASKCBFORMATNAME = 0x030C, - WM_CHANGECBCHAIN = 0x030D, - WM_HSCROLLCLIPBOARD = 0x030E, - WM_QUERYNEWPALETTE = 0x030F, - WM_PALETTEISCHANGING = 0x0310, - WM_PALETTECHANGED = 0x0311, - WM_HOTKEY = 0x0312, - - WM_PRINT = 0x0317, - WM_PRINTCLIENT = 0x0318, - - WM_APPCOMMAND = 0x0319, - - WM_THEMECHANGED = 0x031A, - - WM_CLIPBOARDUPDATE = 0x031D, - - WM_DWMCOMPOSITIONCHANGED = 0x031E, - WM_DWMNCRENDERINGCHANGED = 0x031F, - WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320, - WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321, - - WM_GETTITLEBARINFOEX = 0x033F, - - WM_HANDHELDFIRST = 0x0358, - WM_HANDHELDLAST = 0x035F, - - WM_AFXFIRST = 0x0360, - WM_AFXLAST = 0x037F, - - WM_PENWINFIRST = 0x0380, - WM_PENWINLAST = 0x038F, - - WM_APP = 0x8000, - - WM_USER = 0x0400, - - WM_REFLECT = WM_USER + 0x1C00, - } + Timer = 4, /// - /// Returns true if the current application has focus, false otherwise. + /// Flash continuously until the window comes to the foreground. /// - /// - /// If the current application is focused. - /// - public static bool ApplicationIsActivated() - { - var activatedHandle = GetForegroundWindow(); - if (activatedHandle == IntPtr.Zero) - return false; // No window is currently activated - - _ = GetWindowThreadProcessId(activatedHandle, out var activeProcId); - if (Marshal.GetLastWin32Error() != 0) - return false; - - return activeProcId == Environment.ProcessId; - } - - /// - /// Passes message information to the specified window procedure. - /// - /// - /// The previous window procedure. If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to - /// GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a special internal value - /// meaningful only to CallWindowProc. - /// - /// - /// A handle to the window procedure to receive the message. - /// - /// - /// The message. - /// - /// - /// Additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. - /// - /// - /// More additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. - /// - /// - /// Use the CallWindowProc function for window subclassing. Usually, all windows with the same class share one window procedure. A - /// subclass is a window or set of windows with the same class whose messages are intercepted and processed by another window procedure - /// (or procedures) before being passed to the window procedure of the class. - /// The SetWindowLong function creates the subclass by changing the window procedure associated with a particular window, causing the - /// system to call the new window procedure instead of the previous one.An application must pass any messages not processed by the new - /// window procedure to the previous window procedure by calling CallWindowProc.This allows the application to create a chain of window - /// procedures. - /// - [DllImport("user32.dll")] - public static extern long CallWindowProcW(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, ulong wParam, long lParam); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindowex. - /// Flashes the specified window. It does not change the active state of the window. - /// - /// - /// A pointer to a FLASHWINFO structure. - /// - /// - /// The return value specifies the window's state before the call to the FlashWindowEx function. If the window caption - /// was drawn as active before the call, the return value is nonzero. Otherwise, the return value is zero. - /// - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FlashWindowEx(ref FlashWindowInfo pwfi); - - /// - /// Retrieves a handle to the foreground window (the window with which the user is currently working). The system assigns - /// a slightly higher priority to the thread that creates the foreground window than it does to other threads. - /// - /// - /// The return value is a handle to the foreground window. The foreground window can be NULL in certain circumstances, - /// such as when a window is losing activation. - /// - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - public static extern IntPtr GetForegroundWindow(); - - /// - /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the - /// process that created the window. - /// - /// - /// A handle to the window. - /// - /// - /// A pointer to a variable that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId - /// copies the identifier of the process to the variable; otherwise, it does not. - /// - /// - /// The return value is the identifier of the thread that created the window. - /// - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); - - /// - /// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, - /// such as status or error information. The message box returns an integer value that indicates which button the user - /// clicked. - /// - /// - /// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no - /// owner window. - /// - /// - /// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage - /// return and/or linefeed character between each line. - /// - /// - /// The dialog box title. If this parameter is NULL, the default title is Error. - /// - /// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups - /// of flags. - /// - /// - /// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or - /// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an - /// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK. - /// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function - /// succeeds, the return value is one of the ID* enum values. - /// - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type); - - /// - /// Changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory. - /// - /// - /// A handle to the window and, indirectly, the class to which the window belongs. The SetWindowLongPtr function fails if the - /// process that owns the window specified by the hWnd parameter is at a higher process privilege in the UIPI hierarchy than the - /// process the calling thread resides in. - /// - /// - /// The zero-based offset to the value to be set. Valid values are in the range zero through the number of bytes of extra window - /// memory, minus the size of a LONG_PTR. To set any other value, specify one of the values. - /// - /// - /// The replacement value. - /// - /// - /// If the function succeeds, the return value is the previous value of the specified offset. If the function fails, the return - /// value is zero.To get extended error information, call GetLastError. If the previous value is zero and the function succeeds, - /// the return value is zero, but the function does not clear the last error information. To determine success or failure, clear - /// the last error information by calling SetLastError with 0, then call SetWindowLongPtr.Function failure will be indicated by - /// a return value of zero and a GetLastError result that is nonzero. - /// - [DllImport("user32.dll", SetLastError = true)] - public static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, WindowLongType nIndex, IntPtr dwNewLong); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-flashwinfo. - /// Contains the flash status for a window and the number of times the system should flash the window. - /// - [StructLayout(LayoutKind.Sequential)] - public struct FlashWindowInfo - { - /// - /// The size of the structure, in bytes. - /// - public uint Size; - - /// - /// A handle to the window to be flashed. The window can be either opened or minimized. - /// - public IntPtr Hwnd; - - /// - /// The flash status. This parameter can be one or more of the FlashWindow enum values. - /// - public FlashWindow Flags; - - /// - /// The number of times to flash the window. - /// - public uint Count; - - /// - /// The rate at which the window is to be flashed, in milliseconds. If dwTimeout is zero, the function uses the - /// default cursor blink rate. - /// - public uint Timeout; - } + TimerNoFG = 12, } /// - /// Native imm32 functions. + /// IDC_* from winuser. /// - internal static partial class NativeFunctions + public enum CursorType { /// - /// GCS_* from imm32. - /// These values are used with ImmGetCompositionString and WM_IME_COMPOSITION. + /// Standard arrow and small hourglass. /// - [Flags] - public enum IMEComposition - { - /// - /// Retrieve or update the attribute of the composition string. - /// - CompAttr = 0x0010, - - /// - /// Retrieve or update clause information of the composition string. - /// - CompClause = 0x0020, - - /// - /// Retrieve or update the attributes of the reading string of the current composition. - /// - CompReadAttr = 0x0002, - - /// - /// Retrieve or update the clause information of the reading string of the composition string. - /// - CompReadClause = 0x0004, - - /// - /// Retrieve or update the reading string of the current composition. - /// - CompReadStr = 0x0001, - - /// - /// Retrieve or update the current composition string. - /// - CompStr = 0x0008, - - /// - /// Retrieve or update the cursor position in composition string. - /// - CursorPos = 0x0080, - - /// - /// Retrieve or update the starting position of any changes in composition string. - /// - DeltaStart = 0x0100, - - /// - /// Retrieve or update clause information of the result string. - /// - ResultClause = 0x1000, - - /// - /// Retrieve or update clause information of the reading string. - /// - ResultReadClause = 0x0400, - - /// - /// Retrieve or update the reading string. - /// - ResultReadStr = 0x0200, - - /// - /// Retrieve or update the string of the composition result. - /// - ResultStr = 0x0800, - } + AppStarting = 32650, /// - /// IMN_* from imm32. - /// Input Method Manager Commands, this enum is not exhaustive. + /// Standard arrow. /// - public enum IMECommand - { - /// - /// Notifies the application when an IME is about to change the content of the candidate window. - /// - ChangeCandidate = 0x0003, - - /// - /// Notifies an application when an IME is about to close the candidates window. - /// - CloseCandidate = 0x0004, - - /// - /// Notifies an application when an IME is about to open the candidate window. - /// - OpenCandidate = 0x0005, - - /// - /// Notifies an application when the conversion mode of the input context is updated. - /// - SetConversionMode = 0x0006, - } + Arrow = 32512, /// - /// Returns the input context associated with the specified window. + /// Crosshair. /// - /// Unnamed parameter 1. - /// - /// Returns the handle to the input context. - /// - [DllImport("imm32.dll")] - public static extern IntPtr ImmGetContext(IntPtr hWnd); + Cross = 32515, /// - /// Retrieves information about the composition string. + /// Hand. /// - /// - /// Unnamed parameter 1. - /// - /// - /// Unnamed parameter 2. - /// - /// - /// Pointer to a buffer in which the function retrieves the composition string information. - /// - /// - /// Size, in bytes, of the output buffer, even if the output is a Unicode string. The application sets this parameter to 0 - /// if the function is to return the size of the required output buffer. - /// - /// - /// Returns the number of bytes copied to the output buffer. If dwBufLen is set to 0, the function returns the buffer size, - /// in bytes, required to receive all requested information, excluding the terminating null character. The return value is - /// always the size, in bytes, even if the requested data is a Unicode string. - /// This function returns one of the following negative error codes if it does not succeed: - /// - IMM_ERROR_NODATA.Composition data is not ready in the input context. - /// - IMM_ERROR_GENERAL.General error detected by IME. - /// - [DllImport("imm32.dll")] - public static extern long ImmGetCompositionStringW(IntPtr hImc, IMEComposition arg2, IntPtr lpBuf, uint dwBufLen); + Hand = 32649, /// - /// Retrieves a candidate list. + /// Arrow and question mark. /// - /// - /// Unnamed parameter 1. - /// - /// - /// Zero-based index of the candidate list. - /// - /// - /// Pointer to a CANDIDATELIST structure in which the function retrieves the candidate list. - /// - /// - /// Size, in bytes, of the buffer to receive the candidate list. The application can specify 0 for this parameter if the - /// function is to return the required size of the output buffer only. - /// - /// - /// Returns the number of bytes copied to the candidate list buffer if successful. If the application has supplied 0 for - /// the dwBufLen parameter, the function returns the size required for the candidate list buffer. The function returns 0 - /// if it does not succeed. - /// - [DllImport("imm32.dll")] - public static extern long ImmGetCandidateListW(IntPtr hImc, uint deIndex, IntPtr lpCandList, uint dwBufLen); + Help = 32651, /// - /// Sets the position of the composition window. + /// I-beam. /// - /// - /// Unnamed parameter 1. - /// - /// - /// Pointer to a COMPOSITIONFORM structure that contains the new position and other related information about - /// the composition window. - /// - /// - /// Returns a nonzero value if successful, or 0 otherwise. - /// - [DllImport("imm32.dll", CharSet = CharSet.Auto)] - public static extern bool ImmSetCompositionWindow(IntPtr hImc, ref CompositionForm frm); + IBeam = 32513, /// - /// Releases the input context and unlocks the memory associated in the input context. An application must call this - /// function for each call to the ImmGetContext function. + /// Obsolete for applications marked version 4.0 or later. /// - /// - /// Unnamed parameter 1. - /// - /// - /// Unnamed parameter 2. - /// - /// - /// Returns a nonzero value if successful, or 0 otherwise. - /// - [DllImport("imm32.dll", CharSet = CharSet.Auto)] - public static extern bool ImmReleaseContext(IntPtr hwnd, IntPtr hImc); + Icon = 32641, /// - /// Contains information about a candidate list. + /// Slashed circle. /// - public struct CandidateList - { - /// - /// Size, in bytes, of the structure, the offset array, and all candidate strings. - /// - public int Size; - - /// - /// Candidate style values. This member can have one or more of the IME_CAND_* values. - /// - public int Style; - - /// - /// Number of candidate strings. - /// - public int Count; - - /// - /// Index of the selected candidate string. - /// - public int Selection; - - /// - /// Index of the first candidate string in the candidate window. This varies as the user presses the PAGE UP and PAGE DOWN keys. - /// - public int PageStart; - - /// - /// Number of candidate strings to be shown in one page in the candidate window. The user can move to the next page by pressing IME-defined keys, such as the PAGE UP or PAGE DOWN key. If this number is 0, an application can define a proper value by itself. - /// - public int PageSize; - - // /// - // /// Offset to the start of the first candidate string, relative to the start of this structure. The offsets - // /// for subsequent strings immediately follow this member, forming an array of 32-bit offsets. - // /// - // public IntPtr Offset; // manually handle - } + No = 32648, /// - /// Contains style and position information for a composition window. + /// Obsolete for applications marked version 4.0 or later.Use IDC_SIZEALL. /// - [StructLayout(LayoutKind.Sequential)] - public struct CompositionForm - { - /// - /// Position style. This member can be one of the CFS_* values. - /// - public int Style; - - /// - /// A POINT structure containing the coordinates of the upper left corner of the composition window. - /// - public Point CurrentPos; - - /// - /// A RECT structure containing the coordinates of the upper left and lower right corners of the composition window. - /// - public Rect Area; - } + Size = 32640, /// - /// Contains coordinates for a point. + /// Four-pointed arrow pointing north, south, east, and west. /// - [StructLayout(LayoutKind.Sequential)] - public struct Point - { - /// - /// The X position. - /// - public int X; - - /// - /// The Y position. - /// - public int Y; - } + SizeAll = 32646, /// - /// Contains dimensions for a rectangle. + /// Double-pointed arrow pointing northeast and southwest. /// - [StructLayout(LayoutKind.Sequential)] - public struct Rect - { - /// - /// The left position. - /// - public int Left; + SizeNeSw = 32643, - /// - /// The top position. - /// - public int Top; + /// + /// Double-pointed arrow pointing north and south. + /// + SizeNS = 32645, - /// - /// The right position. - /// - public int Right; + /// + /// Double-pointed arrow pointing northwest and southeast. + /// + SizeNwSe = 32642, - /// - /// The bottom position. - /// - public int Bottom; - } + /// + /// Double-pointed arrow pointing west and east. + /// + SizeWE = 32644, + + /// + /// Vertical arrow. + /// + UpArrow = 32516, + + /// + /// Hourglass. + /// + Wait = 32514, } /// - /// Native kernel32 functions. + /// MB_* from winuser. /// - internal static partial class NativeFunctions + public enum MessageBoxType : uint { /// - /// MEM_* from memoryapi. + /// The default value for any of the various subtypes. /// - [Flags] - public enum AllocationType - { - /// - /// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce - /// placeholders, lpAddress and dwSize must exactly match those of the placeholder. - /// - CoalescePlaceholders = 0x1, + DefaultValue = 0x0, - /// - /// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using - /// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify - /// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER. - /// - PreservePlaceholder = 0x2, - - /// - /// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved - /// memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents - /// will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed. - /// To reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Attempting to commit - /// a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the - /// entire range has already been reserved. The resulting error code is ERROR_INVALID_ADDRESS. An attempt to commit - /// a page that is already committed does not cause the function to fail. This means that you can commit pages without - /// first determining the current commitment state of each page. If lpAddress specifies an address within an enclave, - /// flAllocationType must be MEM_COMMIT. - /// - Commit = 0x1000, - - /// - /// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory - /// or in the paging file on disk. You commit reserved pages by calling VirtualAllocEx again with MEM_COMMIT. To - /// reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Other memory allocation - /// functions, such as malloc and LocalAlloc, cannot use reserved memory until it has been released. - /// - Reserve = 0x2000, - - /// - /// Decommits the specified region of committed pages. After the operation, the pages are in the reserved state. - /// The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit - /// a range of pages without first determining the current commitment state. The MEM_DECOMMIT value is not supported - /// when the lpAddress parameter provides the base address for an enclave. - /// - Decommit = 0x4000, - - /// - /// Releases the specified region of pages, or placeholder (for a placeholder, the address space is released and - /// available for other allocations). After this operation, the pages are in the free state. If you specify this - /// value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function - /// when the region is reserved. The function fails if either of these conditions is not met. If any pages in the - /// region are committed currently, the function first decommits, and then releases them. The function does not - /// fail if you attempt to release pages that are in different states, some reserved and some committed. This means - /// that you can release a range of pages without first determining the current commitment state. - /// - Release = 0x8000, - - /// - /// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages - /// should not be read from or written to the paging file. However, the memory block will be used again later, so - /// it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee - /// that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit - /// the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect. - /// However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns - /// an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable - /// if it is mapped to a paging file. - /// - Reset = 0x80000, - - /// - /// MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier. - /// It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to - /// the caller and attempts to reverse the effects of MEM_RESET. If the function succeeds, that means all data in - /// the specified address range is intact. If the function fails, at least some of the data in the address range - /// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on - /// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the - /// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid - /// protection value, such as PAGE_NOACCESS. - /// - ResetUndo = 0x1000000, - - /// - /// Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages. This value must - /// be used with MEM_RESERVE and no other values. - /// - Physical = 0x400000, - - /// - /// Allocates memory at the highest possible address. This can be slower than regular allocations, especially when - /// there are many allocations. - /// - TopDown = 0x100000, - - /// - /// Causes the system to track pages that are written to in the allocated region. If you specify this value, you - /// must also specify MEM_RESERVE. To retrieve the addresses of the pages that have been written to since the region - /// was allocated or the write-tracking state was reset, call the GetWriteWatch function. To reset the write-tracking - /// state, call GetWriteWatch or ResetWriteWatch. The write-tracking feature remains enabled for the memory region - /// until the region is freed. - /// - WriteWatch = 0x200000, - - /// - /// Allocates memory using large page support. The size and alignment must be a multiple of the large-page minimum. - /// To obtain this value, use the GetLargePageMinimum function. If you specify this value, you must also specify - /// MEM_RESERVE and MEM_COMMIT. - /// - LargePages = 0x20000000, - } + // To indicate the buttons displayed in the message box, specify one of the following values. /// - /// SEM_* from errhandlingapi. + /// The message box contains three push buttons: Abort, Retry, and Ignore. /// - [Flags] - public enum ErrorModes : uint - { - /// - /// Use the system default, which is to display all error dialog boxes. - /// - SystemDefault = 0x0, - - /// - /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the - /// calling process. Best practice is that all applications call the process-wide SetErrorMode function with a parameter - /// of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application. - /// - FailCriticalErrors = 0x0001, - - /// - /// The system automatically fixes memory alignment faults and makes them invisible to the application. It does - /// this for the calling process and any descendant processes. This feature is only supported by certain processor - /// architectures. For more information, see the Remarks section. After this value is set for a process, subsequent - /// attempts to clear the value are ignored. - /// - NoAlignmentFaultExcept = 0x0004, - - /// - /// The system does not display the Windows Error Reporting dialog. - /// - NoGpFaultErrorBox = 0x0002, - - /// - /// The OpenFile function does not display a message box when it fails to find a file. Instead, the error is returned - /// to the caller. This error mode overrides the OF_PROMPT flag. - /// - NoOpenFileErrorBox = 0x8000, - } + AbortRetryIgnore = 0x2, /// - /// PAGE_* from memoryapi. + /// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead + /// of MB_ABORTRETRYIGNORE. /// - [Flags] - public enum MemoryProtection - { - /// - /// Enables execute access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. This flag is not supported by the CreateFileMapping function. - /// - Execute = 0x10, - - /// - /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region - /// results in an access violation. - /// - ExecuteRead = 0x20, - - /// - /// Enables execute, read-only, or read/write access to the committed region of pages. - /// - ExecuteReadWrite = 0x40, - - /// - /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to - /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The - /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - ExecuteWriteCopy = 0x80, - - /// - /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed - /// region results in an access violation. This flag is not supported by the CreateFileMapping function. - /// - NoAccess = 0x01, - - /// - /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed - /// region results in an access violation. - /// - ReadOnly = 0x02, - - /// - /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, - /// attempting to execute code in the committed region results in an access violation. - /// - ReadWrite = 0x04, - - /// - /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to - /// a committed copy-on-write page results in a private copy of the page being made for the process. The private - /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is - /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - WriteCopy = 0x08, - - /// - /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like - /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations - /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable - /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect - /// or CreateFileMapping functions. - /// - TargetsInvalid = 0x40000000, - - /// - /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. - /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information - /// will be maintained while the page protection changes. This flag is only valid when the protection changes to - /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. - /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call - /// targets for CFG. - /// - TargetsNoUpdate = TargetsInvalid, - - /// - /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a - /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time - /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn - /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a - /// system service, the service typically returns a failure status indicator. This value cannot be used with - /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. - /// - Guard = 0x100, - - /// - /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, - /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the - /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared - /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. - /// - NoCache = 0x200, - - /// - /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, - /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory - /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access - /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. - /// - WriteCombine = 0x400, - } + CancelTryContinue = 0x6, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary. - /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference - /// count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer - /// valid. + /// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends + /// a WM_HELP message to the owner. /// - /// - /// A handle to the loaded library module. The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function - /// returns this handle. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended - /// error information, call the GetLastError function. - /// - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FreeLibrary(IntPtr hModule); + Help = 0x4000, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew. - /// Retrieves the fully qualified path for the file that contains the specified module. The module must have been loaded - /// by the current process. To locate the file for a module that was loaded by another process, use the GetModuleFileNameEx - /// function. + /// The message box contains one push button: OK. This is the default. /// - /// - /// A handle to the loaded module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves - /// the path of the executable file of the current process. The GetModuleFileName function does not retrieve the path - /// for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag. For more information, see LoadLibraryEx. - /// - /// - /// A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than - /// the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated - /// string. If the length of the path exceeds the size that the nSize parameter specifies, the function succeeds and - /// the string is truncated to nSize characters including the terminating null character. - /// - /// - /// The size of the lpFilename buffer, in TCHARs. - /// - /// - /// If the function succeeds, the return value is the length of the string that is copied to the buffer, in characters, - /// not including the terminating null character. If the buffer is too small to hold the module name, the string is - /// truncated to nSize characters including the terminating null character, the function returns nSize, and the function - /// sets the last error to ERROR_INSUFFICIENT_BUFFER. If nSize is zero, the return value is zero and the last error - /// code is ERROR_SUCCESS. If the function fails, the return value is 0 (zero). To get extended error information, call - /// GetLastError. - /// - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - [PreserveSig] - public static extern uint GetModuleFileNameW( - [In] IntPtr hModule, - [Out] StringBuilder lpFilename, - [In][MarshalAs(UnmanagedType.U4)] int nSize); + Ok = DefaultValue, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. - /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To - /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. + /// The message box contains two push buttons: OK and Cancel. /// - /// - /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default - /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate - /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure - /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules - /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns - /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve - /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. - /// - /// - /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return - /// value is NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - public static extern IntPtr GetModuleHandleW(string lpModuleName); + OkCancel = 0x1, /// - /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// The message box contains two push buttons: Retry and Cancel. /// - /// - /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary, - /// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules - /// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. - /// - /// - /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be - /// in the low-order word; the high-order word must be zero. - /// - /// - /// If the function succeeds, the return value is the address of the exported function or variable. If the function - /// fails, the return value is NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] - [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")] - public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + RetryCancel = 0x5, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw. - /// Loads the specified module into the address space of the calling process. The specified module may cause other modules - /// to be loaded. For additional load options, use the LoadLibraryEx function. + /// The message box contains two push buttons: Yes and No. /// - /// - /// The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). - /// The name specified is the file name of the module and is not related to the name stored in the library module itself, - /// as specified by the LIBRARY keyword in the module-definition (.def) file. If the string specifies a full path, the - /// function searches only that path for the module. If the string specifies a relative path or a module name without - /// a path, the function uses a standard search strategy to find the module; for more information, see the Remarks. - /// If the function cannot find the module, the function fails.When specifying a path, be sure to use backslashes (\), - /// not forward slashes(/). For more information about paths, see Naming a File or Directory. If the string specifies - /// a module name without a path and the file name extension is omitted, the function appends the default library extension - /// .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point - /// character (.) in the module name string. - /// - /// - /// If the function succeeds, the return value is a handle to the module. If the function fails, the return value is - /// NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern IntPtr LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); + YesNo = 0x4, /// - /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process - /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can - /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. + /// The message box contains three push buttons: Yes, No, and Cancel. /// - /// - /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. - /// - /// - /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the - /// system verifies that all data in the base address and memory of the specified size is accessible for read access, - /// and if it is not accessible the function fails. - /// - /// - /// A pointer to a buffer that receives the contents from the address space of the specified process. - /// - /// - /// The number of bytes to be read from the specified process. - /// - /// - /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead - /// is NULL, the parameter is ignored. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get - /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an - /// area of the process that is inaccessible. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool ReadProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - IntPtr lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesRead); + YesNoCancel = 0x3, + + // To display an icon in the message box, specify one of the following values. /// - /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process - /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can - /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. + /// An exclamation-point icon appears in the message box. /// - /// - /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. - /// - /// - /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the - /// system verifies that all data in the base address and memory of the specified size is accessible for read access, - /// and if it is not accessible the function fails. - /// - /// - /// A pointer to a buffer that receives the contents from the address space of the specified process. - /// - /// - /// The number of bytes to be read from the specified process. - /// - /// - /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead - /// is NULL, the parameter is ignored. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get - /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an - /// area of the process that is inaccessible. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool ReadProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - byte[] lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesRead); + IconExclamation = 0x30, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode. - /// Controls whether the system will handle the specified types of serious errors or whether the process will handle - /// them. + /// An exclamation-point icon appears in the message box. /// - /// - /// The process error mode. This parameter can be one or more of the ErrorMode enum values. - /// - /// - /// The return value is the previous state of the error-mode bit flags. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern ErrorModes SetErrorMode(ErrorModes uMode); + IconWarning = IconExclamation, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter. - /// Enables an application to supersede the top-level exception handler of each thread of a process. After calling this - /// function, if an exception occurs in a process that is not being debugged, and the exception makes it to the unhandled - /// exception filter, that filter will call the exception filter function specified by the lpTopLevelExceptionFilter - /// parameter. + /// An icon consisting of a lowercase letter i in a circle appears in the message box. /// - /// - /// A pointer to a top-level exception filter function that will be called whenever the UnhandledExceptionFilter function - /// gets control, and the process is not being debugged. A value of NULL for this parameter specifies default handling - /// within UnhandledExceptionFilter. The filter function has syntax similar to that of UnhandledExceptionFilter: It - /// takes a single parameter of type LPEXCEPTION_POINTERS, has a WINAPI calling convention, and returns a value of type - /// LONG. The filter function should return one of the EXCEPTION_* enum values. - /// - /// - /// The SetUnhandledExceptionFilter function returns the address of the previous exception filter established with the - /// function. A NULL return value means that there is no current top-level exception handler. - /// - [DllImport("kernel32.dll")] - public static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); + IconInformation = 0x40, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc. - /// Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. - /// Memory allocated by this function is automatically initialized to zero. To allocate memory in the address space - /// of another process, use the VirtualAllocEx function. + /// An icon consisting of a lowercase letter i in a circle appears in the message box. /// - /// - /// The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded - /// down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, - /// the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity - /// on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to - /// allocate the region. If this address is within an enclave that you have not initialized by calling InitializeEnclave, - /// VirtualAlloc allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, - /// and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model. - /// If the address in within an enclave that you initialized, then the allocation operation fails with the - /// ERROR_INVALID_ADDRESS error. - /// - /// - /// The size of the region, in bytes. If the lpAddress parameter is NULL, this value is rounded up to the next page - /// boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress - /// to lpAddress+dwSize. This means that a 2-byte range straddling a page boundary causes both pages to be included - /// in the allocated region. - /// - /// - /// The type of memory allocation. This parameter must contain one of the MEM_* enum values. - /// - /// - /// The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify - /// any one of the memory protection constants. - /// - /// - /// If the function succeeds, the return value is the base address of the allocated region of pages. If the function - /// fails, the return value is NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr VirtualAlloc( - IntPtr lpAddress, - UIntPtr dwSize, - AllocationType flAllocationType, - MemoryProtection flProtect); - - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr VirtualAlloc( - IntPtr lpAddress, - UIntPtr dwSize, - AllocationType flAllocationType, - Memory.MemoryProtection flProtect); + IconAsterisk = IconInformation, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree. - /// Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling - /// process. - /// process. + /// A question-mark icon appears in the message box. + /// The question-mark message icon is no longer recommended because it does not clearly represent a specific type + /// of message and because the phrasing of a message as a question could apply to any message type. In addition, + /// users can confuse the message symbol question mark with Help information. Therefore, do not use this question + /// mark message symbol in your message boxes. The system continues to support its inclusion only for backward + /// compatibility. /// - /// - /// A pointer to the base address of the region of pages to be freed. If the dwFreeType parameter is MEM_RELEASE, this - /// parameter must be the base address returned by the VirtualAlloc function when the region of pages is reserved. - /// - /// - /// The size of the region of memory to be freed, in bytes. If the dwFreeType parameter is MEM_RELEASE, this parameter - /// must be 0 (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAlloc. - /// If the dwFreeType parameter is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes - /// in the range from the lpAddress parameter to (lpAddress+dwSize). This means, for example, that a 2-byte region of - /// memory that straddles a page boundary causes both pages to be decommitted.If lpAddress is the base address returned - /// by VirtualAlloc and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAlloc. - /// After that, the entire region is in the reserved state. - /// - /// - /// The type of free operation. This parameter must be one of the MEM_* enum values. - /// - /// - /// If the function succeeds, the return value is a nonzero value. If the function fails, the return value is 0 (zero). - /// To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualFree( - IntPtr lpAddress, - UIntPtr dwSize, - AllocationType dwFreeType); + IconQuestion = 0x20, /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect. - /// Changes the protection on a region of committed pages in the virtual address space of the calling process. + /// A stop-sign icon appears in the message box. /// - /// - /// The address of the starting page of the region of pages whose access protection attributes are to be changed. All - /// pages in the specified region must be within the same reserved region allocated when calling the VirtualAlloc or - /// VirtualAllocEx function using MEM_RESERVE. The pages cannot span adjacent reserved regions that were allocated by - /// separate calls to VirtualAlloc or VirtualAllocEx using MEM_RESERVE. - /// - /// - /// The size of the region whose access protection attributes are to be changed, in bytes. The region of affected pages - /// includes all pages containing one or more bytes in the range from the lpAddress parameter to (lpAddress+dwSize). - /// This means that a 2-byte range straddling a page boundary causes the protection attributes of both pages to be changed. - /// - /// - /// The memory protection option. This parameter can be one of the memory protection constants. For mapped views, this - /// value must be compatible with the access protection specified when the view was mapped (see MapViewOfFile, - /// MapViewOfFileEx, and MapViewOfFileExNuma). - /// - /// - /// A pointer to a variable that receives the previous access protection value of the first page in the specified region - /// of pages. If this parameter is NULL or does not point to a valid variable, the function fails. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. - /// To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualProtect( - IntPtr lpAddress, - UIntPtr dwSize, - MemoryProtection flNewProtection, - out MemoryProtection lpflOldProtect); - - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualProtect( - IntPtr lpAddress, - UIntPtr dwSize, - Memory.MemoryProtection flNewProtection, - out Memory.MemoryProtection lpflOldProtect); + IconStop = 0x10, /// - /// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or - /// the operation fails. + /// A stop-sign icon appears in the message box. /// - /// - /// A handle to the process memory to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access - /// to the process. - /// - /// - /// A pointer to the base address in the specified process to which data is written. Before data transfer occurs, the - /// system verifies that all data in the base address and memory of the specified size is accessible for write access, - /// and if it is not accessible, the function fails. - /// - /// - /// A pointer to the buffer that contains data to be written in the address space of the specified process. - /// - /// - /// The number of bytes to be written to the specified process. - /// - /// - /// A pointer to a variable that receives the number of bytes transferred into the specified process. This parameter - /// is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get - /// extended error information, call GetLastError.The function fails if the requested write operation crosses into an - /// area of the process that is inaccessible. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool WriteProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - byte[] lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesWritten); + IconError = IconStop, /// - /// Get a handle to the current process. + /// A stop-sign icon appears in the message box. /// - /// Handle to the process. - [DllImport("kernel32.dll")] - public static extern IntPtr GetCurrentProcess(); + IconHand = IconStop, + + // To indicate the default button, specify one of the following values. /// - /// Get the current process ID. + /// The first button is the default button. + /// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified. /// - /// The process ID. - [DllImport("kernel32.dll")] - public static extern uint GetCurrentProcessId(); + DefButton1 = DefaultValue, /// - /// Get the current thread ID. + /// The second button is the default button. /// - /// The thread ID. - [DllImport("kernel32.dll")] - public static extern uint GetCurrentThreadId(); + DefButton2 = 0x100, + + /// + /// The third button is the default button. + /// + DefButton3 = 0x200, + + /// + /// The fourth button is the default button. + /// + DefButton4 = 0x300, + + // To indicate the modality of the dialog box, specify one of the following values. + + /// + /// The user must respond to the message box before continuing work in the window identified by the hWnd parameter. + /// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy + /// of windows in the application, the user may be able to move to other windows within the thread. All child windows + /// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the + /// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified. + /// + ApplModal = DefaultValue, + + /// + /// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style. + /// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate + /// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with + /// windows other than those associated with hWnd. + /// + SystemModal = 0x1000, + + /// + /// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the + /// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle + /// available but still needs to prevent input to other windows in the calling thread without suspending other threads. + /// + TaskModal = 0x2000, + + // To specify other options, use one or more of the following values. + + /// + /// Same as desktop of the interactive window station. For more information, see Window Stations. If the current + /// input desktop is not the default desktop, MessageBox does not return until the user switches to the default + /// desktop. + /// + DefaultDesktopOnly = 0x20000, + + /// + /// The text is right-justified. + /// + Right = 0x80000, + + /// + /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems. + /// + RtlReading = 0x100000, + + /// + /// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function + /// for the message box. + /// + SetForeground = 0x10000, + + /// + /// The message box is created with the WS_EX_TOPMOST window style. + /// + Topmost = 0x40000, + + /// + /// The caller is a service notifying the user of an event. The function displays a message box on the current active + /// desktop, even if there is no user logged on to the computer. + /// + ServiceNotification = 0x200000, } /// - /// Native dbghelp functions. + /// GWL_* from winuser. /// - internal static partial class NativeFunctions + public enum WindowLongType { /// - /// Type of minidump to create. + /// Sets a new extended window style. /// - public enum MiniDumpType : int - { - /// - /// Normal minidump. - /// - MiniDumpNormal, - - /// - /// Minidump with data segments. - /// - MiniDumpWithDataSegs, - - /// - /// Minidump with full memory. - /// - MiniDumpWithFullMemory, - } + ExStyle = -20, /// - /// Initializes the symbol handler for a process. + /// Sets a new application instance handle. /// - /// - /// A handle that identifies the caller. - /// This value should be unique and nonzero, but need not be a process handle. - /// However, if you do use a process handle, be sure to use the correct handle. - /// If the application is a debugger, use the process handle for the process being debugged. - /// Do not use the handle returned by GetCurrentProcess when debugging another process, because calling functions like SymLoadModuleEx can have unexpected results. - /// This parameter cannot be NULL. - /// - /// The path, or series of paths separated by a semicolon (;), that is used to search for symbol files. - /// If this parameter is NULL, the library attempts to form a symbol path from the following sources: - /// - The current working directory of the application - /// - The _NT_SYMBOL_PATH environment variable - /// - The _NT_ALTERNATE_SYMBOL_PATH environment variable - /// Note that the search path can also be set using the SymSetSearchPath function. - /// - /// - /// If this value is , enumerates the loaded modules for the process and effectively calls the SymLoadModule64 function for each module. - /// - /// Whether or not the function succeeded. - [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern bool SymInitialize(IntPtr hProcess, string userSearchPath, bool fInvadeProcess); + HInstance = -6, /// - /// Deallocates all resources associated with the process handle. + /// Sets a new identifier of the child window.The window cannot be a top-level window. /// - /// A handle to the process that was originally passed to the function. - /// Whether or not the function succeeded. - [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern bool SymCleanup(IntPtr hProcess); + Id = -12, /// - /// Creates a minidump. + /// Sets a new window style. /// - /// Target process handle. - /// Target process ID. - /// Output file handle. - /// Type of dump to take. - /// Exception information. - /// User information. - /// Callback. - /// Whether or not the minidump succeeded. - [DllImport("dbghelp.dll")] - public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, int dumpType, ref MinidumpExceptionInformation exceptionInfo, IntPtr userStreamParam, IntPtr callback); + Style = -16, /// - /// Structure describing minidump exception information. + /// Sets the user data associated with the window. This data is intended for use by the application that created the window. Its value is initially zero. /// - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct MinidumpExceptionInformation - { - /// - /// ID of the thread that caused the exception. - /// - public uint ThreadId; + UserData = -21, - /// - /// Pointer to the exception record. - /// - public IntPtr ExceptionPointers; + /// + /// Sets a new address for the window procedure. + /// + WndProc = -4, - /// - /// ClientPointers field. - /// - public int ClientPointers; - } + // The following values are also available when the hWnd parameter identifies a dialog box. + + // /// + // /// Sets the new pointer to the dialog box procedure. + // /// + // DWLP_DLGPROC = DWLP_MSGRESULT + sizeof(LRESULT), + + /// + /// Sets the return value of a message processed in the dialog box procedure. + /// + MsgResult = 0, + + // /// + // /// Sets new extra information that is private to the application, such as handles or pointers. + // /// + // DWLP_USER = DWLP_DLGPROC + sizeof(DLGPROC), } /// - /// Native ws2_32 functions. + /// WM_* from winuser. + /// These are spread throughout multiple files, find the documentation manually if you need it. + /// https://gist.github.com/amgine/2395987. /// - internal static partial class NativeFunctions + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "No documentation available.")] + public enum WindowsMessage + { + WM_NULL = 0x0000, + WM_CREATE = 0x0001, + WM_DESTROY = 0x0002, + WM_MOVE = 0x0003, + WM_SIZE = 0x0005, + WM_ACTIVATE = 0x0006, + WM_SETFOCUS = 0x0007, + WM_KILLFOCUS = 0x0008, + WM_ENABLE = 0x000A, + WM_SETREDRAW = 0x000B, + WM_SETTEXT = 0x000C, + WM_GETTEXT = 0x000D, + WM_GETTEXTLENGTH = 0x000E, + WM_PAINT = 0x000F, + WM_CLOSE = 0x0010, + WM_QUERYENDSESSION = 0x0011, + WM_QUERYOPEN = 0x0013, + WM_ENDSESSION = 0x0016, + WM_QUIT = 0x0012, + WM_ERASEBKGND = 0x0014, + WM_SYSCOLORCHANGE = 0x0015, + WM_SHOWWINDOW = 0x0018, + WM_WININICHANGE = 0x001A, + WM_SETTINGCHANGE = WM_WININICHANGE, + WM_DEVMODECHANGE = 0x001B, + WM_ACTIVATEAPP = 0x001C, + WM_FONTCHANGE = 0x001D, + WM_TIMECHANGE = 0x001E, + WM_CANCELMODE = 0x001F, + WM_SETCURSOR = 0x0020, + WM_MOUSEACTIVATE = 0x0021, + WM_CHILDACTIVATE = 0x0022, + WM_QUEUESYNC = 0x0023, + WM_GETMINMAXINFO = 0x0024, + WM_PAINTICON = 0x0026, + WM_ICONERASEBKGND = 0x0027, + WM_NEXTDLGCTL = 0x0028, + WM_SPOOLERSTATUS = 0x002A, + WM_DRAWITEM = 0x002B, + WM_MEASUREITEM = 0x002C, + WM_DELETEITEM = 0x002D, + WM_VKEYTOITEM = 0x002E, + WM_CHARTOITEM = 0x002F, + WM_SETFONT = 0x0030, + WM_GETFONT = 0x0031, + WM_SETHOTKEY = 0x0032, + WM_GETHOTKEY = 0x0033, + WM_QUERYDRAGICON = 0x0037, + WM_COMPAREITEM = 0x0039, + WM_GETOBJECT = 0x003D, + WM_COMPACTING = 0x0041, + WM_COMMNOTIFY = 0x0044, + WM_WINDOWPOSCHANGING = 0x0046, + WM_WINDOWPOSCHANGED = 0x0047, + WM_POWER = 0x0048, + WM_COPYDATA = 0x004A, + WM_CANCELJOURNAL = 0x004B, + WM_NOTIFY = 0x004E, + WM_INPUTLANGCHANGEREQUEST = 0x0050, + WM_INPUTLANGCHANGE = 0x0051, + WM_TCARD = 0x0052, + WM_HELP = 0x0053, + WM_USERCHANGED = 0x0054, + WM_NOTIFYFORMAT = 0x0055, + WM_CONTEXTMENU = 0x007B, + WM_STYLECHANGING = 0x007C, + WM_STYLECHANGED = 0x007D, + WM_DISPLAYCHANGE = 0x007E, + WM_GETICON = 0x007F, + WM_SETICON = 0x0080, + WM_NCCREATE = 0x0081, + WM_NCDESTROY = 0x0082, + WM_NCCALCSIZE = 0x0083, + WM_NCHITTEST = 0x0084, + WM_NCPAINT = 0x0085, + WM_NCACTIVATE = 0x0086, + WM_GETDLGCODE = 0x0087, + WM_SYNCPAINT = 0x0088, + + WM_NCMOUSEMOVE = 0x00A0, + WM_NCLBUTTONDOWN = 0x00A1, + WM_NCLBUTTONUP = 0x00A2, + WM_NCLBUTTONDBLCLK = 0x00A3, + WM_NCRBUTTONDOWN = 0x00A4, + WM_NCRBUTTONUP = 0x00A5, + WM_NCRBUTTONDBLCLK = 0x00A6, + WM_NCMBUTTONDOWN = 0x00A7, + WM_NCMBUTTONUP = 0x00A8, + WM_NCMBUTTONDBLCLK = 0x00A9, + WM_NCXBUTTONDOWN = 0x00AB, + WM_NCXBUTTONUP = 0x00AC, + WM_NCXBUTTONDBLCLK = 0x00AD, + + WM_INPUT_DEVICE_CHANGE = 0x00FE, + WM_INPUT = 0x00FF, + + WM_KEYFIRST = 0x0100, + WM_KEYDOWN = WM_KEYFIRST, + WM_KEYUP = 0x0101, + WM_CHAR = 0x0102, + WM_DEADCHAR = 0x0103, + WM_SYSKEYDOWN = 0x0104, + WM_SYSKEYUP = 0x0105, + WM_SYSCHAR = 0x0106, + WM_SYSDEADCHAR = 0x0107, + WM_UNICHAR = 0x0109, + WM_KEYLAST = WM_UNICHAR, + + WM_IME_STARTCOMPOSITION = 0x010D, + WM_IME_ENDCOMPOSITION = 0x010E, + WM_IME_COMPOSITION = 0x010F, + WM_IME_KEYLAST = WM_IME_COMPOSITION, + + WM_INITDIALOG = 0x0110, + WM_COMMAND = 0x0111, + WM_SYSCOMMAND = 0x0112, + WM_TIMER = 0x0113, + WM_HSCROLL = 0x0114, + WM_VSCROLL = 0x0115, + WM_INITMENU = 0x0116, + WM_INITMENUPOPUP = 0x0117, + WM_MENUSELECT = 0x011F, + WM_MENUCHAR = 0x0120, + WM_ENTERIDLE = 0x0121, + WM_MENURBUTTONUP = 0x0122, + WM_MENUDRAG = 0x0123, + WM_MENUGETOBJECT = 0x0124, + WM_UNINITMENUPOPUP = 0x0125, + WM_MENUCOMMAND = 0x0126, + + WM_CHANGEUISTATE = 0x0127, + WM_UPDATEUISTATE = 0x0128, + WM_QUERYUISTATE = 0x0129, + + WM_CTLCOLORMSGBOX = 0x0132, + WM_CTLCOLOREDIT = 0x0133, + WM_CTLCOLORLISTBOX = 0x0134, + WM_CTLCOLORBTN = 0x0135, + WM_CTLCOLORDLG = 0x0136, + WM_CTLCOLORSCROLLBAR = 0x0137, + WM_CTLCOLORSTATIC = 0x0138, + MN_GETHMENU = 0x01E1, + + WM_MOUSEFIRST = 0x0200, + WM_MOUSEMOVE = WM_MOUSEFIRST, + WM_LBUTTONDOWN = 0x0201, + WM_LBUTTONUP = 0x0202, + WM_LBUTTONDBLCLK = 0x0203, + WM_RBUTTONDOWN = 0x0204, + WM_RBUTTONUP = 0x0205, + WM_RBUTTONDBLCLK = 0x0206, + WM_MBUTTONDOWN = 0x0207, + WM_MBUTTONUP = 0x0208, + WM_MBUTTONDBLCLK = 0x0209, + WM_MOUSEWHEEL = 0x020A, + WM_XBUTTONDOWN = 0x020B, + WM_XBUTTONUP = 0x020C, + WM_XBUTTONDBLCLK = 0x020D, + WM_MOUSEHWHEEL = 0x020E, + + WM_PARENTNOTIFY = 0x0210, + WM_ENTERMENULOOP = 0x0211, + WM_EXITMENULOOP = 0x0212, + + WM_NEXTMENU = 0x0213, + WM_SIZING = 0x0214, + WM_CAPTURECHANGED = 0x0215, + WM_MOVING = 0x0216, + + WM_POWERBROADCAST = 0x0218, + + WM_DEVICECHANGE = 0x0219, + + WM_MDICREATE = 0x0220, + WM_MDIDESTROY = 0x0221, + WM_MDIACTIVATE = 0x0222, + WM_MDIRESTORE = 0x0223, + WM_MDINEXT = 0x0224, + WM_MDIMAXIMIZE = 0x0225, + WM_MDITILE = 0x0226, + WM_MDICASCADE = 0x0227, + WM_MDIICONARRANGE = 0x0228, + WM_MDIGETACTIVE = 0x0229, + + WM_MDISETMENU = 0x0230, + WM_ENTERSIZEMOVE = 0x0231, + WM_EXITSIZEMOVE = 0x0232, + WM_DROPFILES = 0x0233, + WM_MDIREFRESHMENU = 0x0234, + + WM_IME_SETCONTEXT = 0x0281, + WM_IME_NOTIFY = 0x0282, + WM_IME_CONTROL = 0x0283, + WM_IME_COMPOSITIONFULL = 0x0284, + WM_IME_SELECT = 0x0285, + WM_IME_CHAR = 0x0286, + WM_IME_REQUEST = 0x0288, + WM_IME_KEYDOWN = 0x0290, + WM_IME_KEYUP = 0x0291, + + WM_MOUSEHOVER = 0x02A1, + WM_MOUSELEAVE = 0x02A3, + WM_NCMOUSEHOVER = 0x02A0, + WM_NCMOUSELEAVE = 0x02A2, + + WM_WTSSESSION_CHANGE = 0x02B1, + + WM_TABLET_FIRST = 0x02c0, + WM_TABLET_LAST = 0x02df, + + WM_CUT = 0x0300, + WM_COPY = 0x0301, + WM_PASTE = 0x0302, + WM_CLEAR = 0x0303, + WM_UNDO = 0x0304, + WM_RENDERFORMAT = 0x0305, + WM_RENDERALLFORMATS = 0x0306, + WM_DESTROYCLIPBOARD = 0x0307, + WM_DRAWCLIPBOARD = 0x0308, + WM_PAINTCLIPBOARD = 0x0309, + WM_VSCROLLCLIPBOARD = 0x030A, + WM_SIZECLIPBOARD = 0x030B, + WM_ASKCBFORMATNAME = 0x030C, + WM_CHANGECBCHAIN = 0x030D, + WM_HSCROLLCLIPBOARD = 0x030E, + WM_QUERYNEWPALETTE = 0x030F, + WM_PALETTEISCHANGING = 0x0310, + WM_PALETTECHANGED = 0x0311, + WM_HOTKEY = 0x0312, + + WM_PRINT = 0x0317, + WM_PRINTCLIENT = 0x0318, + + WM_APPCOMMAND = 0x0319, + + WM_THEMECHANGED = 0x031A, + + WM_CLIPBOARDUPDATE = 0x031D, + + WM_DWMCOMPOSITIONCHANGED = 0x031E, + WM_DWMNCRENDERINGCHANGED = 0x031F, + WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320, + WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321, + + WM_GETTITLEBARINFOEX = 0x033F, + + WM_HANDHELDFIRST = 0x0358, + WM_HANDHELDLAST = 0x035F, + + WM_AFXFIRST = 0x0360, + WM_AFXLAST = 0x037F, + + WM_PENWINFIRST = 0x0380, + WM_PENWINLAST = 0x038F, + + WM_APP = 0x8000, + + WM_USER = 0x0400, + + WM_REFLECT = WM_USER + 0x1C00, + } + + /// + /// Returns true if the current application has focus, false otherwise. + /// + /// + /// If the current application is focused. + /// + public static bool ApplicationIsActivated() + { + var activatedHandle = GetForegroundWindow(); + if (activatedHandle == IntPtr.Zero) + return false; // No window is currently activated + + _ = GetWindowThreadProcessId(activatedHandle, out var activeProcId); + if (Marshal.GetLastWin32Error() != 0) + return false; + + return activeProcId == Environment.ProcessId; + } + + /// + /// Passes message information to the specified window procedure. + /// + /// + /// The previous window procedure. If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to + /// GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a special internal value + /// meaningful only to CallWindowProc. + /// + /// + /// A handle to the window procedure to receive the message. + /// + /// + /// The message. + /// + /// + /// Additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. + /// + /// + /// More additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. + /// + /// + /// Use the CallWindowProc function for window subclassing. Usually, all windows with the same class share one window procedure. A + /// subclass is a window or set of windows with the same class whose messages are intercepted and processed by another window procedure + /// (or procedures) before being passed to the window procedure of the class. + /// The SetWindowLong function creates the subclass by changing the window procedure associated with a particular window, causing the + /// system to call the new window procedure instead of the previous one.An application must pass any messages not processed by the new + /// window procedure to the previous window procedure by calling CallWindowProc.This allows the application to create a chain of window + /// procedures. + /// + [DllImport("user32.dll")] + public static extern long CallWindowProcW(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, ulong wParam, long lParam); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindowex. + /// Flashes the specified window. It does not change the active state of the window. + /// + /// + /// A pointer to a FLASHWINFO structure. + /// + /// + /// The return value specifies the window's state before the call to the FlashWindowEx function. If the window caption + /// was drawn as active before the call, the return value is nonzero. Otherwise, the return value is zero. + /// + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FlashWindowEx(ref FlashWindowInfo pwfi); + + /// + /// Retrieves a handle to the foreground window (the window with which the user is currently working). The system assigns + /// a slightly higher priority to the thread that creates the foreground window than it does to other threads. + /// + /// + /// The return value is a handle to the foreground window. The foreground window can be NULL in certain circumstances, + /// such as when a window is losing activation. + /// + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern IntPtr GetForegroundWindow(); + + /// + /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the + /// process that created the window. + /// + /// + /// A handle to the window. + /// + /// + /// A pointer to a variable that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId + /// copies the identifier of the process to the variable; otherwise, it does not. + /// + /// + /// The return value is the identifier of the thread that created the window. + /// + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); + + /// + /// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, + /// such as status or error information. The message box returns an integer value that indicates which button the user + /// clicked. + /// + /// + /// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no + /// owner window. + /// + /// + /// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage + /// return and/or linefeed character between each line. + /// + /// + /// The dialog box title. If this parameter is NULL, the default title is Error. + /// + /// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups + /// of flags. + /// + /// + /// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or + /// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an + /// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK. + /// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function + /// succeeds, the return value is one of the ID* enum values. + /// + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type); + + /// + /// Changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory. + /// + /// + /// A handle to the window and, indirectly, the class to which the window belongs. The SetWindowLongPtr function fails if the + /// process that owns the window specified by the hWnd parameter is at a higher process privilege in the UIPI hierarchy than the + /// process the calling thread resides in. + /// + /// + /// The zero-based offset to the value to be set. Valid values are in the range zero through the number of bytes of extra window + /// memory, minus the size of a LONG_PTR. To set any other value, specify one of the values. + /// + /// + /// The replacement value. + /// + /// + /// If the function succeeds, the return value is the previous value of the specified offset. If the function fails, the return + /// value is zero.To get extended error information, call GetLastError. If the previous value is zero and the function succeeds, + /// the return value is zero, but the function does not clear the last error information. To determine success or failure, clear + /// the last error information by calling SetLastError with 0, then call SetWindowLongPtr.Function failure will be indicated by + /// a return value of zero and a GetLastError result that is nonzero. + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, WindowLongType nIndex, IntPtr dwNewLong); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-flashwinfo. + /// Contains the flash status for a window and the number of times the system should flash the window. + /// + [StructLayout(LayoutKind.Sequential)] + public struct FlashWindowInfo { /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt. - /// The setsockopt function sets a socket option. + /// The size of the structure, in bytes. /// - /// - /// 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); + public uint Size; + + /// + /// A handle to the window to be flashed. The window can be either opened or minimized. + /// + public IntPtr Hwnd; + + /// + /// The flash status. This parameter can be one or more of the FlashWindow enum values. + /// + public FlashWindow Flags; + + /// + /// The number of times to flash the window. + /// + public uint Count; + + /// + /// The rate at which the window is to be flashed, in milliseconds. If dwTimeout is zero, the function uses the + /// default cursor blink rate. + /// + public uint Timeout; } } + +/// +/// Native imm32 functions. +/// +internal static partial class NativeFunctions +{ + /// + /// GCS_* from imm32. + /// These values are used with ImmGetCompositionString and WM_IME_COMPOSITION. + /// + [Flags] + public enum IMEComposition + { + /// + /// Retrieve or update the attribute of the composition string. + /// + CompAttr = 0x0010, + + /// + /// Retrieve or update clause information of the composition string. + /// + CompClause = 0x0020, + + /// + /// Retrieve or update the attributes of the reading string of the current composition. + /// + CompReadAttr = 0x0002, + + /// + /// Retrieve or update the clause information of the reading string of the composition string. + /// + CompReadClause = 0x0004, + + /// + /// Retrieve or update the reading string of the current composition. + /// + CompReadStr = 0x0001, + + /// + /// Retrieve or update the current composition string. + /// + CompStr = 0x0008, + + /// + /// Retrieve or update the cursor position in composition string. + /// + CursorPos = 0x0080, + + /// + /// Retrieve or update the starting position of any changes in composition string. + /// + DeltaStart = 0x0100, + + /// + /// Retrieve or update clause information of the result string. + /// + ResultClause = 0x1000, + + /// + /// Retrieve or update clause information of the reading string. + /// + ResultReadClause = 0x0400, + + /// + /// Retrieve or update the reading string. + /// + ResultReadStr = 0x0200, + + /// + /// Retrieve or update the string of the composition result. + /// + ResultStr = 0x0800, + } + + /// + /// IMN_* from imm32. + /// Input Method Manager Commands, this enum is not exhaustive. + /// + public enum IMECommand + { + /// + /// Notifies the application when an IME is about to change the content of the candidate window. + /// + ChangeCandidate = 0x0003, + + /// + /// Notifies an application when an IME is about to close the candidates window. + /// + CloseCandidate = 0x0004, + + /// + /// Notifies an application when an IME is about to open the candidate window. + /// + OpenCandidate = 0x0005, + + /// + /// Notifies an application when the conversion mode of the input context is updated. + /// + SetConversionMode = 0x0006, + } + + /// + /// Returns the input context associated with the specified window. + /// + /// Unnamed parameter 1. + /// + /// Returns the handle to the input context. + /// + [DllImport("imm32.dll")] + public static extern IntPtr ImmGetContext(IntPtr hWnd); + + /// + /// Retrieves information about the composition string. + /// + /// + /// Unnamed parameter 1. + /// + /// + /// Unnamed parameter 2. + /// + /// + /// Pointer to a buffer in which the function retrieves the composition string information. + /// + /// + /// Size, in bytes, of the output buffer, even if the output is a Unicode string. The application sets this parameter to 0 + /// if the function is to return the size of the required output buffer. + /// + /// + /// Returns the number of bytes copied to the output buffer. If dwBufLen is set to 0, the function returns the buffer size, + /// in bytes, required to receive all requested information, excluding the terminating null character. The return value is + /// always the size, in bytes, even if the requested data is a Unicode string. + /// This function returns one of the following negative error codes if it does not succeed: + /// - IMM_ERROR_NODATA.Composition data is not ready in the input context. + /// - IMM_ERROR_GENERAL.General error detected by IME. + /// + [DllImport("imm32.dll")] + public static extern long ImmGetCompositionStringW(IntPtr hImc, IMEComposition arg2, IntPtr lpBuf, uint dwBufLen); + + /// + /// Retrieves a candidate list. + /// + /// + /// Unnamed parameter 1. + /// + /// + /// Zero-based index of the candidate list. + /// + /// + /// Pointer to a CANDIDATELIST structure in which the function retrieves the candidate list. + /// + /// + /// Size, in bytes, of the buffer to receive the candidate list. The application can specify 0 for this parameter if the + /// function is to return the required size of the output buffer only. + /// + /// + /// Returns the number of bytes copied to the candidate list buffer if successful. If the application has supplied 0 for + /// the dwBufLen parameter, the function returns the size required for the candidate list buffer. The function returns 0 + /// if it does not succeed. + /// + [DllImport("imm32.dll")] + public static extern long ImmGetCandidateListW(IntPtr hImc, uint deIndex, IntPtr lpCandList, uint dwBufLen); + + /// + /// Sets the position of the composition window. + /// + /// + /// Unnamed parameter 1. + /// + /// + /// Pointer to a COMPOSITIONFORM structure that contains the new position and other related information about + /// the composition window. + /// + /// + /// Returns a nonzero value if successful, or 0 otherwise. + /// + [DllImport("imm32.dll", CharSet = CharSet.Auto)] + public static extern bool ImmSetCompositionWindow(IntPtr hImc, ref CompositionForm frm); + + /// + /// Releases the input context and unlocks the memory associated in the input context. An application must call this + /// function for each call to the ImmGetContext function. + /// + /// + /// Unnamed parameter 1. + /// + /// + /// Unnamed parameter 2. + /// + /// + /// Returns a nonzero value if successful, or 0 otherwise. + /// + [DllImport("imm32.dll", CharSet = CharSet.Auto)] + public static extern bool ImmReleaseContext(IntPtr hwnd, IntPtr hImc); + + /// + /// Contains information about a candidate list. + /// + public struct CandidateList + { + /// + /// Size, in bytes, of the structure, the offset array, and all candidate strings. + /// + public int Size; + + /// + /// Candidate style values. This member can have one or more of the IME_CAND_* values. + /// + public int Style; + + /// + /// Number of candidate strings. + /// + public int Count; + + /// + /// Index of the selected candidate string. + /// + public int Selection; + + /// + /// Index of the first candidate string in the candidate window. This varies as the user presses the PAGE UP and PAGE DOWN keys. + /// + public int PageStart; + + /// + /// Number of candidate strings to be shown in one page in the candidate window. The user can move to the next page by pressing IME-defined keys, such as the PAGE UP or PAGE DOWN key. If this number is 0, an application can define a proper value by itself. + /// + public int PageSize; + + // /// + // /// Offset to the start of the first candidate string, relative to the start of this structure. The offsets + // /// for subsequent strings immediately follow this member, forming an array of 32-bit offsets. + // /// + // public IntPtr Offset; // manually handle + } + + /// + /// Contains style and position information for a composition window. + /// + [StructLayout(LayoutKind.Sequential)] + public struct CompositionForm + { + /// + /// Position style. This member can be one of the CFS_* values. + /// + public int Style; + + /// + /// A POINT structure containing the coordinates of the upper left corner of the composition window. + /// + public Point CurrentPos; + + /// + /// A RECT structure containing the coordinates of the upper left and lower right corners of the composition window. + /// + public Rect Area; + } + + /// + /// Contains coordinates for a point. + /// + [StructLayout(LayoutKind.Sequential)] + public struct Point + { + /// + /// The X position. + /// + public int X; + + /// + /// The Y position. + /// + public int Y; + } + + /// + /// Contains dimensions for a rectangle. + /// + [StructLayout(LayoutKind.Sequential)] + public struct Rect + { + /// + /// The left position. + /// + public int Left; + + /// + /// The top position. + /// + public int Top; + + /// + /// The right position. + /// + public int Right; + + /// + /// The bottom position. + /// + public int Bottom; + } +} + +/// +/// Native kernel32 functions. +/// +internal static partial class NativeFunctions +{ + /// + /// MEM_* from memoryapi. + /// + [Flags] + public enum AllocationType + { + /// + /// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce + /// placeholders, lpAddress and dwSize must exactly match those of the placeholder. + /// + CoalescePlaceholders = 0x1, + + /// + /// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using + /// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify + /// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER. + /// + PreservePlaceholder = 0x2, + + /// + /// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved + /// memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents + /// will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed. + /// To reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Attempting to commit + /// a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the + /// entire range has already been reserved. The resulting error code is ERROR_INVALID_ADDRESS. An attempt to commit + /// a page that is already committed does not cause the function to fail. This means that you can commit pages without + /// first determining the current commitment state of each page. If lpAddress specifies an address within an enclave, + /// flAllocationType must be MEM_COMMIT. + /// + Commit = 0x1000, + + /// + /// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory + /// or in the paging file on disk. You commit reserved pages by calling VirtualAllocEx again with MEM_COMMIT. To + /// reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Other memory allocation + /// functions, such as malloc and LocalAlloc, cannot use reserved memory until it has been released. + /// + Reserve = 0x2000, + + /// + /// Decommits the specified region of committed pages. After the operation, the pages are in the reserved state. + /// The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit + /// a range of pages without first determining the current commitment state. The MEM_DECOMMIT value is not supported + /// when the lpAddress parameter provides the base address for an enclave. + /// + Decommit = 0x4000, + + /// + /// Releases the specified region of pages, or placeholder (for a placeholder, the address space is released and + /// available for other allocations). After this operation, the pages are in the free state. If you specify this + /// value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function + /// when the region is reserved. The function fails if either of these conditions is not met. If any pages in the + /// region are committed currently, the function first decommits, and then releases them. The function does not + /// fail if you attempt to release pages that are in different states, some reserved and some committed. This means + /// that you can release a range of pages without first determining the current commitment state. + /// + Release = 0x8000, + + /// + /// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages + /// should not be read from or written to the paging file. However, the memory block will be used again later, so + /// it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee + /// that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit + /// the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect. + /// However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns + /// an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable + /// if it is mapped to a paging file. + /// + Reset = 0x80000, + + /// + /// MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier. + /// It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to + /// the caller and attempts to reverse the effects of MEM_RESET. If the function succeeds, that means all data in + /// the specified address range is intact. If the function fails, at least some of the data in the address range + /// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on + /// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the + /// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid + /// protection value, such as PAGE_NOACCESS. + /// + ResetUndo = 0x1000000, + + /// + /// Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages. This value must + /// be used with MEM_RESERVE and no other values. + /// + Physical = 0x400000, + + /// + /// Allocates memory at the highest possible address. This can be slower than regular allocations, especially when + /// there are many allocations. + /// + TopDown = 0x100000, + + /// + /// Causes the system to track pages that are written to in the allocated region. If you specify this value, you + /// must also specify MEM_RESERVE. To retrieve the addresses of the pages that have been written to since the region + /// was allocated or the write-tracking state was reset, call the GetWriteWatch function. To reset the write-tracking + /// state, call GetWriteWatch or ResetWriteWatch. The write-tracking feature remains enabled for the memory region + /// until the region is freed. + /// + WriteWatch = 0x200000, + + /// + /// Allocates memory using large page support. The size and alignment must be a multiple of the large-page minimum. + /// To obtain this value, use the GetLargePageMinimum function. If you specify this value, you must also specify + /// MEM_RESERVE and MEM_COMMIT. + /// + LargePages = 0x20000000, + } + + /// + /// SEM_* from errhandlingapi. + /// + [Flags] + public enum ErrorModes : uint + { + /// + /// Use the system default, which is to display all error dialog boxes. + /// + SystemDefault = 0x0, + + /// + /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the + /// calling process. Best practice is that all applications call the process-wide SetErrorMode function with a parameter + /// of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application. + /// + FailCriticalErrors = 0x0001, + + /// + /// The system automatically fixes memory alignment faults and makes them invisible to the application. It does + /// this for the calling process and any descendant processes. This feature is only supported by certain processor + /// architectures. For more information, see the Remarks section. After this value is set for a process, subsequent + /// attempts to clear the value are ignored. + /// + NoAlignmentFaultExcept = 0x0004, + + /// + /// The system does not display the Windows Error Reporting dialog. + /// + NoGpFaultErrorBox = 0x0002, + + /// + /// The OpenFile function does not display a message box when it fails to find a file. Instead, the error is returned + /// to the caller. This error mode overrides the OF_PROMPT flag. + /// + NoOpenFileErrorBox = 0x8000, + } + + /// + /// PAGE_* from memoryapi. + /// + [Flags] + public enum MemoryProtection + { + /// + /// Enables execute access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. This flag is not supported by the CreateFileMapping function. + /// + Execute = 0x10, + + /// + /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region + /// results in an access violation. + /// + ExecuteRead = 0x20, + + /// + /// Enables execute, read-only, or read/write access to the committed region of pages. + /// + ExecuteReadWrite = 0x40, + + /// + /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to + /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The + /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + ExecuteWriteCopy = 0x80, + + /// + /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed + /// region results in an access violation. This flag is not supported by the CreateFileMapping function. + /// + NoAccess = 0x01, + + /// + /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed + /// region results in an access violation. + /// + ReadOnly = 0x02, + + /// + /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, + /// attempting to execute code in the committed region results in an access violation. + /// + ReadWrite = 0x04, + + /// + /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to + /// a committed copy-on-write page results in a private copy of the page being made for the process. The private + /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is + /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + WriteCopy = 0x08, + + /// + /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like + /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations + /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable + /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect + /// or CreateFileMapping functions. + /// + TargetsInvalid = 0x40000000, + + /// + /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. + /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information + /// will be maintained while the page protection changes. This flag is only valid when the protection changes to + /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. + /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call + /// targets for CFG. + /// + TargetsNoUpdate = TargetsInvalid, + + /// + /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a + /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time + /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn + /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a + /// system service, the service typically returns a failure status indicator. This value cannot be used with + /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. + /// + Guard = 0x100, + + /// + /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, + /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the + /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared + /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. + /// + NoCache = 0x200, + + /// + /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, + /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory + /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access + /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. + /// + WriteCombine = 0x400, + } + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary. + /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference + /// count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer + /// valid. + /// + /// + /// A handle to the loaded library module. The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function + /// returns this handle. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended + /// error information, call the GetLastError function. + /// + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FreeLibrary(IntPtr hModule); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew. + /// Retrieves the fully qualified path for the file that contains the specified module. The module must have been loaded + /// by the current process. To locate the file for a module that was loaded by another process, use the GetModuleFileNameEx + /// function. + /// + /// + /// A handle to the loaded module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves + /// the path of the executable file of the current process. The GetModuleFileName function does not retrieve the path + /// for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag. For more information, see LoadLibraryEx. + /// + /// + /// A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than + /// the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated + /// string. If the length of the path exceeds the size that the nSize parameter specifies, the function succeeds and + /// the string is truncated to nSize characters including the terminating null character. + /// + /// + /// The size of the lpFilename buffer, in TCHARs. + /// + /// + /// If the function succeeds, the return value is the length of the string that is copied to the buffer, in characters, + /// not including the terminating null character. If the buffer is too small to hold the module name, the string is + /// truncated to nSize characters including the terminating null character, the function returns nSize, and the function + /// sets the last error to ERROR_INSUFFICIENT_BUFFER. If nSize is zero, the return value is zero and the last error + /// code is ERROR_SUCCESS. If the function fails, the return value is 0 (zero). To get extended error information, call + /// GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [PreserveSig] + public static extern uint GetModuleFileNameW( + [In] IntPtr hModule, + [Out] StringBuilder lpFilename, + [In][MarshalAs(UnmanagedType.U4)] int nSize); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. + /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To + /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. + /// + /// + /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default + /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate + /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure + /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules + /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns + /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve + /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return + /// value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetModuleHandleW(string lpModuleName); + + /// + /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). + /// + /// + /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary, + /// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules + /// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be + /// in the low-order word; the high-order word must be zero. + /// + /// + /// If the function succeeds, the return value is the address of the exported function or variable. If the function + /// fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw. + /// Loads the specified module into the address space of the calling process. The specified module may cause other modules + /// to be loaded. For additional load options, use the LoadLibraryEx function. + /// + /// + /// The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). + /// The name specified is the file name of the module and is not related to the name stored in the library module itself, + /// as specified by the LIBRARY keyword in the module-definition (.def) file. If the string specifies a full path, the + /// function searches only that path for the module. If the string specifies a relative path or a module name without + /// a path, the function uses a standard search strategy to find the module; for more information, see the Remarks. + /// If the function cannot find the module, the function fails.When specifying a path, be sure to use backslashes (\), + /// not forward slashes(/). For more information about paths, see Naming a File or Directory. If the string specifies + /// a module name without a path and the file name extension is omitted, the function appends the default library extension + /// .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point + /// character (.) in the module name string. + /// + /// + /// If the function succeeds, the return value is a handle to the module. If the function fails, the return value is + /// NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); + + /// + /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process + /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can + /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. + /// + /// + /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. + /// + /// + /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for read access, + /// and if it is not accessible the function fails. + /// + /// + /// A pointer to a buffer that receives the contents from the address space of the specified process. + /// + /// + /// The number of bytes to be read from the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead + /// is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + IntPtr lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesRead); + + /// + /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process + /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can + /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. + /// + /// + /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. + /// + /// + /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for read access, + /// and if it is not accessible the function fails. + /// + /// + /// A pointer to a buffer that receives the contents from the address space of the specified process. + /// + /// + /// The number of bytes to be read from the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead + /// is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesRead); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode. + /// Controls whether the system will handle the specified types of serious errors or whether the process will handle + /// them. + /// + /// + /// The process error mode. This parameter can be one or more of the ErrorMode enum values. + /// + /// + /// The return value is the previous state of the error-mode bit flags. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern ErrorModes SetErrorMode(ErrorModes uMode); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter. + /// Enables an application to supersede the top-level exception handler of each thread of a process. After calling this + /// function, if an exception occurs in a process that is not being debugged, and the exception makes it to the unhandled + /// exception filter, that filter will call the exception filter function specified by the lpTopLevelExceptionFilter + /// parameter. + /// + /// + /// A pointer to a top-level exception filter function that will be called whenever the UnhandledExceptionFilter function + /// gets control, and the process is not being debugged. A value of NULL for this parameter specifies default handling + /// within UnhandledExceptionFilter. The filter function has syntax similar to that of UnhandledExceptionFilter: It + /// takes a single parameter of type LPEXCEPTION_POINTERS, has a WINAPI calling convention, and returns a value of type + /// LONG. The filter function should return one of the EXCEPTION_* enum values. + /// + /// + /// The SetUnhandledExceptionFilter function returns the address of the previous exception filter established with the + /// function. A NULL return value means that there is no current top-level exception handler. + /// + [DllImport("kernel32.dll")] + public static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc. + /// Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. + /// Memory allocated by this function is automatically initialized to zero. To allocate memory in the address space + /// of another process, use the VirtualAllocEx function. + /// + /// + /// The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded + /// down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, + /// the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity + /// on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to + /// allocate the region. If this address is within an enclave that you have not initialized by calling InitializeEnclave, + /// VirtualAlloc allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, + /// and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model. + /// If the address in within an enclave that you initialized, then the allocation operation fails with the + /// ERROR_INVALID_ADDRESS error. + /// + /// + /// The size of the region, in bytes. If the lpAddress parameter is NULL, this value is rounded up to the next page + /// boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress + /// to lpAddress+dwSize. This means that a 2-byte range straddling a page boundary causes both pages to be included + /// in the allocated region. + /// + /// + /// The type of memory allocation. This parameter must contain one of the MEM_* enum values. + /// + /// + /// The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify + /// any one of the memory protection constants. + /// + /// + /// If the function succeeds, the return value is the base address of the allocated region of pages. If the function + /// fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + UIntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + UIntPtr dwSize, + AllocationType flAllocationType, + Memory.MemoryProtection flProtect); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree. + /// Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling + /// process. + /// process. + /// + /// + /// A pointer to the base address of the region of pages to be freed. If the dwFreeType parameter is MEM_RELEASE, this + /// parameter must be the base address returned by the VirtualAlloc function when the region of pages is reserved. + /// + /// + /// The size of the region of memory to be freed, in bytes. If the dwFreeType parameter is MEM_RELEASE, this parameter + /// must be 0 (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAlloc. + /// If the dwFreeType parameter is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes + /// in the range from the lpAddress parameter to (lpAddress+dwSize). This means, for example, that a 2-byte region of + /// memory that straddles a page boundary causes both pages to be decommitted.If lpAddress is the base address returned + /// by VirtualAlloc and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAlloc. + /// After that, the entire region is in the reserved state. + /// + /// + /// The type of free operation. This parameter must be one of the MEM_* enum values. + /// + /// + /// If the function succeeds, the return value is a nonzero value. If the function fails, the return value is 0 (zero). + /// To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool VirtualFree( + IntPtr lpAddress, + UIntPtr dwSize, + AllocationType dwFreeType); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect. + /// Changes the protection on a region of committed pages in the virtual address space of the calling process. + /// + /// + /// The address of the starting page of the region of pages whose access protection attributes are to be changed. All + /// pages in the specified region must be within the same reserved region allocated when calling the VirtualAlloc or + /// VirtualAllocEx function using MEM_RESERVE. The pages cannot span adjacent reserved regions that were allocated by + /// separate calls to VirtualAlloc or VirtualAllocEx using MEM_RESERVE. + /// + /// + /// The size of the region whose access protection attributes are to be changed, in bytes. The region of affected pages + /// includes all pages containing one or more bytes in the range from the lpAddress parameter to (lpAddress+dwSize). + /// This means that a 2-byte range straddling a page boundary causes the protection attributes of both pages to be changed. + /// + /// + /// The memory protection option. This parameter can be one of the memory protection constants. For mapped views, this + /// value must be compatible with the access protection specified when the view was mapped (see MapViewOfFile, + /// MapViewOfFileEx, and MapViewOfFileExNuma). + /// + /// + /// A pointer to a variable that receives the previous access protection value of the first page in the specified region + /// of pages. If this parameter is NULL or does not point to a valid variable, the function fails. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. + /// To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool VirtualProtect( + IntPtr lpAddress, + UIntPtr dwSize, + MemoryProtection flNewProtection, + out MemoryProtection lpflOldProtect); + + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool VirtualProtect( + IntPtr lpAddress, + UIntPtr dwSize, + Memory.MemoryProtection flNewProtection, + out Memory.MemoryProtection lpflOldProtect); + + /// + /// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or + /// the operation fails. + /// + /// + /// A handle to the process memory to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access + /// to the process. + /// + /// + /// A pointer to the base address in the specified process to which data is written. Before data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for write access, + /// and if it is not accessible, the function fails. + /// + /// + /// A pointer to the buffer that contains data to be written in the address space of the specified process. + /// + /// + /// The number of bytes to be written to the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified process. This parameter + /// is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError.The function fails if the requested write operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool WriteProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesWritten); + + /// + /// Get a handle to the current process. + /// + /// Handle to the process. + [DllImport("kernel32.dll")] + public static extern IntPtr GetCurrentProcess(); + + /// + /// Get the current process ID. + /// + /// The process ID. + [DllImport("kernel32.dll")] + public static extern uint GetCurrentProcessId(); + + /// + /// Get the current thread ID. + /// + /// The thread ID. + [DllImport("kernel32.dll")] + public static extern uint GetCurrentThreadId(); +} + +/// +/// Native dbghelp functions. +/// +internal static partial class NativeFunctions +{ + /// + /// Type of minidump to create. + /// + public enum MiniDumpType : int + { + /// + /// Normal minidump. + /// + MiniDumpNormal, + + /// + /// Minidump with data segments. + /// + MiniDumpWithDataSegs, + + /// + /// Minidump with full memory. + /// + MiniDumpWithFullMemory, + } + + /// + /// Initializes the symbol handler for a process. + /// + /// + /// A handle that identifies the caller. + /// This value should be unique and nonzero, but need not be a process handle. + /// However, if you do use a process handle, be sure to use the correct handle. + /// If the application is a debugger, use the process handle for the process being debugged. + /// Do not use the handle returned by GetCurrentProcess when debugging another process, because calling functions like SymLoadModuleEx can have unexpected results. + /// This parameter cannot be NULL. + /// + /// The path, or series of paths separated by a semicolon (;), that is used to search for symbol files. + /// If this parameter is NULL, the library attempts to form a symbol path from the following sources: + /// - The current working directory of the application + /// - The _NT_SYMBOL_PATH environment variable + /// - The _NT_ALTERNATE_SYMBOL_PATH environment variable + /// Note that the search path can also be set using the SymSetSearchPath function. + /// + /// + /// If this value is , enumerates the loaded modules for the process and effectively calls the SymLoadModule64 function for each module. + /// + /// Whether or not the function succeeded. + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SymInitialize(IntPtr hProcess, string userSearchPath, bool fInvadeProcess); + + /// + /// Deallocates all resources associated with the process handle. + /// + /// A handle to the process that was originally passed to the function. + /// Whether or not the function succeeded. + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SymCleanup(IntPtr hProcess); + + /// + /// Creates a minidump. + /// + /// Target process handle. + /// Target process ID. + /// Output file handle. + /// Type of dump to take. + /// Exception information. + /// User information. + /// Callback. + /// Whether or not the minidump succeeded. + [DllImport("dbghelp.dll")] + public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, int dumpType, ref MinidumpExceptionInformation exceptionInfo, IntPtr userStreamParam, IntPtr callback); + + /// + /// Structure describing minidump exception information. + /// + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct MinidumpExceptionInformation + { + /// + /// ID of the thread that caused the exception. + /// + public uint ThreadId; + + /// + /// Pointer to the exception record. + /// + public IntPtr ExceptionPointers; + + /// + /// ClientPointers field. + /// + public int ClientPointers; + } +} + +/// +/// Native ws2_32 functions. +/// +internal static partial 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/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index 8eaf1a74c..35c006952 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -20,386 +20,385 @@ using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Ipc.Exceptions; using Dalamud.Plugin.Ipc.Internal; -namespace Dalamud.Plugin +namespace Dalamud.Plugin; + +/// +/// This class acts as an interface to various objects needed to interact with Dalamud and the game. +/// +public sealed class DalamudPluginInterface : IDisposable { + private readonly string pluginName; + private readonly PluginConfigurations configs; + /// - /// This class acts as an interface to various objects needed to interact with Dalamud and the game. + /// Initializes a new instance of the class. + /// Set up the interface and populate all fields needed. /// - public sealed class DalamudPluginInterface : IDisposable + /// The internal name of the plugin. + /// Location of the assembly. + /// The reason the plugin was loaded. + /// A value indicating whether this is a dev plugin. + internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev) { - private readonly string pluginName; - private readonly PluginConfigurations configs; + var configuration = Service.Get(); + var dataManager = Service.Get(); + var localization = Service.Get(); - /// - /// Initializes a new instance of the class. - /// Set up the interface and populate all fields needed. - /// - /// The internal name of the plugin. - /// Location of the assembly. - /// The reason the plugin was loaded. - /// A value indicating whether this is a dev plugin. - internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev) + this.UiBuilder = new UiBuilder(pluginName); + + this.pluginName = pluginName; + this.AssemblyLocation = assemblyLocation; + this.configs = Service.Get().PluginConfigs; + this.Reason = reason; + this.IsDev = isDev; + + this.LoadTime = DateTime.Now; + this.LoadTimeUTC = DateTime.UtcNow; + + this.GeneralChatType = configuration.GeneralChatType; + this.Sanitizer = new Sanitizer(dataManager.Language); + if (configuration.LanguageOverride != null) { - var configuration = Service.Get(); - var dataManager = Service.Get(); - var localization = Service.Get(); - - this.UiBuilder = new UiBuilder(pluginName); - - this.pluginName = pluginName; - this.AssemblyLocation = assemblyLocation; - this.configs = Service.Get().PluginConfigs; - this.Reason = reason; - this.IsDev = isDev; - - this.LoadTime = DateTime.Now; - this.LoadTimeUTC = DateTime.UtcNow; - - this.GeneralChatType = configuration.GeneralChatType; - this.Sanitizer = new Sanitizer(dataManager.Language); - if (configuration.LanguageOverride != null) - { - this.UiLanguage = configuration.LanguageOverride; - } + this.UiLanguage = configuration.LanguageOverride; + } + else + { + var currentUiLang = CultureInfo.CurrentUICulture; + if (Localization.ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) + this.UiLanguage = currentUiLang.TwoLetterISOLanguageName; else - { - var currentUiLang = CultureInfo.CurrentUICulture; - if (Localization.ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) - this.UiLanguage = currentUiLang.TwoLetterISOLanguageName; - else - this.UiLanguage = "en"; - } - - localization.LocalizationChanged += this.OnLocalizationChanged; - configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; + this.UiLanguage = "en"; } - /// - /// Delegate for localization change with two-letter iso lang code. - /// - /// The new language code. - public delegate void LanguageChangedDelegate(string langCode); + localization.LocalizationChanged += this.OnLocalizationChanged; + configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; + } - /// - /// Event that gets fired when loc is changed - /// - public event LanguageChangedDelegate LanguageChanged; + /// + /// Delegate for localization change with two-letter iso lang code. + /// + /// The new language code. + public delegate void LanguageChangedDelegate(string langCode); - /// - /// Gets the reason this plugin was loaded. - /// - public PluginLoadReason Reason { get; } + /// + /// Event that gets fired when loc is changed + /// + public event LanguageChangedDelegate LanguageChanged; - /// - /// Gets a value indicating whether this is a dev plugin. - /// - public bool IsDev { get; } + /// + /// Gets the reason this plugin was loaded. + /// + public PluginLoadReason Reason { get; } - /// - /// Gets the time that this plugin was loaded. - /// - public DateTime LoadTime { get; } + /// + /// Gets a value indicating whether this is a dev plugin. + /// + public bool IsDev { get; } - /// - /// Gets the UTC time that this plugin was loaded. - /// - public DateTime LoadTimeUTC { get; } + /// + /// Gets the time that this plugin was loaded. + /// + public DateTime LoadTime { get; } - /// - /// Gets the timespan delta from when this plugin was loaded. - /// - public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; + /// + /// Gets the UTC time that this plugin was loaded. + /// + public DateTime LoadTimeUTC { get; } - /// - /// Gets the directory Dalamud assets are stored in. - /// - public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; + /// + /// Gets the timespan delta from when this plugin was loaded. + /// + public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; - /// - /// Gets the location of your plugin assembly. - /// - public FileInfo AssemblyLocation { get; } + /// + /// Gets the directory Dalamud assets are stored in. + /// + public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; - /// - /// Gets the directory your plugin configurations are stored in. - /// - public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); + /// + /// Gets the location of your plugin assembly. + /// + public FileInfo AssemblyLocation { get; } - /// - /// Gets the config file of your plugin. - /// - public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName); + /// + /// Gets the directory your plugin configurations are stored in. + /// + public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); - /// - /// Gets the instance which allows you to draw UI into the game via ImGui draw calls. - /// - public UiBuilder UiBuilder { get; private set; } + /// + /// Gets the config file of your plugin. + /// + public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName); - /// - /// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds. - /// + /// + /// Gets the instance which allows you to draw UI into the game via ImGui draw calls. + /// + public UiBuilder UiBuilder { get; private set; } + + /// + /// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds. + /// #if DEBUG - public bool IsDebugging => true; + public bool IsDebugging => true; #else public bool IsDebugging => Service.Get().IsDevMenuOpen; #endif - /// - /// Gets the current UI language in two-letter iso format. - /// - public string UiLanguage { get; private set; } + /// + /// Gets the current UI language in two-letter iso format. + /// + public string UiLanguage { get; private set; } - /// - /// Gets serializer class with functions to remove special characters from strings. - /// - public ISanitizer Sanitizer { get; } + /// + /// Gets serializer class with functions to remove special characters from strings. + /// + public ISanitizer Sanitizer { get; } - /// - /// Gets the chat type used by default for plugin messages. - /// - public XivChatType GeneralChatType { get; private set; } + /// + /// Gets the chat type used by default for plugin messages. + /// + public XivChatType GeneralChatType { get; private set; } - /// - /// Gets a list of installed plugin names. - /// - public List PluginNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.Name).ToList(); + /// + /// Gets a list of installed plugin names. + /// + public List PluginNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.Name).ToList(); - /// - /// Gets a list of installed plugin internal names. - /// - public List PluginInternalNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.InternalName).ToList(); + /// + /// Gets a list of installed plugin internal names. + /// + public List PluginInternalNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.InternalName).ToList(); - #region IPC + #region IPC - /// - /// Gets an IPC provider. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC provider. - /// This is thrown when the requested types do not match the previously registered types are different. - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + /// Gets an IPC provider. + /// + /// The return type for funcs. Use object if this is unused. + /// The name of the IPC registration. + /// An IPC provider. + /// This is thrown when the requested types do not match the previously registered types are different. + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - /// Gets an IPC subscriber. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC subscriber. - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + /// Gets an IPC subscriber. + /// + /// The return type for funcs. Use object if this is unused. + /// The name of the IPC registration. + /// An IPC subscriber. + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - #endregion + #endregion - #region Configuration + #region Configuration - /// - /// Save a plugin configuration(inheriting IPluginConfiguration). - /// - /// The current configuration. - public void SavePluginConfig(IPluginConfiguration? currentConfig) + /// + /// Save a plugin configuration(inheriting IPluginConfiguration). + /// + /// The current configuration. + public void SavePluginConfig(IPluginConfiguration? currentConfig) + { + if (currentConfig == null) + return; + + this.configs.Save(currentConfig, this.pluginName); + } + + /// + /// Get a previously saved plugin configuration or null if none was saved before. + /// + /// A previously saved config or null if none was saved before. + public IPluginConfiguration? GetPluginConfig() + { + // This is done to support json deserialization of plugin configurations + // even after running an in-game update of plugins, where the assembly version + // changes. + // Eventually it might make sense to have a separate method on this class + // T GetPluginConfig() where T : IPluginConfiguration + // that can invoke LoadForType() directly instead of via reflection + // This is here for now to support the current plugin API + foreach (var type in Assembly.GetCallingAssembly().GetTypes()) { - if (currentConfig == null) - return; - - this.configs.Save(currentConfig, this.pluginName); - } - - /// - /// Get a previously saved plugin configuration or null if none was saved before. - /// - /// A previously saved config or null if none was saved before. - public IPluginConfiguration? GetPluginConfig() - { - // This is done to support json deserialization of plugin configurations - // even after running an in-game update of plugins, where the assembly version - // changes. - // Eventually it might make sense to have a separate method on this class - // T GetPluginConfig() where T : IPluginConfiguration - // that can invoke LoadForType() directly instead of via reflection - // This is here for now to support the current plugin API - foreach (var type in Assembly.GetCallingAssembly().GetTypes()) + if (type.IsAssignableTo(typeof(IPluginConfiguration))) { - if (type.IsAssignableTo(typeof(IPluginConfiguration))) - { - var mi = this.configs.GetType().GetMethod("LoadForType"); - var fn = mi.MakeGenericMethod(type); - return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName }); - } + var mi = this.configs.GetType().GetMethod("LoadForType"); + var fn = mi.MakeGenericMethod(type); + return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName }); } - - // this shouldn't be a thing, I think, but just in case - return this.configs.Load(this.pluginName); } - /// - /// Get the config directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName. - public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.pluginName); + // this shouldn't be a thing, I think, but just in case + return this.configs.Load(this.pluginName); + } - /// - /// Get the loc directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. - public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc")); + /// + /// Get the config directory. + /// + /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName. + public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.pluginName); - #endregion + /// + /// Get the loc directory. + /// + /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. + public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc")); - #region Chat Links + #endregion - /// - /// Register a chat link handler. - /// - /// The ID of the command. - /// The action to be executed. - /// Returns an SeString payload for the link. - public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) - { - return Service.Get().AddChatLinkHandler(this.pluginName, commandId, commandAction); - } + #region Chat Links - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - public void RemoveChatLinkHandler(uint commandId) - { - Service.Get().RemoveChatLinkHandler(this.pluginName, commandId); - } + /// + /// Register a chat link handler. + /// + /// The ID of the command. + /// The action to be executed. + /// Returns an SeString payload for the link. + public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) + { + return Service.Get().AddChatLinkHandler(this.pluginName, commandId, commandAction); + } - /// - /// Removes all chat link handlers registered by the plugin. - /// - public void RemoveChatLinkHandler() - { - Service.Get().RemoveChatLinkHandler(this.pluginName); - } - #endregion + /// + /// Remove a chat link handler. + /// + /// The ID of the command. + public void RemoveChatLinkHandler(uint commandId) + { + Service.Get().RemoveChatLinkHandler(this.pluginName, commandId); + } - #region Dependency Injection + /// + /// Removes all chat link handlers registered by the plugin. + /// + public void RemoveChatLinkHandler() + { + Service.Get().RemoveChatLinkHandler(this.pluginName); + } + #endregion - /// - /// Create a new object of the provided type using its default constructor, then inject objects and properties. - /// - /// Objects to inject additionally. - /// The type to create. - /// The created and initialized type. - public T? Create(params object[] scopedObjects) where T : class - { - var svcContainer = Service.Get(); + #region Dependency Injection - var realScopedObjects = new object[scopedObjects.Length + 1]; - realScopedObjects[0] = this; - Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); + /// + /// Create a new object of the provided type using its default constructor, then inject objects and properties. + /// + /// Objects to inject additionally. + /// The type to create. + /// The created and initialized type. + public T? Create(params object[] scopedObjects) where T : class + { + var svcContainer = Service.Get(); - return svcContainer.Create(typeof(T), realScopedObjects) as T; - } + var realScopedObjects = new object[scopedObjects.Length + 1]; + realScopedObjects[0] = this; + Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - /// - /// Inject services into properties on the provided object instance. - /// - /// The instance to inject services into. - /// Objects to inject additionally. - /// Whether or not the injection succeeded. - public bool Inject(object instance, params object[] scopedObjects) - { - var svcContainer = Service.Get(); + return svcContainer.Create(typeof(T), realScopedObjects) as T; + } - var realScopedObjects = new object[scopedObjects.Length + 1]; - realScopedObjects[0] = this; - Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); + /// + /// Inject services into properties on the provided object instance. + /// + /// The instance to inject services into. + /// Objects to inject additionally. + /// Whether or not the injection succeeded. + public bool Inject(object instance, params object[] scopedObjects) + { + var svcContainer = Service.Get(); - return svcContainer.InjectProperties(instance, realScopedObjects); - } + var realScopedObjects = new object[scopedObjects.Length + 1]; + realScopedObjects[0] = this; + Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - #endregion + return svcContainer.InjectProperties(instance, realScopedObjects); + } - /// - /// Unregister your plugin and dispose all references. - /// - public void Dispose() - { - this.UiBuilder.Dispose(); - Service.Get().RemoveChatLinkHandler(this.pluginName); - Service.Get().LocalizationChanged -= this.OnLocalizationChanged; - Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; - } + #endregion - private void OnLocalizationChanged(string langCode) - { - this.UiLanguage = langCode; - this.LanguageChanged?.Invoke(langCode); - } + /// + /// Unregister your plugin and dispose all references. + /// + public void Dispose() + { + this.UiBuilder.Dispose(); + Service.Get().RemoveChatLinkHandler(this.pluginName); + Service.Get().LocalizationChanged -= this.OnLocalizationChanged; + Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; + } - private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) - { - this.GeneralChatType = dalamudConfiguration.GeneralChatType; - } + private void OnLocalizationChanged(string langCode) + { + this.UiLanguage = langCode; + this.LanguageChanged?.Invoke(langCode); + } + + private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) + { + this.GeneralChatType = dalamudConfiguration.GeneralChatType; } } diff --git a/Dalamud/Plugin/IDalamudPlugin.cs b/Dalamud/Plugin/IDalamudPlugin.cs index 51d67328d..c752df3d6 100644 --- a/Dalamud/Plugin/IDalamudPlugin.cs +++ b/Dalamud/Plugin/IDalamudPlugin.cs @@ -1,15 +1,14 @@ using System; -namespace Dalamud.Plugin +namespace Dalamud.Plugin; + +/// +/// This interface represents a basic Dalamud plugin. All plugins have to implement this interface. +/// +public interface IDalamudPlugin : IDisposable { /// - /// This interface represents a basic Dalamud plugin. All plugins have to implement this interface. + /// Gets the name of the plugin. /// - public interface IDalamudPlugin : IDisposable - { - /// - /// Gets the name of the plugin. - /// - string Name { get; } - } + string Name { get; } } diff --git a/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs b/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs index e4418b6d3..851e5be33 100644 --- a/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs @@ -1,22 +1,21 @@ -namespace Dalamud.Plugin.Internal.Exceptions +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// This represents a banned plugin that attempted an operation. +/// +internal class BannedPluginException : PluginException { /// - /// This represents a banned plugin that attempted an operation. + /// Initializes a new instance of the class. /// - internal class BannedPluginException : PluginException + /// The message describing the invalid operation. + public BannedPluginException(string message) { - /// - /// Initializes a new instance of the class. - /// - /// The message describing the invalid operation. - public BannedPluginException(string message) - { - this.Message = message; - } - - /// - /// Gets the message describing the invalid operation. - /// - public override string Message { get; } + this.Message = message; } + + /// + /// Gets the message describing the invalid operation. + /// + public override string Message { get; } } diff --git a/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs b/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs index 093f97e69..79f855537 100644 --- a/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs @@ -1,26 +1,25 @@ -namespace Dalamud.Plugin.Internal.Exceptions +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// This exception that is thrown when a plugin is instructed to load while another plugin with the same +/// assembly name is already present and loaded. +/// +internal class DuplicatePluginException : PluginException { /// - /// This exception that is thrown when a plugin is instructed to load while another plugin with the same - /// assembly name is already present and loaded. + /// Initializes a new instance of the class. /// - internal class DuplicatePluginException : PluginException + /// Name of the conflicting assembly. + public DuplicatePluginException(string assemblyName) { - /// - /// Initializes a new instance of the class. - /// - /// Name of the conflicting assembly. - public DuplicatePluginException(string assemblyName) - { - this.AssemblyName = assemblyName; - } - - /// - /// Gets the name of the conflicting assembly. - /// - public string AssemblyName { get; init; } - - /// - public override string Message => $"A plugin with the same assembly name of {this.AssemblyName} is already loaded"; + this.AssemblyName = assemblyName; } + + /// + /// Gets the name of the conflicting assembly. + /// + public string AssemblyName { get; init; } + + /// + public override string Message => $"A plugin with the same assembly name of {this.AssemblyName} is already loaded"; } diff --git a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs index 6b5c8920a..cdb0bf304 100644 --- a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs @@ -1,25 +1,24 @@ using System; using System.IO; -namespace Dalamud.Plugin.Internal.Exceptions +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// This exception represents a file that does not implement IDalamudPlugin. +/// +internal class InvalidPluginException : PluginException { /// - /// This exception represents a file that does not implement IDalamudPlugin. + /// Initializes a new instance of the class. /// - internal class InvalidPluginException : PluginException + /// The invalid file. + public InvalidPluginException(FileInfo dllFile) { - /// - /// Initializes a new instance of the class. - /// - /// The invalid file. - public InvalidPluginException(FileInfo dllFile) - { - this.DllFile = dllFile; - } - - /// - /// Gets the invalid file. - /// - public FileInfo DllFile { get; init; } + this.DllFile = dllFile; } + + /// + /// Gets the invalid file. + /// + public FileInfo DllFile { get; init; } } diff --git a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs index a80d6d51d..978f12b69 100644 --- a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs @@ -1,24 +1,23 @@ using System; -namespace Dalamud.Plugin.Internal.Exceptions +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// This represents an invalid plugin operation. +/// +internal class InvalidPluginOperationException : PluginException { /// - /// This represents an invalid plugin operation. + /// Initializes a new instance of the class. /// - internal class InvalidPluginOperationException : PluginException + /// The message describing the invalid operation. + public InvalidPluginOperationException(string message) { - /// - /// Initializes a new instance of the class. - /// - /// The message describing the invalid operation. - public InvalidPluginOperationException(string message) - { - this.Message = message; - } - - /// - /// Gets the message describing the invalid operation. - /// - public override string Message { get; } + this.Message = message; } + + /// + /// Gets the message describing the invalid operation. + /// + public override string Message { get; } } diff --git a/Dalamud/Plugin/Internal/Exceptions/PluginException.cs b/Dalamud/Plugin/Internal/Exceptions/PluginException.cs index e4b17b686..292be5431 100644 --- a/Dalamud/Plugin/Internal/Exceptions/PluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/PluginException.cs @@ -1,11 +1,10 @@ using System; -namespace Dalamud.Plugin.Internal.Exceptions +namespace Dalamud.Plugin.Internal.Exceptions; + +/// +/// This represents the base Dalamud plugin exception. +/// +internal abstract class PluginException : Exception { - /// - /// This represents the base Dalamud plugin exception. - /// - internal abstract class PluginException : Exception - { - } } diff --git a/Dalamud/Plugin/Internal/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/LocalDevPlugin.cs index 3615c01a9..23860e70a 100644 --- a/Dalamud/Plugin/Internal/LocalDevPlugin.cs +++ b/Dalamud/Plugin/Internal/LocalDevPlugin.cs @@ -8,157 +8,156 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; -namespace Dalamud.Plugin.Internal +namespace Dalamud.Plugin.Internal; + +/// +/// This class represents a dev plugin and all facets of its lifecycle. +/// The DLL on disk, dependencies, loaded assembly, etc. +/// +internal class LocalDevPlugin : LocalPlugin, IDisposable { + private static readonly ModuleLog Log = new("PLUGIN"); + + // Ref to Dalamud.Configuration.DevPluginSettings + private readonly DevPluginSettings devSettings; + + private FileSystemWatcher fileWatcher; + private CancellationTokenSource fileWatcherTokenSource; + private int reloadCounter; + /// - /// This class represents a dev plugin and all facets of its lifecycle. - /// The DLL on disk, dependencies, loaded assembly, etc. + /// Initializes a new instance of the class. /// - internal class LocalDevPlugin : LocalPlugin, IDisposable + /// Path to the DLL file. + /// The plugin manifest. + public LocalDevPlugin(FileInfo dllFile, LocalPluginManifest? manifest) + : base(dllFile, manifest) { - private static readonly ModuleLog Log = new("PLUGIN"); + var configuration = Service.Get(); - // Ref to Dalamud.Configuration.DevPluginSettings - private readonly DevPluginSettings devSettings; - - private FileSystemWatcher fileWatcher; - private CancellationTokenSource fileWatcherTokenSource; - private int reloadCounter; - - /// - /// Initializes a new instance of the class. - /// - /// Path to the DLL file. - /// The plugin manifest. - public LocalDevPlugin(FileInfo dllFile, LocalPluginManifest? manifest) - : base(dllFile, manifest) + if (!configuration.DevPluginSettings.TryGetValue(dllFile.FullName, out this.devSettings)) { - var configuration = Service.Get(); + configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings(); + configuration.Save(); + } - if (!configuration.DevPluginSettings.TryGetValue(dllFile.FullName, out this.devSettings)) - { - configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings(); - configuration.Save(); - } + if (this.AutomaticReload) + { + this.EnableReloading(); + } + } - if (this.AutomaticReload) + /// + /// Gets or sets a value indicating whether this dev plugin should start on boot. + /// + public bool StartOnBoot + { + get => this.devSettings.StartOnBoot; + set => this.devSettings.StartOnBoot = value; + } + + /// + /// Gets or sets a value indicating whether this dev plugin should reload on change. + /// + public bool AutomaticReload + { + get => this.devSettings.AutomaticReloading; + set + { + this.devSettings.AutomaticReloading = value; + + if (this.devSettings.AutomaticReloading) { this.EnableReloading(); } - } - - /// - /// Gets or sets a value indicating whether this dev plugin should start on boot. - /// - public bool StartOnBoot - { - get => this.devSettings.StartOnBoot; - set => this.devSettings.StartOnBoot = value; - } - - /// - /// Gets or sets a value indicating whether this dev plugin should reload on change. - /// - public bool AutomaticReload - { - get => this.devSettings.AutomaticReloading; - set + else { - this.devSettings.AutomaticReloading = value; - - if (this.devSettings.AutomaticReloading) - { - this.EnableReloading(); - } - else - { - this.DisableReloading(); - } + this.DisableReloading(); } } - - /// - public new void Dispose() - { - if (this.fileWatcher != null) - { - this.fileWatcher.Changed -= this.OnFileChanged; - this.fileWatcherTokenSource.Cancel(); - this.fileWatcher.Dispose(); - } - - base.Dispose(); - } - - /// - /// Configure this plugin for automatic reloading and enable it. - /// - public void EnableReloading() - { - if (this.fileWatcher == null) - { - this.fileWatcherTokenSource = new(); - this.fileWatcher = new FileSystemWatcher(this.DllFile.DirectoryName); - this.fileWatcher.Changed += this.OnFileChanged; - this.fileWatcher.Filter = this.DllFile.Name; - this.fileWatcher.NotifyFilter = NotifyFilters.LastWrite; - this.fileWatcher.EnableRaisingEvents = true; - } - } - - /// - /// Disable automatic reloading for this plugin. - /// - public void DisableReloading() - { - if (this.fileWatcher != null) - { - this.fileWatcherTokenSource.Cancel(); - this.fileWatcher.Changed -= this.OnFileChanged; - this.fileWatcher.Dispose(); - this.fileWatcher = null; - } - } - - private void OnFileChanged(object sender, FileSystemEventArgs args) - { - var current = Interlocked.Increment(ref this.reloadCounter); - - Task.Delay(500).ContinueWith( - task => - { - if (this.fileWatcherTokenSource.IsCancellationRequested) - { - Log.Debug($"Skipping reload of {this.Name}, file watcher was cancelled."); - return; - } - - if (current != this.reloadCounter) - { - Log.Debug($"Skipping reload of {this.Name}, file has changed again."); - return; - } - - if (this.State != PluginState.Loaded) - { - Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}."); - return; - } - - var notificationManager = Service.Get(); - - try - { - this.Reload(); - notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success); - } - catch (Exception ex) - { - Log.Error(ex, "DevPlugin reload failed."); - notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error); - } - }, - this.fileWatcherTokenSource.Token); - } + } + + /// + public new void Dispose() + { + if (this.fileWatcher != null) + { + this.fileWatcher.Changed -= this.OnFileChanged; + this.fileWatcherTokenSource.Cancel(); + this.fileWatcher.Dispose(); + } + + base.Dispose(); + } + + /// + /// Configure this plugin for automatic reloading and enable it. + /// + public void EnableReloading() + { + if (this.fileWatcher == null) + { + this.fileWatcherTokenSource = new(); + this.fileWatcher = new FileSystemWatcher(this.DllFile.DirectoryName); + this.fileWatcher.Changed += this.OnFileChanged; + this.fileWatcher.Filter = this.DllFile.Name; + this.fileWatcher.NotifyFilter = NotifyFilters.LastWrite; + this.fileWatcher.EnableRaisingEvents = true; + } + } + + /// + /// Disable automatic reloading for this plugin. + /// + public void DisableReloading() + { + if (this.fileWatcher != null) + { + this.fileWatcherTokenSource.Cancel(); + this.fileWatcher.Changed -= this.OnFileChanged; + this.fileWatcher.Dispose(); + this.fileWatcher = null; + } + } + + private void OnFileChanged(object sender, FileSystemEventArgs args) + { + var current = Interlocked.Increment(ref this.reloadCounter); + + Task.Delay(500).ContinueWith( + task => + { + if (this.fileWatcherTokenSource.IsCancellationRequested) + { + Log.Debug($"Skipping reload of {this.Name}, file watcher was cancelled."); + return; + } + + if (current != this.reloadCounter) + { + Log.Debug($"Skipping reload of {this.Name}, file has changed again."); + return; + } + + if (this.State != PluginState.Loaded) + { + Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}."); + return; + } + + var notificationManager = Service.Get(); + + try + { + this.Reload(); + notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success); + } + catch (Exception ex) + { + Log.Error(ex, "DevPlugin reload failed."); + notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error); + } + }, + this.fileWatcherTokenSource.Token); } } diff --git a/Dalamud/Plugin/Internal/LocalPlugin.cs b/Dalamud/Plugin/Internal/LocalPlugin.cs index 4a17f8369..ba8cb4837 100644 --- a/Dalamud/Plugin/Internal/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/LocalPlugin.cs @@ -11,201 +11,364 @@ using Dalamud.Plugin.Internal.Exceptions; using Dalamud.Plugin.Internal.Loader; using Dalamud.Plugin.Internal.Types; -namespace Dalamud.Plugin.Internal +namespace Dalamud.Plugin.Internal; + +/// +/// This class represents a plugin and all facets of its lifecycle. +/// The DLL on disk, dependencies, loaded assembly, etc. +/// +internal class LocalPlugin : IDisposable { + private static readonly ModuleLog Log = new("LOCALPLUGIN"); + + private readonly FileInfo manifestFile; + private readonly FileInfo disabledFile; + private readonly FileInfo testingFile; + + private PluginLoader loader; + private Assembly pluginAssembly; + private Type? pluginType; + private IDalamudPlugin? instance; + /// - /// This class represents a plugin and all facets of its lifecycle. - /// The DLL on disk, dependencies, loaded assembly, etc. + /// Initializes a new instance of the class. /// - internal class LocalPlugin : IDisposable + /// Path to the DLL file. + /// The plugin manifest. + public LocalPlugin(FileInfo dllFile, LocalPluginManifest? manifest) { - private static readonly ModuleLog Log = new("LOCALPLUGIN"); - - private readonly FileInfo manifestFile; - private readonly FileInfo disabledFile; - private readonly FileInfo testingFile; - - private PluginLoader loader; - private Assembly pluginAssembly; - private Type? pluginType; - private IDalamudPlugin? instance; - - /// - /// Initializes a new instance of the class. - /// - /// Path to the DLL file. - /// The plugin manifest. - public LocalPlugin(FileInfo dllFile, LocalPluginManifest? manifest) + if (dllFile.Name == "FFXIVClientStructs.Generators.dll") { - if (dllFile.Name == "FFXIVClientStructs.Generators.dll") - { - // Could this be done another way? Sure. It is an extremely common source - // of errors in the log through, and should never be loaded as a plugin. - Log.Error($"Not a plugin: {dllFile.FullName}"); - throw new InvalidPluginException(dllFile); - } - - this.DllFile = dllFile; - this.State = PluginState.Unloaded; - - this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); - - try - { - this.pluginAssembly = this.loader.LoadDefaultAssembly(); - } - catch (Exception ex) - { - this.pluginAssembly = null; - this.pluginType = null; - this.loader.Dispose(); - - Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}"); - throw new InvalidPluginException(this.DllFile); - } - - try - { - this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); - } - catch (ReflectionTypeLoadException ex) - { - Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}"); - // Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error. - this.pluginType = ex.Types.FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); - } - - if (this.pluginType == default) - { - this.pluginAssembly = null; - this.pluginType = null; - this.loader.Dispose(); - - Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}"); - throw new InvalidPluginException(this.DllFile); - } - - var assemblyVersion = this.pluginAssembly.GetName().Version; - - // Although it is conditionally used here, we need to set the initial value regardless. - this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); - - // If the parameter manifest was null - if (manifest == null) - { - this.Manifest = new LocalPluginManifest() - { - Author = "developer", - Name = Path.GetFileNameWithoutExtension(this.DllFile.Name), - InternalName = Path.GetFileNameWithoutExtension(this.DllFile.Name), - AssemblyVersion = assemblyVersion, - Description = string.Empty, - ApplicableVersion = GameVersion.Any, - DalamudApiLevel = PluginManager.DalamudApiLevel, - IsHide = false, - }; - - // Save the manifest to disk so there won't be any problems later. - // We'll update the name property after it can be retrieved from the instance. - this.Manifest.Save(this.manifestFile); - } - else - { - this.Manifest = manifest; - } - - // This converts from the ".disabled" file feature to the manifest instead. - this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile); - if (this.disabledFile.Exists) - { - this.Manifest.Disabled = true; - this.disabledFile.Delete(); - } - - // This converts from the ".testing" file feature to the manifest instead. - this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile); - if (this.testingFile.Exists) - { - this.Manifest.Testing = true; - this.testingFile.Delete(); - } - - var pluginManager = Service.Get(); - this.IsBanned = pluginManager.IsManifestBanned(this.Manifest); - this.BanReason = pluginManager.GetBanReason(this.Manifest); - - this.SaveManifest(); + // Could this be done another way? Sure. It is an extremely common source + // of errors in the log through, and should never be loaded as a plugin. + Log.Error($"Not a plugin: {dllFile.FullName}"); + throw new InvalidPluginException(dllFile); } - /// - /// Gets the associated with this plugin. - /// - public DalamudPluginInterface DalamudInterface { get; private set; } + this.DllFile = dllFile; + this.State = PluginState.Unloaded; - /// - /// Gets the path to the plugin DLL. - /// - public FileInfo DllFile { get; } + this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); - /// - /// Gets the plugin manifest, if one exists. - /// - public LocalPluginManifest Manifest { get; private set; } - - /// - /// Gets or sets the current state of the plugin. - /// - public PluginState State { get; protected set; } = PluginState.Unloaded; - - /// - /// Gets the AssemblyName plugin, populated during . - /// - /// Plugin type. - public AssemblyName? AssemblyName { get; private set; } = null; - - /// - /// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest. - /// - public string Name => this.instance?.Name ?? this.Manifest.Name ?? this.DllFile.Name; - - /// - /// Gets an optional reason, if the plugin is banned. - /// - public string BanReason { get; } - - /// - /// Gets a value indicating whether the plugin is loaded and running. - /// - public bool IsLoaded => this.State == PluginState.Loaded; - - /// - /// Gets a value indicating whether the plugin is disabled. - /// - public bool IsDisabled => this.Manifest.Disabled; - - /// - /// Gets a value indicating whether this plugin's API level is out of date. - /// - public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; - - /// - /// Gets a value indicating whether the plugin is for testing use only. - /// - public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing; - - /// - /// Gets a value indicating whether this plugin has been banned. - /// - public bool IsBanned { get; } - - /// - /// Gets a value indicating whether this plugin is dev plugin. - /// - public bool IsDev => this is LocalDevPlugin; - - /// - public void Dispose() + try { + this.pluginAssembly = this.loader.LoadDefaultAssembly(); + } + catch (Exception ex) + { + this.pluginAssembly = null; + this.pluginType = null; + this.loader.Dispose(); + + Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}"); + throw new InvalidPluginException(this.DllFile); + } + + try + { + this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); + } + catch (ReflectionTypeLoadException ex) + { + Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}"); + // Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error. + this.pluginType = ex.Types.FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); + } + + if (this.pluginType == default) + { + this.pluginAssembly = null; + this.pluginType = null; + this.loader.Dispose(); + + Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}"); + throw new InvalidPluginException(this.DllFile); + } + + var assemblyVersion = this.pluginAssembly.GetName().Version; + + // Although it is conditionally used here, we need to set the initial value regardless. + this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); + + // If the parameter manifest was null + if (manifest == null) + { + this.Manifest = new LocalPluginManifest() + { + Author = "developer", + Name = Path.GetFileNameWithoutExtension(this.DllFile.Name), + InternalName = Path.GetFileNameWithoutExtension(this.DllFile.Name), + AssemblyVersion = assemblyVersion, + Description = string.Empty, + ApplicableVersion = GameVersion.Any, + DalamudApiLevel = PluginManager.DalamudApiLevel, + IsHide = false, + }; + + // Save the manifest to disk so there won't be any problems later. + // We'll update the name property after it can be retrieved from the instance. + this.Manifest.Save(this.manifestFile); + } + else + { + this.Manifest = manifest; + } + + // This converts from the ".disabled" file feature to the manifest instead. + this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile); + if (this.disabledFile.Exists) + { + this.Manifest.Disabled = true; + this.disabledFile.Delete(); + } + + // This converts from the ".testing" file feature to the manifest instead. + this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile); + if (this.testingFile.Exists) + { + this.Manifest.Testing = true; + this.testingFile.Delete(); + } + + var pluginManager = Service.Get(); + this.IsBanned = pluginManager.IsManifestBanned(this.Manifest); + this.BanReason = pluginManager.GetBanReason(this.Manifest); + + this.SaveManifest(); + } + + /// + /// Gets the associated with this plugin. + /// + public DalamudPluginInterface DalamudInterface { get; private set; } + + /// + /// Gets the path to the plugin DLL. + /// + public FileInfo DllFile { get; } + + /// + /// Gets the plugin manifest, if one exists. + /// + public LocalPluginManifest Manifest { get; private set; } + + /// + /// Gets or sets the current state of the plugin. + /// + public PluginState State { get; protected set; } = PluginState.Unloaded; + + /// + /// Gets the AssemblyName plugin, populated during . + /// + /// Plugin type. + public AssemblyName? AssemblyName { get; private set; } = null; + + /// + /// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest. + /// + public string Name => this.instance?.Name ?? this.Manifest.Name ?? this.DllFile.Name; + + /// + /// Gets an optional reason, if the plugin is banned. + /// + public string BanReason { get; } + + /// + /// Gets a value indicating whether the plugin is loaded and running. + /// + public bool IsLoaded => this.State == PluginState.Loaded; + + /// + /// Gets a value indicating whether the plugin is disabled. + /// + public bool IsDisabled => this.Manifest.Disabled; + + /// + /// Gets a value indicating whether this plugin's API level is out of date. + /// + public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; + + /// + /// Gets a value indicating whether the plugin is for testing use only. + /// + public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing; + + /// + /// Gets a value indicating whether this plugin has been banned. + /// + public bool IsBanned { get; } + + /// + /// Gets a value indicating whether this plugin is dev plugin. + /// + public bool IsDev => this is LocalDevPlugin; + + /// + public void Dispose() + { + this.instance?.Dispose(); + this.instance = null; + + this.DalamudInterface?.Dispose(); + this.DalamudInterface = null; + + this.pluginType = null; + this.pluginAssembly = null; + + this.loader?.Dispose(); + } + + /// + /// Load this plugin. + /// + /// The reason why this plugin is being loaded. + /// Load while reloading. + public void Load(PluginLoadReason reason, bool reloading = false) + { + var startInfo = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + // Allowed: Unloaded + switch (this.State) + { + case PluginState.InProgress: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working"); + case PluginState.Loaded: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded"); + case PluginState.LoadError: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first"); + case PluginState.UnloadError: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); + } + + if (pluginManager.IsManifestBanned(this.Manifest)) + throw new BannedPluginException($"Unable to load {this.Name}, banned"); + + if (this.Manifest.ApplicableVersion < startInfo.GameVersion) + throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version"); + + if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels) + throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level"); + + if (this.Manifest.Disabled) + throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled"); + + this.State = PluginState.InProgress; + Log.Information($"Loading {this.DllFile.Name}"); + + if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll"))) + { + Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author, this.Manifest.InternalName); + Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!"); + Log.Error("You may not be able to load your plugin. \"False\" needs to be set in your csproj."); + Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies."); + Log.Error("Do not merge FFXIVClientStructs.Generators.dll."); + Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information."); + } + + try + { + this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); + + if (reloading) + { + this.loader.Reload(); + + // Reload the manifest in-case there were changes here too. + if (this.IsDev) + { + var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); + if (manifestFile.Exists) + { + this.Manifest = LocalPluginManifest.Load(manifestFile); + } + } + } + + // Load the assembly + this.pluginAssembly ??= this.loader.LoadDefaultAssembly(); + + this.AssemblyName = this.pluginAssembly.GetName(); + + // Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor. + this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin))); + + // Check for any loaded plugins with the same assembly name + var assemblyName = this.pluginAssembly.GetName().Name; + foreach (var otherPlugin in pluginManager.InstalledPlugins) + { + // During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed + if (otherPlugin == this || otherPlugin.instance == null) + continue; + + var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name; + if (otherPluginAssemblyName == assemblyName) + { + this.State = PluginState.Unloaded; + Log.Debug($"Duplicate assembly: {this.Name}"); + + throw new DuplicatePluginException(assemblyName); + } + } + + // Update the location for the Location and CodeBase patches + PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new(this.DllFile); + + this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev); + + var ioc = Service.Get(); + this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin; + if (this.instance == null) + { + this.State = PluginState.LoadError; + this.DalamudInterface.Dispose(); + Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor"); + return; + } + + // In-case the manifest name was a placeholder. Can occur when no manifest was included. + if (this.instance.Name != this.Manifest.Name) + { + this.Manifest.Name = this.instance.Name; + this.Manifest.Save(this.manifestFile); + } + + this.State = PluginState.Loaded; + Log.Information($"Finished loading {this.DllFile.Name}"); + } + catch (Exception ex) + { + this.State = PluginState.LoadError; + Log.Error(ex, $"Error while loading {this.Name}"); + + throw; + } + } + + /// + /// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay + /// in the plugin list until it has been actually disposed. + /// + /// Unload while reloading. + public void Unload(bool reloading = false) + { + // Allowed: Loaded, LoadError(we are cleaning this up while we're at it) + switch (this.State) + { + case PluginState.InProgress: + throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working"); + case PluginState.Unloaded: + throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded"); + case PluginState.UnloadError: + throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud"); + } + + try + { + this.State = PluginState.InProgress; + Log.Information($"Unloading {this.DllFile.Name}"); + this.instance?.Dispose(); this.instance = null; @@ -215,247 +378,83 @@ namespace Dalamud.Plugin.Internal this.pluginType = null; this.pluginAssembly = null; - this.loader?.Dispose(); - } + if (!reloading) + { + this.loader?.Dispose(); + this.loader = null; + } - /// - /// Load this plugin. - /// - /// The reason why this plugin is being loaded. - /// Load while reloading. - public void Load(PluginLoadReason reason, bool reloading = false) + this.State = PluginState.Unloaded; + Log.Information($"Finished unloading {this.DllFile.Name}"); + } + catch (Exception ex) { - var startInfo = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); + this.State = PluginState.UnloadError; + Log.Error(ex, $"Error while unloading {this.Name}"); - // Allowed: Unloaded - switch (this.State) - { - case PluginState.InProgress: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working"); - case PluginState.Loaded: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded"); - case PluginState.LoadError: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first"); - case PluginState.UnloadError: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); - } - - if (pluginManager.IsManifestBanned(this.Manifest)) - throw new BannedPluginException($"Unable to load {this.Name}, banned"); - - if (this.Manifest.ApplicableVersion < startInfo.GameVersion) - throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version"); - - if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels) - throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level"); - - if (this.Manifest.Disabled) - throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled"); - - this.State = PluginState.InProgress; - Log.Information($"Loading {this.DllFile.Name}"); - - if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll"))) - { - Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author, this.Manifest.InternalName); - Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!"); - Log.Error("You may not be able to load your plugin. \"False\" needs to be set in your csproj."); - Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies."); - Log.Error("Do not merge FFXIVClientStructs.Generators.dll."); - Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information."); - } - - try - { - this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); - - if (reloading) - { - this.loader.Reload(); - - // Reload the manifest in-case there were changes here too. - if (this.IsDev) - { - var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); - if (manifestFile.Exists) - { - this.Manifest = LocalPluginManifest.Load(manifestFile); - } - } - } - - // Load the assembly - this.pluginAssembly ??= this.loader.LoadDefaultAssembly(); - - this.AssemblyName = this.pluginAssembly.GetName(); - - // Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor. - this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin))); - - // Check for any loaded plugins with the same assembly name - var assemblyName = this.pluginAssembly.GetName().Name; - foreach (var otherPlugin in pluginManager.InstalledPlugins) - { - // During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed - if (otherPlugin == this || otherPlugin.instance == null) - continue; - - var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name; - if (otherPluginAssemblyName == assemblyName) - { - this.State = PluginState.Unloaded; - Log.Debug($"Duplicate assembly: {this.Name}"); - - throw new DuplicatePluginException(assemblyName); - } - } - - // Update the location for the Location and CodeBase patches - PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new(this.DllFile); - - this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev); - - var ioc = Service.Get(); - this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin; - if (this.instance == null) - { - this.State = PluginState.LoadError; - this.DalamudInterface.Dispose(); - Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor"); - return; - } - - // In-case the manifest name was a placeholder. Can occur when no manifest was included. - if (this.instance.Name != this.Manifest.Name) - { - this.Manifest.Name = this.instance.Name; - this.Manifest.Save(this.manifestFile); - } - - this.State = PluginState.Loaded; - Log.Information($"Finished loading {this.DllFile.Name}"); - } - catch (Exception ex) - { - this.State = PluginState.LoadError; - Log.Error(ex, $"Error while loading {this.Name}"); - - throw; - } + throw; } - - /// - /// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay - /// in the plugin list until it has been actually disposed. - /// - /// Unload while reloading. - public void Unload(bool reloading = false) - { - // Allowed: Loaded, LoadError(we are cleaning this up while we're at it) - switch (this.State) - { - case PluginState.InProgress: - throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working"); - case PluginState.Unloaded: - throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded"); - case PluginState.UnloadError: - throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud"); - } - - try - { - this.State = PluginState.InProgress; - Log.Information($"Unloading {this.DllFile.Name}"); - - this.instance?.Dispose(); - this.instance = null; - - this.DalamudInterface?.Dispose(); - this.DalamudInterface = null; - - this.pluginType = null; - this.pluginAssembly = null; - - if (!reloading) - { - this.loader?.Dispose(); - this.loader = null; - } - - this.State = PluginState.Unloaded; - Log.Information($"Finished unloading {this.DllFile.Name}"); - } - catch (Exception ex) - { - this.State = PluginState.UnloadError; - Log.Error(ex, $"Error while unloading {this.Name}"); - - throw; - } - } - - /// - /// Reload this plugin. - /// - public void Reload() - { - this.Unload(true); - this.Load(PluginLoadReason.Reload, true); - } - - /// - /// Revert a disable. Must be unloaded first, does not load. - /// - public void Enable() - { - // Allowed: Unloaded, UnloadError - switch (this.State) - { - case PluginState.InProgress: - case PluginState.Loaded: - case PluginState.LoadError: - throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded"); - } - - if (!this.Manifest.Disabled) - throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled"); - - this.Manifest.Disabled = false; - this.SaveManifest(); - } - - /// - /// Disable this plugin, must be unloaded first. - /// - public void Disable() - { - // Allowed: Unloaded, UnloadError - switch (this.State) - { - case PluginState.InProgress: - case PluginState.Loaded: - case PluginState.LoadError: - throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded"); - } - - if (this.Manifest.Disabled) - throw new InvalidPluginOperationException($"Unable to disable {this.Name}, already disabled"); - - this.Manifest.Disabled = true; - this.SaveManifest(); - } - - private void SetupLoaderConfig(LoaderConfig config) - { - config.IsUnloadable = true; - config.LoadInMemory = true; - config.PreferSharedTypes = false; - config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName()); - config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName()); - } - - private void SaveManifest() => this.Manifest.Save(this.manifestFile); } + + /// + /// Reload this plugin. + /// + public void Reload() + { + this.Unload(true); + this.Load(PluginLoadReason.Reload, true); + } + + /// + /// Revert a disable. Must be unloaded first, does not load. + /// + public void Enable() + { + // Allowed: Unloaded, UnloadError + switch (this.State) + { + case PluginState.InProgress: + case PluginState.Loaded: + case PluginState.LoadError: + throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded"); + } + + if (!this.Manifest.Disabled) + throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled"); + + this.Manifest.Disabled = false; + this.SaveManifest(); + } + + /// + /// Disable this plugin, must be unloaded first. + /// + public void Disable() + { + // Allowed: Unloaded, UnloadError + switch (this.State) + { + case PluginState.InProgress: + case PluginState.Loaded: + case PluginState.LoadError: + throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded"); + } + + if (this.Manifest.Disabled) + throw new InvalidPluginOperationException($"Unable to disable {this.Name}, already disabled"); + + this.Manifest.Disabled = true; + this.SaveManifest(); + } + + private void SetupLoaderConfig(LoaderConfig config) + { + config.IsUnloadable = true; + config.LoadInMemory = true; + config.PreferSharedTypes = false; + config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName()); + config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName()); + } + + private void SaveManifest() => this.Manifest.Save(this.manifestFile); } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 4eb06abaf..22e951372 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -21,1182 +21,1181 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal +namespace Dalamud.Plugin.Internal; + +/// +/// Class responsible for loading and unloading plugins. +/// +internal partial class PluginManager : IDisposable { /// - /// Class responsible for loading and unloading plugins. + /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. /// - internal partial class PluginManager : IDisposable + public const int DalamudApiLevel = 4; + + private static readonly ModuleLog Log = new("PLUGINM"); + + private readonly DirectoryInfo pluginDirectory; + private readonly DirectoryInfo devPluginDirectory; + private readonly BannedPlugin[] bannedPlugins; + + /// + /// Initializes a new instance of the class. + /// + public PluginManager() { - /// - /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. - /// - public const int DalamudApiLevel = 4; + var startInfo = Service.Get(); + var configuration = Service.Get(); - private static readonly ModuleLog Log = new("PLUGINM"); + this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory); + this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory); - private readonly DirectoryInfo pluginDirectory; - private readonly DirectoryInfo devPluginDirectory; - private readonly BannedPlugin[] bannedPlugins; + if (!this.pluginDirectory.Exists) + this.pluginDirectory.Create(); - /// - /// Initializes a new instance of the class. - /// - public PluginManager() + if (!this.devPluginDirectory.Exists) + this.devPluginDirectory.Create(); + + this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode; + if (this.SafeMode) { - var startInfo = Service.Get(); - var configuration = Service.Get(); - - this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory); - this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory); - - if (!this.pluginDirectory.Exists) - this.pluginDirectory.Create(); - - if (!this.devPluginDirectory.Exists) - this.devPluginDirectory.Create(); - - this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode; - if (this.SafeMode) - { - configuration.PluginSafeMode = false; - configuration.Save(); - } - - this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs")); - - var bannedPluginsJson = File.ReadAllText(Path.Combine(startInfo.AssetDirectory, "UIRes", "bannedplugin.json")); - this.bannedPlugins = JsonConvert.DeserializeObject(bannedPluginsJson) ?? Array.Empty(); - - this.ApplyPatches(); + configuration.PluginSafeMode = false; + configuration.Save(); } - /// - /// An event that fires when the installed plugins have changed. - /// - public event Action OnInstalledPluginsChanged; + this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs")); - /// - /// An event that fires when the available plugins have changed. - /// - public event Action OnAvailablePluginsChanged; + var bannedPluginsJson = File.ReadAllText(Path.Combine(startInfo.AssetDirectory, "UIRes", "bannedplugin.json")); + this.bannedPlugins = JsonConvert.DeserializeObject(bannedPluginsJson) ?? Array.Empty(); - /// - /// Gets a list of all loaded plugins. - /// - public ImmutableList InstalledPlugins { get; private set; } = ImmutableList.Create(); + this.ApplyPatches(); + } - /// - /// Gets a list of all available plugins. - /// - public ImmutableList AvailablePlugins { get; private set; } = ImmutableList.Create(); + /// + /// An event that fires when the installed plugins have changed. + /// + public event Action OnInstalledPluginsChanged; - /// - /// Gets a list of all plugins with an available update. - /// - public ImmutableList UpdatablePlugins { get; private set; } = ImmutableList.Create(); + /// + /// An event that fires when the available plugins have changed. + /// + public event Action OnAvailablePluginsChanged; - /// - /// Gets a list of all plugin repositories. The main repo should always be first. - /// - public List Repos { get; private set; } = new(); + /// + /// Gets a list of all loaded plugins. + /// + public ImmutableList InstalledPlugins { get; private set; } = ImmutableList.Create(); - /// - /// Gets a value indicating whether plugins are not still loading from boot. - /// - public bool PluginsReady { get; private set; } = false; + /// + /// Gets a list of all available plugins. + /// + public ImmutableList AvailablePlugins { get; private set; } = ImmutableList.Create(); - /// - /// Gets a value indicating whether all added repos are not in progress. - /// - public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress); + /// + /// Gets a list of all plugins with an available update. + /// + public ImmutableList UpdatablePlugins { get; private set; } = ImmutableList.Create(); - /// - /// Gets a value indicating whether the plugin manager started in safe mode. - /// - public bool SafeMode { get; init; } + /// + /// Gets a list of all plugin repositories. The main repo should always be first. + /// + public List Repos { get; private set; } = new(); - /// - /// Gets the object used when initializing plugins. - /// - public PluginConfigurations PluginConfigs { get; } + /// + /// Gets a value indicating whether plugins are not still loading from boot. + /// + public bool PluginsReady { get; private set; } = false; - /// - public void Dispose() + /// + /// Gets a value indicating whether all added repos are not in progress. + /// + public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress); + + /// + /// Gets a value indicating whether the plugin manager started in safe mode. + /// + public bool SafeMode { get; init; } + + /// + /// Gets the object used when initializing plugins. + /// + public PluginConfigurations PluginConfigs { get; } + + /// + public void Dispose() + { + foreach (var plugin in this.InstalledPlugins) { - foreach (var plugin in this.InstalledPlugins) + try + { + plugin.Dispose(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error disposing {plugin.Name}"); + } + } + + this.assemblyLocationMonoHook?.Dispose(); + this.assemblyCodeBaseMonoHook?.Dispose(); + } + + /// + /// Set the list of repositories to use and downloads their contents. + /// Should be called when the Settings window has been updated or at instantiation. + /// + /// Whether the available plugins changed should be evented after. + /// A representing the asynchronous operation. + public async Task SetPluginReposFromConfigAsync(bool notify) + { + var configuration = Service.Get(); + + var repos = new List() { PluginRepository.MainRepo }; + repos.AddRange(configuration.ThirdRepoList + .Where(repo => repo.IsEnabled) + .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); + + this.Repos = repos; + await this.ReloadPluginMastersAsync(notify); + } + + /// + /// Load all plugins, sorted by priority. Any plugins with no explicit definition file or a negative priority + /// are loaded asynchronously. + /// + /// + /// This should only be called during Dalamud startup. + /// + public void LoadAllPlugins() + { + if (this.SafeMode) + { + Log.Information("PluginSafeMode was enabled, not loading any plugins."); + return; + } + + var configuration = Service.Get(); + + var pluginDefs = new List(); + var devPluginDefs = new List(); + + if (!this.pluginDirectory.Exists) + this.pluginDirectory.Create(); + + if (!this.devPluginDirectory.Exists) + this.devPluginDirectory.Create(); + + // Add installed plugins. These are expected to be in a specific format so we can look for exactly that. + foreach (var pluginDir in this.pluginDirectory.GetDirectories()) + { + foreach (var versionDir in pluginDir.GetDirectories()) + { + var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + + if (!manifestFile.Exists) + continue; + + var manifest = LocalPluginManifest.Load(manifestFile); + + pluginDefs.Add(new(dllFile, manifest, false)); + } + } + + // devPlugins are more freeform. Look for any dll and hope to get lucky. + var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); + + foreach (var setting in configuration.DevPluginLoadLocations) + { + if (!setting.IsEnabled) + continue; + + if (Directory.Exists(setting.Path)) + { + devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); + } + else if (File.Exists(setting.Path)) + { + devDllFiles.Add(new FileInfo(setting.Path)); + } + } + + foreach (var dllFile in devDllFiles) + { + // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; + devPluginDefs.Add(new(dllFile, manifest, true)); + } + + // Sort for load order - unloaded definitions have default priority of 0 + pluginDefs.Sort(PluginDef.Sorter); + devPluginDefs.Sort(PluginDef.Sorter); + + // Dev plugins should load first. + pluginDefs.InsertRange(0, devPluginDefs); + + void LoadPlugins(IEnumerable pluginDefs) + { + foreach (var pluginDef in pluginDefs) { try { - plugin.Dispose(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error disposing {plugin.Name}"); - } - } - - this.assemblyLocationMonoHook?.Dispose(); - this.assemblyCodeBaseMonoHook?.Dispose(); - } - - /// - /// Set the list of repositories to use and downloads their contents. - /// Should be called when the Settings window has been updated or at instantiation. - /// - /// Whether the available plugins changed should be evented after. - /// A representing the asynchronous operation. - public async Task SetPluginReposFromConfigAsync(bool notify) - { - var configuration = Service.Get(); - - var repos = new List() { PluginRepository.MainRepo }; - repos.AddRange(configuration.ThirdRepoList - .Where(repo => repo.IsEnabled) - .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); - - this.Repos = repos; - await this.ReloadPluginMastersAsync(notify); - } - - /// - /// Load all plugins, sorted by priority. Any plugins with no explicit definition file or a negative priority - /// are loaded asynchronously. - /// - /// - /// This should only be called during Dalamud startup. - /// - public void LoadAllPlugins() - { - if (this.SafeMode) - { - Log.Information("PluginSafeMode was enabled, not loading any plugins."); - return; - } - - var configuration = Service.Get(); - - var pluginDefs = new List(); - var devPluginDefs = new List(); - - if (!this.pluginDirectory.Exists) - this.pluginDirectory.Create(); - - if (!this.devPluginDirectory.Exists) - this.devPluginDirectory.Create(); - - // Add installed plugins. These are expected to be in a specific format so we can look for exactly that. - foreach (var pluginDir in this.pluginDirectory.GetDirectories()) - { - foreach (var versionDir in pluginDir.GetDirectories()) - { - var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - - if (!manifestFile.Exists) - continue; - - var manifest = LocalPluginManifest.Load(manifestFile); - - pluginDefs.Add(new(dllFile, manifest, false)); - } - } - - // devPlugins are more freeform. Look for any dll and hope to get lucky. - var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); - - foreach (var setting in configuration.DevPluginLoadLocations) - { - if (!setting.IsEnabled) - continue; - - if (Directory.Exists(setting.Path)) - { - devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); - } - else if (File.Exists(setting.Path)) - { - devDllFiles.Add(new FileInfo(setting.Path)); - } - } - - foreach (var dllFile in devDllFiles) - { - // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; - devPluginDefs.Add(new(dllFile, manifest, true)); - } - - // Sort for load order - unloaded definitions have default priority of 0 - pluginDefs.Sort(PluginDef.Sorter); - devPluginDefs.Sort(PluginDef.Sorter); - - // Dev plugins should load first. - pluginDefs.InsertRange(0, devPluginDefs); - - void LoadPlugins(IEnumerable pluginDefs) - { - foreach (var pluginDef in pluginDefs) - { - try - { - this.LoadPlugin(pluginDef.DllFile, pluginDef.Manifest, PluginLoadReason.Boot, pluginDef.IsDev, isBoot: true); - } - catch (InvalidPluginException) - { - // Not a plugin - } - catch (Exception) - { - Log.Error("During boot plugin load, an unexpected error occurred"); - } - } - } - - // Load sync plugins - var syncPlugins = pluginDefs.Where(def => def.Manifest?.LoadPriority > 0); - LoadPlugins(syncPlugins); - - var asyncPlugins = pluginDefs.Where(def => def.Manifest == null || def.Manifest.LoadPriority <= 0); - Task.Run(() => LoadPlugins(asyncPlugins)) - .ContinueWith(task => - { - this.PluginsReady = true; - this.NotifyInstalledPluginsChanged(); - }); - } - - /// - /// Reload all loaded plugins. - /// - public void ReloadAllPlugins() - { - var aggregate = new List(); - - foreach (var plugin in this.InstalledPlugins) - { - if (plugin.IsLoaded) - { - try - { - plugin.Reload(); - } - catch (Exception ex) - { - Log.Error(ex, "Error during reload all"); - - aggregate.Add(ex); - } - } - } - - if (aggregate.Any()) - { - throw new AggregateException(aggregate); - } - } - - /// - /// Reload the PluginMaster for each repo, filter, and event that the list has updated. - /// - /// Whether to notify that available plugins have changed afterwards. - /// A representing the asynchronous operation. - public async Task ReloadPluginMastersAsync(bool notify = true) - { - await Task.WhenAll(this.Repos.Select(repo => repo.ReloadPluginMasterAsync())); - - this.RefilterPluginMasters(notify); - } - - /// - /// Apply visibility and eligibility filters to the available plugins, then event that the list has updated. - /// - /// Whether to notify that available plugins have changed afterwards. - public void RefilterPluginMasters(bool notify = true) - { - this.AvailablePlugins = this.Repos - .SelectMany(repo => repo.PluginMaster) - .Where(this.IsManifestEligible) - .Where(this.IsManifestVisible) - .ToImmutableList(); - - if (notify) - { - this.NotifyAvailablePluginsChanged(); - } - } - - /// - /// Scan the devPlugins folder for new DLL files that are not already loaded into the manager. They are not loaded, - /// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works - /// a little differently. - /// - public void ScanDevPlugins() - { - if (this.SafeMode) - { - Log.Information("PluginSafeMode was enabled, not scanning any dev plugins."); - return; - } - - var configuration = Service.Get(); - - if (!this.devPluginDirectory.Exists) - this.devPluginDirectory.Create(); - - // devPlugins are more freeform. Look for any dll and hope to get lucky. - var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); - - foreach (var setting in configuration.DevPluginLoadLocations) - { - if (!setting.IsEnabled) - continue; - - if (Directory.Exists(setting.Path)) - { - devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); - } - else if (File.Exists(setting.Path)) - { - devDllFiles.Add(new FileInfo(setting.Path)); - } - } - - var listChanged = false; - - foreach (var dllFile in devDllFiles) - { - // This file is already known to us - if (this.InstalledPlugins.Any(lp => lp.DllFile.FullName == dllFile.FullName)) - continue; - - // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; - - try - { - // Add them to the list and let the user decide, nothing is auto-loaded. - this.LoadPlugin(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true); - listChanged = true; + this.LoadPlugin(pluginDef.DllFile, pluginDef.Manifest, PluginLoadReason.Boot, pluginDef.IsDev, isBoot: true); } catch (InvalidPluginException) { // Not a plugin } - catch (Exception ex) + catch (Exception) { - Log.Error(ex, $"During devPlugin scan, an unexpected error occurred"); + Log.Error("During boot plugin load, an unexpected error occurred"); } } + } - if (listChanged) + // Load sync plugins + var syncPlugins = pluginDefs.Where(def => def.Manifest?.LoadPriority > 0); + LoadPlugins(syncPlugins); + + var asyncPlugins = pluginDefs.Where(def => def.Manifest == null || def.Manifest.LoadPriority <= 0); + Task.Run(() => LoadPlugins(asyncPlugins)) + .ContinueWith(task => + { + this.PluginsReady = true; this.NotifyInstalledPluginsChanged(); - } + }); + } - /// - /// Install a plugin from a repository and load it. - /// - /// The plugin definition. - /// If the testing version should be used. - /// The reason this plugin was loaded. - /// A representing the asynchronous operation. - public async Task InstallPluginAsync(RemotePluginManifest repoManifest, bool useTesting, PluginLoadReason reason) + /// + /// Reload all loaded plugins. + /// + public void ReloadAllPlugins() + { + var aggregate = new List(); + + foreach (var plugin in this.InstalledPlugins) { - Log.Debug($"Installing plugin {repoManifest.Name} (testing={useTesting})"); - - var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall; - var version = useTesting ? repoManifest.TestingAssemblyVersion : repoManifest.AssemblyVersion; - - var response = await Util.HttpClient.GetAsync(downloadUrl); - response.EnsureSuccessStatusCode(); - - var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version.ToString())); - - try - { - if (outputDir.Exists) - outputDir.Delete(true); - - outputDir.Create(); - } - catch - { - // ignored, since the plugin may be loaded already - } - - Log.Debug($"Extracting to {outputDir}"); - // This throws an error, even with overwrite=false - // ZipFile.ExtractToDirectory(tempZip.FullName, outputDir.FullName, false); - using (var archive = new ZipArchive(await response.Content.ReadAsStreamAsync())) - { - foreach (var zipFile in archive.Entries) - { - var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName))); - - if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase)) - { - throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); - } - - if (outputFile.Directory == null) - { - throw new IOException("Output directory invalid."); - } - - if (zipFile.Name.IsNullOrEmpty()) - { - // Assuming Empty for Directory - Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}"); - Directory.CreateDirectory(outputFile.Directory.FullName); - continue; - } - - // Ensure directory is created - Directory.CreateDirectory(outputFile.Directory.FullName); - - try - { - zipFile.ExtractToFile(outputFile.FullName, true); - } - catch (Exception ex) - { - if (outputFile.Extension.EndsWith("dll")) - { - throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}"); - } - - Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}"); - } - } - } - - var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - - // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. - File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); - - // Reload as a local manifest, add some attributes, and save again. - var manifest = LocalPluginManifest.Load(manifestFile); - - if (useTesting) - { - manifest.Testing = true; - } - - if (repoManifest.SourceRepo.IsThirdParty) - { - // Only document the url if it came from a third party repo. - manifest.InstalledFromUrl = repoManifest.SourceRepo.PluginMasterUrl; - } - - manifest.Save(manifestFile); - - Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})"); - - var plugin = this.LoadPlugin(dllFile, manifest, reason); - - this.NotifyInstalledPluginsChanged(); - return plugin; - } - - /// - /// Load a plugin. - /// - /// The associated with the main assembly of this plugin. - /// The already loaded definition, if available. - /// The reason this plugin was loaded. - /// If this plugin should support development features. - /// If this plugin is being loaded at boot. - /// Don't load the plugin, just don't do it. - /// The loaded plugin. - public LocalPlugin LoadPlugin(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) - { - var name = manifest?.Name ?? dllFile.Name; - var loadPlugin = !doNotLoad; - - LocalPlugin plugin; - - if (isDev) - { - Log.Information($"Loading dev plugin {name}"); - var devPlugin = new LocalDevPlugin(dllFile, manifest); - loadPlugin &= !isBoot || devPlugin.StartOnBoot; - - // If we're not loading it, make sure it's disabled - if (!loadPlugin && !devPlugin.IsDisabled) - devPlugin.Disable(); - - plugin = devPlugin; - } - else - { - Log.Information($"Loading plugin {name}"); - plugin = new LocalPlugin(dllFile, manifest); - } - - if (loadPlugin) + if (plugin.IsLoaded) { try { - if (plugin.IsDisabled) - plugin.Enable(); - - plugin.Load(reason); + plugin.Reload(); } - catch (InvalidPluginException) + catch (Exception ex) + { + Log.Error(ex, "Error during reload all"); + + aggregate.Add(ex); + } + } + } + + if (aggregate.Any()) + { + throw new AggregateException(aggregate); + } + } + + /// + /// Reload the PluginMaster for each repo, filter, and event that the list has updated. + /// + /// Whether to notify that available plugins have changed afterwards. + /// A representing the asynchronous operation. + public async Task ReloadPluginMastersAsync(bool notify = true) + { + await Task.WhenAll(this.Repos.Select(repo => repo.ReloadPluginMasterAsync())); + + this.RefilterPluginMasters(notify); + } + + /// + /// Apply visibility and eligibility filters to the available plugins, then event that the list has updated. + /// + /// Whether to notify that available plugins have changed afterwards. + public void RefilterPluginMasters(bool notify = true) + { + this.AvailablePlugins = this.Repos + .SelectMany(repo => repo.PluginMaster) + .Where(this.IsManifestEligible) + .Where(this.IsManifestVisible) + .ToImmutableList(); + + if (notify) + { + this.NotifyAvailablePluginsChanged(); + } + } + + /// + /// Scan the devPlugins folder for new DLL files that are not already loaded into the manager. They are not loaded, + /// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works + /// a little differently. + /// + public void ScanDevPlugins() + { + if (this.SafeMode) + { + Log.Information("PluginSafeMode was enabled, not scanning any dev plugins."); + return; + } + + var configuration = Service.Get(); + + if (!this.devPluginDirectory.Exists) + this.devPluginDirectory.Create(); + + // devPlugins are more freeform. Look for any dll and hope to get lucky. + var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); + + foreach (var setting in configuration.DevPluginLoadLocations) + { + if (!setting.IsEnabled) + continue; + + if (Directory.Exists(setting.Path)) + { + devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); + } + else if (File.Exists(setting.Path)) + { + devDllFiles.Add(new FileInfo(setting.Path)); + } + } + + var listChanged = false; + + foreach (var dllFile in devDllFiles) + { + // This file is already known to us + if (this.InstalledPlugins.Any(lp => lp.DllFile.FullName == dllFile.FullName)) + continue; + + // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; + + try + { + // Add them to the list and let the user decide, nothing is auto-loaded. + this.LoadPlugin(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true); + listChanged = true; + } + catch (InvalidPluginException) + { + // Not a plugin + } + catch (Exception ex) + { + Log.Error(ex, $"During devPlugin scan, an unexpected error occurred"); + } + } + + if (listChanged) + this.NotifyInstalledPluginsChanged(); + } + + /// + /// Install a plugin from a repository and load it. + /// + /// The plugin definition. + /// If the testing version should be used. + /// The reason this plugin was loaded. + /// A representing the asynchronous operation. + public async Task InstallPluginAsync(RemotePluginManifest repoManifest, bool useTesting, PluginLoadReason reason) + { + Log.Debug($"Installing plugin {repoManifest.Name} (testing={useTesting})"); + + var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall; + var version = useTesting ? repoManifest.TestingAssemblyVersion : repoManifest.AssemblyVersion; + + var response = await Util.HttpClient.GetAsync(downloadUrl); + response.EnsureSuccessStatusCode(); + + var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version.ToString())); + + try + { + if (outputDir.Exists) + outputDir.Delete(true); + + outputDir.Create(); + } + catch + { + // ignored, since the plugin may be loaded already + } + + Log.Debug($"Extracting to {outputDir}"); + // This throws an error, even with overwrite=false + // ZipFile.ExtractToDirectory(tempZip.FullName, outputDir.FullName, false); + using (var archive = new ZipArchive(await response.Content.ReadAsStreamAsync())) + { + foreach (var zipFile in archive.Entries) + { + var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName))); + + if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); + } + + if (outputFile.Directory == null) + { + throw new IOException("Output directory invalid."); + } + + if (zipFile.Name.IsNullOrEmpty()) + { + // Assuming Empty for Directory + Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}"); + Directory.CreateDirectory(outputFile.Directory.FullName); + continue; + } + + // Ensure directory is created + Directory.CreateDirectory(outputFile.Directory.FullName); + + try + { + zipFile.ExtractToFile(outputFile.FullName, true); + } + catch (Exception ex) + { + if (outputFile.Extension.EndsWith("dll")) + { + throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}"); + } + + Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}"); + } + } + } + + var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + + // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. + File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); + + // Reload as a local manifest, add some attributes, and save again. + var manifest = LocalPluginManifest.Load(manifestFile); + + if (useTesting) + { + manifest.Testing = true; + } + + if (repoManifest.SourceRepo.IsThirdParty) + { + // Only document the url if it came from a third party repo. + manifest.InstalledFromUrl = repoManifest.SourceRepo.PluginMasterUrl; + } + + manifest.Save(manifestFile); + + Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})"); + + var plugin = this.LoadPlugin(dllFile, manifest, reason); + + this.NotifyInstalledPluginsChanged(); + return plugin; + } + + /// + /// Load a plugin. + /// + /// The associated with the main assembly of this plugin. + /// The already loaded definition, if available. + /// The reason this plugin was loaded. + /// If this plugin should support development features. + /// If this plugin is being loaded at boot. + /// Don't load the plugin, just don't do it. + /// The loaded plugin. + public LocalPlugin LoadPlugin(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) + { + var name = manifest?.Name ?? dllFile.Name; + var loadPlugin = !doNotLoad; + + LocalPlugin plugin; + + if (isDev) + { + Log.Information($"Loading dev plugin {name}"); + var devPlugin = new LocalDevPlugin(dllFile, manifest); + loadPlugin &= !isBoot || devPlugin.StartOnBoot; + + // If we're not loading it, make sure it's disabled + if (!loadPlugin && !devPlugin.IsDisabled) + devPlugin.Disable(); + + plugin = devPlugin; + } + else + { + Log.Information($"Loading plugin {name}"); + plugin = new LocalPlugin(dllFile, manifest); + } + + if (loadPlugin) + { + try + { + if (plugin.IsDisabled) + plugin.Enable(); + + plugin.Load(reason); + } + catch (InvalidPluginException) + { + PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); + throw; + } + catch (BannedPluginException) + { + // Out of date plugins get added so they can be updated. + Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}"); + } + catch (Exception ex) + { + if (plugin.IsDev) + { + // Dev plugins always get added to the list so they can be fiddled with in the UI + Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}"); + plugin.Disable(); // Disable here, otherwise you can't enable+load later + } + else if (plugin.IsOutdated) + { + // Out of date plugins get added so they can be updated. + Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}"); + // plugin.Disable(); // Don't disable, or it gets deleted next boot. + } + else { PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); throw; } - catch (BannedPluginException) - { - // Out of date plugins get added so they can be updated. - Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}"); - } - catch (Exception ex) - { - if (plugin.IsDev) - { - // Dev plugins always get added to the list so they can be fiddled with in the UI - Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}"); - plugin.Disable(); // Disable here, otherwise you can't enable+load later - } - else if (plugin.IsOutdated) - { - // Out of date plugins get added so they can be updated. - Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}"); - // plugin.Disable(); // Don't disable, or it gets deleted next boot. - } - else - { - PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); - throw; - } - } } - - this.InstalledPlugins = this.InstalledPlugins.Add(plugin); - return plugin; } - /// - /// Remove a plugin. - /// - /// Plugin to remove. - public void RemovePlugin(LocalPlugin plugin) + this.InstalledPlugins = this.InstalledPlugins.Add(plugin); + return plugin; + } + + /// + /// Remove a plugin. + /// + /// Plugin to remove. + public void RemovePlugin(LocalPlugin plugin) + { + if (plugin.State != PluginState.Unloaded) + throw new InvalidPluginOperationException($"Unable to remove {plugin.Name}, not unloaded"); + + this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); + PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); + + this.NotifyInstalledPluginsChanged(); + this.NotifyAvailablePluginsChanged(); + } + + /// + /// Cleanup disabled plugins. Does not target devPlugins. + /// + public void CleanupPlugins() + { + var configuration = Service.Get(); + var startInfo = Service.Get(); + + foreach (var pluginDir in this.pluginDirectory.GetDirectories()) { - if (plugin.State != PluginState.Unloaded) - throw new InvalidPluginOperationException($"Unable to remove {plugin.Name}, not unloaded"); - - this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); - PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); - - this.NotifyInstalledPluginsChanged(); - this.NotifyAvailablePluginsChanged(); - } - - /// - /// Cleanup disabled plugins. Does not target devPlugins. - /// - public void CleanupPlugins() - { - var configuration = Service.Get(); - var startInfo = Service.Get(); - - foreach (var pluginDir in this.pluginDirectory.GetDirectories()) + try { - try - { - var versionDirs = pluginDir.GetDirectories(); + var versionDirs = pluginDir.GetDirectories(); - versionDirs = versionDirs - .OrderByDescending(dir => - { - var isVersion = Version.TryParse(dir.Name, out var version); - - if (!isVersion) - { - Log.Debug($"Not a version, cleaning up {dir.FullName}"); - dir.Delete(true); - } - - return version; - }) - .Where(version => version != null) - .ToArray(); - - if (versionDirs.Length == 0) + versionDirs = versionDirs + .OrderByDescending(dir => { - Log.Information($"No versions: cleaning up {pluginDir.FullName}"); - pluginDir.Delete(true); - continue; - } - else - { - foreach (var versionDir in versionDirs) + var isVersion = Version.TryParse(dir.Name, out var version); + + if (!isVersion) { - try - { - var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); - if (!dllFile.Exists) - { - Log.Information($"Missing dll: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - if (!manifestFile.Exists) - { - Log.Information($"Missing manifest: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - var manifest = LocalPluginManifest.Load(manifestFile); - if (manifest.Disabled) - { - Log.Information($"Disabled: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - if (manifest.DalamudApiLevel < DalamudApiLevel - 1 && !configuration.LoadAllApiLevels) - { - Log.Information($"Lower API: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - if (manifest.ApplicableVersion < startInfo.GameVersion) - { - Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - } - catch (Exception ex) - { - Log.Error(ex, $"Could not clean up {versionDir.FullName}"); - } + Log.Debug($"Not a version, cleaning up {dir.FullName}"); + dir.Delete(true); } - } - } - catch (Exception ex) + + return version; + }) + .Where(version => version != null) + .ToArray(); + + if (versionDirs.Length == 0) { - Log.Error(ex, $"Could not clean up {pluginDir.FullName}"); - } - } - } - - /// - /// Update all non-dev plugins. - /// - /// Perform a dry run, don't install anything. - /// Success or failure and a list of updated plugin metadata. - public async Task> UpdatePluginsAsync(bool dryRun = false) - { - Log.Information("Starting plugin update"); - - var updatedList = new List(); - - // Prevent collection was modified errors - foreach (var plugin in this.UpdatablePlugins) - { - // Can't update that! - if (plugin.InstalledPlugin.IsDev) + Log.Information($"No versions: cleaning up {pluginDir.FullName}"); + pluginDir.Delete(true); continue; - - var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); - if (result != null) - updatedList.Add(result); - } - - this.NotifyInstalledPluginsChanged(); - - Log.Information("Plugin update OK."); - - return updatedList; - } - - /// - /// Update a single plugin, provided a valid . - /// - /// The available plugin update. - /// Whether to notify that installed plugins have changed afterwards. - /// Whether or not to actually perform the update, or just indicate success. - /// The status of the update. - public async Task UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun) - { - var plugin = metadata.InstalledPlugin; - - var updateStatus = new PluginUpdateStatus - { - InternalName = plugin.Manifest.InternalName, - Name = plugin.Manifest.Name, - Version = metadata.UseTesting - ? metadata.UpdateManifest.TestingAssemblyVersion - : metadata.UpdateManifest.AssemblyVersion, - }; - - updateStatus.WasUpdated = true; - - if (!dryRun) - { - // Unload if loaded - if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) - { - try - { - plugin.Unload(); - } - catch (Exception ex) - { - Log.Error(ex, "Error during unload (update)"); - updateStatus.WasUpdated = false; - return updateStatus; - } - } - - if (plugin.IsDev) - { - try - { - plugin.DllFile.Delete(); - this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); - } - catch (Exception ex) - { - Log.Error(ex, "Error during delete (update)"); - updateStatus.WasUpdated = false; - return updateStatus; - } } else { - try + foreach (var versionDir in versionDirs) { - plugin.Disable(); - this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); - } - catch (Exception ex) - { - Log.Error(ex, "Error during disable (update)"); - updateStatus.WasUpdated = false; - return updateStatus; + try + { + var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); + if (!dllFile.Exists) + { + Log.Information($"Missing dll: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + if (!manifestFile.Exists) + { + Log.Information($"Missing manifest: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + var manifest = LocalPluginManifest.Load(manifestFile); + if (manifest.Disabled) + { + Log.Information($"Disabled: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + if (manifest.DalamudApiLevel < DalamudApiLevel - 1 && !configuration.LoadAllApiLevels) + { + Log.Information($"Lower API: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + if (manifest.ApplicableVersion < startInfo.GameVersion) + { + Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + } + catch (Exception ex) + { + Log.Error(ex, $"Could not clean up {versionDir.FullName}"); + } } } + } + catch (Exception ex) + { + Log.Error(ex, $"Could not clean up {pluginDir.FullName}"); + } + } + } + /// + /// Update all non-dev plugins. + /// + /// Perform a dry run, don't install anything. + /// Success or failure and a list of updated plugin metadata. + public async Task> UpdatePluginsAsync(bool dryRun = false) + { + Log.Information("Starting plugin update"); + + var updatedList = new List(); + + // Prevent collection was modified errors + foreach (var plugin in this.UpdatablePlugins) + { + // Can't update that! + if (plugin.InstalledPlugin.IsDev) + continue; + + var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); + if (result != null) + updatedList.Add(result); + } + + this.NotifyInstalledPluginsChanged(); + + Log.Information("Plugin update OK."); + + return updatedList; + } + + /// + /// Update a single plugin, provided a valid . + /// + /// The available plugin update. + /// Whether to notify that installed plugins have changed afterwards. + /// Whether or not to actually perform the update, or just indicate success. + /// The status of the update. + public async Task UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun) + { + var plugin = metadata.InstalledPlugin; + + var updateStatus = new PluginUpdateStatus + { + InternalName = plugin.Manifest.InternalName, + Name = plugin.Manifest.Name, + Version = metadata.UseTesting + ? metadata.UpdateManifest.TestingAssemblyVersion + : metadata.UpdateManifest.AssemblyVersion, + }; + + updateStatus.WasUpdated = true; + + if (!dryRun) + { + // Unload if loaded + if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) + { try { - await this.InstallPluginAsync(metadata.UpdateManifest, metadata.UseTesting, PluginLoadReason.Update); + plugin.Unload(); } catch (Exception ex) { - Log.Error(ex, "Error during install (update)"); + Log.Error(ex, "Error during unload (update)"); updateStatus.WasUpdated = false; return updateStatus; } } - if (notify && updateStatus.WasUpdated) - this.NotifyInstalledPluginsChanged(); - - return updateStatus; - } - - /// - /// Unload the plugin, delete its configuration, and reload it. - /// - /// The plugin. - /// Throws if the plugin is still loading/unloading. - public void DeleteConfiguration(LocalPlugin plugin) - { - if (plugin.State == PluginState.InProgress) - throw new Exception("Cannot delete configuration for a loading/unloading plugin"); - - if (plugin.IsLoaded) - plugin.Unload(); - - // Let's wait so any handles on files in plugin configurations can be closed - Thread.Sleep(500); - - this.PluginConfigs.Delete(plugin.Name); - - Thread.Sleep(500); - - // Let's indicate "installer" here since this is supposed to be a fresh install - plugin.Load(PluginLoadReason.Installer); - } - - /// - /// Print to chat any plugin updates and whether they were successful. - /// - /// The list of updated plugin metadata. - /// The header text to send to chat prior to any update info. - public void PrintUpdatedPlugins(List updateMetadata, string header) - { - var chatGui = Service.Get(); - - if (updateMetadata != null && updateMetadata.Count > 0) + if (plugin.IsDev) { - chatGui.Print(header); - - foreach (var metadata in updateMetadata) + try { - if (metadata.WasUpdated) - { - chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); - } - else - { - chatGui.PrintChat(new XivChatEntry - { - Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version), - Type = XivChatType.Urgent, - }); - } + plugin.DllFile.Delete(); + this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); + } + catch (Exception ex) + { + Log.Error(ex, "Error during delete (update)"); + updateStatus.WasUpdated = false; + return updateStatus; } - } - } - - /// - /// For a given manifest, determine if the testing version should be used over the normal version. - /// The higher of the two versions is calculated after checking other settings. - /// - /// Manifest to check. - /// A value indicating whether testing should be used. - public bool UseTesting(PluginManifest manifest) - { - var configuration = Service.Get(); - - if (!configuration.DoPluginTest) - return false; - - if (manifest.IsTestingExclusive) - return true; - - var av = manifest.AssemblyVersion; - var tv = manifest.TestingAssemblyVersion; - var hasAv = av != null; - var hasTv = tv != null; - - if (hasAv && hasTv) - { - return tv > av; } else { - return hasTv; - } - } - - /// - /// Gets a value indicating whether the given repo manifest should be visible to the user. - /// - /// Repo manifest. - /// If the manifest is visible. - public bool IsManifestVisible(RemotePluginManifest manifest) - { - var configuration = Service.Get(); - - // Hidden by user - if (configuration.HiddenPluginInternalName.Contains(manifest.InternalName)) - return false; - - // Hidden by manifest - if (manifest.IsHide) - return false; - - return true; - } - - /// - /// Gets a value indicating whether the given manifest is eligible for ANYTHING. These are hard - /// checks that should not allow installation or loading. - /// - /// Plugin manifest. - /// If the manifest is eligible. - public bool IsManifestEligible(PluginManifest manifest) - { - var configuration = Service.Get(); - var startInfo = Service.Get(); - - // Testing exclusive - if (manifest.IsTestingExclusive && !configuration.DoPluginTest) - return false; - - // Applicable version - if (manifest.ApplicableVersion < startInfo.GameVersion) - return false; - - // API level - if (manifest.DalamudApiLevel < DalamudApiLevel && !configuration.LoadAllApiLevels) - return false; - - // Banned - if (this.IsManifestBanned(manifest)) - return false; - - return true; - } - - /// - /// Determine if a plugin has been banned by inspecting the manifest. - /// - /// Manifest to inspect. - /// A value indicating whether the plugin/manifest has been banned. - public bool IsManifestBanned(PluginManifest manifest) - { - var configuration = Service.Get(); - return configuration.LoadBannedPlugins || this.bannedPlugins.Any(ban => ban.Name == manifest.InternalName && ban.AssemblyVersion >= manifest.AssemblyVersion); - } - - /// - /// Get the reason of a banned plugin by inspecting the manifest. - /// - /// Manifest to inspect. - /// The reason of the ban, if any. - public string GetBanReason(PluginManifest manifest) - { - return this.bannedPlugins.FirstOrDefault(ban => ban.Name == manifest.InternalName).Reason; - } - - private void DetectAvailablePluginUpdates() - { - var updatablePlugins = new List(); - - foreach (var plugin in this.InstalledPlugins) - { - var installedVersion = plugin.IsTesting - ? plugin.Manifest.TestingAssemblyVersion - : plugin.Manifest.AssemblyVersion; - - var updates = this.AvailablePlugins - .Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName) - .Select(remoteManifest => - { - var useTesting = this.UseTesting(remoteManifest); - var candidateVersion = useTesting - ? remoteManifest.TestingAssemblyVersion - : remoteManifest.AssemblyVersion; - var isUpdate = candidateVersion > installedVersion; - - return (isUpdate, useTesting, candidateVersion, remoteManifest); - }) - .Where(tpl => tpl.isUpdate) - .ToList(); - - if (updates.Count > 0) + try { - var update = updates.Aggregate((t1, t2) => t1.candidateVersion > t2.candidateVersion ? t1 : t2); - updatablePlugins.Add(new(plugin, update.remoteManifest, update.useTesting)); + plugin.Disable(); + this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); + } + catch (Exception ex) + { + Log.Error(ex, "Error during disable (update)"); + updateStatus.WasUpdated = false; + return updateStatus; } } - this.UpdatablePlugins = updatablePlugins.ToImmutableList(); - } - - private void NotifyAvailablePluginsChanged() - { - this.DetectAvailablePluginUpdates(); - try { - this.OnAvailablePluginsChanged?.Invoke(); + await this.InstallPluginAsync(metadata.UpdateManifest, metadata.UseTesting, PluginLoadReason.Update); } catch (Exception ex) { - Log.Error(ex, $"Error notifying {nameof(this.OnAvailablePluginsChanged)}"); + Log.Error(ex, "Error during install (update)"); + updateStatus.WasUpdated = false; + return updateStatus; } } - private void NotifyInstalledPluginsChanged() + if (notify && updateStatus.WasUpdated) + this.NotifyInstalledPluginsChanged(); + + return updateStatus; + } + + /// + /// Unload the plugin, delete its configuration, and reload it. + /// + /// The plugin. + /// Throws if the plugin is still loading/unloading. + public void DeleteConfiguration(LocalPlugin plugin) + { + if (plugin.State == PluginState.InProgress) + throw new Exception("Cannot delete configuration for a loading/unloading plugin"); + + if (plugin.IsLoaded) + plugin.Unload(); + + // Let's wait so any handles on files in plugin configurations can be closed + Thread.Sleep(500); + + this.PluginConfigs.Delete(plugin.Name); + + Thread.Sleep(500); + + // Let's indicate "installer" here since this is supposed to be a fresh install + plugin.Load(PluginLoadReason.Installer); + } + + /// + /// Print to chat any plugin updates and whether they were successful. + /// + /// The list of updated plugin metadata. + /// The header text to send to chat prior to any update info. + public void PrintUpdatedPlugins(List updateMetadata, string header) + { + var chatGui = Service.Get(); + + if (updateMetadata != null && updateMetadata.Count > 0) { - this.DetectAvailablePluginUpdates(); + chatGui.Print(header); - try + foreach (var metadata in updateMetadata) { - this.OnInstalledPluginsChanged?.Invoke(); + if (metadata.WasUpdated) + { + chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); + } + else + { + chatGui.PrintChat(new XivChatEntry + { + Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version), + Type = XivChatType.Urgent, + }); + } } - catch (Exception ex) - { - Log.Error(ex, $"Error notifying {nameof(this.OnInstalledPluginsChanged)}"); - } - } - - private struct BannedPlugin - { - [JsonProperty] - public string Name { get; private set; } - - [JsonProperty] - public Version AssemblyVersion { get; private set; } - - [JsonProperty] - public string Reason { get; private set; } - } - - private struct PluginDef - { - public PluginDef(FileInfo dllFile, LocalPluginManifest manifest, bool isDev) - { - this.DllFile = dllFile; - this.Manifest = manifest; - this.IsDev = isDev; - } - - public FileInfo DllFile { get; init; } - - public LocalPluginManifest Manifest { get; init; } - - public bool IsDev { get; init; } - - public static int Sorter(PluginDef def1, PluginDef def2) - { - var prio1 = def1.Manifest?.LoadPriority ?? 0; - var prio2 = def2.Manifest?.LoadPriority ?? 0; - return prio2.CompareTo(prio1); - } - } - - private static class Locs - { - public static string DalamudPluginUpdateSuccessful(string name, Version version) => Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}.").Format(name, version); - - public static string DalamudPluginUpdateFailed(string name, Version version) => Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed.").Format(name, version); } } /// - /// Class responsible for loading and unloading plugins. - /// This contains the assembly patching functionality to resolve assembly locations. + /// For a given manifest, determine if the testing version should be used over the normal version. + /// The higher of the two versions is calculated after checking other settings. /// - internal partial class PluginManager + /// Manifest to check. + /// A value indicating whether testing should be used. + public bool UseTesting(PluginManifest manifest) { - /// - /// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading - /// plugins via byte[]. - /// - internal static readonly Dictionary PluginLocations = new(); + var configuration = Service.Get(); - private MonoMod.RuntimeDetour.Hook assemblyLocationMonoHook; - private MonoMod.RuntimeDetour.Hook assemblyCodeBaseMonoHook; + if (!configuration.DoPluginTest) + return false; - /// - /// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin location, or the result from the original method. - private static string AssemblyLocationPatch(Func orig, Assembly self) + if (manifest.IsTestingExclusive) + return true; + + var av = manifest.AssemblyVersion; + var tv = manifest.TestingAssemblyVersion; + var hasAv = av != null; + var hasTv = tv != null; + + if (hasAv && hasTv) { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.Location; - break; - } - } - } - - result ??= string.Empty; - - Log.Verbose($"Assembly.Location // {self.FullName} // {result}"); - return result; + return tv > av; } - - /// - /// Patch method for internal class RuntimeAssembly.CodeBase, also known as Assembly.CodeBase. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin code base, or the result from the original method. - private static string AssemblyCodeBasePatch(Func orig, Assembly self) + else { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.CodeBase; - break; - } - } - } - - result ??= string.Empty; - - Log.Verbose($"Assembly.CodeBase // {self.FullName} // {result}"); - return result; - } - - private static IEnumerable GetStackFrameAssemblyNames() - { - var stackTrace = new StackTrace(); - var stackFrames = stackTrace.GetFrames(); - - foreach (var stackFrame in stackFrames) - { - var methodBase = stackFrame.GetMethod(); - if (methodBase == null) - continue; - - yield return methodBase.Module.Assembly.FullName; - } - } - - private void ApplyPatches() - { - var targetType = typeof(PluginManager).Assembly.GetType(); - - var locationTarget = targetType.GetProperty(nameof(Assembly.Location)).GetGetMethod(); - var locationPatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyLocationPatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyLocationMonoHook = new MonoMod.RuntimeDetour.Hook(locationTarget, locationPatch); - -#pragma warning disable SYSLIB0012 // Type or member is obsolete - var codebaseTarget = targetType.GetProperty(nameof(Assembly.CodeBase)).GetGetMethod(); - var codebasePatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyCodeBasePatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch); -#pragma warning restore SYSLIB0012 // Type or member is obsolete - } - - internal record PluginPatchData - { - /// - /// Initializes a new instance of the class. - /// - /// DLL file being loaded. - public PluginPatchData(FileInfo dllFile) - { - this.Location = dllFile.FullName; - this.CodeBase = new Uri(dllFile.FullName).AbsoluteUri; - } - - /// - /// Gets simulated Assembly.Location output. - /// - public string Location { get; } - - /// - /// Gets simulated Assembly.CodeBase output. - /// - public string CodeBase { get; } + return hasTv; } } + + /// + /// Gets a value indicating whether the given repo manifest should be visible to the user. + /// + /// Repo manifest. + /// If the manifest is visible. + public bool IsManifestVisible(RemotePluginManifest manifest) + { + var configuration = Service.Get(); + + // Hidden by user + if (configuration.HiddenPluginInternalName.Contains(manifest.InternalName)) + return false; + + // Hidden by manifest + if (manifest.IsHide) + return false; + + return true; + } + + /// + /// Gets a value indicating whether the given manifest is eligible for ANYTHING. These are hard + /// checks that should not allow installation or loading. + /// + /// Plugin manifest. + /// If the manifest is eligible. + public bool IsManifestEligible(PluginManifest manifest) + { + var configuration = Service.Get(); + var startInfo = Service.Get(); + + // Testing exclusive + if (manifest.IsTestingExclusive && !configuration.DoPluginTest) + return false; + + // Applicable version + if (manifest.ApplicableVersion < startInfo.GameVersion) + return false; + + // API level + if (manifest.DalamudApiLevel < DalamudApiLevel && !configuration.LoadAllApiLevels) + return false; + + // Banned + if (this.IsManifestBanned(manifest)) + return false; + + return true; + } + + /// + /// Determine if a plugin has been banned by inspecting the manifest. + /// + /// Manifest to inspect. + /// A value indicating whether the plugin/manifest has been banned. + public bool IsManifestBanned(PluginManifest manifest) + { + var configuration = Service.Get(); + return configuration.LoadBannedPlugins || this.bannedPlugins.Any(ban => ban.Name == manifest.InternalName && ban.AssemblyVersion >= manifest.AssemblyVersion); + } + + /// + /// Get the reason of a banned plugin by inspecting the manifest. + /// + /// Manifest to inspect. + /// The reason of the ban, if any. + public string GetBanReason(PluginManifest manifest) + { + return this.bannedPlugins.FirstOrDefault(ban => ban.Name == manifest.InternalName).Reason; + } + + private void DetectAvailablePluginUpdates() + { + var updatablePlugins = new List(); + + foreach (var plugin in this.InstalledPlugins) + { + var installedVersion = plugin.IsTesting + ? plugin.Manifest.TestingAssemblyVersion + : plugin.Manifest.AssemblyVersion; + + var updates = this.AvailablePlugins + .Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName) + .Select(remoteManifest => + { + var useTesting = this.UseTesting(remoteManifest); + var candidateVersion = useTesting + ? remoteManifest.TestingAssemblyVersion + : remoteManifest.AssemblyVersion; + var isUpdate = candidateVersion > installedVersion; + + return (isUpdate, useTesting, candidateVersion, remoteManifest); + }) + .Where(tpl => tpl.isUpdate) + .ToList(); + + if (updates.Count > 0) + { + var update = updates.Aggregate((t1, t2) => t1.candidateVersion > t2.candidateVersion ? t1 : t2); + updatablePlugins.Add(new(plugin, update.remoteManifest, update.useTesting)); + } + } + + this.UpdatablePlugins = updatablePlugins.ToImmutableList(); + } + + private void NotifyAvailablePluginsChanged() + { + this.DetectAvailablePluginUpdates(); + + try + { + this.OnAvailablePluginsChanged?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error notifying {nameof(this.OnAvailablePluginsChanged)}"); + } + } + + private void NotifyInstalledPluginsChanged() + { + this.DetectAvailablePluginUpdates(); + + try + { + this.OnInstalledPluginsChanged?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error notifying {nameof(this.OnInstalledPluginsChanged)}"); + } + } + + private struct BannedPlugin + { + [JsonProperty] + public string Name { get; private set; } + + [JsonProperty] + public Version AssemblyVersion { get; private set; } + + [JsonProperty] + public string Reason { get; private set; } + } + + private struct PluginDef + { + public PluginDef(FileInfo dllFile, LocalPluginManifest manifest, bool isDev) + { + this.DllFile = dllFile; + this.Manifest = manifest; + this.IsDev = isDev; + } + + public FileInfo DllFile { get; init; } + + public LocalPluginManifest Manifest { get; init; } + + public bool IsDev { get; init; } + + public static int Sorter(PluginDef def1, PluginDef def2) + { + var prio1 = def1.Manifest?.LoadPriority ?? 0; + var prio2 = def2.Manifest?.LoadPriority ?? 0; + return prio2.CompareTo(prio1); + } + } + + private static class Locs + { + public static string DalamudPluginUpdateSuccessful(string name, Version version) => Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}.").Format(name, version); + + public static string DalamudPluginUpdateFailed(string name, Version version) => Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed.").Format(name, version); + } +} + +/// +/// Class responsible for loading and unloading plugins. +/// This contains the assembly patching functionality to resolve assembly locations. +/// +internal partial class PluginManager +{ + /// + /// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading + /// plugins via byte[]. + /// + internal static readonly Dictionary PluginLocations = new(); + + private MonoMod.RuntimeDetour.Hook assemblyLocationMonoHook; + private MonoMod.RuntimeDetour.Hook assemblyCodeBaseMonoHook; + + /// + /// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location. + /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. + /// It should never be called manually. + /// + /// A delegate that acts as the original method. + /// The equivalent of `this`. + /// The plugin location, or the result from the original method. + private static string AssemblyLocationPatch(Func orig, Assembly self) + { + var result = orig(self); + + if (string.IsNullOrEmpty(result)) + { + foreach (var assemblyName in GetStackFrameAssemblyNames()) + { + if (PluginLocations.TryGetValue(assemblyName, out var data)) + { + result = data.Location; + break; + } + } + } + + result ??= string.Empty; + + Log.Verbose($"Assembly.Location // {self.FullName} // {result}"); + return result; + } + + /// + /// Patch method for internal class RuntimeAssembly.CodeBase, also known as Assembly.CodeBase. + /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. + /// It should never be called manually. + /// + /// A delegate that acts as the original method. + /// The equivalent of `this`. + /// The plugin code base, or the result from the original method. + private static string AssemblyCodeBasePatch(Func orig, Assembly self) + { + var result = orig(self); + + if (string.IsNullOrEmpty(result)) + { + foreach (var assemblyName in GetStackFrameAssemblyNames()) + { + if (PluginLocations.TryGetValue(assemblyName, out var data)) + { + result = data.CodeBase; + break; + } + } + } + + result ??= string.Empty; + + Log.Verbose($"Assembly.CodeBase // {self.FullName} // {result}"); + return result; + } + + private static IEnumerable GetStackFrameAssemblyNames() + { + var stackTrace = new StackTrace(); + var stackFrames = stackTrace.GetFrames(); + + foreach (var stackFrame in stackFrames) + { + var methodBase = stackFrame.GetMethod(); + if (methodBase == null) + continue; + + yield return methodBase.Module.Assembly.FullName; + } + } + + private void ApplyPatches() + { + var targetType = typeof(PluginManager).Assembly.GetType(); + + var locationTarget = targetType.GetProperty(nameof(Assembly.Location)).GetGetMethod(); + var locationPatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyLocationPatch), BindingFlags.NonPublic | BindingFlags.Static); + this.assemblyLocationMonoHook = new MonoMod.RuntimeDetour.Hook(locationTarget, locationPatch); + +#pragma warning disable SYSLIB0012 // Type or member is obsolete + var codebaseTarget = targetType.GetProperty(nameof(Assembly.CodeBase)).GetGetMethod(); + var codebasePatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyCodeBasePatch), BindingFlags.NonPublic | BindingFlags.Static); + this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch); +#pragma warning restore SYSLIB0012 // Type or member is obsolete + } + + internal record PluginPatchData + { + /// + /// Initializes a new instance of the class. + /// + /// DLL file being loaded. + public PluginPatchData(FileInfo dllFile) + { + this.Location = dllFile.FullName; + this.CodeBase = new Uri(dllFile.FullName).AbsoluteUri; + } + + /// + /// Gets simulated Assembly.Location output. + /// + public string Location { get; } + + /// + /// Gets simulated Assembly.CodeBase output. + /// + public string CodeBase { get; } + } } diff --git a/Dalamud/Plugin/Internal/PluginRepository.cs b/Dalamud/Plugin/Internal/PluginRepository.cs index 3e4e93fb2..2ef0a4f40 100644 --- a/Dalamud/Plugin/Internal/PluginRepository.cs +++ b/Dalamud/Plugin/Internal/PluginRepository.cs @@ -9,103 +9,102 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal +namespace Dalamud.Plugin.Internal; + +/// +/// This class represents a single plugin repository. +/// +internal partial class PluginRepository { + // TODO: Change back to master after api4 release + private const string DalamudPluginsMasterUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/pluginmaster.json"; + + private static readonly ModuleLog Log = new("PLUGINR"); + /// - /// This class represents a single plugin repository. + /// Initializes a new instance of the class. /// - internal partial class PluginRepository + /// The plugin master URL. + /// Whether the plugin repo is enabled. + public PluginRepository(string pluginMasterUrl, bool isEnabled) { - // TODO: Change back to master after api4 release - private const string DalamudPluginsMasterUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/pluginmaster.json"; + this.PluginMasterUrl = pluginMasterUrl; + this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl; + this.IsEnabled = isEnabled; + } - private static readonly ModuleLog Log = new("PLUGINR"); + /// + /// Gets a new instance of the class for the main repo. + /// + public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true); - /// - /// Initializes a new instance of the class. - /// - /// The plugin master URL. - /// Whether the plugin repo is enabled. - public PluginRepository(string pluginMasterUrl, bool isEnabled) + /// + /// Gets the pluginmaster.json URL. + /// + public string PluginMasterUrl { get; } + + /// + /// Gets a value indicating whether this plugin repository is from a third party. + /// + public bool IsThirdParty { get; } + + /// + /// Gets a value indicating whether this repo is enabled. + /// + public bool IsEnabled { get; } + + /// + /// Gets the plugin master list of available plugins. + /// + public ReadOnlyCollection? PluginMaster { get; private set; } + + /// + /// Gets the initialization state of the plugin repository. + /// + public PluginRepositoryState State { get; private set; } + + /// + /// Reload the plugin master asynchronously in a task. + /// + /// The new state. + public async Task ReloadPluginMasterAsync() + { + this.State = PluginRepositoryState.InProgress; + this.PluginMaster = new List().AsReadOnly(); + + try { - this.PluginMasterUrl = pluginMasterUrl; - this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl; - this.IsEnabled = isEnabled; + Log.Information($"Fetching repo: {this.PluginMasterUrl}"); + + // ?ticks causes a cache invalidation. Get a fresh repo every time. + using var response = await Util.HttpClient.GetAsync(this.PluginMasterUrl + "?" + DateTime.Now.Ticks); + response.EnsureSuccessStatusCode(); + + var data = await response.Content.ReadAsStringAsync(); + var pluginMaster = JsonConvert.DeserializeObject>(data); + + if (pluginMaster == null) + { + throw new Exception("Deserialized PluginMaster was null."); + } + + pluginMaster.Sort((pm1, pm2) => pm1.Name.CompareTo(pm2.Name)); + + // Set the source for each remote manifest. Allows for checking if is 3rd party. + foreach (var manifest in pluginMaster) + { + manifest.SourceRepo = this; + } + + this.PluginMaster = pluginMaster.AsReadOnly(); + + Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}"); + this.State = PluginRepositoryState.Success; } - - /// - /// Gets a new instance of the class for the main repo. - /// - public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true); - - /// - /// Gets the pluginmaster.json URL. - /// - public string PluginMasterUrl { get; } - - /// - /// Gets a value indicating whether this plugin repository is from a third party. - /// - public bool IsThirdParty { get; } - - /// - /// Gets a value indicating whether this repo is enabled. - /// - public bool IsEnabled { get; } - - /// - /// Gets the plugin master list of available plugins. - /// - public ReadOnlyCollection? PluginMaster { get; private set; } - - /// - /// Gets the initialization state of the plugin repository. - /// - public PluginRepositoryState State { get; private set; } - - /// - /// Reload the plugin master asynchronously in a task. - /// - /// The new state. - public async Task ReloadPluginMasterAsync() + catch (Exception ex) { - this.State = PluginRepositoryState.InProgress; - this.PluginMaster = new List().AsReadOnly(); - - try - { - Log.Information($"Fetching repo: {this.PluginMasterUrl}"); - - // ?ticks causes a cache invalidation. Get a fresh repo every time. - using var response = await Util.HttpClient.GetAsync(this.PluginMasterUrl + "?" + DateTime.Now.Ticks); - response.EnsureSuccessStatusCode(); - - var data = await response.Content.ReadAsStringAsync(); - var pluginMaster = JsonConvert.DeserializeObject>(data); - - if (pluginMaster == null) - { - throw new Exception("Deserialized PluginMaster was null."); - } - - pluginMaster.Sort((pm1, pm2) => pm1.Name.CompareTo(pm2.Name)); - - // Set the source for each remote manifest. Allows for checking if is 3rd party. - foreach (var manifest in pluginMaster) - { - manifest.SourceRepo = this; - } - - this.PluginMaster = pluginMaster.AsReadOnly(); - - Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}"); - this.State = PluginRepositoryState.Success; - } - catch (Exception ex) - { - Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}"); - this.State = PluginRepositoryState.Fail; - } + Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}"); + this.State = PluginRepositoryState.Fail; } } } diff --git a/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs b/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs index 32dde337c..13523a379 100644 --- a/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs +++ b/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs @@ -1,36 +1,35 @@ -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Information about an available plugin update. +/// +internal record AvailablePluginUpdate { /// - /// Information about an available plugin update. + /// Initializes a new instance of the class. /// - internal record AvailablePluginUpdate + /// The installed plugin to update. + /// The manifest to use for the update. + /// If the testing version should be used for the update. + public AvailablePluginUpdate(LocalPlugin installedPlugin, RemotePluginManifest updateManifest, bool useTesting) { - /// - /// Initializes a new instance of the class. - /// - /// The installed plugin to update. - /// The manifest to use for the update. - /// If the testing version should be used for the update. - public AvailablePluginUpdate(LocalPlugin installedPlugin, RemotePluginManifest updateManifest, bool useTesting) - { - this.InstalledPlugin = installedPlugin; - this.UpdateManifest = updateManifest; - this.UseTesting = useTesting; - } - - /// - /// Gets the currently installed plugin. - /// - public LocalPlugin InstalledPlugin { get; init; } - - /// - /// Gets the available update manifest. - /// - public RemotePluginManifest UpdateManifest { get; init; } - - /// - /// Gets a value indicating whether the update should use the testing URL. - /// - public bool UseTesting { get; init; } + this.InstalledPlugin = installedPlugin; + this.UpdateManifest = updateManifest; + this.UseTesting = useTesting; } + + /// + /// Gets the currently installed plugin. + /// + public LocalPlugin InstalledPlugin { get; init; } + + /// + /// Gets the available update manifest. + /// + public RemotePluginManifest UpdateManifest { get; init; } + + /// + /// Gets a value indicating whether the update should use the testing URL. + /// + public bool UseTesting { get; init; } } diff --git a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs index 21f0bd8d7..59a9a6a80 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs @@ -2,79 +2,78 @@ using System.IO; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as +/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. +/// +internal record LocalPluginManifest : PluginManifest { /// - /// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as - /// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. + /// Gets or sets a value indicating whether the plugin is disabled and should not be loaded. + /// This value supercedes the ".disabled" file functionality and should not be included in the plugin master. /// - internal record LocalPluginManifest : PluginManifest - { - /// - /// Gets or sets a value indicating whether the plugin is disabled and should not be loaded. - /// This value supercedes the ".disabled" file functionality and should not be included in the plugin master. - /// - public bool Disabled { get; set; } = false; + public bool Disabled { get; set; } = false; - /// - /// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled. - /// This value supercedes the ".testing" file functionality and should not be included in the plugin master. - /// - public bool Testing { get; set; } = false; + /// + /// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled. + /// This value supercedes the ".testing" file functionality and should not be included in the plugin master. + /// + public bool Testing { get; set; } = false; - /// - /// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was - /// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null - /// when installed from the main repo. - /// - public string InstalledFromUrl { get; set; } + /// + /// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was + /// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null + /// when installed from the main repo. + /// + public string InstalledFromUrl { get; set; } - /// - /// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party - /// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null. - /// - public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl); + /// + /// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party + /// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null. + /// + public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl); - /// - /// Save a plugin manifest to file. - /// - /// Path to save at. - public void Save(FileInfo manifestFile) => File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); + /// + /// Save a plugin manifest to file. + /// + /// Path to save at. + public void Save(FileInfo manifestFile) => File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); - /// - /// Loads a plugin manifest from file. - /// - /// Path to the manifest. - /// A object. - public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject(File.ReadAllText(manifestFile.FullName)); + /// + /// Loads a plugin manifest from file. + /// + /// Path to the manifest. + /// A object. + public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject(File.ReadAllText(manifestFile.FullName)); - /// - /// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist. - /// - /// Manifest directory. - /// The manifest. - /// The file. - public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll")); + /// + /// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist. + /// + /// Manifest directory. + /// The manifest. + /// The file. + public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll")); - /// - /// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist. - /// - /// The plugin DLL. - /// The file. - public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json")); + /// + /// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist. + /// + /// The plugin DLL. + /// The file. + public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json")); - /// - /// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist. - /// - /// The plugin DLL. - /// The .disabled file. - public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".disabled")); + /// + /// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist. + /// + /// The plugin DLL. + /// The .disabled file. + public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".disabled")); - /// - /// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist. - /// - /// The plugin DLL. - /// The .testing file. - public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".testing")); - } + /// + /// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist. + /// + /// The plugin DLL. + /// The .testing file. + public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".testing")); } diff --git a/Dalamud/Plugin/Internal/Types/PluginManifest.cs b/Dalamud/Plugin/Internal/Types/PluginManifest.cs index ddacb66de..e9f2eadf5 100644 --- a/Dalamud/Plugin/Internal/Types/PluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/PluginManifest.cs @@ -4,162 +4,161 @@ using System.Collections.Generic; using Dalamud.Game; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Information about a plugin, packaged in a json file with the DLL. +/// +internal record PluginManifest { /// - /// Information about a plugin, packaged in a json file with the DLL. + /// Gets the author/s of the plugin. /// - internal record PluginManifest - { - /// - /// Gets the author/s of the plugin. - /// - [JsonProperty] - public string Author { get; init; } + [JsonProperty] + public string Author { get; init; } - /// - /// Gets or sets the public name of the plugin. - /// - [JsonProperty] - public string Name { get; set; } + /// + /// Gets or sets the public name of the plugin. + /// + [JsonProperty] + public string Name { get; set; } - /// - /// Gets a punchline of the plugins functions. - /// - [JsonProperty] - public string? Punchline { get; init; } + /// + /// Gets a punchline of the plugins functions. + /// + [JsonProperty] + public string? Punchline { get; init; } - /// - /// Gets a description of the plugins functions. - /// - [JsonProperty] - public string? Description { get; init; } + /// + /// Gets a description of the plugins functions. + /// + [JsonProperty] + public string? Description { get; init; } - /// - /// Gets a changelog. - /// - [JsonProperty] - public string? Changelog { get; init; } + /// + /// Gets a changelog. + /// + [JsonProperty] + public string? Changelog { get; init; } - /// - /// Gets a list of tags defined on the plugin. - /// - [JsonProperty] - public List? Tags { get; init; } + /// + /// Gets a list of tags defined on the plugin. + /// + [JsonProperty] + public List? Tags { get; init; } - /// - /// Gets a list of category tags defined on the plugin. - /// - [JsonProperty] - public List? CategoryTags { get; init; } + /// + /// Gets a list of category tags defined on the plugin. + /// + [JsonProperty] + public List? CategoryTags { get; init; } - /// - /// Gets a value indicating whether or not the plugin is hidden in the plugin installer. - /// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud. - /// - [JsonProperty] - public bool IsHide { get; init; } + /// + /// Gets a value indicating whether or not the plugin is hidden in the plugin installer. + /// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud. + /// + [JsonProperty] + public bool IsHide { get; init; } - /// - /// Gets the internal name of the plugin, which should match the assembly name of the plugin. - /// - [JsonProperty] - public string InternalName { get; init; } + /// + /// Gets the internal name of the plugin, which should match the assembly name of the plugin. + /// + [JsonProperty] + public string InternalName { get; init; } - /// - /// Gets the current assembly version of the plugin. - /// - [JsonProperty] - public Version AssemblyVersion { get; init; } + /// + /// Gets the current assembly version of the plugin. + /// + [JsonProperty] + public Version AssemblyVersion { get; init; } - /// - /// Gets the current testing assembly version of the plugin. - /// - [JsonProperty] - public Version? TestingAssemblyVersion { get; init; } + /// + /// Gets the current testing assembly version of the plugin. + /// + [JsonProperty] + public Version? TestingAssemblyVersion { get; init; } - /// - /// Gets a value indicating whether the is not null. - /// - [JsonIgnore] - public bool HasAssemblyVersion => this.AssemblyVersion != null; + /// + /// Gets a value indicating whether the is not null. + /// + [JsonIgnore] + public bool HasAssemblyVersion => this.AssemblyVersion != null; - /// - /// Gets a value indicating whether the is not null. - /// - [JsonIgnore] - public bool HasTestingAssemblyVersion => this.TestingAssemblyVersion != null; + /// + /// Gets a value indicating whether the is not null. + /// + [JsonIgnore] + public bool HasTestingAssemblyVersion => this.TestingAssemblyVersion != null; - /// - /// Gets a value indicating whether the plugin is only available for testing. - /// - [JsonProperty] - public bool IsTestingExclusive { get; init; } + /// + /// Gets a value indicating whether the plugin is only available for testing. + /// + [JsonProperty] + public bool IsTestingExclusive { get; init; } - /// - /// Gets an URL to the website or source code of the plugin. - /// - [JsonProperty] - public string? RepoUrl { get; init; } + /// + /// Gets an URL to the website or source code of the plugin. + /// + [JsonProperty] + public string? RepoUrl { get; init; } - /// - /// Gets the version of the game this plugin works with. - /// - [JsonProperty] - [JsonConverter(typeof(GameVersionConverter))] - public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any; + /// + /// Gets the version of the game this plugin works with. + /// + [JsonProperty] + [JsonConverter(typeof(GameVersionConverter))] + public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any; - /// - /// Gets the API level of this plugin. For the current API level, please see - /// for the currently used API level. - /// - [JsonProperty] - public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel; + /// + /// Gets the API level of this plugin. For the current API level, please see + /// for the currently used API level. + /// + [JsonProperty] + public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel; - /// - /// Gets the number of downloads this plugin has. - /// - [JsonProperty] - public long DownloadCount { get; init; } + /// + /// Gets the number of downloads this plugin has. + /// + [JsonProperty] + public long DownloadCount { get; init; } - /// - /// Gets the last time this plugin was updated. - /// - [JsonProperty] - public long LastUpdate { get; init; } + /// + /// Gets the last time this plugin was updated. + /// + [JsonProperty] + public long LastUpdate { get; init; } - /// - /// Gets the download link used to install the plugin. - /// - [JsonProperty] - public string DownloadLinkInstall { get; init; } + /// + /// Gets the download link used to install the plugin. + /// + [JsonProperty] + public string DownloadLinkInstall { get; init; } - /// - /// Gets the download link used to update the plugin. - /// - [JsonProperty] - public string DownloadLinkUpdate { get; init; } + /// + /// Gets the download link used to update the plugin. + /// + [JsonProperty] + public string DownloadLinkUpdate { get; init; } - /// - /// Gets the download link used to get testing versions of the plugin. - /// - [JsonProperty] - public string DownloadLinkTesting { get; init; } + /// + /// Gets the download link used to get testing versions of the plugin. + /// + [JsonProperty] + public string DownloadLinkTesting { get; init; } - /// - /// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority. - /// - [JsonProperty] - public int LoadPriority { get; init; } + /// + /// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority. + /// + [JsonProperty] + public int LoadPriority { get; init; } - /// - /// Gets a list of screenshot image URLs to show in the plugin installer. - /// - public List? ImageUrls { get; init; } + /// + /// Gets a list of screenshot image URLs to show in the plugin installer. + /// + public List? ImageUrls { get; init; } - /// - /// Gets an URL for the plugin's icon. - /// - public string? IconUrl { get; init; } - } + /// + /// Gets an URL for the plugin's icon. + /// + public string? IconUrl { get; init; } } diff --git a/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs b/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs index 46aa2c351..2909ff981 100644 --- a/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs +++ b/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs @@ -1,28 +1,27 @@ -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Values representing plugin repository state. +/// +internal enum PluginRepositoryState { /// - /// Values representing plugin repository state. + /// State is unknown. /// - internal enum PluginRepositoryState - { - /// - /// State is unknown. - /// - Unknown, + Unknown, - /// - /// Currently loading. - /// - InProgress, + /// + /// Currently loading. + /// + InProgress, - /// - /// Load was successful. - /// - Success, + /// + /// Load was successful. + /// + Success, - /// - /// Load failed. - /// - Fail, - } + /// + /// Load failed. + /// + Fail, } diff --git a/Dalamud/Plugin/Internal/Types/PluginState.cs b/Dalamud/Plugin/Internal/Types/PluginState.cs index f32543b39..da5fcf977 100644 --- a/Dalamud/Plugin/Internal/Types/PluginState.cs +++ b/Dalamud/Plugin/Internal/Types/PluginState.cs @@ -1,33 +1,32 @@ -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Values representing plugin load state. +/// +internal enum PluginState { /// - /// Values representing plugin load state. + /// Plugin is defined, but unloaded. /// - internal enum PluginState - { - /// - /// Plugin is defined, but unloaded. - /// - Unloaded, + Unloaded, - /// - /// Plugin has thrown an error during unload. - /// - UnloadError, + /// + /// Plugin has thrown an error during unload. + /// + UnloadError, - /// - /// Currently loading. - /// - InProgress, + /// + /// Currently loading. + /// + InProgress, - /// - /// Load is successful. - /// - Loaded, + /// + /// Load is successful. + /// + Loaded, - /// - /// Plugin has thrown an error during loading. - /// - LoadError, - } + /// + /// Plugin has thrown an error during loading. + /// + LoadError, } diff --git a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs index f0394b9b7..f71c39cab 100644 --- a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs +++ b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs @@ -1,30 +1,29 @@ using System; -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Plugin update status. +/// +internal class PluginUpdateStatus { /// - /// Plugin update status. + /// Gets or sets the plugin internal name. /// - internal class PluginUpdateStatus - { - /// - /// Gets or sets the plugin internal name. - /// - public string InternalName { get; set; } + public string InternalName { get; set; } - /// - /// Gets or sets the plugin name. - /// - public string Name { get; set; } + /// + /// Gets or sets the plugin name. + /// + public string Name { get; set; } - /// - /// Gets or sets the plugin version. - /// - public Version Version { get; set; } + /// + /// Gets or sets the plugin version. + /// + public Version Version { get; set; } - /// - /// Gets or sets a value indicating whether the plugin was updated. - /// - public bool WasUpdated { get; set; } - } + /// + /// Gets or sets a value indicating whether the plugin was updated. + /// + public bool WasUpdated { get; set; } } diff --git a/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs b/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs index cbb989159..973276227 100644 --- a/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs @@ -1,18 +1,17 @@ using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal.Types +namespace Dalamud.Plugin.Internal.Types; + +/// +/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as +/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. +/// +internal record RemotePluginManifest : PluginManifest { /// - /// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as - /// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. + /// Gets or sets the plugin repository this manifest came from. Used in reporting which third party repo a manifest + /// may have come from in the plugins available view. This functionality should not be included in the plugin master. /// - internal record RemotePluginManifest : PluginManifest - { - /// - /// Gets or sets the plugin repository this manifest came from. Used in reporting which third party repo a manifest - /// may have come from in the plugins available view. This functionality should not be included in the plugin master. - /// - [JsonIgnore] - public PluginRepository SourceRepo { get; set; } = null; - } + [JsonIgnore] + public PluginRepository SourceRepo { get; set; } = null; } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs index 053331dce..5cc0ccae9 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs @@ -1,36 +1,35 @@ using System; -namespace Dalamud.Plugin.Ipc.Exceptions +namespace Dalamud.Plugin.Ipc.Exceptions; + +/// +/// This exception is thrown when an IPC errors are encountered. +/// +public abstract class IpcError : Exception { /// - /// This exception is thrown when an IPC errors are encountered. + /// Initializes a new instance of the class. /// - public abstract class IpcError : Exception + public IpcError() { - /// - /// Initializes a new instance of the class. - /// - public IpcError() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public IpcError(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public IpcError(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception. - public IpcError(string message, Exception ex) - : base(message, ex) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public IpcError(string message, Exception ex) + : base(message, ex) + { } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs index bb5f64070..47b204975 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs @@ -1,19 +1,18 @@ -namespace Dalamud.Plugin.Ipc.Exceptions +namespace Dalamud.Plugin.Ipc.Exceptions; + +/// +/// This exception is thrown when an IPC method is invoked and the number of types does not match what was previously registered. +/// +public class IpcLengthMismatchError : IpcError { /// - /// This exception is thrown when an IPC method is invoked and the number of types does not match what was previously registered. + /// Initializes a new instance of the class. /// - public class IpcLengthMismatchError : IpcError + /// Name of the IPC method. + /// The amount of types requested when checking out the IPC. + /// The amount of types registered by the IPC. + public IpcLengthMismatchError(string name, int requestedLength, int actualLength) + : base($"IPC method {name} has a different number of types than was requested. {requestedLength} != {actualLength}") { - /// - /// Initializes a new instance of the class. - /// - /// Name of the IPC method. - /// The amount of types requested when checking out the IPC. - /// The amount of types registered by the IPC. - public IpcLengthMismatchError(string name, int requestedLength, int actualLength) - : base($"IPC method {name} has a different number of types than was requested. {requestedLength} != {actualLength}") - { - } } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs index 6bfd87ba8..1d7803369 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs @@ -1,17 +1,16 @@ -namespace Dalamud.Plugin.Ipc.Exceptions +namespace Dalamud.Plugin.Ipc.Exceptions; + +/// +/// This exception is thrown when an IPC method is invoked, but no actions or funcs have been registered yet. +/// +public class IpcNotReadyError : IpcError { /// - /// This exception is thrown when an IPC method is invoked, but no actions or funcs have been registered yet. + /// Initializes a new instance of the class. /// - public class IpcNotReadyError : IpcError + /// Name of the IPC method. + public IpcNotReadyError(string name) + : base($"IPC method {name} was not registered yet") { - /// - /// Initializes a new instance of the class. - /// - /// Name of the IPC method. - public IpcNotReadyError(string name) - : base($"IPC method {name} was not registered yet") - { - } } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs index 2de5adce8..1aa191b78 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs @@ -1,22 +1,21 @@ using System; -namespace Dalamud.Plugin.Ipc.Exceptions +namespace Dalamud.Plugin.Ipc.Exceptions; + +/// +/// This exception is thrown when an IPC method is checked out, but the type does not match what was previously registered. +/// +public class IpcTypeMismatchError : IpcError { /// - /// This exception is thrown when an IPC method is checked out, but the type does not match what was previously registered. + /// Initializes a new instance of the class. /// - public class IpcTypeMismatchError : IpcError + /// Name of the IPC method. + /// The before type. + /// The after type. + /// The exception that is the cause of the current exception. + public IpcTypeMismatchError(string name, Type requestedType, Type actualType, Exception ex) + : base($"IPC method {name} blew up when converting from {requestedType.Name} to {actualType}", ex) { - /// - /// Initializes a new instance of the class. - /// - /// Name of the IPC method. - /// The before type. - /// The after type. - /// The exception that is the cause of the current exception. - public IpcTypeMismatchError(string name, Type requestedType, Type actualType, Exception ex) - : base($"IPC method {name} blew up when converting from {requestedType.Name} to {actualType}", ex) - { - } } } diff --git a/Dalamud/Plugin/Ipc/ICallGateProvider.cs b/Dalamud/Plugin/Ipc/ICallGateProvider.cs index 62b95c809..333878d07 100644 --- a/Dalamud/Plugin/Ipc/ICallGateProvider.cs +++ b/Dalamud/Plugin/Ipc/ICallGateProvider.cs @@ -4,178 +4,177 @@ using Dalamud.Plugin.Ipc.Internal; #pragma warning disable SA1402 // File may only contain a single type -namespace Dalamud.Plugin.Ipc +namespace Dalamud.Plugin.Ipc; + +/// +public interface ICallGateProvider { - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(); - } + /// + public void SendMessage(); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1); - } + /// + public void SendMessage(T1 arg1); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2); - } + /// + public void SendMessage(T1 arg1, T2 arg2); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3); - } + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - } + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); - } + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); - } + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); - } + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); +} - /// - public interface ICallGateProvider - { - /// - public void RegisterAction(Action action); +/// +public interface ICallGateProvider +{ + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); - } + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); } #pragma warning restore SA1402 // File may only contain a single type diff --git a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs index d5ddd1329..94afa200a 100644 --- a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs +++ b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs @@ -4,151 +4,150 @@ using Dalamud.Plugin.Ipc.Internal; #pragma warning disable SA1402 // File may only contain a single type -namespace Dalamud.Plugin.Ipc +namespace Dalamud.Plugin.Ipc; + +/// +public interface ICallGateSubscriber { - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(); + /// + public void InvokeAction(); - /// - public TRet InvokeFunc(); - } + /// + public TRet InvokeFunc(); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1); + /// + public void InvokeAction(T1 arg1); - /// - public TRet InvokeFunc(T1 arg1); - } + /// + public TRet InvokeFunc(T1 arg1); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2); + /// + public void InvokeAction(T1 arg1, T2 arg2); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); +} - /// - public interface ICallGateSubscriber - { - /// - public void Subscribe(Action action); +/// +public interface ICallGateSubscriber +{ + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); - } + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); } #pragma warning restore SA1402 // File may only contain a single type diff --git a/Dalamud/Plugin/Ipc/Internal/CallGate.cs b/Dalamud/Plugin/Ipc/Internal/CallGate.cs index dd89b02bc..a9a422c05 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGate.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGate.cs @@ -1,31 +1,30 @@ using System.Collections.Generic; -namespace Dalamud.Plugin.Ipc.Internal +namespace Dalamud.Plugin.Ipc.Internal; + +/// +/// This class facilitates inter-plugin communication. +/// +internal class CallGate { + private readonly Dictionary gates = new(); + /// - /// This class facilitates inter-plugin communication. + /// Initializes a new instance of the class. /// - internal class CallGate + internal CallGate() { - private readonly Dictionary gates = new(); + } - /// - /// Initializes a new instance of the class. - /// - internal CallGate() - { - } - - /// - /// Gets the provider associated with the specified name. - /// - /// Name of the IPC registration. - /// A CallGate registered under the given name. - public CallGateChannel GetOrCreateChannel(string name) - { - if (!this.gates.TryGetValue(name, out var gate)) - gate = this.gates[name] = new CallGateChannel(name); - return gate; - } + /// + /// Gets the provider associated with the specified name. + /// + /// Name of the IPC registration. + /// A CallGate registered under the given name. + public CallGateChannel GetOrCreateChannel(string name) + { + if (!this.gates.TryGetValue(name, out var gate)) + gate = this.gates[name] = new CallGateChannel(name); + return gate; } } diff --git a/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs b/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs index 7dbda203e..c4ea95cd5 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs @@ -7,180 +7,179 @@ using Dalamud.Plugin.Ipc.Exceptions; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Plugin.Ipc.Internal +namespace Dalamud.Plugin.Ipc.Internal; + +/// +/// This class implements the channels and serialization needed for the typed gates to interact with each other. +/// +internal class CallGateChannel { /// - /// This class implements the channels and serialization needed for the typed gates to interact with each other. + /// Initializes a new instance of the class. /// - internal class CallGateChannel + /// The name of this IPC registration. + public CallGateChannel(string name) { - /// - /// Initializes a new instance of the class. - /// - /// The name of this IPC registration. - public CallGateChannel(string name) + this.Name = name; + } + + /// + /// Gets the name of the IPC registration. + /// + public string Name { get; init; } + + /// + /// Gets a list of delegate subscriptions for when SendMessage is called. + /// + public List Subscriptions { get; } = new(); + + /// + /// Gets or sets an action for when InvokeAction is called. + /// + public Delegate Action { get; set; } + + /// + /// Gets or sets a func for when InvokeFunc is called. + /// + public Delegate Func { get; set; } + + /// + /// Invoke all actions that have subscribed to this IPC. + /// + /// Message arguments. + internal void SendMessage(object?[]? args) + { + if (this.Subscriptions.Count == 0) + return; + + foreach (var subscription in this.Subscriptions) { - this.Name = name; - } - - /// - /// Gets the name of the IPC registration. - /// - public string Name { get; init; } - - /// - /// Gets a list of delegate subscriptions for when SendMessage is called. - /// - public List Subscriptions { get; } = new(); - - /// - /// Gets or sets an action for when InvokeAction is called. - /// - public Delegate Action { get; set; } - - /// - /// Gets or sets a func for when InvokeFunc is called. - /// - public Delegate Func { get; set; } - - /// - /// Invoke all actions that have subscribed to this IPC. - /// - /// Message arguments. - internal void SendMessage(object?[]? args) - { - if (this.Subscriptions.Count == 0) - return; - - foreach (var subscription in this.Subscriptions) - { - var methodInfo = subscription.GetMethodInfo(); - this.CheckAndConvertArgs(args, methodInfo); - - subscription.DynamicInvoke(args); - } - } - - /// - /// Invoke an action registered for inter-plugin communication. - /// - /// Action arguments. - /// This is thrown when the IPC publisher has not registered a func for calling yet. - internal void InvokeAction(object?[]? args) - { - if (this.Action == null) - throw new IpcNotReadyError(this.Name); - - var methodInfo = this.Action.GetMethodInfo(); + var methodInfo = subscription.GetMethodInfo(); this.CheckAndConvertArgs(args, methodInfo); - this.Action.DynamicInvoke(args); + subscription.DynamicInvoke(args); } + } - /// - /// Invoke a function registered for inter-plugin communication. - /// - /// Func arguments. - /// The return value. - /// The return type. - /// This is thrown when the IPC publisher has not registered a func for calling yet. - internal TRet InvokeFunc(object?[]? args) + /// + /// Invoke an action registered for inter-plugin communication. + /// + /// Action arguments. + /// This is thrown when the IPC publisher has not registered a func for calling yet. + internal void InvokeAction(object?[]? args) + { + if (this.Action == null) + throw new IpcNotReadyError(this.Name); + + var methodInfo = this.Action.GetMethodInfo(); + this.CheckAndConvertArgs(args, methodInfo); + + this.Action.DynamicInvoke(args); + } + + /// + /// Invoke a function registered for inter-plugin communication. + /// + /// Func arguments. + /// The return value. + /// The return type. + /// This is thrown when the IPC publisher has not registered a func for calling yet. + internal TRet InvokeFunc(object?[]? args) + { + if (this.Func == null) + throw new IpcNotReadyError(this.Name); + + var methodInfo = this.Func.GetMethodInfo(); + this.CheckAndConvertArgs(args, methodInfo); + + var result = this.Func.DynamicInvoke(args); + + if (typeof(TRet) != methodInfo.ReturnType) + result = this.ConvertObject(result, typeof(TRet)); + + return (TRet)result; + } + + private void CheckAndConvertArgs(object?[]? args, MethodInfo methodInfo) + { + var paramTypes = methodInfo.GetParameters() + .Select(pi => pi.ParameterType).ToArray(); + + if (args.Length != paramTypes.Length) + throw new IpcLengthMismatchError(this.Name, args.Length, paramTypes.Length); + + for (var i = 0; i < args.Length; i++) { - if (this.Func == null) - throw new IpcNotReadyError(this.Name); + var arg = args[i]; + var paramType = paramTypes[i]; - var methodInfo = this.Func.GetMethodInfo(); - this.CheckAndConvertArgs(args, methodInfo); - - var result = this.Func.DynamicInvoke(args); - - if (typeof(TRet) != methodInfo.ReturnType) - result = this.ConvertObject(result, typeof(TRet)); - - return (TRet)result; - } - - private void CheckAndConvertArgs(object?[]? args, MethodInfo methodInfo) - { - var paramTypes = methodInfo.GetParameters() - .Select(pi => pi.ParameterType).ToArray(); - - if (args.Length != paramTypes.Length) - throw new IpcLengthMismatchError(this.Name, args.Length, paramTypes.Length); - - for (var i = 0; i < args.Length; i++) + var argType = arg.GetType(); + if (argType != paramType) { - var arg = args[i]; - var paramType = paramTypes[i]; - - var argType = arg.GetType(); - if (argType != paramType) + // check the inheritance tree + var baseTypes = this.GenerateTypes(argType.BaseType); + if (baseTypes.Any(t => t == paramType)) { - // check the inheritance tree - var baseTypes = this.GenerateTypes(argType.BaseType); - if (baseTypes.Any(t => t == paramType)) - { - // The source type inherits from the destination type - continue; - } - - args[i] = this.ConvertObject(arg, paramType); + // The source type inherits from the destination type + continue; } - } - } - private IEnumerable GenerateTypes(Type type) - { - while (type != null && type != typeof(object)) - { - yield return type; - type = type.BaseType; - } - } - - private object? ConvertObject(object? obj, Type type) - { - var json = JsonConvert.SerializeObject(obj); - - try - { - return JsonConvert.DeserializeObject(json, type); - } - catch (Exception) - { - Log.Verbose($"Could not convert {obj.GetType().Name} to {type.Name}, will look for compatible type instead"); - } - - // If type -> type fails, try to find an object that matches. - var sourceType = obj.GetType(); - var fieldNames = sourceType.GetFields(BindingFlags.Public | BindingFlags.Instance) - .Select(f => f.Name); - var propNames = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name); - - var assignableTypes = type.Assembly.GetTypes() - .Where(t => type.IsAssignableFrom(t) && type != t) - .ToArray(); - - foreach (var assignableType in assignableTypes) - { - var matchesFields = assignableType.GetFields().All(f => fieldNames.Contains(f.Name)); - var matchesProps = assignableType.GetProperties().All(p => propNames.Contains(p.Name)); - if (matchesFields && matchesProps) - { - type = assignableType; - break; - } - } - - try - { - return JsonConvert.DeserializeObject(json, type); - } - catch (Exception ex) - { - throw new IpcTypeMismatchError(this.Name, obj.GetType(), type, ex); + args[i] = this.ConvertObject(arg, paramType); } } } + + private IEnumerable GenerateTypes(Type type) + { + while (type != null && type != typeof(object)) + { + yield return type; + type = type.BaseType; + } + } + + private object? ConvertObject(object? obj, Type type) + { + var json = JsonConvert.SerializeObject(obj); + + try + { + return JsonConvert.DeserializeObject(json, type); + } + catch (Exception) + { + Log.Verbose($"Could not convert {obj.GetType().Name} to {type.Name}, will look for compatible type instead"); + } + + // If type -> type fails, try to find an object that matches. + var sourceType = obj.GetType(); + var fieldNames = sourceType.GetFields(BindingFlags.Public | BindingFlags.Instance) + .Select(f => f.Name); + var propNames = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(p => p.Name); + + var assignableTypes = type.Assembly.GetTypes() + .Where(t => type.IsAssignableFrom(t) && type != t) + .ToArray(); + + foreach (var assignableType in assignableTypes) + { + var matchesFields = assignableType.GetFields().All(f => fieldNames.Contains(f.Name)); + var matchesProps = assignableType.GetProperties().All(p => propNames.Contains(p.Name)); + if (matchesFields && matchesProps) + { + type = assignableType; + break; + } + } + + try + { + return JsonConvert.DeserializeObject(json, type); + } + catch (Exception ex) + { + throw new IpcTypeMismatchError(this.Name, obj.GetType(), type, ex); + } + } } diff --git a/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs b/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs index da1bcda49..39d5b9f4d 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs @@ -2,349 +2,348 @@ using System; #pragma warning disable SA1402 // File may only contain a single type -namespace Dalamud.Plugin.Ipc.Internal +namespace Dalamud.Plugin.Ipc.Internal; + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage() - => base.SendMessage(); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction() - => base.InvokeAction(); - - /// - public TRet InvokeFunc() - => this.InvokeFunc(); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage() + => base.SendMessage(); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction() + => base.InvokeAction(); + + /// + public TRet InvokeFunc() + => this.InvokeFunc(); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1) - => base.SendMessage(arg1); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1) - => base.InvokeAction(arg1); - - /// - public TRet InvokeFunc(T1 arg1) - => this.InvokeFunc(arg1); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1) + => base.SendMessage(arg1); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1) + => base.InvokeAction(arg1); + + /// + public TRet InvokeFunc(T1 arg1) + => this.InvokeFunc(arg1); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2) - => base.SendMessage(arg1, arg2); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2) - => base.InvokeAction(arg1, arg2); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2) - => this.InvokeFunc(arg1, arg2); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2) + => base.SendMessage(arg1, arg2); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2) + => base.InvokeAction(arg1, arg2); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2) + => this.InvokeFunc(arg1, arg2); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3) - => base.SendMessage(arg1, arg2, arg3); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3) - => base.InvokeAction(arg1, arg2, arg3); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3) - => this.InvokeFunc(arg1, arg2, arg3); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3) + => base.SendMessage(arg1, arg2, arg3); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3) + => base.InvokeAction(arg1, arg2, arg3); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3) + => this.InvokeFunc(arg1, arg2, arg3); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - => base.SendMessage(arg1, arg2, arg3, arg4); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - => base.InvokeAction(arg1, arg2, arg3, arg4); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - => this.InvokeFunc(arg1, arg2, arg3, arg4); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + => base.SendMessage(arg1, arg2, arg3, arg4); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + => base.InvokeAction(arg1, arg2, arg3, arg4); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + => this.InvokeFunc(arg1, arg2, arg3, arg4); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7); } - /// - internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + +/// +internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +{ + /// + public CallGatePubSub(string name) + : base(name) { - /// - public CallGatePubSub(string name) - : base(name) - { - } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } #pragma warning restore SA1402 // File may only contain a single type diff --git a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs index 19b99de05..40c0c4a59 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs @@ -2,90 +2,89 @@ using System; using Dalamud.Plugin.Ipc.Exceptions; -namespace Dalamud.Plugin.Ipc.Internal +namespace Dalamud.Plugin.Ipc.Internal; + +/// +/// This class facilitates inter-plugin communication. +/// +internal abstract class CallGatePubSubBase { /// - /// This class facilitates inter-plugin communication. + /// Initializes a new instance of the class. /// - internal abstract class CallGatePubSubBase + /// The name of the IPC registration. + public CallGatePubSubBase(string name) { - /// - /// Initializes a new instance of the class. - /// - /// The name of the IPC registration. - public CallGatePubSubBase(string name) - { - this.Channel = Service.Get().GetOrCreateChannel(name); - } - - /// - /// Gets the underlying channel implementation. - /// - protected CallGateChannel Channel { get; init; } - - /// - /// Removes a registered Action from inter-plugin communication. - /// - public void UnregisterAction() - => this.Channel.Action = null; - - /// - /// Removes a registered Func from inter-plugin communication. - /// - public void UnregisterFunc() - => this.Channel.Func = null; - - /// - /// Registers an Action for inter-plugin communication. - /// - /// Action to register. - private protected void RegisterAction(Delegate action) - => this.Channel.Action = action; - - /// - /// Registers a Func for inter-plugin communication. - /// - /// Func to register. - private protected void RegisterFunc(Delegate func) - => this.Channel.Func = func; - - /// - /// Subscribe an expression to this registration. - /// - /// Action to subscribe. - private protected void Subscribe(Delegate action) - => this.Channel.Subscriptions.Add(action); - - /// - /// Unsubscribe an expression from this registration. - /// - /// Action to unsubscribe. - private protected void Unsubscribe(Delegate action) - => this.Channel.Subscriptions.Remove(action); - - /// - /// Invoke an action registered for inter-plugin communication. - /// - /// Action arguments. - /// This is thrown when the IPC publisher has not registered an action for calling yet. - private protected void InvokeAction(params object?[]? args) - => this.Channel.InvokeAction(args); - - /// - /// Invoke a function registered for inter-plugin communication. - /// - /// Parameter args. - /// The return value. - /// The return type. - /// This is thrown when the IPC publisher has not registered a func for calling yet. - private protected TRet InvokeFunc(params object?[]? args) - => this.Channel.InvokeFunc(args); - - /// - /// Invoke all actions that have subscribed to this IPC. - /// - /// Delegate arguments. - private protected void SendMessage(params object?[]? args) - => this.Channel.SendMessage(args); + this.Channel = Service.Get().GetOrCreateChannel(name); } + + /// + /// Gets the underlying channel implementation. + /// + protected CallGateChannel Channel { get; init; } + + /// + /// Removes a registered Action from inter-plugin communication. + /// + public void UnregisterAction() + => this.Channel.Action = null; + + /// + /// Removes a registered Func from inter-plugin communication. + /// + public void UnregisterFunc() + => this.Channel.Func = null; + + /// + /// Registers an Action for inter-plugin communication. + /// + /// Action to register. + private protected void RegisterAction(Delegate action) + => this.Channel.Action = action; + + /// + /// Registers a Func for inter-plugin communication. + /// + /// Func to register. + private protected void RegisterFunc(Delegate func) + => this.Channel.Func = func; + + /// + /// Subscribe an expression to this registration. + /// + /// Action to subscribe. + private protected void Subscribe(Delegate action) + => this.Channel.Subscriptions.Add(action); + + /// + /// Unsubscribe an expression from this registration. + /// + /// Action to unsubscribe. + private protected void Unsubscribe(Delegate action) + => this.Channel.Subscriptions.Remove(action); + + /// + /// Invoke an action registered for inter-plugin communication. + /// + /// Action arguments. + /// This is thrown when the IPC publisher has not registered an action for calling yet. + private protected void InvokeAction(params object?[]? args) + => this.Channel.InvokeAction(args); + + /// + /// Invoke a function registered for inter-plugin communication. + /// + /// Parameter args. + /// The return value. + /// The return type. + /// This is thrown when the IPC publisher has not registered a func for calling yet. + private protected TRet InvokeFunc(params object?[]? args) + => this.Channel.InvokeFunc(args); + + /// + /// Invoke all actions that have subscribed to this IPC. + /// + /// Delegate arguments. + private protected void SendMessage(params object?[]? args) + => this.Channel.SendMessage(args); } diff --git a/Dalamud/Plugin/PluginLoadReason.cs b/Dalamud/Plugin/PluginLoadReason.cs index 846525b0f..ade95ae67 100644 --- a/Dalamud/Plugin/PluginLoadReason.cs +++ b/Dalamud/Plugin/PluginLoadReason.cs @@ -1,33 +1,32 @@ -namespace Dalamud.Plugin +namespace Dalamud.Plugin; + +/// +/// This enum reflects reasons for loading a plugin. +/// +public enum PluginLoadReason { /// - /// This enum reflects reasons for loading a plugin. + /// We don't know why this plugin was loaded. /// - public enum PluginLoadReason - { - /// - /// We don't know why this plugin was loaded. - /// - Unknown, + Unknown, - /// - /// This plugin was loaded because it was installed with the plugin installer. - /// - Installer, + /// + /// This plugin was loaded because it was installed with the plugin installer. + /// + Installer, - /// - /// This plugin was loaded because it was just updated. - /// - Update, + /// + /// This plugin was loaded because it was just updated. + /// + Update, - /// - /// This plugin was loaded because it was told to reload. - /// - Reload, + /// + /// This plugin was loaded because it was told to reload. + /// + Reload, - /// - /// This plugin was loaded because the game was started or Dalamud was reinjected. - /// - Boot, - } + /// + /// This plugin was loaded because the game was started or Dalamud was reinjected. + /// + Boot, } diff --git a/Dalamud/SafeMemory.cs b/Dalamud/SafeMemory.cs index 32972ec61..3a93ad652 100644 --- a/Dalamud/SafeMemory.cs +++ b/Dalamud/SafeMemory.cs @@ -2,263 +2,262 @@ using System; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud +namespace Dalamud; + +/// +/// Class facilitating safe memory access. +/// +/// +/// Attention! The performance of these methods is severely worse than regular calls. +/// Please consider using those instead in performance-critical code. +/// +public static class SafeMemory { + private static readonly IntPtr Handle; + + static SafeMemory() + { + Handle = Imports.GetCurrentProcess(); + } + /// - /// Class facilitating safe memory access. + /// Read a byte array from the current process. + /// + /// The address to read from. + /// The amount of bytes to read. + /// The result buffer. + /// Whether or not the read succeeded. + public static bool ReadBytes(IntPtr address, int count, out byte[] buffer) + { + buffer = new byte[count <= 0 ? 0 : count]; + return Imports.ReadProcessMemory(Handle, address, buffer, buffer.Length, out _); + } + + /// + /// Write a byte array to the current process. + /// + /// The address to write to. + /// The buffer to write. + /// Whether or not the write succeeded. + public static bool WriteBytes(IntPtr address, byte[] buffer) + { + return Imports.WriteProcessMemory(Handle, address, buffer, buffer.Length, out _); + } + + /// + /// Read an object of the specified struct from the current process. + /// + /// The type of the struct. + /// The address to read from. + /// The resulting object. + /// Whether or not the read succeeded. + public static bool Read(IntPtr address, out T result) where T : struct + { + if (!ReadBytes(address, SizeCache.Size, out var buffer)) + { + result = default; + return false; + } + + using var mem = new LocalMemory(buffer.Length); + mem.Write(buffer); + + result = mem.Read(); + return true; + } + + /// + /// Read an array of objects of the specified struct from the current process. + /// + /// The type of the struct. + /// The address to read from. + /// The length of the array. + /// An array of the read objects, or null if any entry of the array failed to read. + public static T[]? Read(IntPtr address, int count) where T : struct + { + var size = SizeOf(); + if (!ReadBytes(address, count * size, out var buffer)) + return null; + var result = new T[count]; + using var mem = new LocalMemory(buffer.Length); + mem.Write(buffer); + for (var i = 0; i < result.Length; i++) + result[i] = mem.Read(i * size); + return result; + } + + /// + /// Write a struct to the current process. + /// + /// The type of the struct. + /// The address to write to. + /// The object to write. + /// Whether or not the write succeeded. + public static bool Write(IntPtr address, T obj) where T : struct + { + using var mem = new LocalMemory(SizeCache.Size); + mem.Write(obj); + return WriteBytes(address, mem.Read()); + } + + /// + /// Write an array of structs to the current process. + /// + /// The type of the structs. + /// The address to write to. + /// The array to write. + /// Whether or not the write succeeded. + public static bool Write(IntPtr address, T[] objArray) where T : struct + { + if (objArray == null || objArray.Length == 0) + return true; + var size = SizeCache.Size; + using var mem = new LocalMemory(objArray.Length * size); + for (var i = 0; i < objArray.Length; i++) + mem.Write(objArray[i], i * size); + return WriteBytes(address, mem.Read()); + } + + /// + /// Read a string from the current process(UTF-8). /// /// - /// Attention! The performance of these methods is severely worse than regular calls. - /// Please consider using those instead in performance-critical code. + /// Attention! This will use the .NET Encoding.UTF8 class to decode the text. + /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, + /// since Encoding.UTF8 destroys the FFXIV payload structure. /// - public static class SafeMemory + /// The address to read from. + /// The maximum length of the string. + /// The read string, or null in case the read was not successful. + public static string? ReadString(IntPtr address, int maxLength = 256) { - private static readonly IntPtr Handle; + return ReadString(address, Encoding.UTF8, maxLength); + } - static SafeMemory() - { - Handle = Imports.GetCurrentProcess(); - } + /// + /// Read a string from the current process(UTF-8). + /// + /// + /// Attention! This will use the .NET Encoding class to decode the text. + /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, + /// since Encoding may destroy the FFXIV payload structure. + /// + /// The address to read from. + /// The encoding to use to decode the string. + /// The maximum length of the string. + /// The read string, or null in case the read was not successful. + public static string? ReadString(IntPtr address, Encoding encoding, int maxLength = 256) + { + if (!ReadBytes(address, maxLength, out var buffer)) + return null; + var data = encoding.GetString(buffer); + var eosPos = data.IndexOf('\0'); + return eosPos == -1 ? data : data.Substring(0, eosPos); + } - /// - /// Read a byte array from the current process. - /// - /// The address to read from. - /// The amount of bytes to read. - /// The result buffer. - /// Whether or not the read succeeded. - public static bool ReadBytes(IntPtr address, int count, out byte[] buffer) - { - buffer = new byte[count <= 0 ? 0 : count]; - return Imports.ReadProcessMemory(Handle, address, buffer, buffer.Length, out _); - } + /// + /// Write a string to the current process. + /// + /// + /// Attention! This will use the .NET Encoding class to encode the text. + /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, + /// since Encoding may destroy the FFXIV payload structure. + /// + /// The address to write to. + /// The string to write. + /// Whether or not the write succeeded. + public static bool WriteString(IntPtr address, string str) + { + return WriteString(address, str, Encoding.UTF8); + } - /// - /// Write a byte array to the current process. - /// - /// The address to write to. - /// The buffer to write. - /// Whether or not the write succeeded. - public static bool WriteBytes(IntPtr address, byte[] buffer) - { - return Imports.WriteProcessMemory(Handle, address, buffer, buffer.Length, out _); - } - - /// - /// Read an object of the specified struct from the current process. - /// - /// The type of the struct. - /// The address to read from. - /// The resulting object. - /// Whether or not the read succeeded. - public static bool Read(IntPtr address, out T result) where T : struct - { - if (!ReadBytes(address, SizeCache.Size, out var buffer)) - { - result = default; - return false; - } - - using var mem = new LocalMemory(buffer.Length); - mem.Write(buffer); - - result = mem.Read(); + /// + /// Write a string to the current process. + /// + /// + /// Attention! This will use the .NET Encoding class to encode the text. + /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, + /// since Encoding may destroy the FFXIV payload structure. + /// + /// The address to write to. + /// The string to write. + /// The encoding to use. + /// Whether or not the write succeeded. + public static bool WriteString(IntPtr address, string str, Encoding encoding) + { + if (string.IsNullOrEmpty(str)) return true; + return WriteBytes(address, encoding.GetBytes(str + "\0")); + } + + /// + /// Get the size of the passed type. + /// + /// The type to inspect. + /// The size of the passed type. + public static int SizeOf() where T : struct + { + return SizeCache.Size; + } + + private static class SizeCache + { + // ReSharper disable once StaticMemberInGenericType + public static readonly int Size; + + static SizeCache() + { + var type = typeof(T); + if (type.IsEnum) + type = type.GetEnumUnderlyingType(); + Size = Type.GetTypeCode(type) == TypeCode.Boolean ? 1 : Marshal.SizeOf(type); + } + } + + private static class Imports + { + [DllImport("kernel32", SetLastError = true)] + public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead); + + [DllImport("kernel32", SetLastError = true)] + public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); + + [DllImport("kernel32", SetLastError = false)] + public static extern IntPtr GetCurrentProcess(); + } + + private sealed class LocalMemory : IDisposable + { + private readonly int size; + + private IntPtr hGlobal; + + public LocalMemory(int size) + { + this.size = size; + this.hGlobal = Marshal.AllocHGlobal(this.size); } - /// - /// Read an array of objects of the specified struct from the current process. - /// - /// The type of the struct. - /// The address to read from. - /// The length of the array. - /// An array of the read objects, or null if any entry of the array failed to read. - public static T[]? Read(IntPtr address, int count) where T : struct + ~LocalMemory() => this.Dispose(); + + public byte[] Read() { - var size = SizeOf(); - if (!ReadBytes(address, count * size, out var buffer)) - return null; - var result = new T[count]; - using var mem = new LocalMemory(buffer.Length); - mem.Write(buffer); - for (var i = 0; i < result.Length; i++) - result[i] = mem.Read(i * size); - return result; + var bytes = new byte[this.size]; + Marshal.Copy(this.hGlobal, bytes, 0, this.size); + return bytes; } - /// - /// Write a struct to the current process. - /// - /// The type of the struct. - /// The address to write to. - /// The object to write. - /// Whether or not the write succeeded. - public static bool Write(IntPtr address, T obj) where T : struct + public T Read(int offset = 0) => (T)Marshal.PtrToStructure(this.hGlobal + offset, typeof(T)); + + public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size); + + public void Write(T data, int offset = 0) => Marshal.StructureToPtr(data, this.hGlobal + offset, false); + + public void Dispose() { - using var mem = new LocalMemory(SizeCache.Size); - mem.Write(obj); - return WriteBytes(address, mem.Read()); - } - - /// - /// Write an array of structs to the current process. - /// - /// The type of the structs. - /// The address to write to. - /// The array to write. - /// Whether or not the write succeeded. - public static bool Write(IntPtr address, T[] objArray) where T : struct - { - if (objArray == null || objArray.Length == 0) - return true; - var size = SizeCache.Size; - using var mem = new LocalMemory(objArray.Length * size); - for (var i = 0; i < objArray.Length; i++) - mem.Write(objArray[i], i * size); - return WriteBytes(address, mem.Read()); - } - - /// - /// Read a string from the current process(UTF-8). - /// - /// - /// Attention! This will use the .NET Encoding.UTF8 class to decode the text. - /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, - /// since Encoding.UTF8 destroys the FFXIV payload structure. - /// - /// The address to read from. - /// The maximum length of the string. - /// The read string, or null in case the read was not successful. - public static string? ReadString(IntPtr address, int maxLength = 256) - { - return ReadString(address, Encoding.UTF8, maxLength); - } - - /// - /// Read a string from the current process(UTF-8). - /// - /// - /// Attention! This will use the .NET Encoding class to decode the text. - /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, - /// since Encoding may destroy the FFXIV payload structure. - /// - /// The address to read from. - /// The encoding to use to decode the string. - /// The maximum length of the string. - /// The read string, or null in case the read was not successful. - public static string? ReadString(IntPtr address, Encoding encoding, int maxLength = 256) - { - if (!ReadBytes(address, maxLength, out var buffer)) - return null; - var data = encoding.GetString(buffer); - var eosPos = data.IndexOf('\0'); - return eosPos == -1 ? data : data.Substring(0, eosPos); - } - - /// - /// Write a string to the current process. - /// - /// - /// Attention! This will use the .NET Encoding class to encode the text. - /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, - /// since Encoding may destroy the FFXIV payload structure. - /// - /// The address to write to. - /// The string to write. - /// Whether or not the write succeeded. - public static bool WriteString(IntPtr address, string str) - { - return WriteString(address, str, Encoding.UTF8); - } - - /// - /// Write a string to the current process. - /// - /// - /// Attention! This will use the .NET Encoding class to encode the text. - /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, - /// since Encoding may destroy the FFXIV payload structure. - /// - /// The address to write to. - /// The string to write. - /// The encoding to use. - /// Whether or not the write succeeded. - public static bool WriteString(IntPtr address, string str, Encoding encoding) - { - if (string.IsNullOrEmpty(str)) - return true; - return WriteBytes(address, encoding.GetBytes(str + "\0")); - } - - /// - /// Get the size of the passed type. - /// - /// The type to inspect. - /// The size of the passed type. - public static int SizeOf() where T : struct - { - return SizeCache.Size; - } - - private static class SizeCache - { - // ReSharper disable once StaticMemberInGenericType - public static readonly int Size; - - static SizeCache() - { - var type = typeof(T); - if (type.IsEnum) - type = type.GetEnumUnderlyingType(); - Size = Type.GetTypeCode(type) == TypeCode.Boolean ? 1 : Marshal.SizeOf(type); - } - } - - private static class Imports - { - [DllImport("kernel32", SetLastError = true)] - public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead); - - [DllImport("kernel32", SetLastError = true)] - public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); - - [DllImport("kernel32", SetLastError = false)] - public static extern IntPtr GetCurrentProcess(); - } - - private sealed class LocalMemory : IDisposable - { - private readonly int size; - - private IntPtr hGlobal; - - public LocalMemory(int size) - { - this.size = size; - this.hGlobal = Marshal.AllocHGlobal(this.size); - } - - ~LocalMemory() => this.Dispose(); - - public byte[] Read() - { - var bytes = new byte[this.size]; - Marshal.Copy(this.hGlobal, bytes, 0, this.size); - return bytes; - } - - public T Read(int offset = 0) => (T)Marshal.PtrToStructure(this.hGlobal + offset, typeof(T)); - - public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size); - - public void Write(T data, int offset = 0) => Marshal.StructureToPtr(data, this.hGlobal + offset, false); - - public void Dispose() - { - Marshal.FreeHGlobal(this.hGlobal); - this.hGlobal = IntPtr.Zero; - GC.SuppressFinalize(this); - } + Marshal.FreeHGlobal(this.hGlobal); + this.hGlobal = IntPtr.Zero; + GC.SuppressFinalize(this); } } } diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs index 203b9f286..dc9e78aa9 100644 --- a/Dalamud/Service{T}.cs +++ b/Dalamud/Service{T}.cs @@ -5,121 +5,120 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; -namespace Dalamud +namespace Dalamud; + +/// +/// Basic service locator. +/// +/// +/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. +/// +/// The class you want to store in the service locator. +internal static class Service where T : class { - /// - /// Basic service locator. - /// - /// - /// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. - /// - /// The class you want to store in the service locator. - internal static class Service where T : class + private static readonly ModuleLog Log = new("SVC"); + + private static T? instance; + + static Service() { - private static readonly ModuleLog Log = new("SVC"); + } - private static T? instance; + /// + /// Sets the type in the service locator to the given object. + /// + /// Object to set. + /// The set object. + public static T Set(T obj) + { + SetInstanceObject(obj); - static Service() + return instance!; + } + + /// + /// Sets the type in the service locator via the default parameterless constructor. + /// + /// The set object. + public static T Set() + { + if (instance != null) + throw new Exception($"Service {typeof(T).FullName} was set twice"); + + var obj = (T?)Activator.CreateInstance(typeof(T), true); + + SetInstanceObject(obj); + + return instance!; + } + + /// + /// Sets a type in the service locator via a constructor with the given parameter types. + /// + /// Constructor arguments. + /// The set object. + public static T Set(params object[] args) + { + if (args == null) { + throw new ArgumentNullException(nameof(args), $"Service locator was passed a null for type {typeof(T).FullName} parameterized constructor "); } - /// - /// Sets the type in the service locator to the given object. - /// - /// Object to set. - /// The set object. - public static T Set(T obj) - { - SetInstanceObject(obj); + var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; + var obj = (T?)Activator.CreateInstance(typeof(T), flags, null, args, null, null); - return instance!; + SetInstanceObject(obj); + + return obj; + } + + /// + /// Attempt to pull the instance out of the service locator. + /// + /// The object if registered. + /// Thrown when the object instance is not present in the service locator. + public static T Get() + { + return instance ?? throw new InvalidOperationException($"{typeof(T).FullName} has not been registered in the service locator!"); + } + + /// + /// Attempt to pull the instance out of the service locator. + /// + /// The object if registered, null otherwise. + public static T? GetNullable() + { + return instance; + } + + private static void SetInstanceObject(T instance) + { + Service.instance = instance ?? throw new ArgumentNullException(nameof(instance), $"Service locator received a null for type {typeof(T).FullName}"); + + var availableToPlugins = RegisterInIoCContainer(instance); + + if (availableToPlugins) + Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins"); + else + Log.Information($"Registered {typeof(T).FullName} into service locator privately"); + } + + private static bool RegisterInIoCContainer(T instance) + { + var attr = typeof(T).GetCustomAttribute(); + if (attr == null) + { + return false; } - /// - /// Sets the type in the service locator via the default parameterless constructor. - /// - /// The set object. - public static T Set() + var ioc = Service.GetNullable(); + if (ioc == null) { - if (instance != null) - throw new Exception($"Service {typeof(T).FullName} was set twice"); - - var obj = (T?)Activator.CreateInstance(typeof(T), true); - - SetInstanceObject(obj); - - return instance!; + return false; } - /// - /// Sets a type in the service locator via a constructor with the given parameter types. - /// - /// Constructor arguments. - /// The set object. - public static T Set(params object[] args) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args), $"Service locator was passed a null for type {typeof(T).FullName} parameterized constructor "); - } + ioc.RegisterSingleton(instance); - var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; - var obj = (T?)Activator.CreateInstance(typeof(T), flags, null, args, null, null); - - SetInstanceObject(obj); - - return obj; - } - - /// - /// Attempt to pull the instance out of the service locator. - /// - /// The object if registered. - /// Thrown when the object instance is not present in the service locator. - public static T Get() - { - return instance ?? throw new InvalidOperationException($"{typeof(T).FullName} has not been registered in the service locator!"); - } - - /// - /// Attempt to pull the instance out of the service locator. - /// - /// The object if registered, null otherwise. - public static T? GetNullable() - { - return instance; - } - - private static void SetInstanceObject(T instance) - { - Service.instance = instance ?? throw new ArgumentNullException(nameof(instance), $"Service locator received a null for type {typeof(T).FullName}"); - - var availableToPlugins = RegisterInIoCContainer(instance); - - if (availableToPlugins) - Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins"); - else - Log.Information($"Registered {typeof(T).FullName} into service locator privately"); - } - - private static bool RegisterInIoCContainer(T instance) - { - var attr = typeof(T).GetCustomAttribute(); - if (attr == null) - { - return false; - } - - var ioc = Service.GetNullable(); - if (ioc == null) - { - return false; - } - - ioc.RegisterSingleton(instance); - - return true; - } + return true; } } diff --git a/Dalamud/Support/BugBait.cs b/Dalamud/Support/BugBait.cs index 9c5db6514..8bdb345dd 100644 --- a/Dalamud/Support/BugBait.cs +++ b/Dalamud/Support/BugBait.cs @@ -6,67 +6,66 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Newtonsoft.Json; -namespace Dalamud.Support +namespace Dalamud.Support; + +/// +/// Class responsible for sending feedback. +/// +internal static class BugBait { + private const string BugBaitUrl = "https://kiko.goats.dev/feedback"; + /// - /// Class responsible for sending feedback. + /// Send feedback to Discord. /// - internal static class BugBait + /// The plugin to send feedback about. + /// The content of the feedback. + /// The reporter name. + /// Whether or not the most recent exception to occur should be included in the report. + /// A representing the asynchronous operation. + public static async Task SendFeedback(PluginManifest plugin, string content, string reporter, bool includeException) { - private const string BugBaitUrl = "https://kiko.goats.dev/feedback"; + if (content.IsNullOrWhitespace()) + return; - /// - /// Send feedback to Discord. - /// - /// The plugin to send feedback about. - /// The content of the feedback. - /// The reporter name. - /// Whether or not the most recent exception to occur should be included in the report. - /// A representing the asynchronous operation. - public static async Task SendFeedback(PluginManifest plugin, string content, string reporter, bool includeException) + var model = new FeedbackModel { - if (content.IsNullOrWhitespace()) - return; + Content = content, + Reporter = reporter, + Name = plugin.InternalName, + Version = plugin.AssemblyVersion.ToString(), + DalamudHash = Util.GetGitHash(), + }; - var model = new FeedbackModel - { - Content = content, - Reporter = reporter, - Name = plugin.InternalName, - Version = plugin.AssemblyVersion.ToString(), - DalamudHash = Util.GetGitHash(), - }; - - if (includeException) - { - model.Exception = Troubleshooting.LastException == null ? "Was included, but none happened" : Troubleshooting.LastException?.ToString(); - } - - var postContent = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); - var response = await Util.HttpClient.PostAsync(BugBaitUrl, postContent); - - response.EnsureSuccessStatusCode(); + if (includeException) + { + model.Exception = Troubleshooting.LastException == null ? "Was included, but none happened" : Troubleshooting.LastException?.ToString(); } - private class FeedbackModel - { - [JsonProperty("content")] - public string? Content { get; set; } + var postContent = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); + var response = await Util.HttpClient.PostAsync(BugBaitUrl, postContent); - [JsonProperty("name")] - public string? Name { get; set; } + response.EnsureSuccessStatusCode(); + } - [JsonProperty("dhash")] - public string? DalamudHash { get; set; } + private class FeedbackModel + { + [JsonProperty("content")] + public string? Content { get; set; } - [JsonProperty("version")] - public string? Version { get; set; } + [JsonProperty("name")] + public string? Name { get; set; } - [JsonProperty("reporter")] - public string? Reporter { get; set; } + [JsonProperty("dhash")] + public string? DalamudHash { get; set; } - [JsonProperty("exception")] - public string? Exception { get; set; } - } + [JsonProperty("version")] + public string? Version { get; set; } + + [JsonProperty("reporter")] + public string? Reporter { get; set; } + + [JsonProperty("exception")] + public string? Exception { get; set; } } } diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs index 8034bb979..faddbc923 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Interface.Internal; using Dalamud.Plugin.Internal; @@ -12,110 +11,109 @@ using Dalamud.Utility; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Support +namespace Dalamud.Support; + +/// +/// Class responsible for printing troubleshooting information to the log. +/// +public static class Troubleshooting { /// - /// Class responsible for printing troubleshooting information to the log. + /// Gets the most recent exception to occur. /// - public static class Troubleshooting + public static Exception? LastException { get; private set; } + + /// + /// Log the last exception in a parseable format to serilog. + /// + /// The exception to log. + /// Additional context. + public static void LogException(Exception exception, string context) { - /// - /// Gets the most recent exception to occur. - /// - public static Exception? LastException { get; private set; } + LastException = exception; - /// - /// Log the last exception in a parseable format to serilog. - /// - /// The exception to log. - /// Additional context. - public static void LogException(Exception exception, string context) + try { - LastException = exception; - - try + var payload = new ExceptionPayload { - var payload = new ExceptionPayload - { - Context = context, - When = DateTime.Now, - Info = exception.ToString(), - }; + Context = context, + When = DateTime.Now, + Info = exception.ToString(), + }; - var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); - Log.Information($"LASTEXCEPTION:{encodedPayload}"); - } - catch (Exception ex) - { - Log.Error(ex, "Could not print exception."); - } + var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); + Log.Information($"LASTEXCEPTION:{encodedPayload}"); } - - /// - /// Log troubleshooting information in a parseable format to Serilog. - /// - internal static void LogTroubleshooting() + catch (Exception ex) { - var startInfo = Service.Get(); - var configuration = Service.Get(); - var interfaceManager = Service.GetNullable(); - var pluginManager = Service.GetNullable(); - - try - { - var payload = new TroubleshootingPayload - { - LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest)?.ToArray(), - DalamudVersion = Util.AssemblyVersion, - DalamudGitHash = Util.GetGitHash(), - GameVersion = startInfo.GameVersion.ToString(), - Language = startInfo.Language.ToString(), - DoDalamudTest = configuration.DoDalamudTest, - DoPluginTest = configuration.DoPluginTest, - InterfaceLoaded = interfaceManager?.IsReady ?? false, - ThirdRepo = configuration.ThirdRepoList, - ForcedMinHook = EnvironmentConfiguration.DalamudForceMinHook, - }; - - var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); - Log.Information($"TROUBLESHOOTING:{encodedPayload}"); - } - catch (Exception ex) - { - Log.Error(ex, "Could not print troubleshooting."); - } - } - - private class ExceptionPayload - { - public DateTime When { get; set; } - - public string Info { get; set; } - - public string Context { get; set; } - } - - private class TroubleshootingPayload - { - public PluginManifest[] LoadedPlugins { get; set; } - - public string DalamudVersion { get; set; } - - public string DalamudGitHash { get; set; } - - public string GameVersion { get; set; } - - public string Language { get; set; } - - public bool DoDalamudTest { get; set; } - - public bool DoPluginTest { get; set; } - - public bool InterfaceLoaded { get; set; } - - public bool ForcedMinHook { get; set; } - - public List ThirdRepo { get; set; } + Log.Error(ex, "Could not print exception."); } } + + /// + /// Log troubleshooting information in a parseable format to Serilog. + /// + internal static void LogTroubleshooting() + { + var startInfo = Service.Get(); + var configuration = Service.Get(); + var interfaceManager = Service.GetNullable(); + var pluginManager = Service.GetNullable(); + + try + { + var payload = new TroubleshootingPayload + { + LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest)?.ToArray(), + DalamudVersion = Util.AssemblyVersion, + DalamudGitHash = Util.GetGitHash(), + GameVersion = startInfo.GameVersion.ToString(), + Language = startInfo.Language.ToString(), + DoDalamudTest = configuration.DoDalamudTest, + DoPluginTest = configuration.DoPluginTest, + InterfaceLoaded = interfaceManager?.IsReady ?? false, + ThirdRepo = configuration.ThirdRepoList, + ForcedMinHook = EnvironmentConfiguration.DalamudForceMinHook, + }; + + var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); + Log.Information($"TROUBLESHOOTING:{encodedPayload}"); + } + catch (Exception ex) + { + Log.Error(ex, "Could not print troubleshooting."); + } + } + + private class ExceptionPayload + { + public DateTime When { get; set; } + + public string Info { get; set; } + + public string Context { get; set; } + } + + private class TroubleshootingPayload + { + public PluginManifest[] LoadedPlugins { get; set; } + + public string DalamudVersion { get; set; } + + public string DalamudGitHash { get; set; } + + public string GameVersion { get; set; } + + public string Language { get; set; } + + public bool DoDalamudTest { get; set; } + + public bool DoPluginTest { get; set; } + + public bool InterfaceLoaded { get; set; } + + public bool ForcedMinHook { get; set; } + + public List ThirdRepo { get; set; } + } } diff --git a/Dalamud/Utility/EnumExtensions.cs b/Dalamud/Utility/EnumExtensions.cs index 5a2d9172b..8868e4c1f 100644 --- a/Dalamud/Utility/EnumExtensions.cs +++ b/Dalamud/Utility/EnumExtensions.cs @@ -1,28 +1,27 @@ using System; using System.Linq; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Extension methods for enums. +/// +public static class EnumExtensions { /// - /// Extension methods for enums. + /// Gets an attribute on an enum. /// - public static class EnumExtensions + /// The type of attribute to get. + /// The enum value that has an attached attribute. + /// The attached attribute, if any. + public static TAttribute GetAttribute(this Enum value) + where TAttribute : Attribute { - /// - /// Gets an attribute on an enum. - /// - /// The type of attribute to get. - /// The enum value that has an attached attribute. - /// The attached attribute, if any. - public static TAttribute GetAttribute(this Enum value) - where TAttribute : Attribute - { - var type = value.GetType(); - var name = Enum.GetName(type, value); - return type.GetField(name) // I prefer to get attributes this way - .GetCustomAttributes(false) - .OfType() - .SingleOrDefault(); - } + var type = value.GetType(); + var name = Enum.GetName(type, value); + return type.GetField(name) // I prefer to get attributes this way + .GetCustomAttributes(false) + .OfType() + .SingleOrDefault(); } } diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index dca423eab..94391f767 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,18 +1,17 @@ -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Extension methods for SeStrings. +/// +public static class SeStringExtensions { /// - /// Extension methods for SeStrings. + /// Convert a Lumina SeString into a Dalamud SeString. + /// This conversion re-parses the string. /// - public static class SeStringExtensions - { - /// - /// Convert a Lumina SeString into a Dalamud SeString. - /// This conversion re-parses the string. - /// - /// The original Lumina SeString. - /// The re-parsed Dalamud SeString. - public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData); - } + /// The original Lumina SeString. + /// The re-parsed Dalamud SeString. + public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData); } diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index f452a2a0f..fafb5a7f7 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -1,30 +1,29 @@ -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Extension methods for strings. +/// +public static class StringExtensions { /// - /// Extension methods for strings. + /// An extension method to chain usage of string.Format. /// - public static class StringExtensions - { - /// - /// An extension method to chain usage of string.Format. - /// - /// Format string. - /// Format arguments. - /// Formatted string. - public static string Format(this string format, params object[] args) => string.Format(format, args); + /// Format string. + /// Format arguments. + /// Formatted string. + public static string Format(this string format, params object[] args) => string.Format(format, args); - /// - /// Indicates whether the specified string is null or an empty string (""). - /// - /// The string to test. - /// true if the value parameter is null or an empty string (""); otherwise, false. - public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value); + /// + /// Indicates whether the specified string is null or an empty string (""). + /// + /// The string to test. + /// true if the value parameter is null or an empty string (""); otherwise, false. + public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value); - /// - /// Indicates whether a specified string is null, empty, or consists only of white-space characters. - /// - /// The string to test. - /// true if the value parameter is null or an empty string (""), or if value consists exclusively of white-space characters. - public static bool IsNullOrWhitespace(this string value) => string.IsNullOrWhiteSpace(value); - } + /// + /// Indicates whether a specified string is null, empty, or consists only of white-space characters. + /// + /// The string to test. + /// true if the value parameter is null or an empty string (""), or if value consists exclusively of white-space characters. + public static bool IsNullOrWhitespace(this string value) => string.IsNullOrWhiteSpace(value); } diff --git a/Dalamud/Utility/TexFileExtensions.cs b/Dalamud/Utility/TexFileExtensions.cs index ddfccba9c..5abea692a 100644 --- a/Dalamud/Utility/TexFileExtensions.cs +++ b/Dalamud/Utility/TexFileExtensions.cs @@ -1,32 +1,31 @@ using ImGuiScene; using Lumina.Data.Files; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Extensions to . +/// +public static class TexFileExtensions { /// - /// Extensions to . + /// Returns the image data formatted for . /// - public static class TexFileExtensions + /// The TexFile to format. + /// The formatted image data. + public static byte[] GetRgbaImageData(this TexFile texFile) { - /// - /// Returns the image data formatted for . - /// - /// The TexFile to format. - /// The formatted image data. - public static byte[] GetRgbaImageData(this TexFile texFile) + var imageData = texFile.ImageData; + var dst = new byte[imageData.Length]; + + for (var i = 0; i < dst.Length; i += 4) { - var imageData = texFile.ImageData; - var dst = new byte[imageData.Length]; - - for (var i = 0; i < dst.Length; i += 4) - { - dst[i] = imageData[i + 2]; - dst[i + 1] = imageData[i + 1]; - dst[i + 2] = imageData[i]; - dst[i + 3] = imageData[i + 3]; - } - - return dst; + dst[i] = imageData[i + 2]; + dst[i + 1] = imageData[i + 1]; + dst[i + 2] = imageData[i]; + dst[i + 3] = imageData[i + 3]; } + + return dst; } } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 8a39b7aea..55f6ae3d3 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -15,281 +15,280 @@ using ImGuiNET; using Microsoft.Win32; using Serilog; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Class providing various helper methods for use in Dalamud and plugins. +/// +public static class Util { + private static string gitHashInternal; + /// - /// Class providing various helper methods for use in Dalamud and plugins. + /// Gets an httpclient for usage. + /// Do NOT await this. /// - public static class Util + public static HttpClient HttpClient { get; } = new(); + + /// + /// Gets the assembly version of Dalamud. + /// + public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); + + /// + /// Gets the git hash value from the assembly + /// or null if it cannot be found. + /// + /// The git hash of the assembly. + public static string GetGitHash() { - private static string gitHashInternal; - - /// - /// Gets an httpclient for usage. - /// Do NOT await this. - /// - public static HttpClient HttpClient { get; } = new(); - - /// - /// Gets the assembly version of Dalamud. - /// - public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - - /// - /// Gets the git hash value from the assembly - /// or null if it cannot be found. - /// - /// The git hash of the assembly. - public static string GetGitHash() - { - if (gitHashInternal != null) - return gitHashInternal; - - var asm = typeof(Util).Assembly; - var attrs = asm.GetCustomAttributes(); - - gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value; - + if (gitHashInternal != null) return gitHashInternal; - } - /// - /// Read memory from an offset and hexdump them via Serilog. - /// - /// The offset to read from. - /// The length to read. - public static void DumpMemory(IntPtr offset, int len = 512) + var asm = typeof(Util).Assembly; + var attrs = asm.GetCustomAttributes(); + + gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value; + + return gitHashInternal; + } + + /// + /// Read memory from an offset and hexdump them via Serilog. + /// + /// The offset to read from. + /// The length to read. + public static void DumpMemory(IntPtr offset, int len = 512) + { + try { - try - { - SafeMemory.ReadBytes(offset, len, out var data); - Log.Information(ByteArrayToHex(data)); - } - catch (Exception ex) - { - Log.Error(ex, "Read failed"); - } + SafeMemory.ReadBytes(offset, len, out var data); + Log.Information(ByteArrayToHex(data)); } - - /// - /// Create a hexdump of the provided bytes. - /// - /// The bytes to hexdump. - /// The offset in the byte array to start at. - /// The amount of bytes to display per line. - /// The generated hexdump in string form. - public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16) + catch (Exception ex) { - if (bytes == null) return string.Empty; - - var hexChars = "0123456789ABCDEF".ToCharArray(); - - var offsetBlock = 8 + 3; - var byteBlock = offsetBlock + (bytesPerLine * 3) + ((bytesPerLine - 1) / 8) + 2; - var lineLength = byteBlock + bytesPerLine + Environment.NewLine.Length; - - var line = (new string(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); - var numLines = (bytes.Length + bytesPerLine - 1) / bytesPerLine; - - var sb = new StringBuilder(numLines * lineLength); - - for (var i = 0; i < bytes.Length; i += bytesPerLine) - { - var h = i + offset; - - line[0] = hexChars[(h >> 28) & 0xF]; - line[1] = hexChars[(h >> 24) & 0xF]; - line[2] = hexChars[(h >> 20) & 0xF]; - line[3] = hexChars[(h >> 16) & 0xF]; - line[4] = hexChars[(h >> 12) & 0xF]; - line[5] = hexChars[(h >> 8) & 0xF]; - line[6] = hexChars[(h >> 4) & 0xF]; - line[7] = hexChars[(h >> 0) & 0xF]; - - var hexColumn = offsetBlock; - var charColumn = byteBlock; - - for (var j = 0; j < bytesPerLine; j++) - { - if (j > 0 && (j & 7) == 0) hexColumn++; - - if (i + j >= bytes.Length) - { - line[hexColumn] = ' '; - line[hexColumn + 1] = ' '; - line[charColumn] = ' '; - } - else - { - var by = bytes[i + j]; - line[hexColumn] = hexChars[(by >> 4) & 0xF]; - line[hexColumn + 1] = hexChars[by & 0xF]; - line[charColumn] = by < 32 ? '.' : (char)by; - } - - hexColumn += 3; - charColumn++; - } - - sb.Append(line); - } - - return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); - } - - /// - /// Show all properties and fields of the provided object via ImGui. - /// - /// The object to show. - public static void ShowObject(object obj) - { - var type = obj.GetType(); - - ImGui.Text($"Object Dump({type.Name}) for {obj}({obj.GetHashCode()})"); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.DalamudOrange, "-> Properties:"); - - ImGui.Indent(); - - foreach (var propertyInfo in type.GetProperties()) - { - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {propertyInfo.GetValue(obj)}"); - } - - ImGui.Unindent(); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.HealerGreen, "-> Fields:"); - - ImGui.Indent(); - - foreach (var fieldInfo in type.GetFields()) - { - ImGui.TextColored(ImGuiColors.HealerGreen, $" {fieldInfo.Name}: {fieldInfo.GetValue(obj)}"); - } - - ImGui.Unindent(); - } - - /// - /// Display an error MessageBox and exit the current process. - /// - /// MessageBox body. - /// MessageBox caption (title). - public static void Fatal(string message, string caption) - { - var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError; - _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); - - Environment.Exit(-1); - } - - /// - /// Retrieve a UTF8 string from a null terminated byte array. - /// - /// A null terminated UTF8 byte array. - /// A UTF8 encoded string. - public static string GetUTF8String(byte[] array) - { - var count = 0; - for (; count < array.Length; count++) - { - if (array[count] == 0) - break; - } - - string text; - if (count == array.Length) - { - text = Encoding.UTF8.GetString(array); - Log.Warning($"Warning: text exceeds underlying array length ({text})"); - } - else - { - text = Encoding.UTF8.GetString(array, 0, count); - } - - return text; - } - - /// - /// Compress a string using GZip. - /// - /// The input string. - /// The compressed output bytes. - public static byte[] CompressString(string str) - { - var bytes = Encoding.UTF8.GetBytes(str); - - using var msi = new MemoryStream(bytes); - using var mso = new MemoryStream(); - using var gs = new GZipStream(mso, CompressionMode.Compress); - - CopyTo(msi, gs); - - return mso.ToArray(); - } - - /// - /// Decompress a string using GZip. - /// - /// The input bytes. - /// The compressed output string. - public static string DecompressString(byte[] bytes) - { - using var msi = new MemoryStream(bytes); - using var mso = new MemoryStream(); - using var gs = new GZipStream(msi, CompressionMode.Decompress); - - CopyTo(gs, mso); - - return Encoding.UTF8.GetString(mso.ToArray()); - } - - /// - /// Copy one stream to another. - /// - /// The source stream. - /// The destination stream. - /// The maximum length to copy. - public static void CopyTo(Stream src, Stream dest, int len = 4069) - { - var bytes = new byte[len]; - int cnt; - - while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) dest.Write(bytes, 0, cnt); - } - - /// - /// Heuristically determine if Dalamud is running on Linux/WINE. - /// - /// Whether or not Dalamud is running on Linux/WINE. - public static bool IsLinux() - { - bool Check1() - { - return EnvironmentConfiguration.XlWineOnLinux; - } - - bool Check2() - { - var hModule = NativeFunctions.GetModuleHandleW("ntdll.dll"); - var proc1 = NativeFunctions.GetProcAddress(hModule, "wine_get_version"); - var proc2 = NativeFunctions.GetProcAddress(hModule, "wine_get_build_id"); - - return proc1 != IntPtr.Zero || proc2 != IntPtr.Zero; - } - - bool Check3() - { - return Registry.CurrentUser.OpenSubKey(@"Software\Wine") != null || - Registry.LocalMachine.OpenSubKey(@"Software\Wine") != null; - } - - return Check1() || Check2() || Check3(); + Log.Error(ex, "Read failed"); } } + + /// + /// Create a hexdump of the provided bytes. + /// + /// The bytes to hexdump. + /// The offset in the byte array to start at. + /// The amount of bytes to display per line. + /// The generated hexdump in string form. + public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16) + { + if (bytes == null) return string.Empty; + + var hexChars = "0123456789ABCDEF".ToCharArray(); + + var offsetBlock = 8 + 3; + var byteBlock = offsetBlock + (bytesPerLine * 3) + ((bytesPerLine - 1) / 8) + 2; + var lineLength = byteBlock + bytesPerLine + Environment.NewLine.Length; + + var line = (new string(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + var numLines = (bytes.Length + bytesPerLine - 1) / bytesPerLine; + + var sb = new StringBuilder(numLines * lineLength); + + for (var i = 0; i < bytes.Length; i += bytesPerLine) + { + var h = i + offset; + + line[0] = hexChars[(h >> 28) & 0xF]; + line[1] = hexChars[(h >> 24) & 0xF]; + line[2] = hexChars[(h >> 20) & 0xF]; + line[3] = hexChars[(h >> 16) & 0xF]; + line[4] = hexChars[(h >> 12) & 0xF]; + line[5] = hexChars[(h >> 8) & 0xF]; + line[6] = hexChars[(h >> 4) & 0xF]; + line[7] = hexChars[(h >> 0) & 0xF]; + + var hexColumn = offsetBlock; + var charColumn = byteBlock; + + for (var j = 0; j < bytesPerLine; j++) + { + if (j > 0 && (j & 7) == 0) hexColumn++; + + if (i + j >= bytes.Length) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + var by = bytes[i + j]; + line[hexColumn] = hexChars[(by >> 4) & 0xF]; + line[hexColumn + 1] = hexChars[by & 0xF]; + line[charColumn] = by < 32 ? '.' : (char)by; + } + + hexColumn += 3; + charColumn++; + } + + sb.Append(line); + } + + return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); + } + + /// + /// Show all properties and fields of the provided object via ImGui. + /// + /// The object to show. + public static void ShowObject(object obj) + { + var type = obj.GetType(); + + ImGui.Text($"Object Dump({type.Name}) for {obj}({obj.GetHashCode()})"); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.DalamudOrange, "-> Properties:"); + + ImGui.Indent(); + + foreach (var propertyInfo in type.GetProperties()) + { + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {propertyInfo.GetValue(obj)}"); + } + + ImGui.Unindent(); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.HealerGreen, "-> Fields:"); + + ImGui.Indent(); + + foreach (var fieldInfo in type.GetFields()) + { + ImGui.TextColored(ImGuiColors.HealerGreen, $" {fieldInfo.Name}: {fieldInfo.GetValue(obj)}"); + } + + ImGui.Unindent(); + } + + /// + /// Display an error MessageBox and exit the current process. + /// + /// MessageBox body. + /// MessageBox caption (title). + public static void Fatal(string message, string caption) + { + var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError; + _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + + Environment.Exit(-1); + } + + /// + /// Retrieve a UTF8 string from a null terminated byte array. + /// + /// A null terminated UTF8 byte array. + /// A UTF8 encoded string. + public static string GetUTF8String(byte[] array) + { + var count = 0; + for (; count < array.Length; count++) + { + if (array[count] == 0) + break; + } + + string text; + if (count == array.Length) + { + text = Encoding.UTF8.GetString(array); + Log.Warning($"Warning: text exceeds underlying array length ({text})"); + } + else + { + text = Encoding.UTF8.GetString(array, 0, count); + } + + return text; + } + + /// + /// Compress a string using GZip. + /// + /// The input string. + /// The compressed output bytes. + public static byte[] CompressString(string str) + { + var bytes = Encoding.UTF8.GetBytes(str); + + using var msi = new MemoryStream(bytes); + using var mso = new MemoryStream(); + using var gs = new GZipStream(mso, CompressionMode.Compress); + + CopyTo(msi, gs); + + return mso.ToArray(); + } + + /// + /// Decompress a string using GZip. + /// + /// The input bytes. + /// The compressed output string. + public static string DecompressString(byte[] bytes) + { + using var msi = new MemoryStream(bytes); + using var mso = new MemoryStream(); + using var gs = new GZipStream(msi, CompressionMode.Decompress); + + CopyTo(gs, mso); + + return Encoding.UTF8.GetString(mso.ToArray()); + } + + /// + /// Copy one stream to another. + /// + /// The source stream. + /// The destination stream. + /// The maximum length to copy. + public static void CopyTo(Stream src, Stream dest, int len = 4069) + { + var bytes = new byte[len]; + int cnt; + + while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) dest.Write(bytes, 0, cnt); + } + + /// + /// Heuristically determine if Dalamud is running on Linux/WINE. + /// + /// Whether or not Dalamud is running on Linux/WINE. + public static bool IsLinux() + { + bool Check1() + { + return EnvironmentConfiguration.XlWineOnLinux; + } + + bool Check2() + { + var hModule = NativeFunctions.GetModuleHandleW("ntdll.dll"); + var proc1 = NativeFunctions.GetProcAddress(hModule, "wine_get_version"); + var proc2 = NativeFunctions.GetProcAddress(hModule, "wine_get_build_id"); + + return proc1 != IntPtr.Zero || proc2 != IntPtr.Zero; + } + + bool Check3() + { + return Registry.CurrentUser.OpenSubKey(@"Software\Wine") != null || + Registry.LocalMachine.OpenSubKey(@"Software\Wine") != null; + } + + return Check1() || Check2() || Check3(); + } } diff --git a/Dalamud/Utility/VectorExtensions.cs b/Dalamud/Utility/VectorExtensions.cs index 0a14299c2..f617c8420 100644 --- a/Dalamud/Utility/VectorExtensions.cs +++ b/Dalamud/Utility/VectorExtensions.cs @@ -1,52 +1,51 @@ using System.Numerics; -namespace Dalamud.Utility +namespace Dalamud.Utility; + +/// +/// Extension methods for System.Numerics.VectorN and SharpDX.VectorN. +/// +public static class VectorExtensions { /// - /// Extension methods for System.Numerics.VectorN and SharpDX.VectorN. + /// Converts a SharpDX vector to System.Numerics. /// - public static class VectorExtensions - { - /// - /// Converts a SharpDX vector to System.Numerics. - /// - /// Vector to convert. - /// A converted vector. - public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y); + /// Vector to convert. + /// A converted vector. + public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y); - /// - /// Converts a SharpDX vector to System.Numerics. - /// - /// Vector to convert. - /// A converted vector. - public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); - /// - /// Converts a SharpDX vector to System.Numerics. - /// - /// Vector to convert. - /// A converted vector. - public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); - /// - /// Converts a System.Numerics vector to SharpDX. - /// - /// Vector to convert. - /// A converted vector. - public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y); + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y); - /// - /// Converts a System.Numerics vector to SharpDX. - /// - /// Vector to convert. - /// A converted vector. - public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); - /// - /// Converts a System.Numerics vector to SharpDX. - /// - /// Vector to convert. - /// A converted vector. - public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); - } + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); }